feat: 기업정보 모달 가치/성장성 탭 요약을 해석형(좋은점/주의점)으로

수치 나열 대신 지표를 좋은 점 / ⚠️주의할 점으로 판단:
- 가치: ROE(우수/낮음/적자)·PER(고평가 부담/부담 작음)·PBR(비쌈/청산가치 이하)
- 성장: 매출·영익·EPS 성장/역성장·상승여력·투자의견·목표가 리비전·서프라이즈
규칙 기반·표준 임계치, 특이점 없으면 미표시. (이전 나열식 요약 대체)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-11 12:04:04 +09:00
parent 2d9c6719e8
commit 0845db1915
+37 -25
View File
@@ -4439,10 +4439,14 @@ dl.pending-detail .pending-line-meta { margin-top: 2px; }
.info-tabbtn:hover { color: #d6d8dd; } .info-tabbtn:hover { color: #d6d8dd; }
.info-tabbtn.active { color: #f0f0f0; border-bottom-color: #ff4d5e; } .info-tabbtn.active { color: #f0f0f0; border-bottom-color: #ff4d5e; }
.info-tabpanel[hidden] { display: none; } .info-tabpanel[hidden] { display: none; }
/* 상단 내용 요약 박스 */ /* 상단 해석 박스 좋은 / 주의할 */
.info-summary { background: #11161f; border: 1px solid #232a36; border-left: 3px solid #4a8cf0; border-radius: 8px; padding: 9px 11px; margin: 0 0 12px; } .info-summary { background: #11161f; border: 1px solid #232a36; border-radius: 8px; padding: 10px 12px; margin: 0 0 12px; }
.info-summary-t { font-size: 11px; color: #7a8493; font-weight: 600; margin-bottom: 4px; } .info-jd + .info-jd { margin-top: 8px; }
.info-summary-b { font-size: 13px; color: #cfd5df; line-height: 1.75; } .info-jd > b { display: block; font-size: 12px; margin-bottom: 3px; }
.info-jd-g > b { color: #34d399; }
.info-jd-c > b { color: #fbbf24; }
.info-summary ul { margin: 0; padding-left: 17px; }
.info-summary li { font-size: 12.5px; color: #cfd5df; line-height: 1.65; margin: 1px 0; }
.info-q { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; margin-left: 4px; border-radius: 50%; border: 1px solid rgba(125,211,168,0.4); background: rgba(125,211,168,0.08); color: #7dd3a8; font-size: 10px; font-weight: 700; line-height: 1; cursor: pointer; font-family: inherit; vertical-align: middle; } .info-q { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0; margin-left: 4px; border-radius: 50%; border: 1px solid rgba(125,211,168,0.4); background: rgba(125,211,168,0.08); color: #7dd3a8; font-size: 10px; font-weight: 700; line-height: 1; cursor: pointer; font-family: inherit; vertical-align: middle; }
.info-q:hover { background: rgba(125,211,168,0.2); border-color: #7dd3a8; } .info-q:hover { background: rgba(125,211,168,0.2); border-color: #7dd3a8; }
.info-q:active { transform: translateY(1px); } .info-q:active { transform: translateY(1px); }
@@ -7888,29 +7892,37 @@ def render_html() -> str:
'if(j.reports&&j.reports.length){var r=j.reports[0];var ar="";if(r.target_action&&r.target_action.indexOf("상향")>=0)ar=\' <span style="color:#ef4444">▲</span>\';else if(r.target_action&&r.target_action.indexOf("하향")>=0)ar=\' <span style="color:#3b82f6">▼</span>\';h+=\'<div class="info-sec-title">최신 리포트</div><div style="font-size:12px;color:#9aa4b2"><span style="color:#7a8493">\'+escHtml(r.date||"")+\' \'+escHtml(r.broker||"")+\'</span> \'+(r.target?\'<span style="color:#fbbf24">목표 \'+escHtml(r.target)+ar+\'</span>\':"")+\'</div><div style="font-size:13px;color:#fff;margin-top:2px">\'+escHtml(r.title||"")+\'</div>\';}' 'if(j.reports&&j.reports.length){var r=j.reports[0];var ar="";if(r.target_action&&r.target_action.indexOf("상향")>=0)ar=\' <span style="color:#ef4444">▲</span>\';else if(r.target_action&&r.target_action.indexOf("하향")>=0)ar=\' <span style="color:#3b82f6">▼</span>\';h+=\'<div class="info-sec-title">최신 리포트</div><div style="font-size:12px;color:#9aa4b2"><span style="color:#7a8493">\'+escHtml(r.date||"")+\' \'+escHtml(r.broker||"")+\'</span> \'+(r.target?\'<span style="color:#fbbf24">목표 \'+escHtml(r.target)+ar+\'</span>\':"")+\'</div><div style="font-size:13px;color:#fff;margin-top:2px">\'+escHtml(r.title||"")+\'</div>\';}'
'return h;' 'return h;'
'}' '}'
# 탭 상단 내용 요약 박스 — 핵심 수치를 한 묶음으로 + 간단 해석(규칙 기반, 데이터 그대로). # 탭 상단 해석 박스 — 지표를 ✅좋은 점 / ⚠️주의할 점으로 판단(규칙 기반). 특이점 없으면 미표시.
'function infoSummaryBox(inner){return inner?(\'<div class="info-summary"><div class="info-summary-t">📝 요약</div><div class="info-summary-b">\'+inner+\'</div></div>\'):"";}' 'function _gnum(v){var n=Number(v);return (v==null||v===""||isNaN(n))?null:n;}'
'function companySummary(j){' 'function judgeBox(good,caution){'
'var L=[],per=Number(j.per),pbr=Number(j.pbr),roe=Number(j.roe),val=[];' 'if(!good.length&&!caution.length)return "";'
'if(j.per!=null&&j.per!==""&&!isNaN(per))val.push("PER "+per.toFixed(2)+"");' 'var h="";'
'if(j.pbr!=null&&j.pbr!==""&&!isNaN(pbr))val.push("PBR "+pbr.toFixed(2)+"");' 'if(good.length)h+=\'<div class="info-jd info-jd-g"><b>✅ 좋은 점</b><ul>\'+good.map(function(x){return "<li>"+x+"</li>";}).join("")+\'</ul></div>\';'
'if(val.length)L.push("밸류에이션 "+val.join(" · "));' 'if(caution.length)h+=\'<div class="info-jd info-jd-c"><b>⚠️ 주의할 점</b><ul>\'+caution.map(function(x){return "<li>"+x+"</li>";}).join("")+\'</ul></div>\';'
'if(j.roe!=null&&j.roe!==""&&!isNaN(roe)){var tag=roe<0?"적자":(roe>=15?"우수":(roe>=8?"양호":"다소 낮음"));L.push("수익성 ROE "+roe.toFixed(1)+"% <span class=\\"muted small\\">("+tag+")</span>");}' 'return \'<div class="info-summary">\'+h+\'</div>\';'
'return infoSummaryBox(L.join("<br>"));'
'}' '}'
'function growthSummary(j){' 'function companyJudge(j){'
'var gr=(j.fundamentals&&j.fundamentals.growth)||{},fc=(j.fundamentals&&j.fundamentals.consensus)||{},cn=j.consensus||{},L=[],g=[];' 'var good=[],caution=[],per=_gnum(j.per),pbr=_gnum(j.pbr),roe=_gnum(j.roe);'
'if(gr.sales_yoy!=null)g.push("매출 "+growthDisp(gr.sales_yoy));' 'if(roe!=null){if(roe<0)caution.push("ROE "+roe.toFixed(1)+"% — 자기자본이 손실(수익성 부진)");else if(roe>=15)good.push("ROE "+roe.toFixed(1)+"% — 자본 수익성 우수");else if(roe<8)caution.push("ROE "+roe.toFixed(1)+"% — 자본 수익성 다소 낮음");}'
'if(gr.oper_profit_yoy!=null)g.push("영익 "+growthDisp(gr.oper_profit_yoy));' 'if(per!=null){if(per<=0)caution.push("PER 산정 불가 — 적자 또는 이익 미미");else if(per>=30)caution.push("PER "+per.toFixed(1)+"배 — 기대가 선반영된 고평가 부담");else if(per<=10)good.push("PER "+per.toFixed(1)+"배 — 밸류에이션 부담이 작음");}'
'if(gr.eps_yoy!=null)g.push("EPS "+growthDisp(gr.eps_yoy));' 'if(pbr!=null){if(pbr>=3)caution.push("PBR "+pbr.toFixed(1)+"배 — 자산가치 대비 비싼 편");else if(pbr>0&&pbr<1)good.push("PBR "+pbr.toFixed(1)+"배 — 자산가치(청산가치) 이하");}'
'if(g.length)L.push("성장(YoY) "+g.join(" · "));' 'return judgeBox(good,caution);'
'if(fc.target_price!=null){var p=Number(j.price),tp=Number(fc.target_price),up="";if(p&&tp&&!isNaN(p)&&!isNaN(tp))up=" (상승여력 "+growthDisp((tp/p-1)*100)+")";L.push("목표주가 "+fmtInt(fc.target_price)+""+up+(fc.opinion_label?" · "+escHtml(fc.opinion_label):""));}'
'if(cn.target_change_pct!=null)L.push("목표가 리비전(3M) "+growthDisp(cn.target_change_pct));'
'if(cn.surprise_op!=null)L.push("영익 서프라이즈 "+growthDisp(cn.surprise_op));'
'return infoSummaryBox(L.join("<br>"));'
'}' '}'
'var companyInner=companySummary(j)+titled("가치지표",secGrid("가치지표"))+titled("기본정보",secGrid("기본정보"));' 'function growthJudge(j){'
'var growthInner=growthSummary(j)+secGrid("성장성·컨센서스");' 'var gr=(j.fundamentals&&j.fundamentals.growth)||{},fc=(j.fundamentals&&j.fundamentals.consensus)||{},cn=j.consensus||{},good=[],caution=[];'
'var sy=_gnum(gr.sales_yoy),oy=_gnum(gr.oper_profit_yoy),ey=_gnum(gr.eps_yoy);'
'if(oy!=null){if(oy>=20)good.push("영업이익 +"+oy.toFixed(0)+"% — 이익 고성장");else if(oy<0)caution.push("영업이익 "+oy.toFixed(0)+"% — 이익 역성장");}'
'if(sy!=null){if(sy<0)caution.push("매출 "+sy.toFixed(0)+"% — 외형 역성장");else if(sy>=15)good.push("매출 +"+sy.toFixed(0)+"% — 외형 성장 견조");}'
'if(ey!=null&&ey<0)caution.push("EPS "+ey.toFixed(0)+"% — 주당순이익 감소");'
'var p=_gnum(j.price),tp=_gnum(fc.target_price);'
'if(p&&tp){var up=(tp/p-1)*100;if(up>=15)good.push("목표주가까지 상승여력 +"+up.toFixed(0)+"%");else if(up<0)caution.push("현재가가 목표주가를 "+(-up).toFixed(0)+"% 웃돔 — 추가 상승여력 제한");}'
'if(fc.opinion_label){if(fc.opinion_label.indexOf("매수")>=0)good.push("증권사 투자의견 "+escHtml(fc.opinion_label));else if(fc.opinion_label.indexOf("매도")>=0||fc.opinion_label.indexOf("중립")>=0||fc.opinion_label.indexOf("보유")>=0)caution.push("투자의견 "+escHtml(fc.opinion_label)+" — 적극 매수는 아님");}'
'var tr=_gnum(cn.target_change_pct);if(tr!=null){if(tr>0)good.push("최근 3개월 목표가 상향(+"+tr.toFixed(1)+"%) — 전망 개선");else if(tr<0)caution.push("최근 3개월 목표가 하향("+tr.toFixed(1)+"%) — 전망 악화");}'
'var so=_gnum(cn.surprise_op);if(so!=null){if(so>0)good.push("최근 영업이익 어닝 서프라이즈(+"+so.toFixed(1)+"%)");else if(so<0)caution.push("최근 영업이익이 컨센서스 하회("+so.toFixed(1)+"%)");}'
'return judgeBox(good,caution);'
'}'
'var companyInner=companyJudge(j)+titled("가치지표",secGrid("가치지표"))+titled("기본정보",secGrid("기본정보"));'
'var growthInner=growthJudge(j)+secGrid("성장성·컨센서스");'
'var repInner=infoReportsHtml(j);' 'var repInner=infoReportsHtml(j);'
'var TB=[["summary","요약",sumPanel()],["company","기업정보·가치",companyInner],["growth","성장성·컨센서스",growthInner],["reports","투자리포트",repInner]];' 'var TB=[["summary","요약",sumPanel()],["company","기업정보·가치",companyInner],["growth","성장성·컨센서스",growthInner],["reports","투자리포트",repInner]];'
'var bar=\'<div class="info-tabs" role="tablist">\';var pans="";' 'var bar=\'<div class="info-tabs" role="tablist">\';var pans="";'