diff --git a/app/core/action_engine.py b/app/core/action_engine.py index 553329d..1d08078 100644 --- a/app/core/action_engine.py +++ b/app/core/action_engine.py @@ -693,6 +693,8 @@ def eval_open(c: dict, params: dict, caps: dict, room_amt: float): # (它们是事件与量价, 不是产业逻辑, 见 judge.OPEN_JUDGE_KEYS)。不参与任何判定。 "events": c.get("events"), "events_text": c.get("events_text"), "pricing_state": c.get("pricing_state"), "pricing_text": c.get("pricing_text"), + # 相关快讯 (2026-09-08 台账 050): 原值进账本与提议卡, 整句另送研判。只是上下文, 不进判定。 + "news": c.get("news"), "news_text": c.get("news_text"), # ---- 仓位口径: 只进评审账本做判分锚。judge.py 送研判时会把这几项过滤掉, # 理由见那边的 OPEN_JUDGE_KEYS —— 决策系统本来就不管仓位, 别送过去带偏它。 "target_pct": target_pct, "target_amount": round(full_amt, 2), diff --git a/app/services/judge.py b/app/services/judge.py index e84f1e4..6c1d832 100644 --- a/app/services/judge.py +++ b/app/services/judge.py @@ -48,7 +48,10 @@ OPEN_JUDGE_KEYS = ("price", "score", "theme", "tier", "upside", "heat", # 正是研报里决策智能体的"事件上下文", 不是产业逻辑 —— 与判决依据、论断、逻辑状态、 # 安全边际那几项不同, 这两句要送。送整句不送字典: 研判提示词把硬数字逐行印成 # "键: 值", 字典会印成一串代码, 整句模型才读得顺。 - "events_text", "pricing_text") + "events_text", "pricing_text", + # 相关快讯整句 (2026-09-08 台账 050): 财联社电报近 3 天点名这只票的条目。它是市场 + # 反应与公司快讯, 不是产业逻辑; 正负不由 PMS 判, 交给研判连同事件上下文一起看。 + "news_text") # 新建仓送研判时的必答题 (2026-09-03)。候选卡的理由随硬数字送过去了, 研判要逐条回答 # 「这条理由到今天还成不成立」—— 这是把上游的静态判决与决策系统的当日观点接起来的那一句。 diff --git a/app/services/plan_feed.py b/app/services/plan_feed.py index 849eb1b..6d28e4b 100644 --- a/app/services/plan_feed.py +++ b/app/services/plan_feed.py @@ -167,6 +167,28 @@ def _logic_state_or_none(v): "reasons": _list_or_none(v.get("reasons"), limit=LOGIC_STATE_REASONS_KEEP) or []} +def _news_or_none(v, limit=5): + """相关快讯 (2026-09-08 台账 050): 上游按财联社电报点名算的条目, 只留 count/latest 与最多 limit 条 + {time, level, title, url}。只是上下文: 进硬数字给人看, 整句送研判; 不进任何判定。""" + if not isinstance(v, dict) or not v.get("items"): + return None + items = [] + for it in v.get("items") or []: + if not isinstance(it, dict): + continue + items.append({"time": _text_or_none(it.get("time")), "level": _text_or_none(it.get("level")), + "title": _text_or_none(it.get("title")), "url": _text_or_none(it.get("url"))}) + if len(items) >= limit: + break + if not items: + return None + try: + count = int(v.get("count") or len(items)) + except (TypeError, ValueError): + count = len(items) + return {"count": count, "latest": _text_or_none(v.get("latest")), "items": items} + + def _events_or_none(v): """催化事件归一 (2026-09-08): 上游给 {latest, count, events:[{date, types, orgs, title, n_reports, compound}]}。 只收约定的键, 事件最多五条; 不是字典或没有事件一律 None。""" @@ -264,6 +286,10 @@ def _rows(raw, bucket: str) -> list: "events_text": _text_or_none(it.get("events_text")), "pricing_state": _pricing_or_none(it.get("pricing_state")), "pricing_text": _text_or_none(it.get("pricing_text")), + # 相关快讯 (2026-09-08 台账 050): 财联社电报点名这只票的条目, 原值给人看, 整句送研判 + # (judge.OPEN_JUDGE_KEYS 收 news_text)。只是上下文, 不判正负, 不进任何判定。 + "news": _news_or_none(it.get("news")), + "news_text": _text_or_none(it.get("news_text")), }) return out @@ -458,7 +484,8 @@ def select_candidates(plan: dict, *, held=(), black=(), top_n: int = 30, tiers=N "logic_state": r.get("logic_state"), "valuation_text": r.get("valuation_text"), "events": r.get("events"), "events_text": r.get("events_text"), - "pricing_state": r.get("pricing_state"), "pricing_text": r.get("pricing_text")} + "pricing_state": r.get("pricing_state"), "pricing_text": r.get("pricing_text"), + "news": r.get("news"), "news_text": r.get("news_text")} for r in passed[:n]] return {"date": plan.get("date"), "considered": len(pool), "eligible": len(passed), "items": items, "dropped": dropped, "st_unknown": st_unknown, diff --git a/app/web/static/index.html b/app/web/static/index.html index 126412a..089cd2c 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -1034,6 +1034,7 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}