Skip to content

Commit a720632

Browse files
committed
Summarize horizon conclusions in report lead
1 parent f368bcc commit a720632

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

src/quant_advisor_research/publisher.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,85 @@ def render_final_card(pick: dict[str, Any], *, rank: int, horizon: str) -> str:
173173
"""
174174

175175

176+
def join_zh_items(items: list[str], *, limit: int = 5, empty: str = "暂无") -> str:
177+
cleaned = []
178+
seen = set()
179+
for item in items:
180+
text = str(item or "").strip()
181+
if not text or text in seen:
182+
continue
183+
cleaned.append(text)
184+
seen.add(text)
185+
if not cleaned:
186+
return empty
187+
visible = cleaned[:limit]
188+
suffix = "等" if len(cleaned) > limit else ""
189+
return "、".join(visible) + suffix
190+
191+
192+
def displayed_horizon_symbols(report: dict[str, Any], horizon: str) -> list[str]:
193+
decisions = report.get("final_decisions", {})
194+
if not isinstance(decisions, dict):
195+
return []
196+
final_picks = [pick for pick in decisions.get("recommendations", []) if isinstance(pick, dict)]
197+
return [
198+
str(pick.get("symbol", "")).strip()
199+
for pick in ranked_horizon_picks(final_picks, horizon)
200+
if str(pick.get("symbol", "")).strip()
201+
]
202+
203+
204+
def format_horizon_conclusion(report: dict[str, Any]) -> str:
205+
parts = []
206+
for horizon, label, _window in HORIZON_COLUMNS:
207+
symbols = displayed_horizon_symbols(report, horizon)
208+
value = join_zh_items(symbols, empty="暂无稳定结论")
209+
parts.append(f"{label}{value}")
210+
return ";".join(parts)
211+
212+
213+
def format_momentum_factor_summary(report: dict[str, Any]) -> str:
214+
theme_momentum = report.get("theme_momentum", {})
215+
if not isinstance(theme_momentum, dict) or not theme_momentum.get("available"):
216+
return "动量因子暂无可用主题排序。"
217+
218+
themes = [theme for theme in theme_momentum.get("top_themes", []) if isinstance(theme, dict)]
219+
theme_names = [theme_label(theme.get("theme_id"), theme.get("theme_name")) for theme in themes[:4]]
220+
sectors = [sector_label(theme.get("sector")) for theme in themes[:4]]
221+
sector_text = join_zh_items(sectors, limit=3, empty="暂无明确板块")
222+
theme_text = join_zh_items(theme_names, limit=4, empty="暂无明确主题")
223+
if sector_text == "暂无明确板块":
224+
return f"动量因子领先主题包括 {theme_text}。"
225+
return f"动量因子主要集中在{sector_text}板块,领先主题包括 {theme_text}。"
226+
227+
228+
def format_report_takeaway(report: dict[str, Any]) -> str:
229+
has_long = bool(displayed_horizon_symbols(report, "long"))
230+
has_medium = bool(displayed_horizon_symbols(report, "medium"))
231+
has_short = bool(displayed_horizon_symbols(report, "short"))
232+
if has_long and has_medium and has_short:
233+
return "整体看,当前信号存在跨周期共振,后续重点观察动量延续和风险事件变化。"
234+
if has_long and has_medium:
235+
return "整体看,当前信号更偏中长线,短线暂不强调,后续重点观察动量延续和基本面兑现。"
236+
if has_medium:
237+
return "整体看,当前信号更偏中线,短线暂不强调,后续重点观察动量延续和基本面兑现。"
238+
if has_long:
239+
return "整体看,当前信号更偏长线观察,短线和中线暂未形成稳定排序。"
240+
if has_short:
241+
return "整体看,当前只出现短线机会,持续性仍需要后续数据确认。"
242+
return "整体看,当前暂未形成稳定系统结论,继续等待更清晰的主题和价格确认。"
243+
244+
245+
def render_report_lead(report: dict[str, Any]) -> str:
246+
return " ".join(
247+
[
248+
f"本期结论:{format_horizon_conclusion(report)}。",
249+
format_momentum_factor_summary(report),
250+
format_report_takeaway(report),
251+
]
252+
)
253+
254+
176255
def render_final_decisions_html(report: dict[str, Any]) -> str:
177256
decisions = report.get("final_decisions", {})
178257
if not decisions:
@@ -491,7 +570,7 @@ def render_report_html(report: dict[str, Any]) -> str:
491570
{render_site_mark()}
492571
<p class="eyebrow">Advisory briefing</p>
493572
<h1>{html.escape(display_title)}</h1>
494-
<p class="report-lead">按长线、中线、短线分栏展示系统结论;每张卡片保留股票背景、推荐理由、多源依据和主要风险。</p>
573+
<p class="report-lead">{html.escape(render_report_lead(report))}</p>
495574
</div>
496575
<aside class="date-card" aria-label="报告日期">
497576
<p class="date-label">Report date</p>

tests/test_publisher.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def test_render_report_html_is_direct_public_recommendation_page() -> None:
4242
assert "股票背景" in html
4343
assert "推荐理由" in html
4444
assert "主要风险" in html
45+
assert "按长线、中线、短线分栏展示系统结论" not in html
46+
assert "本期结论:" in html
47+
assert "长线:暂无稳定结论" in html
48+
assert "中线:MU、NVDA" in html
49+
assert "短线:暂无稳定结论" in html
50+
assert "动量因子主要集中在科技板块" in html
4551
assert "<span class=\"pill\">模式:" not in html
4652
assert "受众:" not in html
4753
assert "AI 状态:" not in html
@@ -219,6 +225,7 @@ def test_render_report_html_renders_long_context_as_full_card_when_primary_bucke
219225
assert "长线 / 1-3年" in html
220226
assert "MU background" in html
221227
assert "MU reason" in html
228+
assert "本期结论:长线:MU;中线:MU;短线:暂无稳定结论。" in html
222229
assert "最终推荐" not in html
223230

224231

@@ -285,6 +292,12 @@ def test_render_report_html_includes_theme_momentum_context() -> None:
285292
assert "股票背景" in html
286293
assert "推荐理由" in html
287294
assert "主要风险" in html
295+
assert "按长线、中线、短线分栏展示系统结论" not in html
296+
assert "本期结论:" in html
297+
assert "长线:暂无稳定结论" in html
298+
assert "中线:MU、NVDA" in html
299+
assert "短线:暂无稳定结论" in html
300+
assert "动量因子主要集中在科技板块" in html
288301
assert "存储周期" in html
289302
assert "需复核估值、财报、回撤和流动性" not in html
290303
assert "做什么" not in html

0 commit comments

Comments
 (0)