fe4042572f
Co-Authored-By: Claude Code <noreply@anthropic.com>
83 lines
3.4 KiB
Python
83 lines
3.4 KiB
Python
# sanguo_factor/review_compare.py
|
|
"""双机对拍——流水线 spec §4.2 决议 H.
|
|
|
|
关键指标一致→结果落档进告警流程;分歧超阈→不出降权告警、只开排查 issue
|
|
(宁可误报排查,不可错发告警;分歧大概率=数据版本漂移或环境差异,本身就该被抓).
|
|
对拍两指标: t(t_stat) 与 ic_mean,容差起步默认 tol_t=0.25 / tol_ic=0.02(首年校准).
|
|
双方 t 均缺的因子跳过(数据面同缺口非环境分歧).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
DEFAULT_TOL_T = 0.25
|
|
DEFAULT_TOL_IC = 0.02
|
|
|
|
|
|
def compare(a: dict, b: dict, tol_t: float = DEFAULT_TOL_T,
|
|
tol_ic: float = DEFAULT_TOL_IC) -> dict:
|
|
fa, fb = a.get("factors") or {}, b.get("factors") or {}
|
|
matches: list[str] = []
|
|
mismatches: list[dict] = []
|
|
missing: list[dict] = []
|
|
for name in sorted(set(fa) & set(fb)):
|
|
pa, pb = fa[name], fb[name]
|
|
ta, tb = pa.get("t"), pb.get("t")
|
|
if ta is None and tb is None:
|
|
matches.append(name) # 双缺=同缺口,非分歧
|
|
continue
|
|
if ta is None or tb is None:
|
|
missing.append({"factor": name, "side": "a" if tb is None else "b",
|
|
"t": ta if ta is not None else tb})
|
|
continue
|
|
ia, ib = pa.get("ic_mean"), pb.get("ic_mean")
|
|
delta_t = abs(ta - tb)
|
|
delta_ic = abs((ia or 0.0) - (ib or 0.0))
|
|
if delta_t > tol_t or delta_ic > tol_ic:
|
|
mismatches.append({"factor": name, "a_t": ta, "b_t": tb,
|
|
"delta_t": delta_t, "a_ic": ia, "b_ic": ib})
|
|
else:
|
|
matches.append(name)
|
|
for name in sorted(set(fa) - set(fb)):
|
|
missing.append({"factor": name, "side": "a", "t": fa[name].get("t")})
|
|
for name in sorted(set(fb) - set(fa)):
|
|
missing.append({"factor": name, "side": "b", "t": fb[name].get("t")})
|
|
return {"consistent": not mismatches and not missing,
|
|
"matches": matches, "mismatches": mismatches, "missing": missing}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
ap = argparse.ArgumentParser(description="月度批评双机对拍")
|
|
ap.add_argument("json_a")
|
|
ap.add_argument("json_b")
|
|
ap.add_argument("--tol-t", type=float, default=DEFAULT_TOL_T)
|
|
ap.add_argument("--tol-ic", type=float, default=DEFAULT_TOL_IC)
|
|
args = ap.parse_args(argv)
|
|
try:
|
|
with open(args.json_a, encoding="utf-8") as f:
|
|
a = json.load(f)
|
|
with open(args.json_b, encoding="utf-8") as f:
|
|
b = json.load(f)
|
|
except (OSError, json.JSONDecodeError) as e:
|
|
print(f"[review_compare] 读件失败: {e}", file=sys.stderr)
|
|
return 1
|
|
r = compare(a, b, tol_t=args.tol_t, tol_ic=args.tol_ic)
|
|
print(f"[review_compare] 一致 {len(r['matches'])} · 分歧 {len(r['mismatches'])} · "
|
|
f"缺面 {len(r['missing'])}(容差 t±{args.tol_t} ic±{args.tol_ic})")
|
|
for m in r["mismatches"]:
|
|
print(f" ⚠ 排查 {m['factor']}: a(t={m['a_t']:.3f},ic={m['a_ic']}) vs "
|
|
f"b(t={m['b_t']:.3f},ic={m['b_ic']}) Δt={m['delta_t']:.3f}")
|
|
for m in r["missing"]:
|
|
print(f" ⚠ 缺面 {m['factor']}: 仅 {m['side']} 侧有(t={m['t']})")
|
|
if r["consistent"]:
|
|
print("[review_compare] 一致→结果落档进告警流程(决议 H)")
|
|
return 0
|
|
print("[review_compare] 分歧→不出降权告警,开排查 issue(决议 H)")
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|