feat(cvp): CM≤0 fail-closed 禁写0.0 + 博海BEP扩面(新口径) + 影子CVP标注is_mock + 前端NULL守卫/不适用
ci/woodpecker/push/main Pipeline failed

- kpi_bep_deriver.py: 实体口径注册表(app/utils/bep_caliber.py 单一事实源);博海(6001/6401/6601-03/1602);
  fail-closed② 边际贡献非正/固定成本退化 ⇒ 五条保本值 NULL+pending+remark'不适用'(禁回落0.0)
- dashboard.py /bep-card: 多实体口径 + not_applicable 态返回 label/reason
- predict.py /cvp-detailed: 显式 source=scenario_simulation + is_mock=true(影子CVP标注)
- 前端 BEPBreakEvenCard.vue: 全量数值判空 + '不适用'态;PredictDashboard.vue: 情景模拟·非实测标签

验收 accept-bep-cvp-20261003.py: BEFORE 2/11 → AFTER 11/11 PASS
任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack
This commit is contained in:
Hermes CI Fix
2026-10-03 16:03:12 +08:00
parent 8631ce718f
commit 1f1385ebbf
7 changed files with 662 additions and 147 deletions
+78
View File
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""BEP(本量利·保本点)实体口径注册表 —— **单一事实源**。
被两侧共用,避免口径漂移:
· 派生落库写入面:`backend/scripts/kpi_bep_deriver.py`
· 卡面读取面 :`backend/app/api/dashboard.py::/bep-card`
口径(2026-10-03 财务裁定,任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack):
| 项 | entity 1 陕西酣客 | entity 2 博海 |
|---|---|---|
| 变动成本(分子) | Σ571103 借方(渠补/捐赠支出) | Σ6401 借方(主营业务成本) |
| 收入基数(分母) | Σ5001 贷方(主营业务收入) | Σ6001 贷方(主营业务收入) |
| 变动成本率 | Σ571103借 ÷ Σ5001贷(实测渠补率) | Σ6401借 ÷ Σ6001贷(主营业务成本率) |
| 固定成本 | `5602%`(管理费用,含折旧)借−贷净额 | `6601+6602+6603`(销售/管理/财务费用)借−贷净额 |
| 折旧剔除(现金口径) | `560213`(管理费用-折旧叶子) | `1602` 累计折旧**当期贷方发生额**(见下) |
| 本期利润结转科目 | `3103` 本年利润 | `4103` 本年利润 |
| 结账信号次选(落盘) | `/root/WB_WS/_data/zxt-hanke/{YYYY.MM}/check.md` | 无(只用 DB 信号) |
博海折旧口径说明:博海科目表只到 4 位一级科目(无费用类折旧叶子,**不存在** 酣客式 `560213`),
唯一含「折旧」的是资产类 `1602 累计折旧`;其**当期贷方发生额** = 当期计提折旧额(对方科目
计入 `6602 管理费用`),故取它作现金口径剔除项。实测 2026-01..07 每月 1,468.83 元,
2026-08 起无计提行 ⇒ 2026-08 剔除额为 0(已在回报件列明)。
⚠️ 博海账套的「期间损益结转腿」(`summary='结转期间损溢'`,凭证如 `记-705/记-706`)
**未**标 `carry_forward=1`(全账套 17343 行 carry_forward 恒 0)⇒ 若不显式剔除,
损益类科目「借−贷」净额在已结账月份恒为 0,会把指标静默清零
(口径见 `/srv/hermes/wisdom/_decisions/cma-expense-profit-net-caliber-20261002.md`)。
故博海口径 `exclude_closing=True`。
"""
CLOSING_SUMMARY_LIKE = "%结转%"
# 酣客结账信号次选来源(智享通落盘 check.md;与 app/api/data.py 的 ZXT_LANDING_DIR 同源)
ZXT_LANDING_DIR_HANKE = "/root/WB_WS/_data/zxt-hanke"
ENTITY_CALIBERS = {
1: dict(
name="陕西酣客",
margin_label="实测渠补率",
margin_desc="Σ571103借方 ÷ Σ5001贷方",
revenue_field="credit_amount", revenue_filter=("eq", "5001"),
varcost_field="debit_amount", varcost_filter=("eq", "571103"),
fixed_filters=[("like", "5602")],
fixed_label="管理费",
dep_field="net", dep_filter=("eq", "560213"),
closing_subject="3103",
landing_dir=ZXT_LANDING_DIR_HANKE,
exclude_closing=False,
policy_pct=84.61, # 政策常量:仅对照展示,不参与计算(仅酣客有)
),
2: dict(
name="博海",
margin_label="变动成本率(主营业务成本率)",
margin_desc="Σ6401借方 ÷ Σ6001贷方",
revenue_field="credit_amount", revenue_filter=("eq", "6001"),
varcost_field="debit_amount", varcost_filter=("eq", "6401"),
fixed_filters=[("like", "6601"), ("like", "6602"), ("like", "6603")],
fixed_label="固定成本(6601+6602+6603)",
dep_field="credit_amount", dep_filter=("eq", "1602"),
closing_subject="4103",
landing_dir=None,
exclude_closing=True,
policy_pct=None, # 博海无渠补政策常量
),
}
def caliber_or_none(entity_id: int):
"""取实体口径;未注册返回 None(调用方自行决定放行/拒答)。"""
return ENTITY_CALIBERS.get(entity_id)
def caliber_for(entity_id: int) -> dict:
"""取实体口径;未注册抛 KeyError(写入面用,fail-closed)。"""
cal = ENTITY_CALIBERS.get(entity_id)
if cal is None:
raise KeyError(f"entity={entity_id} 未注册 BEP 口径(见 app/utils/bep_caliber.py)")
return cal