按评审修订观察读数包:增持门口径改只数、清早重复无读数行、复核脚本与台账口径一致

按 docs/观察读数包与加固包评审_2026-09-14.md 第五节,改读数口径与文档一致性(非代码缺陷):
- 增持门拦截改看只数:检查点是两分钟窗口,累计次数 rounds 会把同一只翻倍(一天最多
  八次),会误读。复核脚本第二张表用只数,台账 013 口径同改,rounds 只当诊断。
- 映射重建先清当日 tech_noread(consensus_stat_repo 加 delete_day_kind),以最新一次
  为准。06:30 缺读数、08:40 补上的票不再留旧行让逐只无读数偏高(占比用覆盖行算,一直准)。
- 复核脚本第四张表把「合议交人而人采纳」单列成信息行,不计入分歧——交人本就让人定;
  提议上限按天数放大(三十天窗口够用)。
- DEVLOG 09-14 节点修内部不一致:补文档包、从欠账里删掉已完成的 README/CLAUDE.md。
- record_round 的 params 注明暂未用(保留对齐规格书签名)。

第三十一批加一例(映射重建先清当日无读数),开发机全量 847 例 ALL SUITES PASS。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-14 11:07:39 +08:00
parent 845d6f2d0b
commit 94da25bc25
7 changed files with 73 additions and 21 deletions

View File

@ -1483,15 +1483,19 @@ app/services/strategy_runner.py跟踪止盈评估器 _eval_trail 加盘中 SA
## 2026-09-14 · 观察读数包与加固包(开发机完成,未真机判收)
**做了什么**
下一阶段第一优先级是「补观察读数」。做了两个工作包,都在开发机完成并全量单测通过,尚未在 155 部署。
下一阶段第一优先级是「补观察读数」。做了三个不改行为的工作包(观察读数包、加固包、文档包),都在开发机完成并全量单测通过,尚未在 155 部署。
观察读数包:把每轮扫描里合议相关的判定归类落进新表 pms_consensus_stat第 21 张),供台账 006 到 011 复核。之前这些数只活在扫描返回值与页面即时快照里,复核日期到了拿不出数。动作引擎给合议跳过项(跳过分无评析、看空两类,观察打等开口)与增持门(带 fill/add/dca打类别标签只在对应开关开着的分支里打关掉逐字回旧。落表服务 consensus_stats.record_round 按检查点归类落表:转空离场实弹随时写,路由与装配两类只在检查点写(默认 0935,1030,1330,1445一天最多四次试算不写映射重建时 record_map_cover 记覆盖只数与逐只无读数。复核脚本 scripts/consensus_review.py 与 make consensus-review 打出台账要的四个读数,日报加合议观察小节。新参数 PMS_CONSENSUS_STAT_TIMES 控制检查点。
加固包:评审第五节四处小加固。转空去重集只在 SAR 明确为多时才清除(原来「不是空就清」把缺读数也当翻多,会重复减仓),抽出纯函数 _keep_tech_exit_done。持仓行的择时票带上当日盘中转多留痕decorate_positions 从买入信号同一来源取 flip_map。登记 PMS_CONSENSUS_WEAK_CONFIRM默认开关掉则弱表态放行consensus.decide 加 weak_confirm 参数。第二十四批时钟用例改相对当前时刻构造陈旧告警,任何时刻跑都过。
文档包按拍板点五README 与 CLAUDE.md 里「当前是影子模式、没有系统自动下单」改成「下发模式由 PMS_DISPATCH_MODE 决定、以页面顶部横幅为准」,写清模拟仓 155 现为 ws、真实仓 188 尚未追平上线等 QMT 侧、追平先进影子期。CLAUDE.md 入库(原为未跟踪)。本节点即文档包的开发节点记录。
**动了哪些文件**
观察读数包(提交 f2d6b37新增 app/repo/consensus_stat_repo.py、app/services/consensus_stats.py、scripts/consensus_review.py、scripts/test_batch31_units.py改 ddl_pms_v1.sql第 21 张表、app/core/action_engine.py四个 tag 常量与两处打标签、app/services/proposal_service.py_attach_consensus 写 consensus_seen、scan_and_route 末尾调 record_round、app/services/tech_service.pybuild_map 后调 record_map_cover、app/services/ledger_service.py日报小节、app/services/param_store.pyPMS_CONSENSUS_STAT_TIMES、Makefileconsensus-review 目标、scripts/run_tests.py第 31 批登记、scripts/test_batch6_units.pyDDL 表数 20→21、scripts/test_batch10_units.pyrecord_round 进 SOFT_FAIL、docs/复盘决定台账.md台账 013
加固包(提交 e21b73b改 app/core/consensus.py、app/services/consensus_service.py、app/services/param_store.pyPMS_CONSENSUS_WEAK_CONFIRM、app/services/proposal_service.py、scripts/test_batch24_units.py、scripts/test_batch28_units.py加 3 例、scripts/test_batch30_units.py加 3 例、scripts/run_tests.py例数到 846
文档包(提交 b1ebb36改 README.md、CLAUDE.md影子模式说法、CLAUDE.md 入库、DEVLOG.md本节点
评审修订(提交在文档包之后,按 docs/观察读数包与加固包评审_2026-09-14.md 第五节):复核脚本增持门改只数、台账 013 口径同改、复核脚本第四张表把「交人被采纳」单列信息行、提议上限按天数放大record_map_cover 先清当日 tech_noreadconsensus_stat_repo 加 delete_day_kind以最新一次重建为准本节点修了内部不一致。
**部署方式**
改了 Pythonmake deploy 重建镜像加 force-recreate并建新表 pms_consensus_stat。收盘后在 155 部署先经用户审批本包重建容器、加新表按服务器铁律先列命令拿批准。命令git pull --ff-only && make deploy && make test预期 ALL SUITES PASS、建表输出 21 张含 pms_consensus_stat。观察读数包与加固包一起部署。
@ -1500,7 +1504,7 @@ app/services/strategy_runner.py跟踪止盈评估器 _eval_trail 加盘中 SA
未判收。开发机全量单测 846 例 ALL SUITES PASS新增第 31 批 20 例,第 28 批到 40、第 30 批到 36。155 部署待收盘后。判收要看:部署下一个交易日 09:37 后 make consensus-review DAYS=1 打出当天读数(第一天转空离场应为零);映射重建后 pms_consensus_stat 有 map_cover 行;台账 006 到 009 的第一次正式复核在 09-17。
**还欠着什么**
一,收盘后在 155 部署观察读数包与加固包并真机判收判收读数回填到本节点。二文档包其余项随对应工作包做README 影子模式过时说法要改拍板点五安全相关待用户认框架、CLAUDE.md 入库、台账 012盘中确认随盘中确认包014真实仓初始参数随真实仓准备包、接口契约单票研究面随页面收尾包。三盘中确认包、页面收尾包、分段启用包按规格书第四节在 09-17 起推进。四,今天 09:40 那一跳(策略层已确认有意打开)的摘要盘后要拉给用户判读。
一,收盘后在 155 部署这三个包并真机判收,判收读数回填到本节点。评审建议 15:40 之后部署,避开 15:10/15:20/15:30 三个日结调度位。二,文档包其余项随对应工作包做:台账 012盘中确认随盘中确认包014真实仓初始参数随真实仓准备包、接口契约单票研究面随页面收尾包。三盘中确认包、页面收尾包、分段启用包按规格书第四节在 09-17 起推进;盘中确认包动工前要拍板点第二条的答复,拍板点第三、四条仍待答复。四,今天 09:40 那一跳(策略层已确认有意打开)的摘要盘后要拉给用户判读。
---
<!--

View File

@ -8,7 +8,7 @@
"""
from __future__ import annotations
from app.db.session import execute_many, fetch_all
from app.db.session import execute, execute_many, fetch_all
# 落表的业务列 (与 ddl_pms_v1.sql 的 pms_consensus_stat 对齐)。
# rounds 不在插入列里 —— 插入时用 DDL 的 DEFAULT 1, 重复时在更新子句里 +1。
@ -53,6 +53,14 @@ def list_range(date_from: int, date_to: int, kinds=None) -> list:
f"ORDER BY stat_date ASC, kind ASC, ts_code ASC", p)
def delete_day_kind(stat_date: int, kind: str) -> int:
"""删某北京日期某类别的全部行, 返回删除行数。映射重建时先清当日 tech_noread ——
早上两次重建 (06:30/08:40) 各写一遍逐只无读数, 06:30 缺读数08:40 补上的票那一行会留着,
先清再写即以最新一次为准 (2026-09-14 评审第三条)"""
return execute("DELETE FROM pms_consensus_stat WHERE stat_date = :d AND kind = :k",
{"d": int(stat_date), "k": str(kind)})
def count_by_kind(date_from: int, date_to: int) -> dict:
"""[date_from, date_to] 内按 (日期, 类别) 的计数, 返回 {stat_date: {kind: {rows, rounds}}}。
rows 是行数 (几只票), rounds 是累计记到的次数 (同一票记了几轮)"""

View File

@ -157,7 +157,9 @@ def record_round(out, scanned, params, now=None) -> dict:
"""一轮扫描的合议判定归类落表。调用点在 proposal_service.scan_and_route 末尾 (去重集写回之后)。
tech_exit 随时写 (稀有实弹); 路由与装配两类只在检查点写 (每分钟太密); dry_run 一律不写
返回 {ok, rows, ...}; 失败只记警告不抛 登记进 test_batch10 SOFT_FAIL, 调用点要接住"""
返回 {ok, rows, ...}; 失败只记警告不抛 登记进 test_batch10 SOFT_FAIL, 调用点要接住
params 暂未用到 (检查点从参数中心直接读), 保留是为对齐规格书签名, 也留给后续包备用"""
now = now or datetime.now()
if not isinstance(out, dict) or out.get("dry_run"):
return {"ok": True, "rows": 0, "note": "dry_run 或无 out, 不落表"}
@ -175,6 +177,13 @@ def record_map_cover(*, relevant_codes, state_codes, now=None) -> dict:
调用点在 tech_service.build_map 写完映射之后失败只记警告返回 ok 为假, 不拦建映射"""
now = now or datetime.now()
day = int(now.strftime("%Y%m%d"))
# 先清当天旧的 tech_noread 行 (2026-09-14 评审第三条): 早上 06:30 与 08:40 各重建一次映射,
# 06:30 缺读数、08:40 补上的票, 早上那一行本会留着让逐只无读数偏高。先清再写即以最新一次
# 重建为准。map_cover 行走 upsert 覆盖不受影响, 占比一直准。清失败只记警告, 不拦覆盖行落表。
try:
consensus_stat_repo.delete_day_kind(day, K_TECH_NOREAD)
except Exception as e: # noqa: BLE001
logger.warning("[合议观察] 清当日 tech_noread 失败 (逐只无读数可能偏高, 占比不受影响): %s", e)
rel = [c for c in (relevant_codes or []) if c]
have = set(state_codes or [])
rows = [_row(day, "*", K_MAP_COVER,

View File

@ -198,7 +198,7 @@
**依据。** 没有读数就没有复核,没有复核就不能把这套东西部署到真实仓。这是评审认定的下一阶段第一优先级。落表只加不改行为:失败只记警告、不抛、不拦扫描;开关关掉时动作引擎那几处标签一行都不打,逐字回旧。
**口径。** 技术面无读数占比用映射覆盖那一行算,等于相关票数减覆盖只数再除以相关票数。增持门拦截次数用累计次数算,不是行数。转空离场的确认与否按动作名判,确认转空清仓走 EXIT未确认减三分之一走 TRIM。合议与人工裁决的比对只看带合议六键且已被人裁决的提议,合议看多而人驳回、合议交人而人采纳的逐只列出
**口径。** 技术面无读数占比用映射覆盖那一行算,等于相关票数减覆盖只数再除以相关票数。增持门拦截看只数,也就是当天被拦的持仓只数;检查点是两分钟窗口,累计次数会把同一只翻倍,只当诊断用,不当拦截次数。转空离场的确认与否按动作名判,确认转空清仓走 EXIT未确认减三分之一走 TRIM。合议与人工裁决的差异分两类:真分歧是合议看多而人驳回,逐只列出;合议交人而人采纳只是信息行,交人本就是让人定,不算分歧
**参数。** 落表检查点 PMS_CONSENSUS_STAT_TIMES 默认 0935,1030,1330,1445四位时刻逗号分隔页面可改。设为空串即不写检查点行只留转空离场实弹。

View File

@ -114,8 +114,8 @@ def main():
print(" 窗口内没有观察读数 —— 观察读数包还没部署, 或映射还没重建过。")
# ---- 二、逐日路由分布 ----
print("\n【二】逐日合议路由 (等开口台账线: 每天 1 到 15 只)")
print(" 日期 等开口 等盘中确认 跳过·无评析 跳过·看空 交人 放行 增持门拦")
print("\n【二】逐日合议路由 (只数; 等开口台账线: 每天 1 到 15 只)")
print(" 日期 等开口 等盘中确认 跳过·无评析 跳过·看空 交人 放行 增持门拦(只)")
for d in _dates_in(counts):
c = counts.get(d) or {}
@ -127,7 +127,9 @@ def main():
sb = _n(cst.K_OPEN_SKIP_BEAR)
cf = _n(cst.K_OPEN_CONFIRM)
ps = _n(cst.K_OPEN_PASS)
gb = (c.get(cst.K_GATE_BLOCK) or {}).get("rounds", 0) # 拦截次数用累计次数
# 增持门看只数 (当天被拦的持仓只数)。检查点是两分钟窗口, 累计次数 rounds 会把同一只翻倍
# (一个检查点记两次、一天四个点最多八次), 只当诊断用, 不当拦截次数 (2026-09-14 评审第一条)。
gb = _n(cst.K_GATE_BLOCK)
flag = " ⚠ 等开口超 15" if wt > 15 else ""
print(" %-8s %5d %8d %9d %8d %4d %4d %6d%s"
% (d, wt, wc, sf, sb, cf, ps, gb, flag))
@ -145,15 +147,17 @@ def main():
% (r["stat_date"], r["ts_code"], conf, d.get("qty"),
r.get("rounds"), (r.get("reason") or "")[:40]))
# ---- 四、合议方向与人工裁决比对 ----
print("\n【四】合议方向与人工裁决对不上的 (合议看多而你驳回 / 合议交人而你采纳)")
# ---- 四、合议与人工裁决的差异 ----
print("\n【四】合议与人工裁决的差异")
# 上限按天数放大 (2026-09-14 评审第四条): 三天窗口五百条够用, 三十天可能不够。
_lim = min(5000, max(500, int(args.days) * 200))
try:
props = pms_repo.list_proposals(statuses=(ACCEPTED, DECLINED), limit=500,
props = pms_repo.list_proposals(statuses=(ACCEPTED, DECLINED), limit=_lim,
include_archived=True)
except Exception as e: # noqa: BLE001
print(f" ✗ 提议表读取失败: {type(e).__name__}: {e}")
props = []
mismatches = []
divergences, handoff_ok = [], []
for p in props:
decided = str(p.get("decided_at") or "")[:10].replace("-", "")
if not decided or not (str(a) <= decided <= str(b)):
@ -164,13 +168,22 @@ def main():
continue # 没有合议六键的提议 (旧提议), 不比对
status = p.get("status")
if direction == "看多" and status == DECLINED:
mismatches.append((decided, p.get("ts_code"), "合议看多, 你驳回了", con))
divergences.append((decided, p.get("ts_code"), con))
elif route == "交人" and status == ACCEPTED:
mismatches.append((decided, p.get("ts_code"), "合议交人, 你采纳了", con))
if not mismatches:
print(" 窗口内没有对不上的 (或还没有带合议六键、已被裁决的提议)。")
for d, code, why, con in sorted(mismatches):
print(" %-8s %-10s %s (合议: %s)" % (d, code, why, con.get("reason") or ""))
handoff_ok.append((decided, p.get("ts_code"), con))
# 真分歧: 合议看多而人驳回。这才是台账要盯的「方向对不上」。
print(" 真分歧 (合议看多而你驳回):")
if not divergences:
print(" 无 (或还没有带合议六键、已被裁决的提议)。")
for d, code, con in sorted(divergences):
print(" %-8s %-10s (合议: %s)" % (d, code, con.get("reason") or ""))
# 信息行: 合议交人而人采纳。交人本就是让人定, 人采纳不是分歧 (2026-09-14 评审第二条),
# 单列出来是看合议是不是偏保守 (交人后多半被放行则可考虑放宽)。
print(" 信息行 (合议交人而你采纳; 交人本就让你定, 不算分歧):")
if not handoff_ok:
print(" 无。")
for d, code, con in sorted(handoff_ok):
print(" %-8s %-10s (合议: %s)" % (d, code, con.get("reason") or ""))
print("\n复核口径提醒: 本报告只陈述读数, 阈值调整要先回台账 006 到 011 写一条, "
"不按单次复盘读数回调 (原则一)。")

View File

@ -92,17 +92,17 @@
开关关掉不打标签)/record_round 归类落表 (检查点写路由与装配两类
转空实弹随时写试算不写被闸拒不记confirm 据动作名空串检查点)/
record_map_cover 记覆盖与逐只无读数/日报小节失败不拖垮/upsert 更新子句
只用 VALUES 列不带绑定参数且单表合规/参数登记与检查点窗口 (20 )
只用 VALUES 列不带绑定参数且单表合规/参数登记与检查点窗口/映射重建先清当日无读数 (21 )
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 )
test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) +
目标价到价必定入队 (档位 full 也不自动卖) +
用户设的止损价与目标价单独成列显示 (70 )
846
847
(总数按实跑逐批相加校正过两次: 曾写 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)
09-14 观察读数包第三十一批二十例到 840; 加固包给第二十八批加三例第三十批加三例846; 评审修订第三十一批加一例, 现为 847)
任一子集失败即整体失败 (退出码 1)
哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了):

View File

@ -74,15 +74,18 @@ class _patched:
def __enter__(self):
self._o_get = cst.param_store.get
self._o_up = cst.consensus_stat_repo.upsert_many
self._o_del = cst.consensus_stat_repo.delete_day_kind
cst.param_store.get = lambda k, d=None: (self.times if k == cst.STAT_TIMES_KEY
else self._o_get(k, d))
cst.consensus_stat_repo.upsert_many = lambda rows: (self.captured.extend(rows)
or len(rows))
cst.consensus_stat_repo.delete_day_kind = lambda d, k: 0 # 离线不打库
return self.captured
def __exit__(self, *a):
cst.param_store.get = self._o_get
cst.consensus_stat_repo.upsert_many = self._o_up
cst.consensus_stat_repo.delete_day_kind = self._o_del
return False
@ -285,6 +288,21 @@ def _():
assert {x["ts_code"] for x in bk["tech_noread"]} == {"600002.SH"}
@case("C record_map_cover·重建先清当日 tech_noread (以最新一次为准, 评审第三条)")
def _():
deleted, upserted = [], []
o_del, o_up = csr.delete_day_kind, csr.upsert_many
csr.delete_day_kind = lambda d, k: (deleted.append((d, k)), 0)[1]
csr.upsert_many = lambda rows: (upserted.extend(rows), len(rows))[1]
try:
cst.record_map_cover(relevant_codes=["600000.SH"], state_codes=set(),
now=datetime(2026, 9, 15, 8, 40))
finally:
csr.delete_day_kind, csr.upsert_many = o_del, o_up
assert (20260915, cst.K_TECH_NOREAD) in deleted # 先清当日 tech_noread
assert any(r["kind"] == cst.K_MAP_COVER for r in upserted) # 再写覆盖行
# ================================================================ D 仓库 upsert
@case("D upsert_many·更新子句只用 VALUES 列不带绑定参数; SQL 单表合规")
def _():