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
ci/woodpecker/push/main Pipeline was canceled
This commit is contained in:
@@ -29,16 +29,25 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
|
|||||||
# 月销 = voucher_details subject 5001(主营业务收入) 贷方 YTD 月均(万元)
|
# 月销 = voucher_details subject 5001(主营业务收入) 贷方 YTD 月均(万元)
|
||||||
# 月管理费 = 5602% 借−贷 净额 YTD 月均(carry_forward=0 行域;账面口径含折旧)
|
# 月管理费 = 5602% 借−贷 净额 YTD 月均(carry_forward=0 行域;账面口径含折旧)
|
||||||
# 折旧 = 560213 借−贷 净额 YTD 月均(同上;口径4 见 _decisions/cma-expense-profit-net-caliber-20261002.md)
|
# 折旧 = 560213 借−贷 净额 YTD 月均(同上;口径4 见 _decisions/cma-expense-profit-net-caliber-20261002.md)
|
||||||
# 渠补率 = 厂家政策 84.61%(财务Bot权威口径)
|
# 渠补率 = **实测累计**(Σ571103借方 ÷ Σ5001贷方,YTD)——取自派生落库值 F_BEP_REBATE(单一事实源)
|
||||||
# 保本额 = 月固定成本 ÷ (1-渠补率)
|
# 保本额 = 月固定成本 ÷ (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",
|
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_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_SALES_SUBJECT = "5001"
|
||||||
BEP_ADMIN_PREFIX = "5602"
|
BEP_ADMIN_PREFIX = "5602"
|
||||||
BEP_DEP_SUBJECT = "560213"
|
BEP_DEP_SUBJECT = "560213"
|
||||||
|
# 政策常量:仅作对照展示(2026-10-02 起不参与任何计算)
|
||||||
BEP_REBATE_POLICY_PCT = 84.61
|
BEP_REBATE_POLICY_PCT = 84.61
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +107,13 @@ def get_bep_card(
|
|||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
entity_id: int = Depends(get_entity_id),
|
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(
|
kpis = {k.kpi_code: k for k in db.query(KPIDefinition).filter(
|
||||||
KPIDefinition.entity_id == entity_id,
|
KPIDefinition.entity_id == entity_id,
|
||||||
KPIDefinition.status == "active",
|
KPIDefinition.status == "active",
|
||||||
@@ -109,60 +124,70 @@ def get_bep_card(
|
|||||||
return {"available": False, "message": "BEP账面口径派生KPI未完整注册",
|
return {"available": False, "message": "BEP账面口径派生KPI未完整注册",
|
||||||
"found": list(kpis.keys()), "missing": missing_book}
|
"found": list(kpis.keys()), "missing": missing_book}
|
||||||
|
|
||||||
# 最新派生期间与值(同派生器:source_type='derived');现金2条缺失不阻塞整卡
|
# 派生落库值(同派生器:source_type='derived')——按 code × period 建表,period desc 排序
|
||||||
latest = {}
|
# ⚠️ 不过滤 actual_value IS NULL:fail-closed 落库的 pending 期(值 NULL)必须参与「最新期」判定,
|
||||||
for code in BEP_CODES:
|
# 否则卡面会悄悄跨期回落到上一期,掩盖「本期不出值」的事实。
|
||||||
if code not in kpis:
|
rows = db.query(KPIValue, KPIDefinition.kpi_code).join(
|
||||||
latest[code] = None
|
KPIDefinition, KPIDefinition.id == KPIValue.kpi_id,
|
||||||
continue
|
).filter(
|
||||||
v = db.query(KPIValue).filter(
|
KPIDefinition.entity_id == entity_id,
|
||||||
KPIValue.kpi_id == kpis[code].id,
|
KPIDefinition.kpi_code.in_(BEP_CODES),
|
||||||
KPIValue.source_type == "derived",
|
KPIValue.source_type == "derived",
|
||||||
KPIValue.actual_value.isnot(None),
|
month_period_clause(KPIValue.period),
|
||||||
month_period_clause(KPIValue.period),
|
).order_by(KPIValue.period.desc(), KPIValue.id.desc()).all()
|
||||||
).order_by(KPIValue.period.desc(), KPIValue.id.desc()).first()
|
|
||||||
latest[code] = {"period": v.period, "value": v.actual_value} if v else None
|
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]
|
year = period[:4]
|
||||||
months = int(period[5:7])
|
months = int(period[5:7])
|
||||||
period_from = f"{year}-01"
|
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
|
"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
|
"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
|
"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_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 = {}
|
derived = {c: {"period": period, "value": d[c]} for c in BEP_CODES}
|
||||||
for code in BEP_CODES:
|
|
||||||
lv = latest[code]
|
|
||||||
derived[code] = {"period": lv["period"], "value": lv["value"]} if lv else None
|
|
||||||
|
|
||||||
cover_kpi = kpis["F_BEP_COVER"]
|
cover_kpi = kpis["F_BEP_COVER"]
|
||||||
book_level = _bep_level(book_cover, cover_kpi)
|
book_level = _bep_level(book_cover, cover_kpi)
|
||||||
@@ -182,7 +207,9 @@ def get_bep_card(
|
|||||||
"admin_avg": round(admin_avg, 2),
|
"admin_avg": round(admin_avg, 2),
|
||||||
"dep_avg": round(dep_avg, 2),
|
"dep_avg": round(dep_avg, 2),
|
||||||
"rebate_pct": rebate_pct,
|
"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": {
|
"book": {
|
||||||
"bep_revenue": book_bep,
|
"bep_revenue": book_bep,
|
||||||
@@ -200,7 +227,8 @@ def get_bep_card(
|
|||||||
"gap_level": cash_gap_level,
|
"gap_level": cash_gap_level,
|
||||||
},
|
},
|
||||||
"derived": derived,
|
"derived": derived,
|
||||||
"formula": "保本销售额 = 月固定成本 ÷ (1 − 渠补率84.61%)",
|
"data_source": "derived", # 账面/现金同源:均读派生落库值
|
||||||
|
"formula": f"保本销售额 = 月固定成本 ÷ (1 − 实测渠补率{rebate_pct:.2f}%)",
|
||||||
"readonly": True,
|
"readonly": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,20 @@
|
|||||||
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
|
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
|
||||||
F_BEP_REVENUE_CASH 现金保本销售额(万元) = (月均管理费-月均折旧) ÷ 边际贡献率 (现金口径,剔除折旧560213,中性无阈值)
|
F_BEP_REVENUE_CASH 现金保本销售额(万元) = (月均管理费-月均折旧) ÷ 边际贡献率 (现金口径,剔除折旧560213,中性无阈值)
|
||||||
F_BEP_COVER_CASH 现金保本覆盖率(%) = 月均销售 ÷ 现金保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
|
F_BEP_COVER_CASH 现金保本覆盖率(%) = 月均销售 ÷ 现金保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
|
||||||
|
F_BEP_REBATE 实测渠补率(%) = Σ(571103借方) ÷ Σ(5001贷方) × 100 (YTD累计; 卡面单一事实源,无阈值)
|
||||||
|
|
||||||
口径(与财务Bot报告一致):
|
口径(与财务Bot报告一致):
|
||||||
- 销售收入: voucher_details.subject_code='5001'(主营业务收入) 贷方合计,当年1月至目标月 YTD 月均
|
- 销售收入: voucher_details.subject_code='5001'(主营业务收入) 贷方合计,当年1月至目标月 YTD 月均
|
||||||
- 管理费: subject_code LIKE '5602%'(管理费用,含560213折旧) 借−贷净额合计(carry_forward=0),YTD 月均 → 账面口径
|
- 管理费: subject_code LIKE '5602%'(管理费用,含560213折旧) 借−贷净额合计(carry_forward=0),YTD 月均 → 账面口径
|
||||||
- 折旧: subject_code = '560213'(管理费用-折旧叶子) 借−贷净额合计(carry_forward=0),YTD 月均 → 现金口径剔除项(eq精确匹配,防5602130误含)
|
- 折旧: subject_code = '560213'(管理费用-折旧叶子) 借−贷净额合计(carry_forward=0),YTD 月均 → 现金口径剔除项(eq精确匹配,防5602130误含)
|
||||||
- 渠补率: 厂家政策定死 84.61% (571103渠补支出/5001销售, 1-7月累计凭证验证≈84.0%~84.6%)
|
- 渠补率: **实测累计口径**(2026-10-02 财务裁定,任总 11:43 CST 放行「改,这样数据更加真实」)
|
||||||
- 边际贡献率 = 1 - 渠补率 = 15.39%
|
渠补率(实测) = Σ(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 显式指定;默认自动选最近「已结账」完整月(当月跳过)。
|
- 目标月: --period 显式指定;默认自动选最近「已结账」完整月(当月跳过)。
|
||||||
结账信号优先:DB 损益结转凭证(carry_forward=1 & subject_code='3103') → check.md 文件;
|
结账信号优先: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")
|
logger = logging.getLogger("cma.bep_deriver")
|
||||||
|
|
||||||
# 厂家政策渠补率(定死不可谈判,财务Bot报告权威值 2026-09-08)
|
# 厂家政策渠补率(定死不可谈判,财务Bot报告权威值 2026-09-08)
|
||||||
|
# ⚠️ 2026-10-02 起**仅作对照展示**(返回值 rebate_policy_pct / remark),不参与任何计算。
|
||||||
|
# 参与计算的渠补率 = 逐期实测累计(见 compute_rebate_rate / 文件头「渠补率」条)。
|
||||||
POLICY_REBATE_RATE_PCT = 84.61
|
POLICY_REBATE_RATE_PCT = 84.61
|
||||||
|
|
||||||
# 派生KPI 编码 → (kpi_code, 名称)
|
# 派生KPI 编码 → (kpi_code, 名称)
|
||||||
@@ -57,8 +66,15 @@ TARGET_KPIS = [
|
|||||||
("F_BEP_GAP", "保本缺口"),
|
("F_BEP_GAP", "保本缺口"),
|
||||||
("F_BEP_REVENUE_CASH", "现金口径保本销售额"),
|
("F_BEP_REVENUE_CASH", "现金口径保本销售额"),
|
||||||
("F_BEP_COVER_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 文案用现金口径)
|
# 现金口径 KPI 编码(计算路径独立于账面,remark 文案用现金口径)
|
||||||
CASH_CODES = {"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH"}
|
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)
|
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:
|
def _month_sales(db, entity_id: int, period: str) -> float:
|
||||||
"""单月销售收入(万元)——用于目标月完整度判断"""
|
"""单月销售收入(万元)——用于目标月完整度判断"""
|
||||||
return _sum_field(db, entity_id, period, period, "credit_amount",
|
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
|
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:
|
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(
|
kpis = {k.kpi_code: k for k in db.query(KPIDefinition).filter(
|
||||||
KPIDefinition.entity_id == entity_id,
|
KPIDefinition.entity_id == entity_id,
|
||||||
KPIDefinition.kpi_code.in_([c for c, _ in TARGET_KPIS]),
|
KPIDefinition.kpi_code.in_([c for c, _ in TARGET_KPIS]),
|
||||||
KPIDefinition.status == "active",
|
KPIDefinition.status == "active",
|
||||||
).all()}
|
).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:
|
if missing:
|
||||||
raise RuntimeError(f"entity={entity_id} 缺少派生KPI定义: {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]
|
year = period[:4]
|
||||||
period_from = f"{year}-01"
|
period_from = f"{year}-01"
|
||||||
@@ -307,7 +368,6 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
|
|||||||
# 1. 取数(元 → 万元)
|
# 1. 取数(元 → 万元)
|
||||||
sales_sum_w = _sum_field(db, entity_id, period_from, period_to, "credit_amount", ("eq", SALES_CODE)) / 10000.0
|
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
|
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
|
dep_sum_w = _sum_field(db, entity_id, period_from, period_to, "net", ("eq", DEP_SUBJECT)) / 10000.0
|
||||||
|
|
||||||
sales_avg = sales_sum_w / months
|
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
|
dep_avg = dep_sum_w / months
|
||||||
cash_admin_avg = admin_avg - dep_avg # 现金口径固定成本(剔除折旧纸面费用)
|
cash_admin_avg = admin_avg - dep_avg # 现金口径固定成本(剔除折旧纸面费用)
|
||||||
|
|
||||||
# 2. 渠补率:厂家政策值优先,日志附凭证累计校验
|
# 2. 渠补率:默认逐期实测累计(不再取政策常量;常量仅对照)
|
||||||
calc_rebate = (rebate_sum_w / sales_sum_w * 100.0) if sales_sum_w > 0 else 0.0
|
rebate_info = compute_rebate_rate(db, entity_id, period_from, period_to)
|
||||||
if rebate_rate_pct is None:
|
calc_pct = rebate_info["rebate_pct"]
|
||||||
rebate_rate_pct = POLICY_REBATE_RATE_PCT
|
if rebate_rate_pct is not None:
|
||||||
cm_pct = 100.0 - rebate_rate_pct # 边际贡献率(%)
|
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条路径不变;现金口径在账面基础上剔除折旧
|
# 3. 派生计算(万元 / %)——账面3条路径不变;现金口径在账面基础上剔除折旧
|
||||||
bep = admin_avg / (cm_pct / 100.0) if cm_pct > 0 else 0.0
|
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_GAP": round(gap, 1),
|
||||||
"F_BEP_REVENUE_CASH": round(cash_bep, 1),
|
"F_BEP_REVENUE_CASH": round(cash_bep, 1),
|
||||||
"F_BEP_COVER_CASH": round(cash_cover, 1),
|
"F_BEP_COVER_CASH": round(cash_cover, 1),
|
||||||
|
"F_BEP_REBATE": round(rebate_pct, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 4. upsert kpi_values (source_type='derived',幂等)
|
# 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 = {}
|
value_rows = {}
|
||||||
changed = []
|
changed = []
|
||||||
for code, _ in TARGET_KPIS:
|
for code, _ in TARGET_KPIS:
|
||||||
kpi = kpis[code]
|
kpi = kpis.get(code)
|
||||||
if code in CASH_CODES:
|
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}万"
|
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:
|
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(
|
existing = db.query(KPIValue).filter(
|
||||||
KPIValue.kpi_id == kpi.id,
|
KPIValue.kpi_id == kpi.id,
|
||||||
KPIValue.period == period,
|
KPIValue.period == period,
|
||||||
@@ -437,6 +518,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
|
|||||||
"entity_id": entity_id,
|
"entity_id": entity_id,
|
||||||
"period": period,
|
"period": period,
|
||||||
"months": months,
|
"months": months,
|
||||||
|
"data_status": "ok",
|
||||||
"sales_sum_w": round(sales_sum_w, 2),
|
"sales_sum_w": round(sales_sum_w, 2),
|
||||||
"sales_avg": round(sales_avg, 2),
|
"sales_avg": round(sales_avg, 2),
|
||||||
"admin_sum_w": round(admin_sum_w, 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_sum_w": round(dep_sum_w, 2),
|
||||||
"dep_avg": round(dep_avg, 2),
|
"dep_avg": round(dep_avg, 2),
|
||||||
"cash_admin_avg": round(cash_admin_avg, 2),
|
"cash_admin_avg": round(cash_admin_avg, 2),
|
||||||
"rebate_sum_w": round(rebate_sum_w, 2),
|
"rebate_sum_w": rebate_info["rebate_sum_w"],
|
||||||
"rebate_policy_pct": rebate_rate_pct,
|
"rebate_sales_base_w": rebate_info["sales_sum_w"],
|
||||||
"rebate_calc_pct": round(calc_rebate, 2),
|
"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),
|
"cm_pct": round(cm_pct, 2),
|
||||||
"values": values,
|
"values": values,
|
||||||
"written": written,
|
"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():
|
def main():
|
||||||
ap = argparse.ArgumentParser(description="保本点派生KPI执行器")
|
ap = argparse.ArgumentParser(description="保本点派生KPI执行器")
|
||||||
ap.add_argument("--period", default=None, help="目标期间 YYYY-MM(默认自动选最近完整月)")
|
ap.add_argument("--period", default=None, help="目标期间 YYYY-MM(默认自动选最近完整月)")
|
||||||
ap.add_argument("--entity", type=int, default=1, help="entity_id(默认1=陕西酣客)")
|
ap.add_argument("--entity", type=int, default=1, help="entity_id(默认1=陕西酣客)")
|
||||||
ap.add_argument("--rebate-rate", type=float, default=None,
|
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="只计算不落库")
|
ap.add_argument("--dry-run", action="store_true", help="只计算不落库")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
@@ -469,18 +619,24 @@ def main():
|
|||||||
r = run_derive(db, args.entity, period, args.rebate_rate,
|
r = run_derive(db, args.entity, period, args.rebate_rate,
|
||||||
write_kpi=not args.dry_run)
|
write_kpi=not args.dry_run)
|
||||||
print(f"保本点派生计算完成 entity={r['entity_id']} period={r['period']} "
|
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['sales_sum_w']:.2f}万 月均 {r['sales_avg']:.2f}万")
|
||||||
print(f" 管理费累计 {r['admin_sum_w']:.2f}万 月均 {r['admin_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) | "
|
print(f" 折旧累计 {r['dep_sum_w']:.2f}万 月均 {r['dep_avg']:.2f}万 (560213) | "
|
||||||
f"现金固定成本月均 {r['cash_admin_avg']:.2f}万 (管理费-折旧)")
|
f"现金固定成本月均 {r['cash_admin_avg']:.2f}万 (管理费-折旧)")
|
||||||
print(f" 渠补支出累计 {r['rebate_sum_w']:.2f}万 | 渠补率: 政策{r['rebate_policy_pct']:.2f}% "
|
print(f" 渠补支出累计 {r['rebate_sum_w']:.2f}万 | 实测渠补率(YTD) {r['rebate_actual_pct']:.2f}% "
|
||||||
f"(凭证累计校验 {r['rebate_calc_pct']:.2f}%) | 边际贡献率 {r['cm_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_REVENUE 保本销售额 = {r['values']['F_BEP_REVENUE']} 万元")
|
||||||
print(f" F_BEP_COVER 保本覆盖率 = {r['values']['F_BEP_COVER']} %")
|
print(f" F_BEP_COVER 保本覆盖率 = {r['values']['F_BEP_COVER']} %")
|
||||||
print(f" F_BEP_GAP 保本缺口 = {r['values']['F_BEP_GAP']} %")
|
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_REVENUE_CASH 现金保本销售额 = {r['values']['F_BEP_REVENUE_CASH']} 万元")
|
||||||
print(f" F_BEP_COVER_CASH 现金保本覆盖率 = {r['values']['F_BEP_COVER_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']}")
|
print(f" 落库: {r['written']}")
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
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}"
|
||||||
Reference in New Issue
Block a user