fix(kpi): BEP保本KPI阈值按>=85口径修正 + 新增现金口径派生KPI(剔除折旧560213) + bep-card现金读派生单一事实源 (fin-fix-bep-threshold-20260908)
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user