fix(cvp): O1 residue - e2 rebate_policy_pct 置 null(口径单一源 = bep_caliber)
ci/woodpecker/push/main Pipeline failed
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:
@@ -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(无渠补科目)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user