Files
projectAIpopular/gateway/sense/promotion.py
T
tzt 1aaa05cacc feat(sense): T-X5 云端评判晋升表×conformal 双闸门——本地档自动接管(采纳 cortiq promotion)
- 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)
2026-09-18 23:01:52 +08:00

82 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""云端评判式晋升表(T-X5,采纳 cortiq promotion.rs,与 conformal 双闸门)。
定位:conformal 阈值给出「分对率 >= 1-α」的统计保证(单条查询粒度),
本表在其之上给出「某类任务(label)可由本地档接管」的**节奏自动化**:
- 观察:人工核验 verdictapprove=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)