Files
cma-management/backend/tests/test_bep_rebate_actual_caliber.py
T

348 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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}"