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
+25 -10
View File
@@ -26,7 +26,10 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
# 渠补率 = 厂家政策 84.61%(财务Bot权威口径)
# 保本额 = 月固定成本 ÷ (1-渠补率)
# 账面口径(含折旧) / 现金口径(剔除折旧) 双口径只读返回,无写操作。
BEP_CODES = ("F_BEP_REVENUE", "F_BEP_COVER", "F_BEP_GAP")
# 现金口径数值优先读派生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")
BEP_BOOK_CODES = ("F_BEP_REVENUE", "F_BEP_COVER", "F_BEP_GAP") # 账面3条必须齐全才 available
BEP_SALES_SUBJECT = "5001"
BEP_ADMIN_PREFIX = "5602"
BEP_DEP_SUBJECT = "560213"
@@ -86,12 +89,17 @@ def get_bep_card(
KPIDefinition.status == "active",
KPIDefinition.kpi_code.in_(BEP_CODES),
).all()}
if len(kpis) != 3:
return {"available": False, "message": "BEP派生KPI未完整注册", "found": list(kpis.keys())}
missing_book = [c for c in BEP_BOOK_CODES if c not in kpis]
if missing_book:
return {"available": False, "message": "BEP账面口径派生KPI未完整注册",
"found": list(kpis.keys()), "missing": missing_book}
# 最新派生期间与值(同派生器:source_type='derived')
# 最新派生期间与值(同派生器: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",
@@ -99,7 +107,7 @@ def get_bep_card(
).order_by(KPIValue.period.desc(), KPIValue.id.desc()).first()
latest[code] = {"period": v.period, "value": v.actual_value} if v else None
periods = [latest[c]["period"] for c in BEP_CODES if latest[c]]
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)
@@ -122,13 +130,19 @@ def get_bep_card(
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_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_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
# 参考最新落库派生值(存在时使用,保证与 Phase1 KPI 一致)
# 回显最新落库派生值(账面3条+现金2条,存在时输出)
derived = {}
for code in BEP_CODES:
lv = latest[code]
@@ -136,7 +150,8 @@ def get_bep_card(
cover_kpi = kpis["F_BEP_COVER"]
book_level = _bep_level(book_cover, cover_kpi)
cash_level = _bep_level(cash_cover, cover_kpi)
cash_cover_kpi = kpis.get("F_BEP_COVER_CASH") or cover_kpi
cash_level = _bep_level(cash_cover, cash_cover_kpi)
gap_kpi = kpis["F_BEP_GAP"]
book_gap_level = _bep_level(book_gap, gap_kpi)
cash_gap_level = _bep_level(cash_gap, gap_kpi)
+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()
+17 -1
View File
@@ -3,8 +3,10 @@
-- 口径: 财务Bot权威文件 陕西酣客_保本点分析_2026年1-7月.md
-- 渠补率=84.61%(厂家政策,571103/5001累计验证84.05%~84.61%区间)
-- 边际贡献率=1-渠补率=15.39%
-- 保本销售额=月均管理费(5602借方,含折旧) ÷ 15.39% → 26.2万/15.39% = 170.3万
-- 保本销售额=月均管理费(5602借方,含折旧) ÷ 15.39% → 26.2万/15.39% = 170.3万 (账面口径)
-- 覆盖率=月均销售(5001贷方)÷保本销售额 → 113.5/170.3 = 66.7%
-- 现金口径(剔除折旧560213): 现金保本销售额=(月均管理费-月均折旧) ÷ 15.39% (~121.4万)
-- 现金覆盖率=月均销售÷现金保本额 (~93.5%);两口径并列展示,账面红≠现金红
INSERT INTO kpi_definitions
(entity_id, kpi_code, kpi_name, dimension, category, formula, formula_desc,
@@ -31,4 +33,18 @@ VALUES
'反向指标(缺口越小越好): <=0绿(已保本) <=15黄 >15红; 当前约33%=月销距保本差33%',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 5001月均)', '财务部', 'monthly', '%', 0,
'ratio', '<=0', '<=15', '>15',
'strategic', 'active', 'finance-bot', 'Epic2', 1),
(1, 'F_BEP_REVENUE_CASH', '现金口径保本销售额', 'finance', 'profitability',
'round((月管理费月均-月折旧月均)/(1-渠补率),1)',
'现金口径=月管理费(5602借方月均,剔除折旧560213)÷(1-渠补率84.61%); 中性参考值, 无阈值',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 5602/560213月均)', '财务部', 'monthly', '万元', 0,
'ratio', NULL, NULL, NULL,
'strategic', 'active', 'finance-bot', 'Epic2', 1),
(1, 'F_BEP_COVER_CASH', '现金口径保本覆盖率', 'finance', 'profitability',
'round(实际月销/现金口径保本额*100,1)',
'实际月销(5001贷方月均)÷现金口径保本额; 正向指标: >=100绿 >=85黄 <85红; 当前约93.5%(2026-07)',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 5001/5602/560213月均)', '财务部', 'monthly', '%', 100,
'ratio', '>=100', '>=85', '<85',
'strategic', 'active', 'finance-bot', 'Epic2', 1);