fix(bep-caliber): rebate rate uses YTD actual voucher cumulative (571103 debit / 5001 credit), book+cash card read derived, fail-closed
ci/woodpecker/push/main Pipeline was canceled

This commit is contained in:
Hermes CI Fix
2026-10-02 12:20:39 +08:00
parent 6be903fbc9
commit 14ed29bcbf
4 changed files with 623 additions and 72 deletions
@@ -0,0 +1,347 @@
"""BEP 渠补率改「实测累计」口径 单测 — finance-20261002-bep-rebate-actual-caliber-fullstack
口径依据(财务Bot 2026-10-02 裁定,任总 11:43 CST 放行「改,这样数据更加真实」):
渠补率(实测) = Σ(571103 **借方**) ÷ Σ(5001 **贷方**) × 100,期间 = 年初至当期(YTD),2 位小数;
政策常量 84.61% **仅作对照展示**,不参与任何计算;
fail-closed:571103 或 5001 缺数/为 0 ⇒ 该期**不出保本值**(pending + NULL),不静默回落常量。
覆盖两面:
· scripts/kpi_bep_deriver.py —— 派生落库写入面(去常量、YTD 现算、fail-closed)
· app/api/dashboard.py::/bep-card —— 卡面读取面(rebate_pct 读派生值、book/cash 同源读派生值)
夹具数值刻意复刻财务复算口径,使「改前(84.61%常量) / 改后(85.49%实测)」两套输出可逐项区分:
2026-01..07:Σ5001贷 = 794.35 万 | Σ571103借 = 672.10 万 ⇒ 渠补率 84.61%(= 常量,故该期值不变)
2026-01..08:Σ5001贷 = 808.79 万 | Σ571103借 = 691.43 万 ⇒ 渠补率 85.49%
月均管理费(含折旧) 25.774 万 / 现金固定成本月均 18.2605 万(8 个月)
⇒ 改前(15.39%):保本 167.5 / 118.7,覆盖 60.4% / 85.2%
⇒ 改后(14.51%):保本 177.6 / 125.8,覆盖 56.9% / 80.3%
"""
import datetime
import json
import os
import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 值变复核提示件默认落 finance 的 _reports/;测试期重定向到临时目录(防污染对端 profile)
os.environ["BEP_REVIEW_NOTE_DIR"] = tempfile.mkdtemp(prefix="bep-test-review-notes-")
import pytest # noqa: E402
from app.models import KPIDefinition, KPIValue, VoucherDetail # noqa: E402
from app.api.dashboard import get_bep_card # noqa: E402
from scripts import kpi_bep_deriver as D # noqa: E402
ENTITY = 1
D_DATE_07 = datetime.datetime(2026, 7, 31)
D_DATE_08 = datetime.datetime(2026, 8, 31)
# ── 财务复算锚值(单位:元)────────────────────────────────────────────────
# 2026-01..07 累计
M07 = dict(period="2026-07", d=D_DATE_07,
sales=7943500.0, rebate=6721000.0, admin_other=1307810.0, dep=526050.0)
# 2026-08 单月增量(累计口径 01..08 = M07 + M08)
M08 = dict(period="2026-08", d=D_DATE_08,
sales=144400.0, rebate=193300.0, admin_other=153030.0, dep=75030.0)
KPI_CODES = (
("F_BEP_REVENUE", "保本销售额", "万元", None, None, None),
("F_BEP_COVER", "保本覆盖率", "%", ">=100", ">=85", "<85"),
("F_BEP_GAP", "保本缺口", "%", "<=0", "<=15", ">15"),
("F_BEP_REVENUE_CASH", "现金口径保本销售额", "万元", None, None, None),
("F_BEP_COVER_CASH", "现金口径保本覆盖率", "%", ">=100", ">=85", "<85"),
("F_BEP_REBATE", "实测渠补率", "%", None, None, None),
)
_seq = [700000]
def _voucher(db, row, subject_code, debit=0.0, credit=0.0, cf=0):
_seq[0] += 1
db.add(VoucherDetail(entity_id=ENTITY, voucher_no=str(_seq[0]),
voucher_date=row["d"], subject_code=subject_code,
subject_name="单测科目", debit_amount=debit,
credit_amount=credit, summary="渠补率口径单测",
carry_forward=cf, period=row["period"]))
db.flush()
def _month_rows(db, row, rebate_credit=0.0):
"""一个月的凭证行:收入(5001贷) / 渠补(571103借) / 管理费(5602% 含折旧 560213)"""
_voucher(db, row, "5001", credit=row["sales"])
if row["rebate"] is not None:
_voucher(db, row, "571103", debit=row["rebate"]) # 含显式 0 行(「存在但为 0」负例)
# 571103 贷方冲销行(负对照:证明实现走**借方**口径而非净额)
if rebate_credit:
_voucher(db, row, "571103", credit=rebate_credit)
_voucher(db, row, "560201", debit=row["admin_other"]) # 管理费其他明细
_voucher(db, row, "560213", debit=row["dep"]) # 管理费用-折旧(含在 5602% 内)
def _kpi_defs(db, include_rebate=True):
for code, name, unit, g, y, r in KPI_CODES:
if code == "F_BEP_REBATE" and not include_rebate:
continue
db.add(KPIDefinition(entity_id=ENTITY, kpi_code=code, kpi_name=name,
dimension="finance", category="profitability",
formula="单测", data_source="单测夹具", data_owner="财务部",
frequency="monthly", unit=unit,
threshold_green=g, threshold_yellow=y, threshold_red=r,
kpi_level="strategic", status="active"))
db.flush()
def _fixture_01_08(db, rebate_credit=0.0):
_kpi_defs(db)
_month_rows(db, M07)
_month_rows(db, M08, rebate_credit=rebate_credit)
db.flush()
def _derived_rows(db, period="2026-08"):
out = {}
for code, *_ in KPI_CODES:
kpi = db.query(KPIDefinition).filter(KPIDefinition.entity_id == ENTITY,
KPIDefinition.kpi_code == code).first()
if kpi is None:
continue
v = db.query(KPIValue).filter(KPIValue.kpi_id == kpi.id,
KPIValue.period == period,
KPIValue.source_type == "derived").first()
if v is not None:
out[code] = v
return out
# ── 一、派生器:实测累计口径 ────────────────────────────────────────────────
def test_compute_rebate_rate_uses_ytd_actual(db):
"""实测渠补率 = Σ571103借方 ÷ Σ5001贷方(YTD 累计,2 位小数)"""
_fixture_01_08(db)
info_07 = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-07")
info_08 = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-08")
assert info_07["available"] is True
assert abs(info_07["rebate_pct"] - 84.61) < 0.005, info_07
assert abs(info_07["rebate_sum_w"] - 672.10) < 0.01, info_07
assert abs(info_07["sales_sum_w"] - 794.35) < 0.01, info_07
assert info_08["available"] is True
assert abs(info_08["rebate_pct"] - 85.49) < 0.005, info_08
def test_deriver_debit_side_negative_control(db):
"""571103 取**借方**而非净额(负对照):夹具含 10 万贷方冲销行。
若实现退回「净额」口径 ⇒ 渠补率 = (691.43-10)/808.79 = 84.25% ≠ 85.49%,本用例必红。
"""
_fixture_01_08(db, rebate_credit=100000.0)
info = D.compute_rebate_rate(db, ENTITY, "2026-01", "2026-08")
assert abs(info["rebate_pct"] - 85.49) < 0.005, info
net_w = D._sum_field(db, ENTITY, "2026-01", "2026-08", "net", ("eq", D.REBATE_CODE)) / 10000.0
assert abs(net_w - 681.43) < 0.01, net_w # 净额口径 = 691.43 − 10
assert net_w != info["rebate_sum_w"] # 两种口径在本夹具下必然不同
def test_deriver_2026_08_values_follow_actual_rebate(db):
"""派生器落库:改后 177.6 / 125.8 / 56.9 / 80.3,渠补率 85.49(政策常量仅对照)"""
_fixture_01_08(db)
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "ok", r
assert abs(r["rebate_actual_pct"] - 85.49) < 0.005, r
assert r["rebate_policy_pct"] == 84.61 # 常量保留(仅对照)
assert abs(r["values"]["F_BEP_REVENUE"] - 177.6) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REVENUE_CASH"] - 125.8) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_COVER"] - 56.9) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_COVER_CASH"] - 80.3) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REBATE"] - 85.49) < 0.005, r["values"]
# 边界外反证:若仍吃常量 84.61(旧口径)= 167.5 / 118.7
assert r["values"]["F_BEP_REVENUE"] != 167.5, r["values"]
rows = _derived_rows(db, "2026-08")
assert set(rows) == {c for c, *_ in KPI_CODES}, sorted(rows)
assert abs(float(rows["F_BEP_REBATE"].actual_value) - 85.49) < 0.005
assert "571103" in rows["F_BEP_REBATE"].remark and "政策常量" in rows["F_BEP_REBATE"].remark
def test_deriver_2026_07_unchanged_regression(db):
"""回归:1-7 月实测渠补率恰为 84.61% ⇒ 与政策常量路径逐项等值(170.2 / 121.4)"""
_kpi_defs(db)
_month_rows(db, M07)
db.flush()
r_auto = D.run_derive(db, ENTITY, "2026-07", None, write_kpi=True)
r_policy = D.run_derive(db, ENTITY, "2026-07", 84.61, write_kpi=True)
assert abs(r_auto["rebate_actual_pct"] - 84.61) < 0.005, r_auto
assert r_auto["values"] == r_policy["values"], (r_auto["values"], r_policy["values"])
assert abs(r_auto["values"]["F_BEP_REVENUE"] - 170.2) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_REVENUE_CASH"] - 121.4) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_COVER"] - 66.7) < 0.05, r_auto["values"]
assert abs(r_auto["values"]["F_BEP_COVER_CASH"] - 93.5) < 0.05, r_auto["values"]
def test_deriver_policy_constant_not_used(db):
"""政策常量不参与计算(负对照):渠补率 50% ⇒ 保本 = 月固定成本 ÷ 50%,而非 ÷ 15.39%"""
_kpi_defs(db)
row = dict(period="2026-08", d=D_DATE_08, sales=8000000.0, rebate=4000000.0,
admin_other=800000.0, dep=0.0)
_month_rows(db, row)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=False)
assert abs(r["rebate_actual_pct"] - 50.0) < 0.005, r
# admin_avg = 80万/8个月 = 10 万 ⇒ 10/0.5 = 20.0 万(常量口径会是 10/0.1539 = 65.0 万)
assert abs(r["values"]["F_BEP_REVENUE"] - 20.0) < 0.05, r["values"]
assert abs(r["values"]["F_BEP_REVENUE"] - 65.0) > 0.5, r["values"]
# ── 二、派生器:fail-closed(缺数/为 0 不出值、不回落常量)─────────────────
def test_deriver_fail_closed_when_rebate_missing(db):
_kpi_defs(db)
r07 = dict(M07)
r07["rebate"] = None # 全年 571103 完全缺数
r08 = dict(M08)
r08["rebate"] = None
_month_rows(db, r07)
_month_rows(db, r08)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "pending", r
assert r["rebate_actual_pct"] is None, r
assert "渠补率不可得" in r["reason"], r
rows = _derived_rows(db, "2026-08")
assert set(rows) == {c for c, *_ in KPI_CODES}, sorted(rows)
for code, v in rows.items():
assert v.actual_value is None, (code, v.actual_value) # 不出保本值
assert v.data_status == "pending", (code, v.data_status)
assert "渠补率不可得" in (v.remark or ""), (code, v.remark)
assert rows["F_BEP_REVENUE"].actual_value != 167.5 # 未回落常量口径
def test_deriver_fail_closed_when_rebate_zero(db):
"""571103 存在但合计为 0 ⇒ 同样 fail-closed(不得静默回落 84.61)"""
_kpi_defs(db)
r07 = dict(M07)
r07["rebate"] = 0.0 # 显式 0 行
r08 = dict(M08)
r08["rebate"] = 0.0
_month_rows(db, r07)
_month_rows(db, r08)
db.flush()
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r["data_status"] == "pending", r
assert r["rebate_sum_w"] == 0.0, r
rows = _derived_rows(db, "2026-08")
assert rows["F_BEP_REVENUE"].actual_value is None
assert rows["F_BEP_REVENUE"].data_status == "pending"
# ── 三、卡面 /bep-card:rebate_pct 读派生值 + book/cash 同源 ───────────────
def test_card_reads_derived_values_and_rebate_pct(db):
_fixture_01_08(db)
D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
card = get_bep_card(db=db, entity_id=ENTITY)
assert card["available"] is True, card
assert card["period"] == "2026-08", card
assert abs(card["inputs"]["rebate_pct"] - 85.49) < 0.005, card["inputs"]
assert card["inputs"]["rebate_policy_pct"] == 84.61, card["inputs"]
assert abs(card["inputs"]["cm_pct"] - 14.51) < 0.005, card["inputs"]
assert abs(card["book"]["bep_revenue"] - 177.6) < 0.05, card["book"]
assert abs(card["book"]["cover"] - 56.9) < 0.05, card["book"]
assert abs(card["cash"]["bep_revenue"] - 125.8) < 0.05, card["cash"]
assert abs(card["cash"]["cover"] - 80.3) < 0.05, card["cash"]
assert card["data_source"] == "derived"
def test_card_book_and_cash_both_read_derived_rows(db):
"""book / cash 同源回归:直接改库内派生值,两条路径都必须随之变(不再实时复算 voucher)"""
_fixture_01_08(db)
D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
first = get_bep_card(db=db, entity_id=ENTITY)
assert abs(first["book"]["bep_revenue"] - 177.6) < 0.05
assert abs(first["cash"]["bep_revenue"] - 125.8) < 0.05
# 临时改库内派生值(夹具库,非生产)——两路径应同步跟随
for code, new_val in (("F_BEP_REVENUE", 999.9), ("F_BEP_REVENUE_CASH", 888.8),
("F_BEP_REBATE", 60.0)):
kpi = db.query(KPIDefinition).filter(KPIDefinition.entity_id == ENTITY,
KPIDefinition.kpi_code == code).first()
row = db.query(KPIValue).filter(KPIValue.kpi_id == kpi.id,
KPIValue.period == "2026-08",
KPIValue.source_type == "derived").first()
row.actual_value = new_val
db.commit()
second = get_bep_card(db=db, entity_id=ENTITY)
assert abs(second["book"]["bep_revenue"] - 999.9) < 0.001, second["book"]
assert abs(second["cash"]["bep_revenue"] - 888.8) < 0.001, second["cash"]
assert abs(second["inputs"]["rebate_pct"] - 60.0) < 0.001, second["inputs"]
assert abs(second["inputs"]["cm_pct"] - 40.0) < 0.001, second["inputs"]
# 反证:实时复算渠道(voucher 现值 177.6)不得再决定卡面值
assert abs(second["book"]["bep_revenue"] - 177.6) > 1.0
def test_card_fail_closed_when_derived_rebate_absent(db):
"""派生渠补率不可得 ⇒ 卡面不出保本值(available=False),不回落常量、不跨期回落"""
_kpi_defs(db)
_month_rows(db, M07)
_month_rows(db, M08)
db.flush()
# 1) 07 期正常派生(当时 571103 数据齐全)
r07 = D.run_derive(db, ENTITY, "2026-07", None, write_kpi=True)
assert r07["data_status"] == "ok"
# 2) 全年 571103 缺数 ⇒ 08 期 fail-closed(已落 07 值不动)
db.query(VoucherDetail).filter(
VoucherDetail.entity_id == ENTITY,
VoucherDetail.subject_code == "571103",
).delete(synchronize_session=False)
db.commit()
r08 = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
assert r08["data_status"] == "pending", r08
card = get_bep_card(db=db, entity_id=ENTITY)
# 08 期六条不齐(渠补率 NULL)⇒ 卡面不得跨期回落到 07 期,也不得回落常量
assert card["available"] is False, card
assert card["period"] == "2026-08", card
assert "渠补率" in card["message"], card
assert card.get("rebate_policy_pct") == 84.61, card
assert "book" not in card
# ── 四、验收探针(自测判别力):同一用例在改前 / 改后两树上跑 ────────────────
# 只依赖两版共有的 API(run_derive 返回的 values / get_bep_card),故可在 HEAD 快照树上复跑:
# 改前(政策常量 84.61%)⇒ FAIL,且失败信息里出现 167.5 / 118.7
# 改后(实测 85.49%) ⇒ PASS,输出 177.6 / 125.8 / 56.9 / 80.3 / rebate_pct 85.49
def test_acceptance_probe_2026_08(db):
"""验收探针:2026-08 口径数值(财务复算预告逐项比对)"""
_fixture_01_08(db)
r = D.run_derive(db, ENTITY, "2026-08", None, write_kpi=True)
vals = r.get("values", {})
card = get_bep_card(db=db, entity_id=ENTITY)
obs = {
"deriver.F_BEP_REVENUE": vals.get("F_BEP_REVENUE"),
"deriver.F_BEP_REVENUE_CASH": vals.get("F_BEP_REVENUE_CASH"),
"deriver.F_BEP_COVER": vals.get("F_BEP_COVER"),
"deriver.F_BEP_COVER_CASH": vals.get("F_BEP_COVER_CASH"),
"deriver.F_BEP_REBATE": vals.get("F_BEP_REBATE"),
"card.available": card.get("available"),
"card.inputs.rebate_pct": (card.get("inputs") or {}).get("rebate_pct"),
"card.inputs.rebate_policy_pct": (card.get("inputs") or {}).get("rebate_policy_pct"),
"card.book.bep_revenue": (card.get("book") or {}).get("bep_revenue"),
"card.book.cover": (card.get("book") or {}).get("cover"),
"card.cash.bep_revenue": (card.get("cash") or {}).get("bep_revenue"),
"card.cash.cover": (card.get("cash") or {}).get("cover"),
}
print("\n[ACCEPTANCE-PROBE] " + json.dumps(obs, ensure_ascii=False))
checks = [
("deriver.F_BEP_REVENUE", obs["deriver.F_BEP_REVENUE"], 177.6, 0.2),
("deriver.F_BEP_REVENUE_CASH", obs["deriver.F_BEP_REVENUE_CASH"], 125.8, 0.2),
("deriver.F_BEP_COVER", obs["deriver.F_BEP_COVER"], 56.9, 0.3),
("deriver.F_BEP_COVER_CASH", obs["deriver.F_BEP_COVER_CASH"], 80.3, 0.3),
("card.book.bep_revenue", obs["card.book.bep_revenue"], 177.6, 0.2),
("card.cash.bep_revenue", obs["card.cash.bep_revenue"], 125.8, 0.2),
("card.inputs.rebate_pct", obs["card.inputs.rebate_pct"], 85.49, 0.02),
]
for name, got, want, tol in checks:
assert got is not None, f"{name}: got None, want {want}±{tol}"
assert abs(float(got) - want) <= tol, f"{name}: got {got}, want {want}±{tol}"