"""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}" # ── 五、O1 收敛(2026-10-04):政策渠补率口径单一源,端点不得回落硬编码常量 ──────── def test_o1_policy_pct_single_source_no_hardcoded_fallback(): """O1:dashboard 模块不得再定义硬编码政策常量;policy_pct 唯一来源 = bep_caliber。 改前(HEAD 587d5b3):`dashboard.BEP_REBATE_POLICY_PCT == 84.61` 且端点对 e2 回落该常量 ⇒ 本用例第一条断言必红(改前/改后判据,非事后追认)。 改后:无该属性;e1 = 84.61(同端点负对照)、e2 = None(无渠补科目)。 """ from app.api import dashboard as DASH from app.utils import bep_caliber as CAL assert not hasattr(DASH, "BEP_REBATE_POLICY_PCT"), ( "dashboard 仍在定义硬编码政策渠补率常量 ⇒ 口径非单一源") assert CAL.caliber_for(1)["policy_pct"] == 84.61 # e1 保留(负对照) assert CAL.caliber_for(2)["policy_pct"] is None # e2 置 None(无渠补科目)