@@ -73,7 +73,6 @@ def render_final_decisions_html(report: dict[str, Any]) -> str:
7373 if not decisions :
7474 return ""
7575 final_picks = decisions .get ("recommendations" , [])
76- watchlist = decisions .get ("watchlist" , [])
7776 buckets = decisions .get ("horizon_buckets" , {})
7877 horizon_rows = []
7978 for horizon , label , window in (
@@ -102,49 +101,23 @@ def render_final_decisions_html(report: dict[str, Any]) -> str:
102101 <div><dt>政策/新闻</dt><dd>{ html .escape (display_number (pick .get ('source_score' )))} </dd></div>
103102 <div><dt>动量</dt><dd>{ html .escape (display_number (pick .get ('momentum_score' )))} </dd></div>
104103 <div><dt>AI信号仓库</dt><dd>{ html .escape (display_number (pick .get ('ai_signal_score' )))} </dd></div>
105- <div><dt>仓位/数量</dt><dd>不提供</dd></div>
106104 </dl>
107- <p><strong>做什么 :</strong>{ html .escape (str (pick .get ('business_summary' , '' )))} </p>
108- <p><strong>为什么有前景 :</strong>{ html .escape (str (pick .get ('prospect_summary' , '' )))} </p>
105+ <p><strong>股票背景 :</strong>{ html .escape (str (pick .get ('business_summary' , '' )))} </p>
106+ <p><strong>推荐理由 :</strong>{ html .escape (str (pick .get ('prospect_summary' , '' )))} </p>
109107 <p><strong>多源依据:</strong></p>
110108 <ul>{ reasons } </ul>
111109 <p><strong>主要风险:</strong>{ html .escape (str (pick .get ('risk_summary' , '' )))} </p>
112- <p class="position-note"><strong>买多少:</strong>{ html .escape (str (pick .get ('buy_amount_guidance' , '不提供买入数量或仓位。' )))} </p>
113110 </article>
114111 """
115112 )
116113
117- watch_rows = []
118- for item in watchlist :
119- watch_rows .append (
120- "<tr>"
121- f"<td>{ html .escape (str (item .get ('symbol' , '' )))} </td>"
122- f"<td>{ html .escape (str (item .get ('primary_horizon_label' , '' )))} </td>"
123- f"<td>{ html .escape (display_number (item .get ('combined_score' )))} </td>"
124- f"<td>{ html .escape (str (item .get ('business_summary' , '' )))} </td>"
125- f"<td>{ html .escape (str (item .get ('prospect_summary' , '' )))} </td>"
126- "</tr>"
127- )
128- watch_html = ""
129- if watch_rows :
130- watch_html = f"""
131- <h3>观察名单(不是最终推荐)</h3>
132- <p>这些标的可能有主题或动量,但事件证据、风险或推荐层级还不足,不作为本期最终推荐。</p>
133- <table>
134- <thead><tr><th>股票</th><th>周期</th><th>综合分</th><th>做什么</th><th>为什么可跟踪</th></tr></thead>
135- <tbody>{ '' .join (watch_rows )} </tbody>
136- </table>
137- """
138-
139114 final_intro = "、" .join (str (pick .get ("symbol" , "" )) for pick in final_picks ) or "暂无"
140115 return f"""
141116 <section class="final-decisions">
142117 <h2>本期最终结论</h2>
143118 <p><strong>最终推荐:</strong>{ html .escape (final_intro )} 。合成口径以 AI信号仓库(AiLongHorizonSignalPipelines)和动量为主,政策/新闻事件用于提高置信度和提示风险。</p>
144- <p><strong>买多少:</strong>不提供买入金额、股数、仓位或账户级配置;这里只能作为非个性化模型研究。</p>
145119 <ul class="horizon-list">{ '' .join (horizon_rows )} </ul>
146120 <div class="final-grid">{ '' .join (cards )} </div>
147- { watch_html }
148121 </section>
149122 """
150123
@@ -236,52 +209,7 @@ def render_report_html(report: dict[str, Any]) -> str:
236209 <ul>{ warning_items } </ul>
237210 </section>
238211 """
239- theme_candidates_html = render_theme_first_candidates_html (report )
240- theme_momentum_html = render_theme_momentum_html (report )
241212 final_decisions_html = render_final_decisions_html (report )
242- recommendation_cards = []
243- background_count = 0
244- for rec in report ["recommendations" ]:
245- reasons = "\n " .join (f"<li>{ html .escape (reason )} </li>" for reason in rec ["reasons" ])
246- risks = "\n " .join (f"<li>{ html .escape (risk )} </li>" for risk in rec ["risk_notes" ])
247- refs = "\n " .join (
248- f'<li><a href="{ html .escape (ref )} ">{ html .escape (ref )} </a></li>' if ref .startswith ("http" ) else f"<li>{ html .escape (ref )} </li>"
249- for ref in rec ["evidence_refs" ]
250- )
251- if rec .get ("recommendation_tier" ) == "monitor" :
252- background_count += 1
253- continue
254- card_html = f"""
255- <article class="recommendation">
256- <header>
257- <h2>{ html .escape (rec ['symbol' ])} <span>{ html .escape (rec ['rating_label' ])} </span></h2>
258- <p>{ html .escape (rec .get ('name' ) or rec ['symbol' ])} </p>
259- </header>
260- <dl>
261- <div><dt>周期</dt><dd>{ html .escape (rec ['primary_horizon_label' ])} </dd></div>
262- <div><dt>窗口</dt><dd>{ html .escape (rec ['primary_horizon_window' ])} </dd></div>
263- <div><dt>层级</dt><dd>{ html .escape (rec ['recommendation_tier_label' ])} </dd></div>
264- <div><dt>来源</dt><dd>{ html .escape (rec ['source_confidence_label' ])} </dd></div>
265- <div><dt>风格</dt><dd>{ html .escape (rec ['strategy_style' ])} </dd></div>
266- <div><dt>分数</dt><dd>{ rec ['score' ]} </dd></div>
267- <div><dt>证据</dt><dd>{ rec ['evidence_score' ]} </dd></div>
268- <div><dt>风险</dt><dd>{ rec ['risk_score' ]} </dd></div>
269- </dl>
270- <p class="horizon-note">{ html .escape (rec ['horizon_note' ])} </p>
271- <h3>推荐理由</h3>
272- <ul>{ reasons } </ul>
273- <h3>风险提示</h3>
274- <ul>{ risks } </ul>
275- <h3>证据链接</h3>
276- <ul>{ refs } </ul>
277- </article>
278- """
279- recommendation_cards .append (card_html )
280- background_html = ""
281- if background_count :
282- background_html = f"""
283- <p class="monitor-note">另有 { background_count } 个仅监控标的未在页面展开;完整 JSON 保留用于复盘。页面只展示推荐、观察和来源核验标的。</p>
284- """
285213 return f"""<!doctype html>
286214<html lang="zh-CN">
287215<head>
@@ -308,7 +236,6 @@ def render_report_html(report: dict[str, Any]) -> str:
308236 .final-card h3 span {{ font-size: .85rem; color: #0f766e; margin-left: 8px; }}
309237 .final-card header p {{ margin: 4px 0 12px; color: #57606a; }}
310238 .final-card p {{ line-height: 1.55; color: #334155; }}
311- .position-note {{ background: #fff7ed; border: 1px solid #fed7aa; border-radius: 8px; padding: 10px 12px; }}
312239 .theme-candidates {{ background: #f0fdf4; border: 1px solid #bbf7d0; padding: 16px; margin-bottom: 20px; border-radius: 8px; }}
313240 .candidate-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }}
314241 .candidate-card {{ background: #fff; border: 1px solid #bbf7d0; border-radius: 8px; padding: 14px; }}
@@ -346,8 +273,7 @@ def render_report_html(report: dict[str, Any]) -> str:
346273 <span class="pill">受众:{ html .escape (report ['audience_scope' ])} </span>
347274 <span class="pill">AI 状态:{ html .escape (str (report ['summary' ]['ai_regime' ]))} </span>
348275 <span class="pill">来源:{ html .escape (source_mode )} </span>
349- <span class="pill">主题候选:{ report ['summary' ].get ('theme_first_candidate_count' , 0 )} </span>
350- <span class="pill">推荐数:{ report ['summary' ]['recommendation_count' ]} </span>
276+ <span class="pill">最终推荐:{ len (report .get ('final_decisions' , {}).get ('recommendations' , []))} </span>
351277 </div>
352278 </section>
353279 <section class="policy">
@@ -356,15 +282,6 @@ def render_report_html(report: dict[str, Any]) -> str:
356282 </section>
357283 { warning_html }
358284 { final_decisions_html }
359- { theme_candidates_html }
360- { theme_momentum_html }
361- <section class="recommendation-section">
362- <h2>推荐与观察</h2>
363- <p>这里只展开已进入推荐、观察或来源核验的标的;纯监控标的不作为本期结论展示。
364- “先核验来源”表示低置信来源不能直接升级。</p>
365- </section>
366- { '' .join (recommendation_cards )}
367- { background_html }
368285 </main>
369286</body>
370287</html>
@@ -376,14 +293,13 @@ def render_index_html(reports: list[dict[str, Any]]) -> str:
376293 for report in sorted (reports , key = lambda item : item ["as_of" ], reverse = True ):
377294 filename = report_filename (report )
378295 top_symbols = ", " .join (report ["summary" ].get ("top_recommended_symbols" , []))
379- theme_candidate_symbols = ", " .join (report ["summary" ].get ("top_theme_candidate_symbols" , []))
380296 top_themes = format_theme_ids (report ["summary" ].get ("top_theme_ids" , []))
381297 source_mode = str (report ["summary" ].get ("source_mode" , "unknown" ))
382298 items .append (
383299 f"""
384300 <li>
385301 <a href="{ html .escape (filename )} ">{ html .escape (report ['as_of' ])} { html .escape (cadence_label (report ))} 复盘</a>
386- <span>来源:{ html .escape (source_mode )} ;主题 :{ html .escape (top_themes or '无' )} ;主题候选: { html . escape ( theme_candidate_symbols or '无' ) } ;推荐 :{ html .escape (top_symbols or '无' )} </span>
302+ <span>来源:{ html .escape (source_mode )} ;主要信号 :{ html .escape (top_themes or '无' )} ;最终推荐 :{ html .escape (top_symbols or '无' )} </span>
387303 </li>
388304 """
389305 )
@@ -431,12 +347,11 @@ def render_feed_xml(reports: list[dict[str, Any]], *, site_url: str, feed_title:
431347 ET .SubElement (item , "guid" ).text = link
432348 ET .SubElement (item , "pubDate" ).text = format_datetime (report ["generated_at" ])
433349 top_symbols = ", " .join (report ["summary" ].get ("top_recommended_symbols" , []))
434- theme_candidate_symbols = ", " .join (report ["summary" ].get ("top_theme_candidate_symbols" , []))
435350 top_themes = format_theme_ids (report ["summary" ].get ("top_theme_ids" , []))
436351 source_mode = str (report ["summary" ].get ("source_mode" , "unknown" ))
437352 ET .SubElement (item , "description" ).text = (
438353 f"模式={ report ['mode' ]} ;来源={ source_mode } ;主题={ top_themes or '无' } ;"
439- f"主题候选= { theme_candidate_symbols or '无' } ;推荐 ={ top_symbols or '无' } 。"
354+ f"最终推荐 ={ top_symbols or '无' } 。"
440355 "非个性化模型输出;不包含下单、仓位配置或账户级建议。"
441356 )
442357 rss = ET .Element ("rss" , {"version" : "2.0" })
0 commit comments