All checks were successful
CI / lint (push) Successful in 44s
CI / notebooks-smoke (push) Successful in 1m33s
Deploy / changes (push) Successful in 14s
CI / test (push) Successful in 3m9s
Infra CI / zotero (push) Successful in 14s
Infra CI / notebooks (push) Successful in 55s
Infra CI / api (push) Successful in 1m9s
Infra CI / docs (push) Successful in 1m23s
Infra CI / llm (push) Successful in 1m1s
Infra CI / mc (push) Successful in 16s
Infra CI / bcda (push) Successful in 1m43s
Deploy / notebooks (push) Has been skipped
Deploy / zotero (push) Has been skipped
Deploy / docs (push) Has been skipped
Package Supply Chain / pkg-supply-chain (push) Successful in 1m30s
Deploy / api (push) Successful in 1m17s
Deploy / llm (push) Successful in 1m18s
Deploy / mc (push) Has been skipped
Deploy / bcda (push) Successful in 1m42s
Deploy / report (push) Successful in 1m36s
Ports the public CMS "LEAD Model PY 2027 Alignment and Financial Methodology Paper" v1.2 (bib E4PEU3HV) as generic engines: parameters come from the paper, everything CMS computes per ACO or per year comes from the caller. No participant data, no organization-specific rules. - lead.params: Table 5.2.1 discounts, 3% Quality Withhold, 2% retention incentive, Box 5.3.3 Stop-Loss bands, Tables 5.4.1/5.4.3 corridors, 2% sequestration, base-year weights, Table 3.5.1 ACPT guardrails, risk-score growth caps, REA/PSA shares and caps, voluntary guardrail and 500 minimum, Table 2.6.1 base-year alignment minimums. - lead.settlement: Final Financial Settlement, Table 5.6.1 line by line (settle(SettlementInputs) -> SettlementLines). - lead.stop_loss: Box 5.3.3 charge, banded payout, neutrality factor, application to PY expenditure. - lead.monies_owed: Table 5.5.1 Total Monies Owed, APO reconciliation. - lead.timing: run-out end, settlement-basis rule, settlement-stage inputs. - lead.benchmark: §3 formulas — risk standardization, two-/three-way trend, base-year weights and sufficiency, voluntary guardrail, regional FFS, spending designation, REA, PSA, risk-score cap, hybrid alignment dollars, category blend. - lead.express: the same as narwhals Exprs; express.settlement.settle settles a frame of scenarios on any backend. Rounding: every dollar line rounds to the cent (half away from zero) as it is produced; PBPMs, rates and factors never do. It is the only rule under which Tables 3.3.3, 3.5.3.2, 3.5.4, 5.5.1 and 5.6.1 all reproduce (3.5.4 line 24 sums cent-rounded cells; 3.3.3 carries unrounded PBPMs). The Expr cents snaps its ÷100 with round(2): polars 2.0 divides a column by a literal as a reciprocal multiply (k × 0.01), one ulp off k/100 for ~15% of values. Missing inputs stay missing (None/null or a refused input model); the only zeros are the paper's own (Professional "N/A" discount, no Stop-Loss election, sequestration on losses, ineligible REA/PSA). Paper inconsistencies recorded in tests: Table 5.5.1 line 2 is a cent below Table 5.6.1 line 28 (.43 vs .44); Box 3.3.2 prints 1.104 then says 1.103; §3.5.1 prose widens the ACPT lower bound a year later than Table 3.5.1 (the table is followed). Tests: 283 in tests/lead — every published line to the cent, Expr builders on polars and DuckDB, bit-for-bit scalar/frame parity over 160 scenarios; 100% of src/lead.
114 lines
3.0 KiB
Python
114 lines
3.0 KiB
Python
"""lead.params carries the paper's printed parameters; lead.money rounds
|
|
dollar lines to the cent half away from zero."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from lead.money import cents
|
|
from lead.params import (
|
|
ACPT_GUARDRAILS,
|
|
BASE_YEAR_ALIGNMENT_MINIMUMS,
|
|
BASE_YEAR_WEIGHTS,
|
|
DISCOUNT_RATES,
|
|
QUALITY_WITHHOLD_RATE,
|
|
RETENTION_INCENTIVE_RATE,
|
|
RISK_CORRIDORS,
|
|
RISK_SCORE_GROWTH_CAPS,
|
|
SEQUESTRATION_RATE,
|
|
model_year,
|
|
)
|
|
|
|
|
|
def test_settlement_rates():
|
|
assert (QUALITY_WITHHOLD_RATE, RETENTION_INCENTIVE_RATE, SEQUESTRATION_RATE) == (
|
|
0.03,
|
|
0.02,
|
|
0.02,
|
|
)
|
|
|
|
|
|
def test_tables_5_4_1_and_5_4_3_risk_corridors():
|
|
def table(option, scenario):
|
|
return [(c.lower, c.upper, c.rate) for c in RISK_CORRIDORS[(option, scenario)]]
|
|
|
|
for scenario in ("savings", "losses"):
|
|
assert table("Global", scenario) == [
|
|
(0.0, 0.15, 1.00),
|
|
(0.15, 0.35, 0.50),
|
|
(0.35, 0.50, 0.25),
|
|
(0.50, None, 0.10),
|
|
]
|
|
assert table("Professional", "savings")[0] == (0.0, 0.10, 0.60)
|
|
assert table("Professional", "losses")[0] == (0.0, 0.10, 0.50)
|
|
assert (
|
|
table("Professional", "losses")[1:]
|
|
== table("Professional", "savings")[1:]
|
|
== [
|
|
(0.10, 0.15, 0.35),
|
|
(0.15, 0.20, 0.15),
|
|
(0.20, None, 0.05),
|
|
]
|
|
)
|
|
|
|
|
|
def test_table_3_5_1_acpt_guardrails():
|
|
assert ACPT_GUARDRAILS[1] == (-0.002, 0.003)
|
|
assert ACPT_GUARDRAILS[4] == (-0.008, 0.012)
|
|
assert {ACPT_GUARDRAILS[y] for y in range(5, 11)} == {(-0.010, 0.015)}
|
|
|
|
|
|
def test_benchmark_parameters():
|
|
assert dict(RISK_SCORE_GROWTH_CAPS) == {
|
|
"A&D": 0.03,
|
|
"ESRD": 0.03,
|
|
"High Needs": 0.04,
|
|
}
|
|
assert dict(BASE_YEAR_WEIGHTS["Newly Entering"]) == {
|
|
"BY1": 0.1,
|
|
"BY2": 0.3,
|
|
"BY3": 0.6,
|
|
}
|
|
assert sum(BASE_YEAR_WEIGHTS["Renewing"].values()) == pytest.approx(1.0)
|
|
assert BASE_YEAR_ALIGNMENT_MINIMUMS["newly_entering"][:5] == (
|
|
600,
|
|
1200,
|
|
1800,
|
|
2400,
|
|
3000,
|
|
)
|
|
assert BASE_YEAR_ALIGNMENT_MINIMUMS["high_needs_esrd"][9] == 1000
|
|
|
|
|
|
def test_parameters_are_read_only():
|
|
with pytest.raises(TypeError):
|
|
DISCOUNT_RATES["Lower-spending"][2027] = 0.0 # type: ignore[index]
|
|
|
|
|
|
def test_model_year():
|
|
assert (model_year(2027), model_year(2036)) == (1, 10)
|
|
with pytest.raises(ValueError, match="outside the LEAD model"):
|
|
model_year(2037)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"amount,expected",
|
|
[
|
|
(184_593.6544, 184_593.65),
|
|
(164_756.19264, 164_756.19),
|
|
(2_948_334.2768, 2_948_334.28),
|
|
(0.125, 0.13), # half away from zero, not half to even
|
|
(-0.125, -0.13),
|
|
(2.5, 2.5),
|
|
(-0.001, 0.0),
|
|
(0.0, 0.0),
|
|
(1_877_213.43, 1_877_213.43),
|
|
],
|
|
)
|
|
def test_cents(amount, expected):
|
|
assert cents(amount) == expected
|
|
|
|
|
|
def test_cents_never_returns_negative_zero():
|
|
assert str(cents(-0.001)) == "0.0"
|