From 8bcca3146ba7d06b26f6e5e7d8318c0fe563bd3c Mon Sep 17 00:00:00 2001 From: claude_dev Date: Tue, 1 Sep 2026 19:17:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(data):=20vintage=E8=87=AA=E6=A3=80=E8=A1=A5?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E7=8E=87=E6=92=95=E8=A3=82=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E2=80=94=E2=80=94VPS=E7=9C=9F=E6=95=B0=E5=AE=9E=E6=B5=8B?= =?UTF-8?q?=E6=9A=B4=E9=9C=B2v1=E7=9B=B2=E5=8C=BA:cashflow=E6=B4=97?= =?UTF-8?q?=E5=BA=93=E4=B8=AD=E6=AE=B58%vs=E4=B8=A4=E8=A1=A8100%=E4=BD=86m?= =?UTF-8?q?ax=5Fperiod=E5=90=8C=E4=B8=BAQ2'26,=E4=BB=85=E7=9C=8Bmax?= =?UTF-8?q?=E6=8A=A5=E4=BA=86OK=E6=BC=8F=E6=8E=89=E6=92=95=E8=A3=82;?= =?UTF-8?q?=E8=A1=A5=3D=E4=B8=89=E6=8A=A5=E8=A1=A8coverage=E6=9C=80?= =?UTF-8?q?=E5=A4=A7-=E6=9C=80=E5=B0=8F>50pp=E5=91=8A=E8=AD=A6(=E4=B8=89?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=90=8C=E6=89=B9=E6=8A=AB=E9=9C=B2=E7=A8=B3?= =?UTF-8?q?=E6=80=81=E5=BA=94=E5=90=8C=E6=AD=A5,=E4=B8=AD=E6=AE=B5/?= =?UTF-8?q?=E6=9F=90=E8=A1=A8=E5=A4=A7=E9=9D=A2=E7=A7=AF=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=8D=B3=E8=A7=A6=E5=8F=91);+1=E7=9C=9F=E5=BD=A2=E6=80=81?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/data_platform/static_vintage_check.py | 11 ++++++++++- tests/data_platform/test_static_vintage_check.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/data_platform/static_vintage_check.py b/scripts/data_platform/static_vintage_check.py index 2322bc3..6f13668 100644 --- a/scripts/data_platform/static_vintage_check.py +++ b/scripts/data_platform/static_vintage_check.py @@ -5,7 +5,7 @@ 背景: sanguo-ak-quarter 财报季全量洗库逐股重写 balance→income→cashflow, 单表 ~8h、全程 20h+: 洗库中段两表 vintage 撕裂 (09-01 09:32 balance 已带 中报而 income 停在 Q1'26 → value ROE 配对归零清仓)。本脚本盘前盘点各报表 -最新报告期分布, 跨表不一致/估值断更大声告警, 并落 vintage_status.json 供 +最新报告期分布, 跨表不一致/覆盖率撕裂/估值断更大声告警, 并落 vintage_status.json 供 策略侧盘前预判 (契约: tables. = {max_period, coverage, n_at_max, n_valid, n_empty, n_unreadable, newest_mtime}, checked_at 为扫描时刻)。 @@ -124,6 +124,15 @@ def check_all(root: Path) -> Tuple[dict, list]: alerts.append( "报表 vintage 撕裂: %s (跨表配对将缺期, 疑似洗库进行中/中段, " "策略盘前勿按新报告期决策)" % detail) + # 覆盖率撕裂: 三报表同批披露, 稳态 coverage 应同步; 最大-最小 >50pp + # = 洗库中段 / 某表大面积抓取失败 (09-01 晚实测形态: cashflow 中段 8% + # vs balance/income 100%, 但 max_period 同为 Q2'26, 仅看 max 会漏报) + covs = {t: status["tables"][t]["coverage"] for t in STATEMENT_TABLES} + if min(covs.values()) > 0 and max(covs.values()) - min(covs.values()) > 0.5: + detail = ", ".join(f"{t}={c:.0%}" for t, c in sorted(covs.items())) + alerts.append( + "表间覆盖率撕裂: %s (三报表同批披露应同步; 疑似洗库进行中" + "/某表大面积抓取失败)" % detail) status["valuation"] = valuation_freshness(root) v = status["valuation"] if v.get("age_hours") is not None and v["age_hours"] > VALUATION_STALE_HOURS: diff --git a/tests/data_platform/test_static_vintage_check.py b/tests/data_platform/test_static_vintage_check.py index b49e3ca..2428341 100644 --- a/tests/data_platform/test_static_vintage_check.py +++ b/tests/data_platform/test_static_vintage_check.py @@ -76,6 +76,19 @@ class TestCheckAll: _, alerts = svc.check_all(tmp_path) assert alerts == [] + def test_coverage_tear_alert(self, tmp_path): + """max_period 相同但覆盖率撕裂 (洗库中段形态): 某表大面积滞后 → 告警。 + + 09-01 晚真数实测暴露的 v1 盲区: cashflow 洗到 8% vs 两表 100%, + max 同为 Q2'26, 仅看 max 会漏报。 + """ + _mk_table(tmp_path, "balance", {"a": ["2026-06-30"], "b": ["2026-06-30"]}) + _mk_table(tmp_path, "income", {"a": ["2026-06-30"], "b": ["2026-06-30"]}) + _mk_table(tmp_path, "cashflow", { + "a": ["2026-06-30"], "b": ["2026-03-31"], "c": ["2026-03-31"]}) + _, alerts = svc.check_all(tmp_path) + assert any("覆盖率撕裂" in a and "cashflow=33%" in a for a in alerts) + def test_valuation_stale_alert(self, tmp_path): """估值表 (每日 ak-eod) 超 48h 未刷新 → 断更告警。""" _mk_table(tmp_path, "balance", {"a": ["2026-06-30"]})