From 0be832d579b3e1372d285d8845b1f4c1314d489b Mon Sep 17 00:00:00 2001 From: claude_dev Date: Tue, 29 Sep 2026 07:50:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(monitor+xt=5Feod):=200929=20=E6=99=A8?= =?UTF-8?q?=E5=A4=8D=E7=9B=98=E4=B8=89=E4=BF=AE=E2=80=94=E2=80=94ETF=20?= =?UTF-8?q?=E6=BC=8F=E9=87=87=E5=AE=88=E5=8D=AB+mirror=20=E5=81=87?= =?UTF-8?q?=E9=BB=84=E6=A0=B9=E4=BF=AE+seats=20=E5=B8=A6=E6=A0=A1=E5=87=86?= =?UTF-8?q?=20[vps]=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ①xt_eod.py ETF 清单空 rc=1 抬码: 昨夜 21:00 桥板块接口死(universe 2266→0 + redis rpc timeout), bj_codes(本地 sqlite 恒 39)掩护合并空守卫→rc=0 静默漏采, 0928 全 ETF 停 0924(监控 dbbardata_daily RED 首战抓到); 修=ETF 清单空仍写北交所但 rc=1, schtask last_result=1 触发告警; 当晨已重跑 sanguo-xt-daily 补 0928 ETF 窗口(幂等 INSERT OR REPLACE) ②nas_monitor_check vps-mirror 连黄根修: push.log DONE 行只带 [HH:MM:SS] 不带日期, 旧按 want_day 过滤行永剩 start 行(0926~28 连黄实锤, 实际镜像日日 DONE fail=0); 改按 want_day 的 start 段内出现 DONE fail=0 判(+回归测试) ③dragon_tiger_seats 带 (0,400)→(200,2000): 实测 670~810, 旧上限真交易日必黄(0928 670 黄实案) runbook 监控注记同步(xt rc=1 分诊+mirror 修+带校准); tests 620 绿 Co-Authored-By: Claude Code --- docs/deployment/vps-production-runbook.md | 2 +- scripts/data_platform/monitor_registry.py | 2 +- scripts/data_platform/xt_eod.py | 8 ++++++ scripts/nas_sync/nas_monitor_check.py | 18 ++++++++++--- tests/data_platform/test_nas_monitor_check.py | 27 +++++++++++++++++++ 5 files changed, 52 insertions(+), 5 deletions(-) diff --git a/docs/deployment/vps-production-runbook.md b/docs/deployment/vps-production-runbook.md index 65797aa2..b60be41a 100644 --- a/docs/deployment/vps-production-runbook.md +++ b/docs/deployment/vps-production-runbook.md @@ -87,7 +87,7 @@ | sanguo-strategy-monitor-morn | 每日 08:05 | `strategy_monitor_wrapper.ps1 -Shift morn`(盘前引擎面:期望数/进程数/会话日零重启§11.0/报错风暴/日志新鲜度;08:05 relogin 后 85min) | 策略监控 | | sanguo-strategy-monitor-eve | 每日 21:30 | 同 wrapper -Shift eve(盘后产物+订单链:IS 日报出跑/恒等式 resid/订单残留/成交 vs 台账/周报出跑;错峰 20:45 日报+21:00 xt_eod 之后、23:50 data-eve 之前;infra 09-28 核批无碰撞) | 策略监控 | -> 监控注记(spec §20.15):监控 wrapper 恒 exit 0(红态由 alerts/health 承载,防污染调度层自检的 Last Result 判读);NAS 监控 v2 挂 `sanguo-vps-mirror` 16:00 班尾(`/volume1/stock/monitor_nas/run_monitor_nas.sh`→事件 JSON scp 至 VPS `data/monitor/inbox/`,VPS 两班摄取落库 host=nas;**runner 属宿主生效位,CI 不覆盖——改 repo 副本后须 `scp -O` 同步 `/volume1/stock/monitor_nas/`**,09-28 已同步);告警消费入口=前端「运维告警」页 `/ops-alerts`(四类 tab+ack,API `GET /api/v1/alerts`)。NAS 检查器语义(**09-28 三修校准**):dmsk=季频披露驱动域,改**季频锚**(最新 dt= 分区 ≥ 最近已过法定披露截止的季末:Q1→04-30/半年→09-30/三季→10-31/年报→次年 04-30;旧 mtime 80h 对季频必假黄,0927 Q2 水位 06-30 停滞实案);vps-mirror 完成度在 16:00 班前查昨日 DONE 行(早班查当日必空)。VPS 侧日历=确认集(dbbardata 000300)∪近 15 日工作日假定集——未确认日缺失降黄、确认日缺失才红(治 dbbardata 晚间源滞后塌期望的循环依赖,09-25 首跑实案);**row-band 豁免按文件日期(latest)判**(09-28 修:lag 型 margin/margin_sse 的 exp=ok[-1-lag]=确认日,旧按 exp 判对闸门空写非确认日 0 行文件永不豁免);**schtask weekday 限 72h**(09-28 修:weekday 任务周五晚→周一晚班次跨度 69h,26h 限周一早班必假黄;真死 >72h 仍黄+next_run=N/A red 兜底;bs-daily `last_result=1`=eod 腿 baostock 网络错等**真源故障照常黄,非口径问题**)。事件摄取=NAS events 文件带 `green_keys` 回传(VPS 据此 resolve NAS 告警);过程层日志窗=26h(防旧失败每日重炒)。 +> 监控注记(spec §20.15):监控 wrapper 恒 exit 0(红态由 alerts/health 承载,防污染调度层自检的 Last Result 判读;**例外=`sanguo-xt-daily` 09-29 起 ETF/基金清单空时 rc=1 抬码**——桥板块接口死(redis rpc timeout)时 bj_codes(本地 sqlite 恒 39)掩护旧合并守卫致 rc=0 静默漏采 ETF 一晚[0928 全 ETF 停 0924 实案,靠 dbbardata_daily RED 抓到],现 ETF 清单空→仍写北交所但 rc=1,分诊见 last_result=1+日志「FATAL ETF/基金 universe EMPTY」);NAS 监控 v2 挂 `sanguo-vps-mirror` 16:00 班尾(`/volume1/stock/monitor_nas/run_monitor_nas.sh`→事件 JSON scp 至 VPS `data/monitor/inbox/`,VPS 两班摄取落库 host=nas;**runner 属宿主生效位,CI 不覆盖——改 repo 副本后须 `scp -O` 同步 `/volume1/stock/monitor_nas/`**,09-28 已同步);告警消费入口=前端「运维告警」页 `/ops-alerts`(四类 tab+ack,API `GET /api/v1/alerts`)。NAS 检查器语义(**09-28 三修校准**):dmsk=季频披露驱动域,改**季频锚**(最新 dt= 分区 ≥ 最近已过法定披露截止的季末:Q1→04-30/半年→09-30/三季→10-31/年报→次年 04-30;旧 mtime 80h 对季频必假黄,0927 Q2 水位 06-30 停滞实案);vps-mirror 完成度在 16:00 班前查昨日 DONE 行(早班查当日必空;**09-29 修:push.log 的 DONE 行只带 [HH:MM:SS] 不带日期,旧按日期过滤行永剩 start→连黄[0926~28 实锤],改按 want_day 的 start 段内是否出现 DONE fail=0 判**)。VPS 侧日历=确认集(dbbardata 000300)∪近 15 日工作日假定集——未确认日缺失降黄、确认日缺失才红(治 dbbardata 晚间源滞后塌期望的循环依赖,09-25 首跑实案;09-29 首战立功:0928 ETF 缺写即被此规则抓红);**row-band 豁免按文件日期(latest)判**(09-28 修:lag 型 margin/margin_sse 的 exp=ok[-1-lag]=确认日,旧按 exp 判对闸门空写非确认日 0 行文件永不豁免);row-band 数值=经验带(dragon_tiger_seats 09-29 校准 (0,400)→(200,2000),实测 670~810);**schtask weekday 限 72h**(09-28 修:weekday 任务周五晚→周一晚班次跨度 69h,26h 限周一早班必假黄;真死 >72h 仍黄+next_run=N/A red 兜底;bs-daily `last_result=1`=eod 腿 baostock 网络错等**真源故障照常黄,非口径问题**)。事件摄取=NAS events 文件带 `green_keys` 回传(VPS 据此 resolve NAS 告警);过程层日志窗=26h(防旧失败每日重炒)。 > **策略面监控注记(2026-09-28 v1 上线,方案档=docs/superpowers/plans/2026-09-28-strategy-ops-monitor.md)**:`scripts/strategy_monitor/` 三件套照数据轨——schtask 本机跑(**不经 ssh,生产读权限闸门问题根治**)+只读体检不修数据+wrapper 恒 exit 0+`source="strategy"` 写同一 alerts.db(API 零改动;前端 /ops-alerts 类别=数据/策略)。十检两班:morn=引擎期望数/进程数(wmic needle 自匹配防)/会话日零重启(§11.0 自动化)/日志报错风暴/新鲜度;eve=IS 日报出跑/恒等式 resid≤1e-4/order_events 无残留/成交 vs 台账双对账(容差 0.01 元)/周报出跑;noon 班不注册按需手动。health 落 `data/monitor/health_strategy_{date}_{shift}.json`+`health_strategy_latest.json`(**health_latest.json 是数据域的,永不写**);绿班幂等 resolve_passed 消解(sw_industry 悬死案同纪律);交易日历=确认集∪工作日假定同款语义(`interval='d'`)。阈值初值跑两周后校准(数据轨同款纪律)。schtask 注册=`register_strategy_monitor_schtasks.ps1` 随 promote 上 VPS 后由 infra 执行一次(工法照 factor_monthly register 先例)。 diff --git a/scripts/data_platform/monitor_registry.py b/scripts/data_platform/monitor_registry.py index adc14b9e..e5f85a2f 100644 --- a/scripts/data_platform/monitor_registry.py +++ b/scripts/data_platform/monitor_registry.py @@ -39,7 +39,7 @@ _PER_DATE = [ _pd("hot_rank", (99, 101), ready="18:30", producer="nas"), _pd("margin", (200, 4000), ready="21:00", lag=1), _pd("margin_sse", (200, 4000), ready="21:00", lag=1), - _pd("dragon_tiger_seats", (0, 400)), + _pd("dragon_tiger_seats", (200, 2000)), # 实测 670~810(0923~28); 旧(0,400)上限低致真交易日必黄(0928 670 实案) _pd("nb_daily", (30, 150)), ] diff --git a/scripts/data_platform/xt_eod.py b/scripts/data_platform/xt_eod.py index 8f552161..f3115839 100644 --- a/scripts/data_platform/xt_eod.py +++ b/scripts/data_platform/xt_eod.py @@ -91,6 +91,11 @@ def main(): if not u: log("FATAL empty universe (miniQMT 未连?)") return 2 + # 0929 修: 桥板块接口死(universe 2266→0, redis rpc timeout)时 bj_codes + # (本地 sqlite 恒 39)掩护合并守卫 → rc=0 静默漏采 ETF 一整晚(0928 全 + # ETF 停 0924 实案, 靠监控 dbbardata_daily RED 才抓到). ETF 清单空仍写 + # bj, 但 rc=1 抬出码 → schtask last_result=1 触发监控告警. + etf_empty = not etf_codes if args.limit: u = u[:args.limit] @@ -168,6 +173,9 @@ def main(): log(f"DONE ok={ok} empty={empty} fail={fail} rows={rows}" f"{' [DRY-RUN]' if args.dry_run else ''}") + if etf_empty: + log("FATAL ETF/基金 universe EMPTY (桥板块接口死?) — 本班只写北交所, rc=1") + return 1 return 0 diff --git a/scripts/nas_sync/nas_monitor_check.py b/scripts/nas_sync/nas_monitor_check.py index 0c5c55a6..f4a0fb57 100644 --- a/scripts/nas_sync/nas_monitor_check.py +++ b/scripts/nas_sync/nas_monitor_check.py @@ -151,13 +151,25 @@ def build_checks(now): # (09-26 实证: 早班查当日必空=假黄) want_day = now.strftime("%Y-%m-%d") if now.hour >= 17 else \ (now - dt.timedelta(days=1)).strftime("%Y-%m-%d") + # 0929 修: push.log 里 DONE 行只带 [HH:MM:SS] 不带日期(只有 start 行 + # 带日期), 旧按 want_day 过滤行永远只剩 start → 连黄(0926~28 实锤). + # 改按「want_day 的 start 段内出现过 DONE fail=0」判: 遍历记当前 start + # 日期, DONE 归属其最近一次 start. mirror_ok = False try: with open(os.path.join(MIRROR_DIR, "push.log"), encoding="utf-8", errors="replace") as fh: - lines = [l for l in fh.read().splitlines() if want_day in l] - mirror_ok = any("vps-mirror DONE" in l and "fail=0" in l - for l in lines[-20:]) + cur_day = None + done_days = set() + for line in fh: + if "vps-mirror start" in line: + for part in line.split(): + if len(part) == 10 and part[:2] == "20" and part[4] == "-": + cur_day = part + break + elif "vps-mirror DONE" in line and cur_day and "fail=0" in line: + done_days.add(cur_day) + mirror_ok = want_day in done_days except OSError: pass add("nas-vps-mirror", "vps-mirror 当日完成", "green" if mirror_ok else "yellow", diff --git a/tests/data_platform/test_nas_monitor_check.py b/tests/data_platform/test_nas_monitor_check.py index 01228501..6b3139f9 100644 --- a/tests/data_platform/test_nas_monitor_check.py +++ b/tests/data_platform/test_nas_monitor_check.py @@ -80,3 +80,30 @@ def test_dmsk_quarterly_cadence(tmp_path, monkeypatch): dmsk = [c for c in checks if c["key"] == "nas-dmsk"][0] assert dmsk["status"] == "yellow" assert dmsk["evidence"]["min_expected"] == "2026-06-30" + + +def test_mirror_done_line_has_no_date_beside_start_line(tmp_path, monkeypatch): + """push.log 实际格式: start 行带日期, DONE 行只带 [HH:MM:SS] 不带日期 + (0926~28 连黄实锤=旧按 want_day 过滤行, DONE 永被滤掉只剩 start). + 修后按「want_day 的 start 段内出现过 DONE fail=0」判绿; 只有 start + 无 DONE → 黄.""" + monkeypatch.setattr(nmc, "MIRROR_DIR", str(tmp_path)) + (tmp_path / "push.log").write_text( + "=== 2026-09-27 17:32:31 vps-mirror start (dry=0) ===\n" + "PLAN\tpush=4\tbytes=27831650\n" + "[17:34:00] vps-mirror DONE pushed=4 fail=0\n" + "=== 2026-09-28 17:32:49 vps-mirror start (dry=0) ===\n" + "PLAN\tpush=4\tbytes=29334473\n" + "[17:33:12] vps-mirror DONE pushed=4 fail=0\n", + encoding="utf-8") + checks, _ = nmc.build_checks(dt.datetime(2026, 9, 28, 17, 35)) + mirror = [c for c in checks if c["key"] == "nas-vps-mirror"][0] + assert mirror["status"] == "green" + # 当日只有 start 无 DONE(未完成/挂死) → 黄 + (tmp_path / "push.log").write_text( + "=== 2026-09-28 17:32:49 vps-mirror start (dry=0) ===\n" + "PLAN\tpush=4\tbytes=29334473\n", + encoding="utf-8") + checks, _ = nmc.build_checks(dt.datetime(2026, 9, 28, 17, 35)) + mirror = [c for c in checks if c["key"] == "nas-vps-mirror"][0] + assert mirror["status"] == "yellow"