From fbad890b7d8d7c9989dd87bf7e4795b7f794ce06 Mon Sep 17 00:00:00 2001 From: zlt Date: Mon, 7 Sep 2026 15:47:27 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E4=BB=B6=20PMS=20=E4=BE=A7?= =?UTF-8?q?=EF=BC=9A=E5=AE=89=E5=85=A8=E8=BE=B9=E9=99=85=E6=95=B4=E5=8F=A5?= =?UTF-8?q?=E9=9A=8F=E5=80=99=E9=80=89=E9=80=8F=E4=BC=A0=E5=88=B0=E7=A1=AC?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E4=B8=8E=E6=8F=90=E8=AE=AE=E5=8D=A1=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E9=80=81=E7=A0=94=E5=88=A4=EF=BC=88=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E5=8D=81=E4=BA=8C=E6=89=B9=E5=8A=A0=E4=B8=80=E4=BE=8B=EF=BC=8C?= =?UTF-8?q?=E5=85=A8=E5=A5=97=20677=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/action_engine.py | 2 ++ app/services/plan_feed.py | 6 +++++- scripts/run_tests.py | 6 +++--- scripts/test_batch22_units.py | 20 ++++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/core/action_engine.py b/app/core/action_engine.py index 20b6eef..92d50d0 100644 --- a/app/core/action_engine.py +++ b/app/core/action_engine.py @@ -687,6 +687,8 @@ def eval_open(c: dict, params: dict, caps: dict, room_amt: float): # 逻辑状态四态 (2026-09-07 第三件): 入场那一刻「研究证据还在不在」的读数, 进账本与提议卡, # 持仓视图的「当初为什么买」从这里回溯; 不送研判 (judge.OPEN_JUDGE_KEYS 不收它)。 "logic_state": c.get("logic_state"), + # 安全边际三情景的整句 (2026-09-07 第四件): 只给人看, 不送研判。 + "valuation_text": c.get("valuation_text"), # ---- 仓位口径: 只进评审账本做判分锚。judge.py 送研判时会把这几项过滤掉, # 理由见那边的 OPEN_JUDGE_KEYS —— 决策系统本来就不管仓位, 别送过去带偏它。 "target_pct": target_pct, "target_amount": round(full_amt, 2), diff --git a/app/services/plan_feed.py b/app/services/plan_feed.py index e3d7ab6..551070e 100644 --- a/app/services/plan_feed.py +++ b/app/services/plan_feed.py @@ -222,6 +222,9 @@ def _rows(raw, bucket: str) -> list: "logic": _list_or_none(it.get("logic"), limit=LOGIC_KEEP), # 逻辑状态四态 (2026-09-07 第三件): 跟着候选进硬数字, 不送研判 (judge.OPEN_JUDGE_KEYS 不收)。 "logic_state": _logic_state_or_none(it.get("logic_state")), + # 安全边际三情景的整句 (2026-09-07 第四件): 上游按盈利预测算的悲观/中性/乐观估值与赔率, + # 只给人看 (提议卡与账本), 不参与任何判定, 也不送研判。 + "valuation_text": _text_or_none(it.get("valuation_text")), }) return out @@ -413,7 +416,8 @@ def select_candidates(plan: dict, *, held=(), black=(), top_n: int = 30, tiers=N "basis": r.get("basis"), "logic": r.get("logic"), # 逻辑状态 (2026-09-07 第三件): 动作引擎放进硬数字; 判决候选而逻辑存疑的在 scan_open # 里强制交人。它不改判决、不改排序 —— 收敛规则是单调的 (选股系统 logic_state.apply_to_card)。 - "logic_state": r.get("logic_state")} + "logic_state": r.get("logic_state"), + "valuation_text": r.get("valuation_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/scripts/run_tests.py b/scripts/run_tests.py index 38f1745..22b9344 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -59,16 +59,16 @@ test_batch22_units.py 逻辑状态四态接入 (2026-09-07 第三件): 解析层收逻辑状态/硬数字带它而研判 白名单不收/判决候选而逻辑存疑强制交人/持仓存疑停增持侧/研究走弱 减持默认关且开了必交人/同轮减持优先级/早上取回与映射新鲜度/策略 - 买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名 (15 例) + 买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名/安全边际整句透传 (16 例) test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许 直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象 打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例) test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) + 目标价到价必定入队 (档位 full 也不自动卖) + 用户设的止损价与目标价单独成列显示 (70 例) - 共 676 例 + 共 677 例 (总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条 - 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 现为 676) + 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 现为 677) 任一子集失败即整体失败 (退出码 1)。 哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了): diff --git a/scripts/test_batch22_units.py b/scripts/test_batch22_units.py index 03da209..52c2929 100644 --- a/scripts/test_batch22_units.py +++ b/scripts/test_batch22_units.py @@ -201,6 +201,26 @@ def _(): assert "logic_state" not in jd.OPEN_JUDGE_KEYS +@case("安全边际整句·解析层收 valuation_text, 候选与硬数字带它, 研判白名单不收 (第四件, 只给人看)") +def _(): + txt = "安全边际(2026 年度预测,9 家):悲观 42.64 元(-15.0%)、中性 56.75 元(+13.1%);赔率 0.87 比 1" + row = pf._rows([{"code": "600000.SH", "rank": 1, "score": 210, "valuation_text": txt}], "main")[0] + assert row["valuation_text"] == txt, row + assert pf._rows([{"code": "600000.SH", "rank": 1, "score": 210, "valuation_text": ""}], "main")[0]["valuation_text"] is None + plan = {"date": "2026-09-04", "main": [row], "observe": []} + item = pf.select_candidates(plan, top_n=10)["items"][0] + assert item["valuation_text"] == txt, item + c = {**item, "price": 10.0} + params = {"scale": 1_000_000, "stock_target_default": 0.06, "batch_split": (0.5, 0.25, 0.25)} + with _Patch() as p: + p(ae, "check_all_caps", lambda **kw: []) + p(ae, "_new_name_ctx", lambda caps, c: caps) + cand, why = ae.eval_open(c, params, {"names_count": 0, "max_names": 10}, 500_000) + assert cand and cand["hard_numbers"]["valuation_text"] == txt, why + assert "valuation_text" not in jd._judge_hard_numbers("OPEN", cand["hard_numbers"]) + assert "valuation_text" not in jd.OPEN_JUDGE_KEYS + + # ================================================================ 二, 新建仓与持仓的分流 def _scan_open(cands, *, route_logic=True): params = {"scale": 1_000_000, "stock_target_default": 0.06, "batch_split": (0.5, 0.25, 0.25),