diff --git a/app/services/plan_feed.py b/app/services/plan_feed.py index cb22d2c..40a99c8 100644 --- a/app/services/plan_feed.py +++ b/app/services/plan_feed.py @@ -367,6 +367,11 @@ def parse_plan(payload, *, requested=None) -> dict: # 这两项是上游自己的版本戳; 旧版应答没有, 缺就是 None (名册快照那套指纹照旧管用)。 "plan_version": _text_or_none(payload.get("plan_version")), "generated_at": _text_or_none(payload.get("generated_at")), + # 计划来源与新鲜度、赛道硬门槛 (2026-09-14 §5.3): 接口读快照还是现算 (snapshot/live)、 + # 快照生成时刻、赛道闸开没开。旧版应答没有这三项, 缺就是 None。页面顶栏「上游计划」芯片显示。 + "plan_source": _text_or_none(payload.get("plan_source")), + "snapshot_generated_at": _text_or_none(payload.get("snapshot_generated_at")), + "gate_on": (bool(payload.get("gate_on")) if payload.get("gate_on") is not None else None), "heat_date": _text_or_none(payload.get("heat_date")), "market_snapshot_days": [str(x) for x in (payload.get("market_snapshot_days") or [])], "theme_cap": _int_or_none(payload.get("theme_cap")), @@ -876,7 +881,8 @@ def snapshot(plan: dict, *, keep: int = 200) -> dict: digest = pdf.digest_of(roster) meta = {k: plan.get(k) for k in ("counts", "returned", "requested", "funnel", "theme_cap", "heat_date", "market_snapshot_days", - "age_tdays", "url")} + "age_tdays", "url", + "plan_source", "snapshot_generated_at", "gate_on")} truncated = plan.get("truncated") or {} n = pms_repo.insert_plan_snapshot( plan_date=plan.get("date"), digest=digest, roster=pdf.roster_rows(roster), meta=meta, diff --git a/scripts/run_tests.py b/scripts/run_tests.py index e32059e..968b1b4 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -16,7 +16,8 @@ test_batch6_units.py ws 通道: 测试向量/签名/公钥/水位/弃洞/DDL/逐笔入账 (68 例) test_batch7_units.py 上游选股计划: 解析/新鲜度/候选筛选/取数守卫/ 候选卡五键透传与截断/仅展示在候选阶段剔除/ - 研究理由两键透传与不进送研判名单 (36 例) + 研究理由两键透传与不进送研判名单 + + 来源字段 plan_source/snapshot_generated_at/gate_on (§5.3) (38 例) test_batch8_units.py 榜单变化: 名册指纹/三种语义/尾部闸/落库往返 (60 例) test_batch9_units.py 成本价体检 / 对账按日推进 / 行业闸 / 取整记账 (49 例) test_batch10_units.py 静默失败专项: 关键路径不许丢返回值 + 八条实例 + @@ -116,7 +117,8 @@ (总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条 减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 到 677; 参考目标价一例, 到 678; 催化事件与定价状态透传一例, 到 679; 两个期限的头一例, 现为 680; 09-10 建议档位对齐第二十六批十七例到 697; 09-11 技术面接入工作包一第二十七批二十七例到 724; 三源合议工作包二纯逻辑第二十八批三十七例到 763; 接入下单链路第二十九批二十四例到 787; 工作包三离场纪律第三十批十八例到 805; part3 盘中 SAR 止损线补九例到 814; part4 弱基本面紧止盈补六例到 820; 09-14 观察读数包第三十一批二十例到 840; 加固包给第二十八批加三例、第三十批加三例到 846; 评审修订第三十一批加一例到 847; 09-14 盘中确认包第三十二批十九例到 866; 页面收尾包第三十三批五例到 871; - 09-14 系统自决包第三十四批二十二例、test_wiring 目标价自动清仓加一例, 现为 894) + 09-14 系统自决包第三十四批二十二例、test_wiring 目标价自动清仓加一例到 894; + 09-14 选股打分包 §5.3 来源字段给第七批加两例, 现为 896) 任一子集失败即整体失败 (退出码 1)。 哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了): diff --git a/scripts/test_batch7_units.py b/scripts/test_batch7_units.py index e49b129..01a32bd 100644 --- a/scripts/test_batch7_units.py +++ b/scripts/test_batch7_units.py @@ -115,6 +115,28 @@ def _(): for k in ("basis", "logic"): assert k in r and r[k] is None, (k, r.get(k)) assert p["plan_version"] is None and p["generated_at"] is None + # 来源三键 (2026-09-14 §5.3): 旧样例没有, 一律 None (缺就是 None, 不臆造) + assert p["plan_source"] is None and p["snapshot_generated_at"] is None + assert p["gate_on"] is None + + +@case("解析·来源字段 (§5.3): plan_source / snapshot_generated_at 原样带出") +def _(): + d = {"date": "2026-09-14", "main": SAMPLE["main"], "observe": [], + "plan_source": "snapshot", "snapshot_generated_at": "2026-09-14T06:31:07", + "gate_on": True} + p = pf.parse_plan(d) + assert p["plan_source"] == "snapshot", p["plan_source"] + assert p["snapshot_generated_at"] == "2026-09-14T06:31:07", p["snapshot_generated_at"] + + +@case("解析·赛道闸 gate_on 布尔归一: 真为 True、缺为 None (不当成 False)、现算来源 live") +def _(): + assert pf.parse_plan({"date": "2026-09-14", "main": SAMPLE["main"], "observe": [], + "gate_on": True})["gate_on"] is True + p2 = pf.parse_plan({"date": "2026-09-14", "main": SAMPLE["main"], "observe": [], + "plan_source": "live"}) + assert p2["plan_source"] == "live" and p2["gate_on"] is None, p2 @case("解析·研究理由两键: 原样透传 / 论断截到三条 / 缺键为 None / 绝不进送研判的名单")