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.
63 lines
2.4 KiB
Python
63 lines
2.4 KiB
Python
"""narwhals ``Expr`` builders for the LEAD financial methodology.
|
||
|
||
Each builder takes column names or ``nw.Expr`` objects and returns an
|
||
``nw.Expr``, so the same methodology runs on polars, DuckDB, pandas, or any
|
||
other narwhals backend, one row per scenario, beneficiary, or benchmark
|
||
cell. The scalar twins live in ``lead.settlement``, ``lead.stop_loss``,
|
||
``lead.benchmark``; both sides perform the same float operations, so a
|
||
frame and a loop over the scalars agree to the bit.
|
||
|
||
Null in, null out: a missing input produces a null line, never a silent
|
||
zero. The zeros these builders do produce are the paper's own, as in the
|
||
scalars.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import narwhals as nw
|
||
|
||
IntoExpr = str | nw.Expr
|
||
"""A column name or a narwhals expression."""
|
||
|
||
|
||
def col(value: IntoExpr) -> nw.Expr:
|
||
"""Return ``value`` as an expression (a string names a column)."""
|
||
return nw.col(value) if isinstance(value, str) else value
|
||
|
||
|
||
def lit_or_col(value: IntoExpr | float | int) -> nw.Expr:
|
||
"""Return a literal for a number, otherwise :func:`col`."""
|
||
return nw.lit(value) if isinstance(value, int | float) else col(value)
|
||
|
||
|
||
def cents(value: IntoExpr) -> nw.Expr:
|
||
"""Round a dollar expression to the cent, half away from zero.
|
||
|
||
The rounding is :func:`lead.money.cents`'s: ``floor(|x| × 100 + 0.5)``,
|
||
not the backends' ``round``, which differ on ties (polars half to even,
|
||
DuckDB away from zero). The final ``÷ 100`` is snapped with
|
||
``round(2)`` because polars divides a column by a literal as a multiply
|
||
by its reciprocal (``k × 0.01``), one ulp off ``k / 100`` for about one
|
||
value in seven; its input is within an ulp of a whole cent, so no tie
|
||
rule is involved and every backend returns the scalar's float.
|
||
|
||
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 5.6.1 / Table 3.5.4.
|
||
"""
|
||
amount = col(value)
|
||
whole_cents = (
|
||
nw.when(amount < 0)
|
||
.then(-((-amount * 100.0 + 0.5).floor()))
|
||
.otherwise((amount * 100.0 + 0.5).floor())
|
||
)
|
||
return (whole_cents / 100.0).round(2)
|
||
|
||
|
||
def ratio(numerator: IntoExpr, denominator: IntoExpr) -> nw.Expr:
|
||
"""numerator ÷ denominator; null unless the denominator is positive.
|
||
|
||
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3 (every ratio it
|
||
defines has a positive denominator).
|
||
"""
|
||
den = col(denominator)
|
||
return nw.when(den > 0).then(col(numerator) / den)
|