fix(kpi): BEP保本KPI阈值按>=85口径修正 + 新增现金口径派生KPI(剔除折旧560213) + bep-card现金读派生单一事实源 (fin-fix-bep-threshold-20260908)

This commit is contained in:
Hermes CI Fix
2026-09-08 15:57:47 +08:00
parent cf81ee0ccd
commit 5bcc65b47f
3 changed files with 78 additions and 19 deletions
+36 -8
View File
@@ -1,13 +1,16 @@
"""保本点(盈亏平衡)派生KPI执行器 — 管理会计OS (cma-bep-kpi 2026-09-08)
按财务Bot权威口径(陕西酣客_保本点分析_2026年1-7月.md)自动计算并落库三个派生KPI:
F_BEP_REVENUE 保本销售额(万元) = 月均管理费 ÷ 边际贡献率
F_BEP_COVER 保本覆盖率(%) = 月均销售 ÷ 保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
按财务Bot权威口径(陕西酣客_保本点分析_2026年1-7月.md)自动计算并落库五个派生KPI:
F_BEP_REVENUE 保本销售额(万元) = 月均管理费 ÷ 边际贡献率 (账面口径,含折旧)
F_BEP_COVER 保本覆盖率(%) = 月均销售 ÷ 保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
F_BEP_REVENUE_CASH 现金保本销售额(万元) = (月均管理费-月均折旧) ÷ 边际贡献率 (现金口径,剔除折旧560213,中性无阈值)
F_BEP_COVER_CASH 现金保本覆盖率(%) = 月均销售 ÷ 现金保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
口径(与财务Bot报告一致):
- 销售收入: voucher_details.subject_code='5001'(主营业务收入) 贷方合计,当年1月至目标月 YTD 月均
- 管理费: subject_code LIKE '5602%'(管理费用,含560213折旧) 借方合计,YTD 月均 → 账面口径
- 折旧: subject_code = '560213'(管理费用-折旧叶子) 借方合计,YTD 月均 → 现金口径剔除项(eq精确匹配,防5602130误含)
- 渠补率: 厂家政策定死 84.61% (571103渠补支出/5001销售, 1-7月累计凭证验证≈84.0%~84.6%)
- 边际贡献率 = 1 - 渠补率 = 15.39%
- 目标月: --period 显式指定;默认自动选最近完整月(当月跳过;最近月销售额<近6月均30%视为未结账回退)
@@ -45,10 +48,16 @@ TARGET_KPIS = [
("F_BEP_REVENUE", "保本销售额"),
("F_BEP_COVER", "保本覆盖率"),
("F_BEP_GAP", "保本缺口"),
("F_BEP_REVENUE_CASH", "现金口径保本销售额"),
("F_BEP_COVER_CASH", "现金口径保本覆盖率"),
]
# 现金口径 KPI 编码(计算路径独立于账面,remark 文案用现金口径)
CASH_CODES = {"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH"}
SALES_CODE = "5001" # 主营业务收入
ADMIN_PREFIX = "5602" # 管理费用(含折旧叶子560213)
DEP_SUBJECT = "560213" # 管理费用-折旧(现金口径剔除项,eq 精确匹配防 5602130 误含)
REBATE_CODE = "571103" # 渠补/捐赠支出(校验用,不直接进公式)
@@ -160,7 +169,7 @@ def _eval_static_threshold(value: float, kpi) -> tuple:
def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
write_kpi: bool = True) -> dict:
"""计算并落库三个派生KPI。返回结果摘要。"""
"""计算并落库五个派生KPI(账面3条+现金2条)。返回结果摘要。"""
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]),
@@ -179,9 +188,12 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
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, "debit_amount", ("like", ADMIN_PREFIX)) / 10000.0
rebate_sum_w = _sum_field(db, entity_id, period_from, period_to, "debit_amount", ("eq", REBATE_CODE)) / 10000.0
dep_sum_w = _sum_field(db, entity_id, period_from, period_to, "debit_amount", ("eq", DEP_SUBJECT)) / 10000.0
sales_avg = sales_sum_w / months
admin_avg = admin_sum_w / months
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
@@ -189,15 +201,19 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
rebate_rate_pct = POLICY_REBATE_RATE_PCT
cm_pct = 100.0 - rebate_rate_pct # 边际贡献率(%)
# 3. 派生计算(万元 / %)
# 3. 派生计算(万元 / %)——账面3条路径不变;现金口径在账面基础上剔除折旧
bep = admin_avg / (cm_pct / 100.0) if cm_pct > 0 else 0.0
cover = (sales_avg / bep * 100.0) if bep > 0 else 0.0
gap = ((bep - sales_avg) / bep * 100.0) if bep > 0 else 0.0
cash_bep = cash_admin_avg / (cm_pct / 100.0) if cm_pct > 0 else 0.0
cash_cover = (sales_avg / cash_bep * 100.0) if cash_bep > 0 else 0.0
values = {
"F_BEP_REVENUE": round(bep, 1),
"F_BEP_COVER": round(cover, 1),
"F_BEP_GAP": round(gap, 1),
"F_BEP_REVENUE_CASH": round(cash_bep, 1),
"F_BEP_COVER_CASH": round(cash_cover, 1),
}
# 4. upsert kpi_values (source_type='derived',幂等)
@@ -206,6 +222,11 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
value_rows = {}
for code, _ in TARGET_KPIS:
kpi = kpis[code]
if code in CASH_CODES:
remark = (f"现金口径: 管理费月均{admin_avg:.2f}万-折旧月均{dep_avg:.2f}万"
f"=固定现金{cash_admin_avg:.2f}万 ÷ 边际贡献率{cm_pct:.2f}% → 保本{cash_bep:.2f}万")
else:
remark = f"保本派生器: 销售月均{sales_avg:.1f}万/管理费月均{admin_avg:.1f}万/渠补率{rebate_rate_pct:.2f}%"
existing = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
@@ -216,7 +237,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
if existing:
existing.actual_value = values[code]
existing.source_batch = batch
existing.remark = f"保本派生器: 销售月均{sales_avg:.1f}万/管理费月均{admin_avg:.1f}万/渠补率{rebate_rate_pct:.2f}%"
existing.remark = remark
existing.data_status = "pending"
else:
existing = KPIValue(
@@ -227,7 +248,7 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
source_type="derived",
source_batch=batch,
data_status="pending",
remark=f"保本派生器: 销售月均{sales_avg:.1f}万/管理费月均{admin_avg:.1f}万/渠补率{rebate_rate_pct:.2f}%",
remark=remark,
)
db.add(existing)
db.flush()
@@ -281,6 +302,9 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float,
"sales_avg": round(sales_avg, 2),
"admin_sum_w": round(admin_sum_w, 2),
"admin_avg": round(admin_avg, 2),
"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),
@@ -308,11 +332,15 @@ def main():
f"(YTD {r['months']}个月) mode={'dry-run' if args.dry_run else 'write'}")
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" 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" 落库: {r['written']}")
finally:
db.close()