fix(cvp): BEP ⑤ 口径统一落地(变动成本率=完整口径)
ci/woodpecker/push/main Pipeline failed

- 派生器/口径:F_BEP_VC_RATE 新增;卡面/公式行文案改「月固定成本 ÷ 边际贡献率」
- 保本额 = 月固定成本 ÷ (1 − 变动成本率);实测渠补率(F_BEP_REBATE)降为 e1 专用 chip
- 报告卡口径说明文字同步(固定成本=固定费用合计)

任务: finance-20261003-cvp-fix-and-bohai-coverage-fullstack ⑤
This commit is contained in:
wecom-fullstack
2026-10-04 19:53:12 +08:00
committed by Hermes CI Fix
parent 4fe956fcf9
commit 587d5b3983
5 changed files with 240 additions and 77 deletions
+35 -15
View File
@@ -35,8 +35,10 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
# 月销 = voucher_details subject 5001(主营业务收入) 贷方 YTD 月均(万元)
# 月管理费 = 5602% 借−贷 净额 YTD 月均(carry_forward=0 行域;账面口径含折旧)
# 折旧 = 560213 借−贷 净额 YTD 月均(同上;口径4 见 _decisions/cma-expense-profit-net-caliber-20261002.md)
# 渠补率 = **实测累计**(Σ571103借方 ÷ Σ5001贷方,YTD)——取自派生落库值 F_BEP_REBATE(单一事实源)
# 保本额 = 月固定成本 ÷ (1-渠补率)
# 变动成本率 = **实测累计**(⑤ 口径统一:酣客=Σ(5401+5403+571103−530103)÷Σ5001贷;
# 博海=Σ6401借÷Σ6001贷)——取自派生落库值 F_BEP_VC_RATE(单一事实源)
# 实测渠补率 = Σ571103借 ÷ Σ5001贷(酣客专用,F_BEP_REBATE,仅作 chip 展示,**不再充当变动成本率**)
# 保本额 = 月固定成本 ÷ 边际贡献率(边际贡献率 = 1 − 变动成本率)
# 2026-10-02 口径修订(任总 11:43 CST 放行「改,这样数据更加真实」):
# ① 渠补率不再用政策常量 84.61 参与计算,改用派生落库的实测累计值;
# 政策常量仅作对照返回(inputs.rebate_policy_pct),**不参与计算**;
@@ -46,10 +48,16 @@ router = APIRouter(prefix="/api/cma/dashboard", tags=["驾驶舱"],
# 不出保本值、不回落政策常量(不跨期回落,避免混期口径)。
# 账面口径(含折旧) / 现金口径(剔除折旧) 双口径只读返回,无写操作。
BEP_CODES = ("F_BEP_REVENUE", "F_BEP_COVER", "F_BEP_GAP",
"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH", "F_BEP_REBATE")
"F_BEP_REVENUE_CASH", "F_BEP_COVER_CASH",
"F_BEP_VC_RATE", "F_BEP_REBATE")
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编码(卡面单一事实源)
# 保本公式所用「变动成本率」编码(⑤ 口径统一 2026-10-03:边际贡献率 = 1 − 变动成本率)
BEP_VC_CODE = "F_BEP_VC_RATE"
# 实测渠补率编码(酣客 e1 专用、**可选**;⑤ 后**不再充当变动成本率**,仅作卡面 chip)
BEP_REBATE_CODE = "F_BEP_REBATE"
# fail-closed 必备六条:VC_RATE 必填;REBATE 因博海(e2)无渠补科目而不作必备
BEP_REQUIRED_CODES = tuple(c for c in BEP_CODES if c != BEP_REBATE_CODE)
# 展示用输入口径常量(**仅酣客(entity1)**;多实体一律走 app/utils/bep_caliber.py 的 ENTITY_CALIBERS,
# 2026-10-03 扩面:博海 entity2 为 6001/6401/6601+6602+6603/1602)
BEP_SALES_SUBJECT = "5001"
@@ -153,6 +161,10 @@ def get_bep_card(
· 渠补率 = 派生落库的**实测累计值**(F_BEP_REBATE,单一事实源);政策常量仅对照返回;
· 账面 / 现金两条路径**统一读派生落库值**(不再实时复算 voucher);
· fail-closed:六条派生值同期不齐(尤其缺渠补率)⇒ available=False,不出保本值、不回落常量。
2026-10-03 口径统一(⑤,任总 18:00 CST):**边际贡献率 = 1 − 变动成本率**(F_BEP_VC_RATE,
完整口径:销货成本+税金及附加+渠补−厂补 / 博海 6401÷6001),不再用渠补率充当变动成本率;
实测渠补率 F_BEP_REBATE 降为 e1 专用展示 chip(e2 无渠补科目 ⇒ None,不再纳入必备集)。
"""
cal = caliber_or_none(entity_id)
if cal is None:
@@ -199,20 +211,21 @@ def get_bep_card(
months = int(period[5:7])
period_from = f"{year}-01"
# fail-closed:六条派生值(含变动成本率)本期必须齐全,缺一条即不出保本值
missing_at_period = [c for c in BEP_CODES if by_code.get(c, {}).get(period) is None]
# fail-closed:必备派生值(含变动成本率)本期必须齐全,缺一条即不出保本值
missing_at_period = [c for c in BEP_REQUIRED_CODES if by_code.get(c, {}).get(period) is None]
if missing_at_period:
# ① 不适用(派生器 fail-closed 落库:remark 已注明「保本点不适用」)
# —— 与「缺数」区分:卡面显示「不适用」而非「数据不齐」,且不得显示 0。
na_remarks = [remark_map.get((c, period), "") for c in missing_at_period]
na_hit = [r for r in na_remarks if "不适用" in r]
if na_hit and BEP_REBATE_CODE not in missing_at_period:
if na_hit and BEP_VC_CODE not in missing_at_period:
reason = na_hit[0]
label = ("边际贡献为负 · 保本点不适用" if "边际贡献非正" in reason
else "固定成本退化 · 保本点不适用")
return {"available": False, "not_applicable": True, "entity_id": entity_id,
"period": period, "months": months,
"label": label, "reason": reason,
"vc_pct": by_code.get(BEP_VC_CODE, {}).get(period),
"rebate_pct": by_code.get(BEP_REBATE_CODE, {}).get(period),
"rebate_policy_pct": (cal.get("policy_pct")
if cal.get("policy_pct") is not None else BEP_REBATE_POLICY_PCT),
@@ -221,8 +234,8 @@ def get_bep_card(
"derived_periods": book_periods[:6],
"data_source": "derived", "readonly": True}
# ② 缺数(真不齐)
if BEP_REBATE_CODE in missing_at_period:
msg = (f"{cal['margin_label']}不可得:本期未落 F_BEP_REBATE 派生值 ⇒ 不出保本值"
if BEP_VC_CODE in missing_at_period:
msg = (f"{cal['margin_label']}不可得:本期未落 F_BEP_VC_RATE 派生值 ⇒ 不出保本值"
"(fail-closed,不回落政策常量、不跨期回落)")
else:
msg = ("本期派生值不齐 %s ⇒ 不出保本值(fail-closed,不跨期回落)"
@@ -233,16 +246,19 @@ def get_bep_card(
else BEP_REBATE_POLICY_PCT),
"derived_periods": book_periods[:6], "readonly": True}
# 六条派生落库值(同期)—— 账面 / 现金两条路径同一来源,卡面不做实时复算
d = {c: by_code[c][period] for c in BEP_CODES}
# 派生落库值(同期)—— 账面 / 现金两条路径同一来源,卡面不做实时复算
d = {c: by_code.get(c, {}).get(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)
# ⑤ 口径统一(任总 2026-10-03 18:00「统一」):保本公式用**变动成本率**(F_BEP_VC_RATE),
# 边际贡献率 = 1 − 变动成本率;F_BEP_REBATE(实测渠补率)仅酣客(e1)有,**不再充当变动成本率**。
vc_pct = float(d[BEP_VC_CODE])
cm_pct = round(100.0 - vc_pct, 2)
rebate_pct = (float(d[BEP_REBATE_CODE]) if d.get(BEP_REBATE_CODE) is not None else None)
# 输入口径(万元,展示用;数值口径一律以派生落库值为准)——按实体口径现算
inp = _bep_inputs(db, entity_id, cal, period_from, period, months)
@@ -272,9 +288,12 @@ def get_bep_card(
"sales_avg": round(sales_avg, 2),
"admin_avg": round(admin_avg, 2),
"dep_avg": round(dep_avg, 2),
"vc_pct": vc_pct,
"rebate_pct": rebate_pct,
"rebate_policy_pct": BEP_REBATE_POLICY_PCT, # 政策常量:仅作对照,不参与计算
"rebate_source": "derived:F_BEP_REBATE(Σ571103借方÷Σ5001贷方, YTD累计)",
"rebate_source": ("derived:F_BEP_VC_RATE(⑤完整口径:销货成本+税金及附加+渠补−厂补)"
if entity_id == 1
else "derived:F_BEP_VC_RATE(Σ6401借÷Σ6001贷)"),
"cm_pct": cm_pct,
},
"book": {
@@ -294,7 +313,8 @@ def get_bep_card(
},
"derived": derived,
"data_source": "derived", # 账面/现金同源:均读派生落库值
"formula": f"保本销售额 = 月固定成本 ÷ (1 − 实测渠补率{rebate_pct:.2f}%)",
"formula": (f"保本销售额 = 月固定成本 ÷ 边际贡献率{cm_pct:.2f}%"
f"(边际贡献率 = 1 − 变动成本率{vc_pct:.2f}%)"),
"readonly": True,
}
+25 -11
View File
@@ -5,18 +5,24 @@
· 派生落库写入面:`backend/scripts/kpi_bep_deriver.py`
· 卡面读取面 :`backend/app/api/dashboard.py::/bep-card`
口径(2026-10-03 财务裁定,任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack):
口径(2026-10-03 财务裁定 + 任总 18:00「统一」,任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack):
| 项 | entity 1 陕西酣客 | entity 2 博海 |
|---|---|---|
| 变动成本(分子) | Σ571103 借方(渠补/捐赠支出) | Σ6401 借方(主营业务成本) |
| 变动成本(分子) | **(Σ5401借 + Σ5403借 + Σ571103借 − Σ530103贷)**〔⑤口径统一〕 | Σ6401 借方(主营业务成本) |
| 收入基数(分母) | Σ5001 贷方(主营业务收入) | Σ6001 贷方(主营业务收入) |
| 变动成本率 | Σ571103借 ÷ Σ5001贷(实测渠补率) | Σ6401借 ÷ Σ6001贷(主营业务成本率) |
| 固定成本 | `5602%`(管理费用,含折旧)借−贷净额 | `6601+6602+6603`(销售/管理/财务费用)借−贷净额 |
| 变动成本率 | 上式 ÷ Σ5001贷(**完整口径**,非渠补率) | Σ6401借 ÷ Σ6001贷(主营业务成本率) |
| 实测渠补率(F_BEP_REBATE) | Σ571103借 ÷ Σ5001贷(**保留**为渠补单一事实源,**不再**充当变动成本率) | 无(None) |
| 固定成本 | `5601%` + `5602%` + `5603%` 借−贷净额〔⑤扩容〕 | `6601+6602+6603`(销售/管理/财务费用)借−贷净额 |
| 折旧剔除(现金口径) | `560213`(管理费用-折旧叶子) | `1602` 累计折旧**当期贷方发生额**(见下) |
| 本期利润结转科目 | `3103` 本年利润 | `4103` 本年利润 |
| 结账信号次选(落盘) | `/root/WB_WS/_data/zxt-hanke/{YYYY.MM}/check.md` | 无(只用 DB 信号) |
⑤ 口径统一依据(任总 2026-10-03 18:00 DM「统一」):完整口径唯一可勾稽至营业利润
`808.80 − 672.65 − 224.16 = −88.01 ≈ −88.00` 既定营业利润。
2026-08 锚点(YTD 月均):变动成本率 **83.17%**、边际贡献率 **16.83%**、固定月均 **28.02 万**、
保本 **166.45 万/月**、覆盖率 **60.74%**、缺口 **39.26%**、现金保本 **≈121.8 万/月**。
博海折旧口径说明:博海科目表只到 4 位一级科目(无费用类折旧叶子,**不存在** 酣客式 `560213`),
唯一含「折旧」的是资产类 `1602 累计折旧`;其**当期贷方发生额** = 当期计提折旧额(对方科目
计入 `6602 管理费用`),故取它作现金口径剔除项。实测 2026-01..07 每月 1,468.83 元,
@@ -33,15 +39,22 @@ CLOSING_SUMMARY_LIKE = "%结转%"
# 酣客结账信号次选来源(智享通落盘 check.md;与 app/api/data.py 的 ZXT_LANDING_DIR 同源)
ZXT_LANDING_DIR_HANKE = "/root/WB_WS/_data/zxt-hanke"
# 变动成本「带号科目项」记法: terms = [(sign, subject_code, field), ...]
# sign='+' ⇒ 取该科目 field 的发生额相加;sign='-' ⇒ 取该科目 field 的发生额相减
# field ∈ {'debit' → debit_amount, 'credit' → credit_amount, 'net' → 借−贷}
ENTITY_CALIBERS = {
1: dict(
name="陕西酣客",
margin_label="实测渠补率",
margin_desc="Σ571103借方 ÷ Σ5001贷方",
margin_label="变动成本率",
margin_desc="(Σ5401借+Σ5403借+Σ571103借−Σ530103贷) ÷ Σ5001贷",
# ⑤ 口径统一(2026-10-03 任总「统一」):变动成本 = 销货成本 + 税金及附加 + 渠补 − 厂补
varcost_terms=[("+", "5401", "debit"), ("+", "5403", "debit"),
("+", "571103", "debit"), ("-", "530103", "credit")],
# F_BEP_REBATE 实测渠补率专用(单一事实源保留;不再充当变动成本率)
rebate_terms=[("+", "571103", "debit")],
revenue_field="credit_amount", revenue_filter=("eq", "5001"),
varcost_field="debit_amount", varcost_filter=("eq", "571103"),
fixed_filters=[("like", "5602")],
fixed_label="管理费",
fixed_filters=[("like", "5601"), ("like", "5602"), ("like", "5603")],
fixed_label="固定成本(5601+5602+5603)",
dep_field="net", dep_filter=("eq", "560213"),
closing_subject="3103",
landing_dir=ZXT_LANDING_DIR_HANKE,
@@ -52,15 +65,16 @@ ENTITY_CALIBERS = {
name="博海",
margin_label="变动成本率(主营业务成本率)",
margin_desc="Σ6401借方 ÷ Σ6001贷方",
varcost_terms=[("+", "6401", "debit")],
rebate_terms=None, # 博海无渠补科目
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, # 博海无渠补政策常量
policy_pct=None,
),
}
+133 -44
View File
@@ -6,11 +6,14 @@
F_BEP_GAP 保本缺口(%) = (保本销售额-月均销售) ÷ 保本销售额 × 100 (反向: <=0绿 <=15黄 >15红)
F_BEP_REVENUE_CASH 现金保本销售额(万元) = (月均固定成本-月均折旧) ÷ 边际贡献率 (现金口径,中性无阈值)
F_BEP_COVER_CASH 现金保本覆盖率(%) = 月均销售 ÷ 现金保本销售额 × 100 (正向: >=100绿 >=85黄 <85红)
F_BEP_REBATE 变动/渠补率(%) = 见下方「实体口径注册表」 (YTD累计; 卡面单一事实源,无阈值)
F_BEP_VC_RATE 变动成本率(%) = 见下方「实体口径注册表」 (YTD累计; 保本公式分母,无阈值)
F_BEP_REBATE 实测渠补率(%) = e1 专用展示 chip(Σ571103÷Σ5001,YTD; e2 无渠补科目 ⇒ NULL)
实体口径注册表(2026-10-03 新增,任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack):
· entity 1 陕西酣客:变动成本率 = Σ571103借方 ÷ Σ5001贷方(实测渠补率);固定成本 = 5602% 净额;
折旧剔除 = 560213(管理费用-折旧叶子)。
· entity 1 陕西酣客:变动成本率 = Σ(5401销货成本 + 5403税金及附加 + 571103渠补 − 530103厂补) ÷ Σ5001贷方
〔⑤口径统一 2026-10-03,任总 18:00「统一」;勾稽:808.80 − 672.65 − 224.16 = −88.01 ≈ −88.00 营业利润〕;
固定成本 = 5601% + 5602% + 5603% 净额;折旧剔除 = 560213(管理费用-折旧叶子)。
实测渠补率 F_BEP_REBATE 保留(仍是渠补单一事实源),但**不再充当变动成本率**。
· entity 2 博海:**无渠补科目(571103)**,变动成本 = Σ6401借(主营业务成本),
变动成本率 = Σ6401借 ÷ Σ6001贷;固定成本 = (6601 销售费用 + 6602 管理费用 + 6603 财务费用) 净额;
折旧剔除 = 1602 累计折旧「当期贷方发生额」(博海科目表只到 4 位一级,无费用类折旧叶子;
@@ -21,11 +24,11 @@
口径(与财务Bot报告一致):
- 销售收入: entity1 = subject_code='5001'(主营业务收入) 贷方合计;entity2 = '6001' 贷方合计。当年1月至目标月 YTD 月均
- 固定成本: entity1 = subject_code LIKE '5602%'(管理费用,含560213折旧) 借−贷净额合计(carry_forward=0),YTD 月均
- 固定成本: entity1 = subject_code LIKE '5601%'/'5602%'/'5603%'(销售/管理/财务费用,含560213折旧) 借−贷净额合计(carry_forward=0),YTD 月均〔⑤扩容〕
entity2 = 6601/6602/6603 借−贷净额合计(同上,但另需剔除结转腿)
- 折旧: entity1 = '560213' 借−贷净额;entity2 = '1602' 贷方发生额(现金口径剔除项)
- 变动成本率: **实测累计口径**(2026-10-02 财务裁定,任总 11:43 CST 放行「改,这样数据更加真实」)
酣客 渠补率(实测) = Σ(571103 借方) ÷ Σ(5001 贷方) × 100,期间 = 年初至当期(YTD),保留 2 位小数。
- 变动成本率: **完整实测口径**(⑤口径统一 2026-10-03,任总 18:00「统一」;勾稽件 _reports/陕西酣客_边际贡献式损益表_20261003.md)
酣客 = Σ(5401销货成本 + 5403税金及附加 + 571103渠补 − 530103厂补) ÷ Σ(5001 贷方) × 100,YTD,保留 2 位小数。
`POLICY_REBATE_RATE_PCT = 84.61` 政策常量**仅作对照展示**(写进返回值 rebate_policy_pct / remark),
**不参与任何计算**;`--rebate-rate` 显式传值时视为人工诊断覆盖(正常调用不再使用)。
- 边际贡献率 = 100 − 变动成本率
@@ -85,13 +88,17 @@ TARGET_KPIS = [
("F_BEP_GAP", "保本缺口"),
("F_BEP_REVENUE_CASH", "现金口径保本销售额"),
("F_BEP_COVER_CASH", "现金口径保本覆盖率"),
("F_BEP_VC_RATE", "变动成本率"),
("F_BEP_REBATE", "实测渠补率"),
]
# 实测渠补率 KPI 编码(卡面单一事实源;无阈值,不参与预警级别判定)
# 变动成本率 KPI(⑤ 口径统一 2026-10-03:保本公式分子口径 = 销货成本+税金及附加+渠补−厂补)
VC_RATE_KPI = "F_BEP_VC_RATE"
# 实测渠补率 KPI 编码(卡面单一事实源;无阈值,不参与预警级别判定;⑤ 后**不再充当变动成本率**)
REBATE_KPI = "F_BEP_REBATE"
# 保本口径 KPI(缺 F_BEP_REBATE 定义时不阻断派生主流程,仅跳过渠补率落库并告警)
# 保本口径 KPI(缺 F_BEP_REBATE 定义时不阻断派生主流程,见 run_derive 告警分支)
CORE_KPI_CODES = [c for c, _ in TARGET_KPIS if c != REBATE_KPI]
# 现金口径 KPI 编码(计算路径独立于账面,remark 文案用现金口径)
@@ -222,6 +229,42 @@ def _subject_clause(kind: str, code: str):
raise ValueError(f"未知科目过滤类型: {kind}")
# 带号科目项的字段映射(terms 记法见 bep_caliber.ENTITY_CALIBERS)
_TERM_FIELD = {
"debit": lambda: VoucherDetail.debit_amount,
"credit": lambda: VoucherDetail.credit_amount,
"net": lambda: VoucherDetail.debit_amount - VoucherDetail.credit_amount,
}
def _sum_terms(db, entity_id: int, period_from: str, period_to: str,
terms, *, exclude_closing: bool = False) -> float:
"""按「带号科目项」聚合金额(元)。terms = [(sign, code, field), ...]
· sign='+' ⇒ 加该科目 field 发生额;sign='-' ⇒ 减之
· field ∈ {debit, credit, net}
· code 以 '%' 结尾时按前缀(like)匹配,否则精确匹配
⑤ 口径统一(2026-10-03):酣客变动成本 = (Σ5401借 + Σ5403借 + Σ571103借 − Σ530103贷)。
"""
total = 0.0
for sign, code, field in terms:
expr = _TERM_FIELD[field]()
q = db.query(func.sum(expr)).filter(
VoucherDetail.entity_id == entity_id,
VoucherDetail.carry_forward == 0,
VoucherDetail.period >= period_from,
VoucherDetail.period <= period_to,
)
q = (q.filter(VoucherDetail.subject_code.like(code)) if code.endswith("%")
else q.filter(VoucherDetail.subject_code == code))
if exclude_closing:
q = q.filter(or_(VoucherDetail.summary.is_(None),
~VoucherDetail.summary.like(CLOSING_SUMMARY_LIKE)))
v = float(q.scalar() or 0.0)
total += v if sign == "+" else -v
return total
def compute_margin_rate(db, entity_id: int, period_from: str, period_to: str,
caliber: dict = None) -> dict:
"""变动成本率(YTD 累计,2 位小数)—— 按实体口径现算。
@@ -236,9 +279,8 @@ def compute_margin_rate(db, entity_id: int, period_from: str, period_to: str,
(调用方不得回落政策常量,须按「不可得」处理)。
"""
cal = caliber or ENTITY_CALIBERS[entity_id]
num_raw = _sum_field(db, entity_id, period_from, period_to,
cal["varcost_field"], cal["varcost_filter"],
exclude_closing=cal["exclude_closing"])
num_raw = _sum_terms(db, entity_id, period_from, period_to,
cal["varcost_terms"], exclude_closing=cal["exclude_closing"])
den_raw = _sum_field(db, entity_id, period_from, period_to,
cal["revenue_field"], cal["revenue_filter"],
exclude_closing=cal["exclude_closing"])
@@ -256,7 +298,7 @@ def compute_margin_rate(db, entity_id: int, period_from: str, period_to: str,
info["reason"] = f"{cal['margin_label']}不可得:{cal['revenue_filter'][1]} 累计缺数或为 0"
return info
if num_w <= 0:
info["reason"] = f"{cal['margin_label']}不可得:{cal['varcost_filter'][1]} 累计缺数或为 0"
info["reason"] = f"{cal['margin_label']}不可得:变动成本累计缺数或为 0"
return info
info["available"] = True
info["rebate_pct"] = round(num_w / den_w * 100.0, 2)
@@ -264,17 +306,39 @@ def compute_margin_rate(db, entity_id: int, period_from: str, period_to: str,
def compute_rebate_rate(db, entity_id: int, period_from: str, period_to: str) -> dict:
"""实测累计渠补率(2026-10-02 财务裁定口径)—— 酣客(entity1) 专用薄封装。
"""实测累计**渠补率**(F_BEP_REBATE,2026-10-02 口径)—— 酣客(entity1) 专用。
渠补率(实测) = Σ(571103 借方) ÷ Σ(5001 贷方) × 100,期间 = 年初至当期(YTD),保留 2 位小数。
· 571103 = 渠补/捐赠支出(损益类费用科目)⇒ 本期发生额落**借方**;
· 5001 = 主营业务收入 ⇒ 本期发生额落**贷方**;
· 行域 = carry_forward=0(与 _sum_field 一致,排除期间损益结转腿)。
· 行域 = carry_forward=0(排除期间损益结转腿)。
保留本函数名/键名以兼容既有调用方与单测;通用实现见 compute_margin_rate。
⚠️ ⑤ 口径统一(2026-10-03)后:本函数只算**渠补口径**,是 F_BEP_REBATE 的单一事实源;
保本公式所用的**变动成本率**(销货成本+税金及附加+渠补−厂补)见 compute_margin_rate。
博海(rebate_terms=None)无渠补科目 ⇒ 返回 available=False(F_BEP_REBATE=NULL)。
"""
return compute_margin_rate(db, entity_id, period_from, period_to,
ENTITY_CALIBERS.get(entity_id))
cal = ENTITY_CALIBERS.get(entity_id)
if not cal or not cal.get("rebate_terms"):
return {"available": False, "rebate_pct": None, "rebate_sum_w": 0.0,
"sales_sum_w": 0.0, "label": "实测渠补率",
"reason": f"{cal['name'] if cal else entity_id} 无渠补科目"}
num_raw = _sum_terms(db, entity_id, period_from, period_to,
cal["rebate_terms"], exclude_closing=cal["exclude_closing"])
den_raw = _sum_field(db, entity_id, period_from, period_to,
cal["revenue_field"], cal["revenue_filter"],
exclude_closing=cal["exclude_closing"])
num_w, den_w = num_raw / 10000.0, den_raw / 10000.0
info = {"rebate_sum_w": round(num_w, 2), "sales_sum_w": round(den_w, 2),
"rebate_pct": None, "available": False, "reason": "", "label": "实测渠补率"}
if den_w <= 0:
info["reason"] = "渠补率不可得:Σ5001贷累计缺数或为 0"
return info
if num_w <= 0:
info["reason"] = "渠补率不可得:Σ571103借累计缺数或为 0"
return info
info["available"] = True
info["rebate_pct"] = round(num_w / den_w * 100.0, 2)
return info
def _month_sales(db, entity_id: int, period: str, caliber: dict = None) -> float:
@@ -460,9 +524,11 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
dep_avg = dep_sum_w / months
cash_admin_avg = admin_avg - dep_avg # 现金口径固定成本(剔除折旧纸面费用)
# 2. 变动成本率:默认逐期实测累计(不再取政策常量;常量仅对照,仅 entity1 有)
# 2. 变动成本率(⑤ 后=销货成本+税金及附加+渠补−厂补 完整口径):默认逐期实测累计
rebate_info = compute_margin_rate(db, entity_id, period_from, period_to, cal)
calc_pct = rebate_info["rebate_pct"]
# ⑤:渠补率(e1 专用;e2 无 ⇒ available=False)。F_BEP_REBATE 专用,独立于上面变动成本率。
rebate_kpi_info = compute_rebate_rate(db, entity_id, period_from, period_to)
if rebate_rate_pct is not None:
rebate_info = dict(rebate_info, available=True, rebate_pct=round(float(rebate_rate_pct), 2),
reason="人工覆盖(--rebate-rate)")
@@ -484,7 +550,8 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
period, cal["margin_label"], rebate_pct)
return _fail_closed_cm_non_positive(db, entity_id, period, months, rebate_info, cal,
kpis, write_kpi, sales_avg=sales_avg,
admin_avg=admin_avg, dep_avg=dep_avg)
admin_avg=admin_avg, dep_avg=dep_avg,
rebate_kpi_info=rebate_kpi_info)
# 2c. fail-closed ③(2026-10-03,同族补充):固定成本退化 ⇒ 保本点不适用
# 边际贡献率 > 0 但分子(固定成本)退化时,公式给出 保本额≈0 / 覆盖率趋于无穷,
@@ -498,7 +565,8 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
return _fail_closed_cm_non_positive(db, entity_id, period, months, rebate_info, cal,
kpis, write_kpi, sales_avg=sales_avg,
admin_avg=admin_avg, dep_avg=dep_avg, guard="fixed",
cash_admin_avg=cash_admin_avg)
cash_admin_avg=cash_admin_avg,
rebate_kpi_info=rebate_kpi_info)
# 3. 派生计算(万元 / %)——账面3条路径不变;现金口径在账面基础上剔除折旧
bep = admin_avg / (cm_pct / 100.0)
@@ -513,7 +581,9 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
"F_BEP_GAP": round(gap, 1),
"F_BEP_REVENUE_CASH": round(cash_bep, 1),
"F_BEP_COVER_CASH": round(cash_cover, 1),
"F_BEP_REBATE": round(rebate_pct, 2),
"F_BEP_VC_RATE": round(rebate_pct, 2), # 变动成本率(⑤ 保本分子口径)
"F_BEP_REBATE": (round(rebate_kpi_info["rebate_pct"], 2)
if rebate_kpi_info["available"] else None), # 实测渠补率(e1;e2=NULL)
}
# 4. upsert kpi_values (source_type='derived',幂等)
@@ -526,15 +596,20 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
if kpi is None: # 定义缺失(仅可能为 F_BEP_REBATE,见上方告警)
written[code] = "no-definition"
continue
if code == REBATE_KPI:
if entity_id == 1:
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}% 仅供对照,不参与计算)")
else:
remark = (f"{cal['margin_label']}(YTD累计): {cal['margin_desc']} = "
if code == VC_RATE_KPI:
remark = (f"变动成本率(YTD累计): {cal['margin_desc']} = "
f"{rebate_info['rebate_sum_w']:.2f}万 ÷ {rebate_info['sales_sum_w']:.2f}万 "
f"× 100 = {rebate_pct:.2f}%({cal['name']}口径,无渠补科目)")
f"× 100 = {rebate_pct:.2f}%(⑤口径统一:销货成本+税金及附加+渠补−厂补;"
f"保本额 = 固定成本月均 ÷ (1−变动成本率))")
elif code == REBATE_KPI:
if rebate_kpi_info["available"]:
rr = rebate_kpi_info["rebate_pct"]
remark = (f"实测渠补率(YTD累计): Σ571103借方 {rebate_kpi_info['rebate_sum_w']:.2f}万 ÷ "
f"Σ5001贷方 {rebate_kpi_info['sales_sum_w']:.2f}万 × 100 = {rr:.2f}%"
f"(政策常量 {POLICY_REBATE_RATE_PCT:.2f}% 仅供对照;⑤ 后**不再充当变动成本率**,"
f"变动成本率见 F_BEP_VC_RATE)")
else:
remark = f"{cal['name']}无渠补科目(实测渠补率不适用);⑤ 后变动成本率见 F_BEP_VC_RATE"
elif code in CASH_CODES:
if entity_id == 1:
remark = (f"现金口径: 管理费月均{admin_avg:.2f}万-折旧月均{dep_avg:.2f}万"
@@ -560,9 +635,11 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
is_new = existing is None
if existing:
old_value = float(existing.actual_value) if existing.actual_value is not None else None
new_value = float(values[code])
value_changed = old_value is None or abs(old_value - new_value) > 1e-9
existing.actual_value = values[code]
nv = values[code]
new_value = float(nv) if nv is not None else None
value_changed = ((old_value is None) != (new_value is None)
or (old_value is not None and abs(old_value - new_value) > 1e-9))
existing.actual_value = nv
existing.source_batch = batch
if value_changed:
existing.remark = (f"{remark}|值已更新(batch={batch}, "
@@ -651,8 +728,10 @@ def run_derive(db, entity_id: int, period: str, rebate_rate_pct: float = None,
"rebate_sum_w": rebate_info["rebate_sum_w"],
"rebate_sales_base_w": rebate_info["sales_sum_w"],
"rebate_actual_pct": rebate_pct,
"vc_rate_pct": rebate_pct,
"rebate_kpi_pct": (rebate_kpi_info["rebate_pct"] if rebate_kpi_info["available"] else None),
"rebate_policy_pct": POLICY_REBATE_RATE_PCT,
"rebate_source": rebate_info.get("reason") or "YTD实测累计(Σ571103借方/Σ5001贷方)",
"rebate_source": rebate_info.get("reason") or "YTD实测累计(⑤完整口径)",
"rebate_calc_pct": round(calc_pct, 2) if calc_pct is not None else None,
"cm_pct": round(cm_pct, 2),
"values": values,
@@ -734,7 +813,8 @@ def _fail_closed_cm_non_positive(db, entity_id: int, period: str, months: int,
rebate_info: dict, cal: dict, kpis: dict, write_kpi: bool,
sales_avg: float = None, admin_avg: float = None,
dep_avg: float = None, guard: str = "cm",
cash_admin_avg: float = None) -> dict:
cash_admin_avg: float = None,
rebate_kpi_info: dict = None) -> dict:
"""fail-closed ② 落库:保本点**不适用**(2026-10-03 财务裁定)。
五条保本派生值(REVENUE/COVER/GAP/REVENUE_CASH/COVER_CASH)写
@@ -758,14 +838,16 @@ def _fail_closed_cm_non_positive(db, entity_id: int, period: str, months: int,
detail = (f"边际贡献非正,保本点不适用({cal['margin_label']} {rebate_pct:.2f}% ≥ 100%,"
f"边际贡献率 {cm_pct:.2f}% ⇒ 每增一元销售不产生正贡献,保本点无经济意义;"
f"fail-closed,禁写 0.0)")
if entity_id == 1:
rebate_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}% 仅供对照,不参与计算)")
else:
rebate_remark = (f"{cal['margin_label']}(YTD累计): {cal['margin_desc']} = "
vc_rate_remark = (f"变动成本率(YTD累计): {cal['margin_desc']} = "
f"{rebate_info['rebate_sum_w']:.2f}万 ÷ {rebate_info['sales_sum_w']:.2f}万 "
f"× 100 = {rebate_pct:.2f}%({cal['name']}口径,无渠补科目)")
f"× 100 = {rebate_pct:.2f}%(⑤口径统一;保本点因边际贡献非正/固定成本退化不适用)")
if rebate_kpi_info and rebate_kpi_info.get("available"):
rebate_remark = (f"实测渠补率(YTD累计): Σ571103借方 {rebate_kpi_info['rebate_sum_w']:.2f}万 ÷ "
f"Σ5001贷方 {rebate_kpi_info['sales_sum_w']:.2f}万 × 100 = "
f"{rebate_kpi_info['rebate_pct']:.2f}%(政策常量 {POLICY_REBATE_RATE_PCT:.2f}% 仅供对照;"
f"⑤ 后不再充当变动成本率)")
else:
rebate_remark = f"{cal['name']}无渠补科目(实测渠补率不适用);变动成本率见 F_BEP_VC_RATE"
batch = f"bep-{datetime.now().strftime('%Y%m%d%H%M%S')}"
written = {}
changed = []
@@ -775,9 +857,16 @@ def _fail_closed_cm_non_positive(db, entity_id: int, period: str, months: int,
if kpi is None:
written[code] = "no-definition"
continue
is_rebate = (code == REBATE_KPI)
new_val = round(float(rebate_pct), 2) if is_rebate else None
rm = rebate_remark if is_rebate else detail
if code == VC_RATE_KPI:
new_val = round(float(rebate_pct), 2) # 变动成本率可测 ⇒ 照常落库
rm = vc_rate_remark
elif code == REBATE_KPI:
new_val = (round(float(rebate_kpi_info["rebate_pct"]), 2)
if rebate_kpi_info and rebate_kpi_info.get("available") else None)
rm = rebate_remark
else:
new_val = None
rm = detail
existing = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
@@ -0,0 +1,39 @@
-- CMA BEP 变动成本率派生KPI定义 — F_BEP_VC_RATE(2026-10-03 ⑤ 口径统一)
-- 依据: 任总 2026-10-03 18:00 DM「统一」+ 任务 finance-20261003-cvp-fix-and-bohai-coverage-fullstack §追加⑤
-- 口径: 变动成本率 = 变动成本 ÷ 收入基数(YTD 累计,2位小数)
-- · 酣客(e1): 变动成本 = Σ5401借(销货成本)+Σ5403借(税金及附加)+Σ571103借(渠补)−Σ530103贷(厂补)
-- · 博海(e2): 变动成本 = Σ6401借(主营业务成本)
-- 保本额 = 固定成本月均 ÷ (1 − 变动成本率)
-- 说明: F_BEP_REBATE(实测渠补率/博海主营成本率) 保留,⑤ 后**不再充当变动成本率**
-- 幂等: 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_VC_RATE', '变动成本率', 'finance', 'profitability',
'round((sum(5401借)+sum(5403借)+sum(571103借)-sum(530103贷))/sum(5001贷)*100,2)',
'变动成本率(YTD累计,⑤完整口径)=(Σ5401借+Σ5403借+Σ571103借−Σ530103贷)÷Σ5001贷; 供保本点卡面单一事实源; 缺数期不出值(fail-closed)',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 5401/5403/571103/530103/5001 累计)', '财务部', 'monthly', '%', 0.00,
'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_VC_RATE'
);
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 2, 'F_BEP_VC_RATE', '变动成本率(主营业务成本率)', 'finance', 'profitability',
'round(sum(6401借)/sum(6001贷)*100,2)',
'变动成本率(实测,YTD累计)=Σ6401借方÷Σ6001贷方(剔结转账腿); 博海无渠补科目; 供保本点卡面单一事实源, 无阈值',
'derived', 'BEP派生器 kpi_bep_deriver.py(voucher_details 6401/6001 累计)', '财务部', 'monthly', '%', 0,
'ratio', NULL, NULL, NULL,
'strategic', 'active', 'finance-bot', 'Epic2', 1
FROM DUAL
WHERE NOT EXISTS (
SELECT 1 FROM kpi_definitions WHERE entity_id = 2 AND kpi_code = 'F_BEP_VC_RATE'
);
+8 -7
View File
@@ -81,10 +81,10 @@
<div class="detail-title">📐 公式</div>
<div class="detail-line">{{ card.formula }}</div>
<div v-if="mode === 'book'" class="detail-line sub">
账面:固定成本{{ fmt(inputs.admin_avg) }}万 ÷ {{ fmt(inputs.cm_pct, 2) }}% = 保本{{ fmt(current.bep_revenue) }}万(管理费含折旧)
账面:固定成本{{ fmt(inputs.admin_avg) }}万 ÷ {{ fmt(inputs.cm_pct, 2) }}% = 保本{{ fmt(current.bep_revenue) }}万(固定成本含折旧)
</div>
<div v-if="mode === 'cash'" class="detail-line sub">
现金:管理费{{ fmt(inputs.admin_avg) }} − 折旧{{ fmt(inputs.dep_avg) }} = {{ fmt(current.admin_avg) }}万 ÷ {{ fmt(inputs.cm_pct, 2) }}% = 保本{{ fmt(current.bep_revenue) }}万
现金:固定成本{{ fmt(inputs.admin_avg) }} − 折旧{{ fmt(inputs.dep_avg) }} = {{ fmt(current.admin_avg) }}万 ÷ {{ fmt(inputs.cm_pct, 2) }}% = 保本{{ fmt(current.bep_revenue) }}万
</div>
</div>
<div class="detail-col">
@@ -96,9 +96,10 @@
<div class="detail-kpis">
<div class="detail-title">📊 输入KPI</div>
<div class="kpi-chips">
<span class="kpi-chip">月管理费 <b>{{ fmt(inputs.admin_avg) }}</b>万</span>
<span class="kpi-chip">月收入(5001) <b>{{ fmt(inputs.sales_avg) }}</b>万</span>
<span class="kpi-chip">渠补率 <b>{{ fmt(inputs.rebate_pct, 2) }}</b>%</span>
<span class="kpi-chip">月固定成本 <b>{{ fmt(inputs.admin_avg) }}</b>万</span>
<span class="kpi-chip">月收入 <b>{{ fmt(inputs.sales_avg) }}</b>万</span>
<span class="kpi-chip">变动成本率 <b>{{ fmt(inputs.vc_pct, 2) }}</b>%</span>
<span v-if="inputs.rebate_pct != null" class="kpi-chip">渠补率 <b>{{ fmt(inputs.rebate_pct, 2) }}</b>%</span>
<span class="kpi-chip">折旧 <b>{{ fmt(inputs.dep_avg) }}</b>万/月</span>
</div>
<div class="kpi-src-note">数据源:voucher_details 凭证聚合(YTD月均口径)· {{ card.period }} · 纯只读</div>
@@ -149,8 +150,8 @@ const lvlText = computed(() => ({ green: '已保本', yellow: '未达保本', re
const gapColor = computed(() => (current.value.gap <= 0 ? '#67c23a' : current.value.gap <= 50 ? '#e6a23c' : '#f56c6c'))
const modeNote = computed(() =>
mode.value === 'cash'
? '固定成本=管理费−折旧(现金视角)'
: '固定成本=管理费(含折旧,账面视角)'
? '固定成本=固定费用合计−折旧(现金视角)'
: '固定成本=固定费用合计(含折旧,账面视角)'
)
const insight = computed(() => {