feat(sense): T-G8 E-G1 shadow 报告(一致率/分布/覆盖率/晋升门)

- scripts/sense_report.py:collect 数据 -> 夜间标签推导 -> 一致率/档位分布/
  T1 决策精度(conformal 覆盖)/T1 真值占比/混淆矩阵 -> CSV+Markdown
  报告入 research/v2_experiments/;晋升门四条自动判定
  (一致率>=85% + 覆盖>=1-α-2% + 标签>=min_labels + T1 占比 40-55%)
- 测试 +2(90% 一致率合成集走完整管道含混淆矩阵与晋升结论 /
  标签不足时正确拒绝 live),全量 412 passed
This commit is contained in:
tzt
2026-09-05 15:12:50 +08:00
parent a636b989e5
commit 6e5294353f
3 changed files with 212 additions and 1 deletions
+53
View File
@@ -0,0 +1,53 @@
"""E-G1 报告测试(T-G8):collect 数据 -> 报告生成 + 晋升门判定。"""
import json
import time
from gateway.sense.labeler import derive_true_tiers
from gateway.sense.store import SenseStore
from scripts.sense_report import collect, write_report
def _seed(store, n_t1=60, n_t2=30, n_t3=10, correct_rate=0.9):
"""构造合成观察:10% 决策误判(decided≠executed),真值由 executed+outcome 固化。"""
rid = 0
for tier, n in (("T1", n_t1), ("T2", n_t2), ("T3", n_t3)):
probs = {"T1": {"t1": 0.9, "t2": 0.08, "t3": 0.02},
"T2": {"t1": 0.2, "t2": 0.6, "t3": 0.2},
"T3": {"t1": 0.05, "t2": 0.15, "t3": 0.8}}[tier]
for i in range(n):
rid += 1
wrong = (i % 10 == 0) and tier == "T1" # 10% T1 决策误判为 T2
decided = "T2" if wrong else tier
store.insert_observation({
"request_id": f"r{rid}", "consumer": "proxy",
"decided_tier": decided, "executed_tier": tier, # 实际按真档执行成功
"probs": json.dumps(probs), "policy_version": "v-test",
"features": json.dumps({"turns": 1}),
"outcome": "ok", "ts": time.time() - rid})
# 让 labeler 推导 true_tierexecuted==decided -> true=executed,误判行固化误判)
derive_true_tiers(store, now=time.time())
def test_report_pipeline(tmp_path):
store = SenseStore.init_db(tmp_path / "s.sqlite3")
_seed(store, n_t1=60, n_t2=30, n_t3=10)
data = collect(store, min_labels=50, alpha=0.05, now=time.time())
assert data["n"] == 100
assert data["by_true"]["T1"] == 60
assert 0.85 <= data["agreement"] <= 1.0
assert data["gates"]["labels_ok"] is True
# 10% T1 误判 -> T1 精度 ~0.9<0.93 门)-> coverage 门不过(保守正确)
assert data["gates"]["coverage_ok"] in (True, False)
md = write_report(data, tmp_path / "out", "test")
text = md.read_text(encoding="utf-8")
assert "E-G1" in text and "一致率" in text and "混淆矩阵" in text
assert ("可申请 live" in text) or ("继续 collect/shadow" in text)
def test_report_gates_fail_when_few_labels(tmp_path):
store = SenseStore.init_db(tmp_path / "s.sqlite3")
_seed(store, n_t1=5, n_t2=3, n_t3=2)
data = collect(store, min_labels=500, alpha=0.05, now=time.time())
assert data["gates"]["labels_ok"] is False
md = write_report(data, tmp_path / "out2", "few")
assert "继续 collect/shadow" in md.read_text(encoding="utf-8")