fix(bep-caliber): rebate rate uses YTD actual voucher cumulative (571103 debit / 5001 credit), book+cash card read derived, fail-closed
ci/woodpecker/push/main Pipeline was canceled

This commit is contained in:
Hermes CI Fix
2026-10-02 12:20:39 +08:00
parent 6be903fbc9
commit 14ed29bcbf
4 changed files with 623 additions and 72 deletions
+78 -50
View File
@@ -29,16 +29,25 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
# 月销 = voucher_details subject 5001(主营业务收入) 贷方 YTD 月均(万元)
# 月管理费 = 5602% 借−贷 净额 YTD 月均(carry_forward=0 行域;账面口径含折旧)
# 折旧 = 560213 借−贷 净额 YTD 月均(同上;口径4 见 _decisions/cma-expense-profit-net-caliber-20261002.md)
# 渠补率 = 厂家政策 84.61%(财务Bot权威口径)
# 渠补率 = **实测累计**(Σ571103借方 ÷ Σ5001贷方,YTD)——取自派生落库值 F_BEP_REBATE(单一事实源)
# 保本额 = 月固定成本 ÷ (1-渠补率)
# 2026-10-02 口径修订(任总 11:43 CST 放行「改,这样数据更加真实」):
# ① 渠补率不再用政策常量 84.61 参与计算,改用派生落库的实测累计值;
# 政策常量仅作对照返回(inputs.rebate_policy_pct),**不参与计算**;
# ② 账面/现金两条路径**统一读派生落库值**——此前 book_bep 实时复算 voucher、
# cash_bep 读派生值,9 月数据入库而派生器未跑时同卡两口径不同步;
# ③ fail-closed:六条派生值(含渠补率)**同期齐全**才出卡;缺数 ⇒ available=False,
# 不出保本值、不回落政策常量(不跨期回落,避免混期口径)。
# 账面口径(含折旧) / 现金口径(剔除折旧) 双口径只读返回,无写操作。
# 现金口径数值优先读派生KPI(F_BEP_REVENUE_CASH/F_BEP_COVER_CASH)最新落库值,无派生值时回退现算。
BEP_CODES = ("F_BEP_REVENUE", "F_BEP_COVER", "F_BEP_GAP",
"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH")
"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH", "F_BEP_REBATE")
BEP_BOOK_CODES = ("F_BEP_REVENUE", "F_BEP_COVER", "F_BEP_GAP") # 账面3条必须齐全才 available
BEP_CASH_CODES = ("F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH")
BEP_REBATE_CODE = "F_BEP_REBATE" # 实测渠补率派生KPI编码(卡面单一事实源)
BEP_SALES_SUBJECT = "5001"
BEP_ADMIN_PREFIX = "5602"
BEP_DEP_SUBJECT = "560213"
# 政策常量:仅作对照展示(2026-10-02 起不参与任何计算)
BEP_REBATE_POLICY_PCT = 84.61
@@ -98,7 +107,13 @@ def get_bep_card(
db: Session = Depends(get_db),
entity_id: int = Depends(get_entity_id),
):
"""本量利·保本点 卡片数据(只读,A区)——账面/现金双口径"""
"""本量利·保本点 卡片数据(只读,A区)——账面/现金双口径
2026-10-02 口径修订(任总 11:43 CST 放行):
· 渠补率 = 派生落库的**实测累计值**(F_BEP_REBATE,单一事实源);政策常量仅对照返回;
· 账面 / 现金两条路径**统一读派生落库值**(不再实时复算 voucher);
· fail-closed:六条派生值同期不齐(尤其缺渠补率)⇒ available=False,不出保本值、不回落常量。
"""
kpis = {k.kpi_code: k for k in db.query(KPIDefinition).filter(
KPIDefinition.entity_id == entity_id,
KPIDefinition.status == "active",
@@ -109,60 +124,70 @@ def get_bep_card(
return {"available": False, "message": "BEP账面口径派生KPI未完整注册",
"found": list(kpis.keys()), "missing": missing_book}
# 最新派生期间与值(同派生器:source_type='derived');现金2条缺失不阻塞整卡
latest = {}
for code in BEP_CODES:
if code not in kpis:
latest[code] = None
continue
v = db.query(KPIValue).filter(
KPIValue.kpi_id == kpis[code].id,
KPIValue.source_type == "derived",
KPIValue.actual_value.isnot(None),
month_period_clause(KPIValue.period),
).order_by(KPIValue.period.desc(), KPIValue.id.desc()).first()
latest[code] = {"period": v.period, "value": v.actual_value} if v else None
# 派生落库值(同派生器:source_type='derived')——按 code × period 建表,period desc 排序
# ⚠️ 不过滤 actual_value IS NULL:fail-closed 落库的 pending 期(值 NULL)必须参与「最新期」判定,
# 否则卡面会悄悄跨期回落到上一期,掩盖「本期不出值」的事实。
rows = db.query(KPIValue, KPIDefinition.kpi_code).join(
KPIDefinition, KPIDefinition.id == KPIValue.kpi_id,
).filter(
KPIDefinition.entity_id == entity_id,
KPIDefinition.kpi_code.in_(BEP_CODES),
KPIValue.source_type == "derived",
month_period_clause(KPIValue.period),
).order_by(KPIValue.period.desc(), KPIValue.id.desc()).all()
by_code = {} # {code: {period: value|None}}
for v, code in rows:
by_code.setdefault(code, {}).setdefault(v.period, v.actual_value)
# 卡面期 = 账面三条派生行的**最新期间**(与派生器目标期一致)
book_periods = sorted({p for c in BEP_BOOK_CODES for p in by_code.get(c, {})}, reverse=True)
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}
period = book_periods[0]
# fail-closed:六条派生值(含实测渠补率)本期必须齐全,缺一条即不出保本值
missing_at_period = [c for c in BEP_CODES if by_code.get(c, {}).get(period) is None]
if missing_at_period:
if BEP_REBATE_CODE in missing_at_period:
msg = ("渠补率(实测)不可得:本期未落 F_BEP_REBATE 派生值 ⇒ 不出保本值"
"(fail-closed,不回落政策常量、不跨期回落)")
else:
msg = ("本期派生值不齐 %s ⇒ 不出保本值(fail-closed,不跨期回落)"
% ",".join(missing_at_period))
return {"available": False, "entity_id": entity_id, "period": period,
"message": msg, "missing_codes": missing_at_period,
"rebate_policy_pct": BEP_REBATE_POLICY_PCT,
"derived_periods": book_periods[:6], "readonly": True}
periods = [latest[c]["period"] for c in BEP_BOOK_CODES if latest[c]]
if not periods:
return {"available": False, "message": "尚无派生值,请先运行 kpi_bep_deriver.py"}
period = max(periods)
year = period[:4]
months = int(period[5:7])
period_from = f"{year}-01"
period_to = period
# 输入口径(万元 / %)——与派生器一致
sales_avg = _bep_sum_field(db, entity_id, period_from, period_to,
# 六条派生落库值(同期)—— 账面 / 现金两条路径同一来源,卡面不做实时复算
d = {c: by_code[c][period] for c in BEP_CODES}
book_bep = float(d["F_BEP_REVENUE"])
book_cover = float(d["F_BEP_COVER"])
book_gap = float(d["F_BEP_GAP"])
cash_bep = float(d["F_BEP_REVENUE_CASH"])
cash_cover = float(d["F_BEP_COVER_CASH"])
cash_gap = round(100.0 - cash_cover, 1) # 缺口 = 100 − 覆盖率(与派生器同口径)
rebate_pct = float(d[BEP_REBATE_CODE]) # 实测累计渠补率(单一事实源)
cm_pct = round(100.0 - rebate_pct, 2)
# 输入口径(万元,展示用;数值口径一律以派生落库值为准)
sales_avg = _bep_sum_field(db, entity_id, period_from, period,
"credit_amount", ("eq", BEP_SALES_SUBJECT)) / 10000.0 / months
admin_avg = _bep_sum_field(db, entity_id, period_from, period_to,
admin_avg = _bep_sum_field(db, entity_id, period_from, period,
"net", ("like", BEP_ADMIN_PREFIX)) / 10000.0 / months
dep_avg = _bep_sum_field(db, entity_id, period_from, period_to,
dep_avg = _bep_sum_field(db, entity_id, period_from, period,
"net", ("eq", BEP_DEP_SUBJECT)) / 10000.0 / months
rebate_pct = BEP_REBATE_POLICY_PCT
cm_pct = 100.0 - rebate_pct
# 账面口径:管理费含折旧(与派生落库值一致)
book_bep = round(admin_avg / (cm_pct / 100.0), 1)
book_cover = round(sales_avg / book_bep * 100.0, 1) if book_bep else 0.0
book_gap = round((book_bep - sales_avg) / book_bep * 100.0, 1) if book_bep else 0.0
# 现金口径:管理费-折旧;优先读派生KPI最新落库值(单一事实源),缺失时回退现算
cash_admin = admin_avg - dep_avg
cash_derived_bep = latest["F_BEP_REVENUE_CASH"]["value"] if latest.get("F_BEP_REVENUE_CASH") else None
cash_derived_cover = latest["F_BEP_COVER_CASH"]["value"] if latest.get("F_BEP_COVER_CASH") else None
if cash_derived_bep is not None and cash_derived_cover is not None:
cash_bep = cash_derived_bep
cash_cover = cash_derived_cover
else:
cash_bep = round(cash_admin / (cm_pct / 100.0), 1) if cash_admin > 0 else 0.0
cash_cover = round(sales_avg / cash_bep * 100.0, 1) if cash_bep else 0.0
cash_gap = round((cash_bep - sales_avg) / cash_bep * 100.0, 1) if cash_bep else 0.0
# 回显最新落库派生值(账面3条+现金2条,存在时输出)
derived = {}
for code in BEP_CODES:
lv = latest[code]
derived[code] = {"period": lv["period"], "value": lv["value"]} if lv else None
# 回显六条派生落库值(同期)
derived = {c: {"period": period, "value": d[c]} for c in BEP_CODES}
cover_kpi = kpis["F_BEP_COVER"]
book_level = _bep_level(book_cover, cover_kpi)
@@ -182,7 +207,9 @@ def get_bep_card(
"admin_avg": round(admin_avg, 2),
"dep_avg": round(dep_avg, 2),
"rebate_pct": rebate_pct,
"cm_pct": round(cm_pct, 2),
"rebate_policy_pct": BEP_REBATE_POLICY_PCT, # 政策常量:仅作对照,不参与计算
"rebate_source": "derived:F_BEP_REBATE(Σ571103借方÷Σ5001贷方, YTD累计)",
"cm_pct": cm_pct,
},
"book": {
"bep_revenue": book_bep,
@@ -200,7 +227,8 @@ def get_bep_card(
"gap_level": cash_gap_level,
},
"derived": derived,
"formula": "保本销售额 = 月固定成本 ÷ (1 − 渠补率84.61%)",
"data_source": "derived", # 账面/现金同源:均读派生落库值
"formula": f"保本销售额 = 月固定成本 ÷ (1 − 实测渠补率{rebate_pct:.2f}%)",
"readonly": True,
}
+178 -22
View File
@@ -6,13 +6,20 @@
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
F_BEP_REVENUE_CASH 现金保本销售额(万元) = (月均管理费-月均折旧) ÷ 边际贡献率 (现金口径,剔除折旧560213,中性无阈值)
F_BEP_COVER_CASH 现金保本覆盖率(%) = 月均销售 ÷ 现金保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
F_BEP_REBATE 实测渠补率(%) = Σ(571103借方) ÷ Σ(5001贷方) × 100 (YTD累计; 卡面单一事实源,无阈值)
口径(与财务Bot报告一致):
- 销售收入: voucher_details.subject_code='5001'(主营业务收入) 贷方合计,当年1月至目标月 YTD 月均
- 管理费: subject_code LIKE '5602%'(管理费用,含560213折旧) 借−贷净额合计(carry_forward=0),YTD 月均 → 账面口径
- 折旧: subject_code = '560213'(管理费用-折旧叶子) 借−贷净额合计(carry_forward=0),YTD 月均 → 现金口径剔除项(eq精确匹配,防5602130误含)
- 渠补率: 厂家政策定死 84.61% (571103渠补支出/5001销售, 1-7月累计凭证验证≈84.0%~84.6%)
- 边际贡献率 = 1 - 渠补率 = 15.39%
- 渠补率: **实测累计口径**(2026-10-02 财务裁定,任总 11:43 CST 放行「改,这样数据更加真实」)
渠补率(实测) = Σ(571103 借方) ÷ Σ(5001 贷方) × 100,期间 = 年初至当期(YTD),保留 2 位小数。
`POLICY_REBATE_RATE_PCT = 84.61` 政策常量**仅作对照展示**(写进返回值 rebate_policy_pct / remark),
**不参与任何计算**;`--rebate-rate` 显式传值时视为人工诊断覆盖(正常调用不再使用)。
- 边际贡献率 = 100 − 渠补率(实测)
- fail-closed 边界(2026-10-02 裁定):某期 571103 或 5001 缺数/为 0 ⇒ **该期不出保本值**
(六条派生行写 actual_value=NULL + data_status='pending' + remark 注明「渠补率不可得」),
**不得静默回落 84.61 常量**;累计口径天然平滑单月波动(8 月单月 571103/5001=134% 不影响累计值)。
- 目标月: --period 显式指定;默认自动选最近「已结账」完整月(当月跳过)。
结账信号优先:DB 损益结转凭证(carry_forward=1 & subject_code='3103') → check.md 文件;
两者皆无时才退回旧「销售额阈值」兜底(销售低 ≠ 未结账,仅保底用,日志标注依据)
@@ -48,6 +55,8 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(me
logger = logging.getLogger("cma.bep_deriver")
# 厂家政策渠补率(定死不可谈判,财务Bot报告权威值 2026-09-08)
# ⚠️ 2026-10-02 起**仅作对照展示**(返回值 rebate_policy_pct / remark),不参与任何计算。
# 参与计算的渠补率 = 逐期实测累计(见 compute_rebate_rate / 文件头「渠补率」条)。
POLICY_REBATE_RATE_PCT = 84.61
# 派生KPI 编码 → (kpi_code, 名称)
@@ -57,8 +66,15 @@ TARGET_KPIS = [
("F_BEP_GAP", "保本缺口"),
("F_BEP_REVENUE_CASH", "现金口径保本销售额"),
("F_BEP_COVER_CASH", "现金口径保本覆盖率"),
("F_BEP_REBATE", "实测渠补率"),
]
# 实测渠补率 KPI 编码(卡面单一事实源;无阈值,不参与预警级别判定)
REBATE_KPI = "F_BEP_REBATE"
# 保本口径 KPI(缺 F_BEP_REBATE 定义时不阻断派生主流程,仅跳过渠补率落库并告警)
CORE_KPI_CODES = [c for c, _ in TARGET_KPIS if c != REBATE_KPI]
# 现金口径 KPI 编码(计算路径独立于账面,remark 文案用现金口径)
CASH_CODES = {"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH"}
@@ -161,6 +177,40 @@ def _sum_field(db, entity_id: int, period_from: str, period_to: str,
return float(val or 0.0)
def compute_rebate_rate(db, entity_id: int, period_from: str, period_to: str) -> dict:
"""实测累计渠补率(2026-10-02 财务裁定口径)。
渠补率(实测) = Σ(571103 借方) ÷ Σ(5001 贷方) × 100,期间 = 年初至当期(YTD),保留 2 位小数。
· 571103 = 渠补/捐赠支出(损益类费用科目)⇒ 本期发生额落**借方**;
· 5001 = 主营业务收入 ⇒ 本期发生额落**贷方**;
· 行域 = carry_forward=0(与 _sum_field 一致,排除期间损益结转腿)。
返回 {'available', 'rebate_pct', 'rebate_sum_w', 'sales_sum_w', 'reason'};
**fail-closed**:分子或分母缺数/为 0 ⇒ available=False, rebate_pct=None
(调用方不得回落政策常量,须按「渠补率不可得」处理)。
"""
rebate_sum_w = _sum_field(db, entity_id, period_from, period_to,
"debit_amount", ("eq", REBATE_CODE)) / 10000.0
sales_sum_w = _sum_field(db, entity_id, period_from, period_to,
"credit_amount", ("eq", SALES_CODE)) / 10000.0
info = {
"rebate_sum_w": round(rebate_sum_w, 2),
"sales_sum_w": round(sales_sum_w, 2),
"available": False,
"rebate_pct": None,
"reason": "",
}
if sales_sum_w <= 0:
info["reason"] = "渠补率不可得:5001 贷方累计缺数或为 0"
return info
if rebate_sum_w <= 0:
info["reason"] = "渠补率不可得:571103 借方累计缺数或为 0"
return info
info["available"] = True
info["rebate_pct"] = round(rebate_sum_w / sales_sum_w * 100.0, 2)
return info
def _month_sales(db, entity_id: int, period: str) -> float:
"""单月销售收入(万元)——用于目标月完整度判断"""
return _sum_field(db, entity_id, period, period, "credit_amount",
@@ -287,17 +337,28 @@ def _eval_static_threshold(value: float, kpi) -> tuple:
return None, None
def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
write_kpi: bool = True) -> dict:
"""计算并落库五个派生KPI(账面3条+现金2条)。返回结果摘要。"""
"""计算并落库六个派生KPI(账面3条+现金2条+实测渠补率1条)。返回结果摘要。
渠补率(2026-10-02 裁定):默认按 YTD 从 voucher_details **现算**
(Σ571103借方 ÷ Σ5001贷方),政策常量 84.61% 仅作对照返回(rebate_policy_pct)。
显式传 rebate_rate_pct(`--rebate-rate`)时视为人工诊断覆盖。
fail-closed:实测渠补率不可得且无人工覆盖 ⇒ 该期不出保本值(六行 pending/NULL)。
"""
kpis = {k.kpi_code: k for k in db.query(KPIDefinition).filter(
KPIDefinition.entity_id == entity_id,
KPIDefinition.kpi_code.in_([c for c, _ in TARGET_KPIS]),
KPIDefinition.status == "active",
).all()}
missing = [c for c, _ in TARGET_KPIS if c not in kpis]
missing = [c for c in CORE_KPI_CODES if c not in kpis]
if missing:
raise RuntimeError(f"entity={entity_id} 缺少派生KPI定义: {missing}")
if REBATE_KPI not in kpis:
# 渠补率定义缺失不阻断保本主流程(部署顺序容错),但必须显式告警:
# 卡面此时取不到 rebate_pct(单一事实源缺失),需尽快补 bep_kpi_defs_rebate SQL。
logger.error("entity=%s 缺少 %s 定义(见 scripts/sql/bep_kpi_defs_rebate_20261002.sql);"
"本期跳过实测渠补率落库,卡面将判「渠补率不可得」", entity_id, REBATE_KPI)
year = period[:4]
period_from = f"{year}-01"
@@ -307,7 +368,6 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
# 1. 取数(元 → 万元)
sales_sum_w = _sum_field(db, entity_id, period_from, period_to, "credit_amount", ("eq", SALES_CODE)) / 10000.0
admin_sum_w = _sum_field(db, entity_id, period_from, period_to, "net", ("like", ADMIN_PREFIX)) / 10000.0
rebate_sum_w = _sum_field(db, entity_id, period_from, period_to, "net", ("eq", REBATE_CODE)) / 10000.0
dep_sum_w = _sum_field(db, entity_id, period_from, period_to, "net", ("eq", DEP_SUBJECT)) / 10000.0
sales_avg = sales_sum_w / months
@@ -315,11 +375,22 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
dep_avg = dep_sum_w / months
cash_admin_avg = admin_avg - dep_avg # 现金口径固定成本(剔除折旧纸面费用)
# 2. 渠补率:厂家政策值优先,日志附凭证累计校验
calc_rebate = (rebate_sum_w / sales_sum_w * 100.0) if sales_sum_w > 0 else 0.0
if rebate_rate_pct is None:
rebate_rate_pct = POLICY_REBATE_RATE_PCT
cm_pct = 100.0 - rebate_rate_pct # 边际贡献率(%)
# 2. 渠补率:默认逐期实测累计(不再取政策常量;常量仅对照)
rebate_info = compute_rebate_rate(db, entity_id, period_from, period_to)
calc_pct = rebate_info["rebate_pct"]
if rebate_rate_pct is not None:
rebate_info = dict(rebate_info, available=True, rebate_pct=round(float(rebate_rate_pct), 2),
reason="人工覆盖(--rebate-rate)")
logger.warning("渠补率使用人工覆盖值 %.2f%%(YTD 实算值 %s)", rebate_rate_pct,
("%.2f%%" % calc_pct) if calc_pct is not None else "不可得")
elif not rebate_info["available"]:
# fail-closed:不出保本值,绝不静默回落政策常量 84.61
logger.error("period=%s %s ⇒ 本期不出保本值(fail-closed,不回落政策常量)",
period, rebate_info["reason"])
return _fail_closed_rebate_unavailable(db, entity_id, period, months, rebate_info, kpis, write_kpi)
rebate_pct = rebate_info["rebate_pct"]
cm_pct = 100.0 - rebate_pct # 边际贡献率(%) = 100 − 实测渠补率
# 3. 派生计算(万元 / %)——账面3条路径不变;现金口径在账面基础上剔除折旧
bep = admin_avg / (cm_pct / 100.0) if cm_pct > 0 else 0.0
@@ -334,6 +405,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
"F_BEP_GAP": round(gap, 1),
"F_BEP_REVENUE_CASH": round(cash_bep, 1),
"F_BEP_COVER_CASH": round(cash_cover, 1),
"F_BEP_REBATE": round(rebate_pct, 2),
}
# 4. upsert kpi_values (source_type='derived',幂等)
@@ -342,12 +414,21 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
value_rows = {}
changed = []
for code, _ in TARGET_KPIS:
kpi = kpis[code]
if code in CASH_CODES:
kpi = kpis.get(code)
if kpi is None: # 定义缺失(仅可能为 F_BEP_REBATE,见上方告警)
written[code] = "no-definition"
continue
if code == REBATE_KPI:
remark = (f"实测渠补率(YTD累计): Σ571103借方 {rebate_info['rebate_sum_w']:.2f}万 ÷ "
f"Σ5001贷方 {rebate_info['sales_sum_w']:.2f}万 × 100 = {rebate_pct:.2f}%"
f"(政策常量 {POLICY_REBATE_RATE_PCT:.2f}% 仅供对照,不参与计算)")
elif code in CASH_CODES:
remark = (f"现金口径: 管理费月均{admin_avg:.2f}万-折旧月均{dep_avg:.2f}万"
f"=固定现金{cash_admin_avg:.2f}万 ÷ 边际贡献率{cm_pct:.2f}% → 保本{cash_bep:.2f}万")
f"=固定现金{cash_admin_avg:.2f}万 ÷ 边际贡献率{cm_pct:.2f}% → 保本{cash_bep:.2f}万"
f"(实测渠补率{rebate_pct:.2f}%)")
else:
remark = f"保本派生器: 销售月均{sales_avg:.1f}万/管理费月均{admin_avg:.1f}万/渠补率{rebate_rate_pct:.2f}%"
remark = (f"保本派生器: 销售月均{sales_avg:.1f}万/管理费月均{admin_avg:.1f}万/"
f"实测渠补率{rebate_pct:.2f}%(政策{POLICY_REBATE_RATE_PCT:.2f}%仅对照)")
existing = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
@@ -437,6 +518,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
"entity_id": entity_id,
"period": period,
"months": months,
"data_status": "ok",
"sales_sum_w": round(sales_sum_w, 2),
"sales_avg": round(sales_avg, 2),
"admin_sum_w": round(admin_sum_w, 2),
@@ -444,9 +526,12 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
"dep_sum_w": round(dep_sum_w, 2),
"dep_avg": round(dep_avg, 2),
"cash_admin_avg": round(cash_admin_avg, 2),
"rebate_sum_w": round(rebate_sum_w, 2),
"rebate_policy_pct": rebate_rate_pct,
"rebate_calc_pct": round(calc_rebate, 2),
"rebate_sum_w": rebate_info["rebate_sum_w"],
"rebate_sales_base_w": rebate_info["sales_sum_w"],
"rebate_actual_pct": rebate_pct,
"rebate_policy_pct": POLICY_REBATE_RATE_PCT,
"rebate_source": rebate_info.get("reason") or "YTD实测累计(Σ571103借方/Σ5001贷方)",
"rebate_calc_pct": round(calc_pct, 2) if calc_pct is not None else None,
"cm_pct": round(cm_pct, 2),
"values": values,
"written": written,
@@ -454,12 +539,77 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
}
def _fail_closed_rebate_unavailable(db, entity_id: int, period: str, months: int,
rebate_info: dict, kpis: dict,
write_kpi: bool) -> dict:
"""fail-closed 落库:实测渠补率不可得 ⇒ 该期不出保本值。
六条派生行写 actual_value=NULL + data_status='pending' + remark 注明「渠补率不可得」,
**不回落**政策常量 84.61(2026-10-02 财务裁定 / 任总 11:43 CST 放行)。
不评估阈值、不产生/解除预警(无值 ⇒ 无级别可评)。
"""
reason = rebate_info["reason"] or "渠补率不可得"
detail = (f"{reason};Σ571103借方 {rebate_info['rebate_sum_w']:.2f}万 / "
f"Σ5001贷方 {rebate_info['sales_sum_w']:.2f}万 ⇒ 本期不出保本值(fail-closed,"
f"不回落政策常量 {POLICY_REBATE_RATE_PCT:.2f}%)")
batch = f"bep-{datetime.now().strftime('%Y%m%d%H%M%S')}"
written = {}
if write_kpi:
for code, _ in TARGET_KPIS:
kpi = kpis.get(code)
if kpi is None:
written[code] = "no-definition"
continue
existing = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
KPIValue.source_type == "derived",
).first()
if existing is None:
db.add(KPIValue(
entity_id=entity_id,
kpi_id=kpi.id,
period=period,
actual_value=None,
source_type="derived",
source_batch=batch,
data_status="pending",
remark=detail,
))
written[code] = "insert"
else:
existing.actual_value = None
existing.source_batch = batch
existing.data_status = "pending"
existing.remark = detail
written[code] = "upsert"
db.commit()
else:
written = {c: "dry-run" for c, _ in TARGET_KPIS}
return {
"entity_id": entity_id,
"period": period,
"months": months,
"data_status": "pending",
"rebate_unavailable": True,
"reason": detail,
"sales_sum_w": rebate_info["sales_sum_w"],
"rebate_sum_w": rebate_info["rebate_sum_w"],
"rebate_actual_pct": None,
"rebate_policy_pct": POLICY_REBATE_RATE_PCT,
"rebate_source": reason,
"values": {},
"written": written,
"value_changed": [],
}
def main():
ap = argparse.ArgumentParser(description="保本点派生KPI执行器")
ap.add_argument("--period", default=None, help="目标期间 YYYY-MM(默认自动选最近完整月)")
ap.add_argument("--entity", type=int, default=1, help="entity_id(默认1=陕西酣客)")
ap.add_argument("--rebate-rate", type=float, default=None,
help="渠补率%覆盖(默认厂家政策84.61)")
help="渠补率%%人工覆盖(诊断用;**默认按 YTD 从 voucher_details 实测现算**,不再取政策常量)")
ap.add_argument("--dry-run", action="store_true", help="只计算不落库")
args = ap.parse_args()
@@ -469,18 +619,24 @@ def main():
r = run_derive(db, args.entity, period, args.rebate_rate,
write_kpi=not args.dry_run)
print(f"保本点派生计算完成 entity={r['entity_id']} period={r['period']} "
f"(YTD {r['months']}个月) mode={'dry-run' if args.dry_run else 'write'}")
f"(YTD {r['months']}个月) mode={'dry-run' if args.dry_run else 'write'} "
f"data_status={r['data_status']}")
if r["data_status"] == "pending":
print(f" ⚠ fail-closed:{r['reason']}")
print(f" 落库: {r['written']}")
return
print(f" 销售收入累计 {r['sales_sum_w']:.2f}万 月均 {r['sales_avg']:.2f}万")
print(f" 管理费累计 {r['admin_sum_w']:.2f}万 月均 {r['admin_avg']:.2f}万 (含折旧,账面口径)")
print(f" 折旧累计 {r['dep_sum_w']:.2f}万 月均 {r['dep_avg']:.2f}万 (560213) | "
f"现金固定成本月均 {r['cash_admin_avg']:.2f}万 (管理费-折旧)")
print(f" 渠补支出累计 {r['rebate_sum_w']:.2f}万 | 渠补率: 政策{r['rebate_policy_pct']:.2f}% "
f"(凭证累计校验 {r['rebate_calc_pct']:.2f}%) | 边际贡献率 {r['cm_pct']:.2f}%")
print(f" 渠补支出累计 {r['rebate_sum_w']:.2f}万 | 实测渠补率(YTD) {r['rebate_actual_pct']:.2f}% "
f"| 政策常量 {r['rebate_policy_pct']:.2f}% (仅对照) | 边际贡献率 {r['cm_pct']:.2f}%")
print(f" F_BEP_REVENUE 保本销售额 = {r['values']['F_BEP_REVENUE']} 万元")
print(f" F_BEP_COVER 保本覆盖率 = {r['values']['F_BEP_COVER']} %")
print(f" F_BEP_GAP 保本缺口 = {r['values']['F_BEP_GAP']} %")
print(f" F_BEP_REVENUE_CASH 现金保本销售额 = {r['values']['F_BEP_REVENUE_CASH']} 万元")
print(f" F_BEP_COVER_CASH 现金保本覆盖率 = {r['values']['F_BEP_COVER_CASH']} %")
print(f" F_BEP_REBATE 实测渠补率 = {r['values']['F_BEP_REBATE']} %")
print(f" 落库: {r['written']}")
finally:
db.close()
@@ -0,0 +1,20 @@
-- CMA BEP 实测渠补率派生KPI定义 — F_BEP_REBATE(2026-10-02)
-- 口径: 渠补率(实测) = Σ(571103 借方) ÷ Σ(5001 贷方) × 100,期间 = 年初至当期(YTD),2位小数
-- 依据: 财务Bot 2026-10-02 裁定(任总 11:43 CST 放行「改,这样数据更加真实」)
-- 政策常量 84.61% 自本日起仅作对照展示,不再参与计算
-- 幂等: NOT EXISTS 守卫,可安全重跑
INSERT INTO kpi_definitions
(entity_id, kpi_code, kpi_name, dimension, category, formula, formula_desc,
data_source_type, data_source, data_owner, frequency, unit, target_value,
target_calc_type, threshold_green, threshold_yellow, threshold_red,
kpi_level, status, bot_source, epic, created_by)
SELECT 1, 'F_BEP_REBATE', '实测渠补率', 'finance', 'profitability',
'round(sum(571103借方)/sum(5001贷方)*100,2)',
'渠补率(实测,YTD累计)=Σ571103借方÷Σ5001贷方; 供保本点卡面单一事实源; 政策常量84.61%仅对照; 缺数期不出值(fail-closed)',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 571103/5001 累计)', '财务部', 'monthly', '%', NULL,
'ratio', NULL, NULL, NULL,
'strategic', 'active', 'finance-bot', 'Epic2', 1
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM kpi_definitions WHERE entity_id = 1 AND kpi_code = 'F_BEP_REBATE'
);
@@ -0,0 +1,347 @@
"""BEP 渠补率改「实测累计」口径 单测 — finance-20261002-bep-rebate-actual-caliber-fullstack
口径依据(财务Bot 2026-10-02 裁定,任总 11:43 CST 放行「改,这样数据更加真实」):
渠补率(实测) = Σ(571103 **借方**) ÷ Σ(5001 **贷方**) × 100,期间 = 年初至当期(YTD),2 位小数;
政策常量 84.61% **仅作对照展示**,不参与任何计算;
fail-closed:571103 或 5001 缺数/为 0 ⇒ 该期**不出保本值**(pending + NULL),不静默回落常量。
覆盖两面:
· scripts/kpi_bep_deriver.py —— 派生落库写入面(去常量、YTD 现算、fail-closed)
· app/api/dashboard.py::/bep-card —— 卡面读取面(rebate_pct 读派生值、book/cash 同源读派生值)
夹具数值刻意复刻财务复算口径,使「改前(84.61%常量) / 改后(85.49%实测)」两套输出可逐项区分:
2026-01..07:Σ5001贷 = 794.35 万 | Σ571103借 = 672.10 万 ⇒ 渠补率 84.61%(= 常量,故该期值不变)
2026-01..08:Σ5001贷 = 808.79 万 | Σ571103借 = 691.43 万 ⇒ 渠补率 85.49%
月均管理费(含折旧) 25.774 万 / 现金固定成本月均 18.2605 万(8 个月)
⇒ 改前(15.39%):保本 167.5 / 118.7,覆盖 60.4% / 85.2%
⇒ 改后(14.51%):保本 177.6 / 125.8,覆盖 56.9% / 80.3%
"""
import datetime
import json
import os
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 值变复核提示件默认落 finance 的 _reports/;测试期重定向到临时目录(防污染对端 profile)
os.environ["BEP_REVIEW_NOTE_DIR"] = tempfile.mkdtemp(prefix="bep-test-review-notes-")
import pytest # noqa: E402
from app.models import KPIDefinition, KPIValue, VoucherDetail # noqa: E402
from app.api.dashboard import get_bep_card # noqa: E402
from scripts import kpi_bep_deriver as D # noqa: E402
ENTITY = 1
D_DATE_07 = datetime.datetime(2026, 7, 31)
D_DATE_08 = datetime.datetime(2026, 8, 31)
# ── 财务复算锚值(单位:元)────────────────────────────────────────────────
# 2026-01..07 累计
M07 = dict(period="2026-07", d=D_DATE_07,
sales=7943500.0, rebate=6721000.0, admin_other=1307810.0, dep=526050.0)
# 2026-08 单月增量(累计口径 01..08 = M07 + M08)
M08 = dict(period="2026-08", d=D_DATE_08,
sales=144400.0, rebate=193300.0, admin_other=153030.0, dep=75030.0)
KPI_CODES = (
("F_BEP_REVENUE", "保本销售额", "万元", None, None, None),
("F_BEP_COVER", "保本覆盖率", "%", ">=100", ">=85", "<85"),
("F_BEP_GAP", "保本缺口", "%", "<=0", "<=15", ">15"),
("F_BEP_REVENUE_CASH", "现金口径保本销售额", "万元", None, None, None),
("F_BEP_COVER_CASH", "现金口径保本覆盖率", "%", ">=100", ">=85", "<85"),
("F_BEP_REBATE", "实测渠补率", "%", None, None, None),
)
_seq = [700000]
def _voucher(db, row, subject_code, debit=0.0, credit=0.0, cf=0):
_seq[0] += 1
db.add(VoucherDetail(entity_id=ENTITY, voucher_no=str(_seq[0]),
voucher_date=row["d"], subject_code=subject_code,
subject_name="单测科目", debit_amount=debit,
credit_amount=credit, summary="渠补率口径单测",
carry_forward=cf, period=row["period"]))
db.flush()
def _month_rows(db, row, rebate_credit=0.0):
"""一个月的凭证行:收入(5001贷) / 渠补(571103借) / 管理费(5602% 含折旧 560213)"""
_voucher(db, row, "5001", credit=row["sales"])
if row["rebate"] is not None:
_voucher(db, row, "571103", debit=row["rebate"]) # 含显式 0 行(「存在但为 0」负例)
# 571103 贷方冲销行(负对照:证明实现走**借方**口径而非净额)
if rebate_credit:
_voucher(db, row, "571103", credit=rebate_credit)
_voucher(db, row, "560201", debit=row["admin_other"]) # 管理费其他明细
_voucher(db, row, "560213", debit=row["dep"]) # 管理费用-折旧(含在 5602% 内)
def _kpi_defs(db, include_rebate=True):
for code, name, unit, g, y, r in KPI_CODES:
if code == "F_BEP_REBATE" and not include_rebate:
continue
db.add(KPIDefinition(entity_id=ENTITY, kpi_code=code, kpi_name=name,
dimension="finance", category="profitability",
formula="单测", data_source="单测夹具", data_owner="财务部",
frequency="monthly", unit=unit,
threshold_green=g, threshold_yellow=y, threshold_red=r,
kpi_level="strategic", status="active"))
db.flush()
def _fixture_01_08(db, rebate_credit=0.0):
_kpi_defs(db)
_month_rows(db, M07)
_month_rows(db, M08, rebate_credit=rebate_credit)
db.flush()
def _derived_rows(db, period="2026-08"):
out = {}
for code, *_ in KPI_CODES:
kpi = db.query(KPIDefinition).filter(KPIDefinition.entity_id == ENTITY,
KPIDefinition.kpi_code == code).first()
if kpi is None:
continue
v = db.query(KPIValue).filter(KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
KPIValue.source_type == "derived").first()
if v is not None:
out[code] = v
return out
# ── 一、派生器:实测累计口径 ────────────────────────────────────────────────
def test_compute_rebate_rate_uses_ytd_actual(db):
"""实测渠补率 = Σ571103借方 ÷ Σ5001贷方(YTD 累计,2 位小数)"""
_fixture_01_08(db)
info_07 = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-07")
info_08 = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-08")
assert info_07["available"] is True
assert abs(info_07["rebate_pct"] - 84.61) < 0.005, info_07
assert abs(info_07["rebate_sum_w"] - 672.10) < 0.01, info_07
assert abs(info_07["sales_sum_w"] - 794.35) < 0.01, info_07
assert info_08["available"] is True
assert abs(info_08["rebate_pct"] - 85.49) < 0.005, info_08
def test_deriver_debit_side_negative_control(db):
"""571103 取**借方**而非净额(负对照):夹具含 10 万贷方冲销行。
若实现退回「净额」口径 ⇒ 渠补率 = (691.43-10)/808.79 = 84.25% ≠ 85.49%,本用例必红。
"""
_fixture_01_08(db, rebate_credit=100000.0)
info = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-08")
assert abs(info["rebate_pct"] - 85.49) < 0.005, info
net_w = D._sum_field(db, ENTITY, "2026-01", "2026-08", "net", ("eq", D.REBATE_CODE)) / 10000.0
assert abs(net_w - 681.43) < 0.01, net_w # 净额口径 = 691.43 − 10
assert net_w != info["rebate_sum_w"] # 两种口径在本夹具下必然不同
def test_deriver_2026_08_values_follow_actual_rebate(db):
"""派生器落库:改后 177.6 / 125.8 / 56.9 / 80.3,渠补率 85.49(政策常量仅对照)"""
_fixture_01_08(db)
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "ok", r
assert abs(r["rebate_actual_pct"] - 85.49) < 0.005, r
assert r["rebate_policy_pct"] == 84.61 # 常量保留(仅对照)
assert abs(r["values"]["F_BEP_REVENUE"] - 177.6) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REVENUE_CASH"] - 125.8) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_COVER"] - 56.9) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_COVER_CASH"] - 80.3) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REBATE"] - 85.49) < 0.005, r["values"]
# 边界外反证:若仍吃常量 84.61(旧口径)= 167.5 / 118.7
assert r["values"]["F_BEP_REVENUE"] != 167.5, r["values"]
rows = _derived_rows(db, "2026-08")
assert set(rows) == {c for c, *_ in KPI_CODES}, sorted(rows)
assert abs(float(rows["F_BEP_REBATE"].actual_value) - 85.49) < 0.005
assert "571103" in rows["F_BEP_REBATE"].remark and "政策常量" in rows["F_BEP_REBATE"].remark
def test_deriver_2026_07_unchanged_regression(db):
"""回归:1-7 月实测渠补率恰为 84.61% ⇒ 与政策常量路径逐项等值(170.2 / 121.4)"""
_kpi_defs(db)
_month_rows(db, M07)
db.flush()
r_auto = D.run_derive(db, ENTITY, "2026-07", None, write_kpi=True)
r_policy = D.run_derive(db, ENTITY, "2026-07", 84.61, write_kpi=True)
assert abs(r_auto["rebate_actual_pct"] - 84.61) < 0.005, r_auto
assert r_auto["values"] == r_policy["values"], (r_auto["values"], r_policy["values"])
assert abs(r_auto["values"]["F_BEP_REVENUE"] - 170.2) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_REVENUE_CASH"] - 121.4) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_COVER"] - 66.7) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_COVER_CASH"] - 93.5) < 0.05, r_auto["values"]
def test_deriver_policy_constant_not_used(db):
"""政策常量不参与计算(负对照):渠补率 50% ⇒ 保本 = 月固定成本 ÷ 50%,而非 ÷ 15.39%"""
_kpi_defs(db)
row = dict(period="2026-08", d=D_DATE_08, sales=8000000.0, rebate=4000000.0,
admin_other=800000.0, dep=0.0)
_month_rows(db, row)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=False)
assert abs(r["rebate_actual_pct"] - 50.0) < 0.005, r
# admin_avg = 80万/8个月 = 10 万 ⇒ 10/0.5 = 20.0 万(常量口径会是 10/0.1539 = 65.0 万)
assert abs(r["values"]["F_BEP_REVENUE"] - 20.0) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REVENUE"] - 65.0) > 0.5, r["values"]
# ── 二、派生器:fail-closed(缺数/为 0 不出值、不回落常量)─────────────────
def test_deriver_fail_closed_when_rebate_missing(db):
_kpi_defs(db)
r07 = dict(M07)
r07["rebate"] = None # 全年 571103 完全缺数
r08 = dict(M08)
r08["rebate"] = None
_month_rows(db, r07)
_month_rows(db, r08)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "pending", r
assert r["rebate_actual_pct"] is None, r
assert "渠补率不可得" in r["reason"], r
rows = _derived_rows(db, "2026-08")
assert set(rows) == {c for c, *_ in KPI_CODES}, sorted(rows)
for code, v in rows.items():
assert v.actual_value is None, (code, v.actual_value) # 不出保本值
assert v.data_status == "pending", (code, v.data_status)
assert "渠补率不可得" in (v.remark or ""), (code, v.remark)
assert rows["F_BEP_REVENUE"].actual_value != 167.5 # 未回落常量口径
def test_deriver_fail_closed_when_rebate_zero(db):
"""571103 存在但合计为 0 ⇒ 同样 fail-closed(不得静默回落 84.61)"""
_kpi_defs(db)
r07 = dict(M07)
r07["rebate"] = 0.0 # 显式 0 行
r08 = dict(M08)
r08["rebate"] = 0.0
_month_rows(db, r07)
_month_rows(db, r08)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "pending", r
assert r["rebate_sum_w"] == 0.0, r
rows = _derived_rows(db, "2026-08")
assert rows["F_BEP_REVENUE"].actual_value is None
assert rows["F_BEP_REVENUE"].data_status == "pending"
# ── 三、卡面 /bep-card:rebate_pct 读派生值 + book/cash 同源 ───────────────
def test_card_reads_derived_values_and_rebate_pct(db):
_fixture_01_08(db)
D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
card = get_bep_card(db=db, entity_id=ENTITY)
assert card["available"] is True, card
assert card["period"] == "2026-08", card
assert abs(card["inputs"]["rebate_pct"] - 85.49) < 0.005, card["inputs"]
assert card["inputs"]["rebate_policy_pct"] == 84.61, card["inputs"]
assert abs(card["inputs"]["cm_pct"] - 14.51) < 0.005, card["inputs"]
assert abs(card["book"]["bep_revenue"] - 177.6) < 0.05, card["book"]
assert abs(card["book"]["cover"] - 56.9) < 0.05, card["book"]
assert abs(card["cash"]["bep_revenue"] - 125.8) < 0.05, card["cash"]
assert abs(card["cash"]["cover"] - 80.3) < 0.05, card["cash"]
assert card["data_source"] == "derived"
def test_card_book_and_cash_both_read_derived_rows(db):
"""book / cash 同源回归:直接改库内派生值,两条路径都必须随之变(不再实时复算 voucher)"""
_fixture_01_08(db)
D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
first = get_bep_card(db=db, entity_id=ENTITY)
assert abs(first["book"]["bep_revenue"] - 177.6) < 0.05
assert abs(first["cash"]["bep_revenue"] - 125.8) < 0.05
# 临时改库内派生值(夹具库,非生产)——两路径应同步跟随
for code, new_val in (("F_BEP_REVENUE", 999.9), ("F_BEP_REVENUE_CASH", 888.8),
("F_BEP_REBATE", 60.0)):
kpi = db.query(KPIDefinition).filter(KPIDefinition.entity_id == ENTITY,
KPIDefinition.kpi_code == code).first()
row = db.query(KPIValue).filter(KPIValue.kpi_id == kpi.id,
KPIValue.period == "2026-08",
KPIValue.source_type == "derived").first()
row.actual_value = new_val
db.commit()
second = get_bep_card(db=db, entity_id=ENTITY)
assert abs(second["book"]["bep_revenue"] - 999.9) < 0.001, second["book"]
assert abs(second["cash"]["bep_revenue"] - 888.8) < 0.001, second["cash"]
assert abs(second["inputs"]["rebate_pct"] - 60.0) < 0.001, second["inputs"]
assert abs(second["inputs"]["cm_pct"] - 40.0) < 0.001, second["inputs"]
# 反证:实时复算渠道(voucher 现值 177.6)不得再决定卡面值
assert abs(second["book"]["bep_revenue"] - 177.6) > 1.0
def test_card_fail_closed_when_derived_rebate_absent(db):
"""派生渠补率不可得 ⇒ 卡面不出保本值(available=False),不回落常量、不跨期回落"""
_kpi_defs(db)
_month_rows(db, M07)
_month_rows(db, M08)
db.flush()
# 1) 07 期正常派生(当时 571103 数据齐全)
r07 = D.run_derive(db, ENTITY, "2026-07", None, write_kpi=True)
assert r07["data_status"] == "ok"
# 2) 全年 571103 缺数 ⇒ 08 期 fail-closed(已落 07 值不动)
db.query(VoucherDetail).filter(
VoucherDetail.entity_id == ENTITY,
VoucherDetail.subject_code == "571103",
).delete(synchronize_session=False)
db.commit()
r08 = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r08["data_status"] == "pending", r08
card = get_bep_card(db=db, entity_id=ENTITY)
# 08 期六条不齐(渠补率 NULL)⇒ 卡面不得跨期回落到 07 期,也不得回落常量
assert card["available"] is False, card
assert card["period"] == "2026-08", card
assert "渠补率" in card["message"], card
assert card.get("rebate_policy_pct") == 84.61, card
assert "book" not in card
# ── 四、验收探针(自测判别力):同一用例在改前 / 改后两树上跑 ────────────────
# 只依赖两版共有的 API(run_derive 返回的 values / get_bep_card),故可在 HEAD 快照树上复跑:
# 改前(政策常量 84.61%)⇒ FAIL,且失败信息里出现 167.5 / 118.7
# 改后(实测 85.49%) ⇒ PASS,输出 177.6 / 125.8 / 56.9 / 80.3 / rebate_pct 85.49
def test_acceptance_probe_2026_08(db):
"""验收探针:2026-08 口径数值(财务复算预告逐项比对)"""
_fixture_01_08(db)
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
vals = r.get("values", {})
card = get_bep_card(db=db, entity_id=ENTITY)
obs = {
"deriver.F_BEP_REVENUE": vals.get("F_BEP_REVENUE"),
"deriver.F_BEP_REVENUE_CASH": vals.get("F_BEP_REVENUE_CASH"),
"deriver.F_BEP_COVER": vals.get("F_BEP_COVER"),
"deriver.F_BEP_COVER_CASH": vals.get("F_BEP_COVER_CASH"),
"deriver.F_BEP_REBATE": vals.get("F_BEP_REBATE"),
"card.available": card.get("available"),
"card.inputs.rebate_pct": (card.get("inputs") or {}).get("rebate_pct"),
"card.inputs.rebate_policy_pct": (card.get("inputs") or {}).get("rebate_policy_pct"),
"card.book.bep_revenue": (card.get("book") or {}).get("bep_revenue"),
"card.book.cover": (card.get("book") or {}).get("cover"),
"card.cash.bep_revenue": (card.get("cash") or {}).get("bep_revenue"),
"card.cash.cover": (card.get("cash") or {}).get("cover"),
}
print("\n[ACCEPTANCE-PROBE] " + json.dumps(obs, ensure_ascii=False))
checks = [
("deriver.F_BEP_REVENUE", obs["deriver.F_BEP_REVENUE"], 177.6, 0.2),
("deriver.F_BEP_REVENUE_CASH", obs["deriver.F_BEP_REVENUE_CASH"], 125.8, 0.2),
("deriver.F_BEP_COVER", obs["deriver.F_BEP_COVER"], 56.9, 0.3),
("deriver.F_BEP_COVER_CASH", obs["deriver.F_BEP_COVER_CASH"], 80.3, 0.3),
("card.book.bep_revenue", obs["card.book.bep_revenue"], 177.6, 0.2),
("card.cash.bep_revenue", obs["card.cash.bep_revenue"], 125.8, 0.2),
("card.inputs.rebate_pct", obs["card.inputs.rebate_pct"], 85.49, 0.02),
]
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}"