- gateway/sense/promotion.py:PromotionTable 状态机(candidate/promoted/demoted)
* 晋升门:n_total>=n_min 且 通过率>=promote_lb 且 soak 浸泡期满足
* 退化:promoted 期间通过率<demote_lb 自动降级;降级后可凭数据恢复
* 一票否决:tier=T3 观察不进通过率统计,已晋升者立即降级
* 持久化 sense_promotion 表(CREATE IF NOT EXISTS 幂等)
- grader:live 模式对已晋升标签的 T2 决策(无 T3 信号/非 fallback)升级 T1,
hard_gates.promotion_applied 如实标注;T1/T3/fallback 路径不受影响
- 回填链路:T1 审计抽样带 promo:<label> 标签 -> 人工 verdict approve=ok
经 /review/{id} 提交时回填晋升表(失败不影响审核主流程)
- 双闸门语义:conformal 阈值保证单条决策风险率,晋升表保证标签级接管节奏;
collect 数据不足(n_min 未满)不晋升,全部迁移可经 T-X4 留痕审计
pytest 474 passed(T-X4 后 466 + 8)
82 lines
3.7 KiB
Python
82 lines
3.7 KiB
Python
"""云端评判式晋升表(T-X5,采纳 cortiq promotion.rs,与 conformal 双闸门)。
|
||
|
||
定位:conformal 阈值给出「分对率 >= 1-α」的统计保证(单条查询粒度),
|
||
本表在其之上给出「某类任务(label)可由本地档接管」的**节奏自动化**:
|
||
|
||
- 观察:人工核验 verdict(approve=ok)等质量信号按 label 累计通过率;
|
||
- 晋升:n_total >= n_min 且 通过率 >= promote_lb 且 soak 浸泡期满足
|
||
-> state=promoted(此后 grader 对该 label 的 T2 决策可升级 T1);
|
||
- 退化:promoted 期间通过率 < demote_lb -> demoted(自动回退分级路径);
|
||
- 一票否决:tier=T3(高复杂度/仓库级)观察不进通过率统计,且若已晋升
|
||
立即降级——与 cortiq "HIGH tier escalation is served by the cloud" 同构。
|
||
|
||
灰度纪律(沿用 D-G7):collect 数据不足(n_min 未满)不得晋升;
|
||
全部状态迁移可由 T-X4 的决策留痕审计。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import time
|
||
from typing import Any, Dict, Optional
|
||
|
||
|
||
def promotion_label(consumer: str, domain: str = "") -> str:
|
||
"""晋升粒度:消费方×领域(domain 缺省归 general;调用方可传更细粒度)。"""
|
||
return f"{consumer or 'default'}:{domain or 'general'}"
|
||
|
||
|
||
class PromotionTable:
|
||
"""标签晋升状态机(candidate / promoted / demoted;持久化于 sense_promotion)。"""
|
||
|
||
def __init__(self, store, now=None, n_min: int = 20, promote_lb: float = 0.95,
|
||
soak_days: float = 3.0, demote_lb: float = 0.85,
|
||
high_tier_veto: bool = True):
|
||
self.store = store
|
||
self._now = now or (lambda: time.time())
|
||
self.n_min = max(1, int(n_min))
|
||
self.promote_lb = float(promote_lb)
|
||
self.demote_lb = float(demote_lb)
|
||
self.soak_s = float(soak_days) * 86400.0
|
||
self.high_tier_veto = bool(high_tier_veto)
|
||
|
||
def observe(self, label: str, ok: bool, tier: str = "",
|
||
ts: Optional[float] = None) -> Dict[str, Any]:
|
||
"""记录一次质量信号并推进状态机;返回迁移后的行。"""
|
||
ts = float(ts) if ts is not None else float(self._now())
|
||
row = self.store.get_promotion(label) or {
|
||
"label": label, "n_total": 0, "n_ok": 0, "state": "candidate",
|
||
"first_ts": int(ts), "promoted_ts": 0, "last_ts": 0}
|
||
|
||
# 一票否决:T3 观察不进通过率统计;已晋升者立即降级
|
||
if self.high_tier_veto and str(tier).upper() == "T3":
|
||
row["last_ts"] = int(ts)
|
||
if row["state"] == "promoted":
|
||
row["state"] = "demoted"
|
||
row["promoted_ts"] = 0
|
||
self.store.upsert_promotion(row)
|
||
return row
|
||
|
||
row["n_total"] = int(row["n_total"]) + 1
|
||
row["n_ok"] = int(row["n_ok"]) + (1 if ok else 0)
|
||
row["last_ts"] = int(ts)
|
||
rate = row["n_ok"] / row["n_total"] if row["n_total"] else 0.0
|
||
soaked = (ts - int(row["first_ts"])) >= self.soak_s
|
||
|
||
if row["state"] == "promoted":
|
||
if row["n_total"] >= self.n_min and rate < self.demote_lb:
|
||
row["state"] = "demoted" # 退化自动降级
|
||
row["promoted_ts"] = 0
|
||
else:
|
||
# candidate / demoted 共用同一晋升门(降级后可凭数据恢复)
|
||
if row["n_total"] >= self.n_min and rate >= self.promote_lb and soaked:
|
||
row["state"] = "promoted"
|
||
row["promoted_ts"] = int(ts)
|
||
self.store.upsert_promotion(row)
|
||
return row
|
||
|
||
def is_promoted(self, label: str) -> bool:
|
||
row = self.store.get_promotion(label)
|
||
return bool(row) and row["state"] == "promoted"
|
||
|
||
def snapshot(self, label: str) -> Optional[Dict[str, Any]]:
|
||
return self.store.get_promotion(label)
|