fix(cvp): O1 residue - e2 rebate_policy_pct 置 null(口径单一源 = bep_caliber)
ci/woodpecker/push/main Pipeline failed

问题:/bep-card 端点对 e2(博海)回落模块内硬编码常量 BEP_REBATE_POLICY_PCT=84.61,
而 bep_caliber.ENTITY_CALIBERS[2]["policy_pct"] 为 None(博海无渠补科目)⇒ 下游 API
消费者会把 payload 里的 rebate_policy_pct=84.61 误读为「博海有 84.61% 政策渠补率」。

改动(严格限定 dashboard.py 一个模块 + 1 条守护用例):
- 删除模块级常量 BEP_REBATE_POLICY_PCT;四处回落分支改为直接取口径值
  (未注册口径分支置 None;其余取 cal.get("policy_pct") / 已算出的 policy_pct)。
- 口径单一事实源维持 app/utils/bep_caliber.py,端点不做任何回落。
- 新增 tests/test_bep_rebate_actual_caliber.py::test_o1_policy_pct_single_source_no_hardcoded_fallback
  (改前必红 / 改后必绿,e1=84.61 作同端点负对照)。

改前/改后(端点函数直调,生产库只读,e1/e2 同批):
  改前 e2 inputs.rebate_policy_pct = 84.61 → 改后 null;e1 恒为 84.61(负对照不变)。
This commit is contained in:
Hermes CI Fix
2026-10-04 22:37:20 +08:00
parent 587d5b3983
commit 3ed9fa0611
2 changed files with 32 additions and 9 deletions
+13 -9
View File
@@ -42,6 +42,10 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
# 2026-10-02 口径修订(任总 11:43 CST 放行「改,这样数据更加真实」):
# ① 渠补率不再用政策常量 84.61 参与计算,改用派生落库的实测累计值;
# 政策常量仅作对照返回(inputs.rebate_policy_pct),**不参与计算**;
# ⚠️ 2026-10-04(O1 收敛):该对照值的**唯一来源** = app/utils/bep_caliber.py 的
# ENTITY_CALIBERS[entity]["policy_pct"](酣客 e1 = 84.61;博海 e2 = None,无渠补科目)。
# 端点各处一律直接取口径值,**不得**再回落本模块内的硬编码常量——否则 e2 会被下游
# API 消费者误读为「博海有 84.61% 政策渠补率」(口径单一源原则)。
# ② 账面/现金两条路径**统一读派生落库值**——此前 book_bep 实时复算 voucher、
# cash_bep 读派生值,9 月数据入库而派生器未跑时同卡两口径不同步;
# ③ fail-closed:六条派生值(含渠补率)**同期齐全**才出卡;缺数 ⇒ available=False,
@@ -63,8 +67,9 @@ BEP_REQUIRED_CODES = tuple(c for c in BEP_CODES if c != BEP_REBATE_CODE)
BEP_SALES_SUBJECT = "5001"
BEP_ADMIN_PREFIX = "5602"
BEP_DEP_SUBJECT = "560213"
# 政策常量:仅作对照展示(2026-10-02 起不参与任何计算;博海无渠补科目 ⇒ None)
BEP_REBATE_POLICY_PCT = 84.61
# ⚠️ 2026-10-04(O1 收敛):本模块**不再**定义政策渠补率常量(原 BEP_REBATE_POLICY_PCT)。
# 政策常量的唯一来源 = app/utils/bep_caliber.py::ENTITY_CALIBERS[entity]["policy_pct"]
# (酣客 e1 = 84.61;博海 e2 = None,因无渠补科目)。端点各处直接取口径值,不做回落。
def _bep_sum_field(db, entity_id: int, period_from: str, period_to: str,
@@ -170,7 +175,8 @@ def get_bep_card(
if cal is None:
return {"available": False, "entity_id": entity_id,
"message": f"entity={entity_id} 未注册 BEP 口径(见 app/utils/bep_caliber.py)",
"rebate_policy_pct": BEP_REBATE_POLICY_PCT, "readonly": True}
# 未注册口径 ⇒ 无政策常量来源(口径单一源):不得回落硬编码值
"rebate_policy_pct": None, "readonly": True}
kpis = {k.kpi_code: k for k in db.query(KPIDefinition).filter(
KPIDefinition.entity_id == entity_id,
@@ -205,7 +211,7 @@ def get_bep_card(
if not book_periods:
return {"available": False, "entity_id": entity_id,
"message": "尚无派生值,请先运行 kpi_bep_deriver.py",
"rebate_policy_pct": BEP_REBATE_POLICY_PCT, "readonly": True}
"rebate_policy_pct": cal.get("policy_pct"), "readonly": True}
period = book_periods[0]
year = period[:4]
months = int(period[5:7])
@@ -227,8 +233,7 @@ def get_bep_card(
"label": label, "reason": reason,
"vc_pct": by_code.get(BEP_VC_CODE, {}).get(period),
"rebate_pct": by_code.get(BEP_REBATE_CODE, {}).get(period),
"rebate_policy_pct": (cal.get("policy_pct")
if cal.get("policy_pct") is not None else BEP_REBATE_POLICY_PCT),
"rebate_policy_pct": cal.get("policy_pct"),
"entity_name": cal["name"],
"missing_codes": missing_at_period,
"derived_periods": book_periods[:6],
@@ -242,8 +247,7 @@ def get_bep_card(
% ",".join(missing_at_period))
return {"available": False, "entity_id": entity_id, "period": period,
"message": msg, "missing_codes": missing_at_period,
"rebate_policy_pct": (cal.get("policy_pct") if cal.get("policy_pct") is not None
else BEP_REBATE_POLICY_PCT),
"rebate_policy_pct": cal.get("policy_pct"),
"derived_periods": book_periods[:6], "readonly": True}
# 派生落库值(同期)—— 账面 / 现金两条路径同一来源,卡面不做实时复算
@@ -290,7 +294,7 @@ def get_bep_card(
"dep_avg": round(dep_avg, 2),
"vc_pct": vc_pct,
"rebate_pct": rebate_pct,
"rebate_policy_pct": BEP_REBATE_POLICY_PCT, # 政策常量:仅作对照,不参与计算
"rebate_policy_pct": policy_pct, # 口径单一源:bep_caliber.ENTITY_CALIBERS[entity]["policy_pct"]
"rebate_source": ("derived:F_BEP_VC_RATE(⑤完整口径:销货成本+税金及附加+渠补−厂补)"
if entity_id == 1
else "derived:F_BEP_VC_RATE(Σ6401借÷Σ6001贷)"),
@@ -345,3 +345,22 @@ def test_acceptance_probe_2026_08(db):
for name, got, want, tol in checks:
assert got is not None, f"{name}: got None, want {want}±{tol}"
assert abs(float(got) - want) <= tol, f"{name}: got {got}, want {want}±{tol}"
# ── 五、O1 收敛(2026-10-04):政策渠补率口径单一源,端点不得回落硬编码常量 ────────
def test_o1_policy_pct_single_source_no_hardcoded_fallback():
"""O1:dashboard 模块不得再定义硬编码政策常量;policy_pct 唯一来源 = bep_caliber。
改前(HEAD 587d5b3):`dashboard.BEP_REBATE_POLICY_PCT == 84.61` 且端点对 e2 回落该常量
⇒ 本用例第一条断言必红(改前/改后判据,非事后追认)。
改后:无该属性;e1 = 84.61(同端点负对照)、e2 = None(无渠补科目)。
"""
from app.api import dashboard as DASH
from app.utils import bep_caliber as CAL
assert not hasattr(DASH, "BEP_REBATE_POLICY_PCT"), (
"dashboard 仍在定义硬编码政策渠补率常量 ⇒ 口径非单一源")
assert CAL.caliber_for(1)["policy_pct"] == 84.61 # e1 保留(负对照)
assert CAL.caliber_for(2)["policy_pct"] is None # e2 置 None(无渠补科目)