feat(lead): LEAD PY2027 financial methodology — settlement and benchmark engines
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.
This commit is contained in:
kert
2026-10-27 13:38:44 -04:00
parent e053b15a34
commit cb9976e160
26 changed files with 4978 additions and 3 deletions

View File

@@ -71,6 +71,10 @@ llm = [
ccw = [
"pydantic>=2.0.0",
]
lead = [
"stack[conf]",
"narwhals>=2.17.0",
]
cli = [
"stack[aco]",
"stack[api]",
@@ -180,6 +184,7 @@ all = [
"stack[rex]",
"stack[lake]",
"stack[nlm]",
"stack[lead]",
]
[dependency-groups]
@@ -247,6 +252,6 @@ link-mode = "copy"
python-preference = "managed"
[tool.uv.build-backend]
module-name = ["aco", "api", "bcda", "bib", "bls", "ccw", "cli", "cms", "conf", "llm", "mail", "nlm", "opps", "perf", "pfs", "prisma", "qpp", "rec", "rex", "sem"]
module-name = ["aco", "api", "bcda", "bib", "bls", "ccw", "cli", "cms", "conf", "lead", "llm", "mail", "nlm", "opps", "perf", "pfs", "prisma", "qpp", "rec", "rex", "sem"]
namespace = true
source-exclude = ["compose.yml","infra/**","data/**","notebooks/**","tuva/**","assets/**","docs/**","dev/**","bundle/**","cloud/**"]

37
src/lead/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
"""LEAD Model PY 2027 financial methodology — CMS's published rules.
The Long-term Enhanced ACO Design (LEAD) Model is CMS Innovation Center's
ACO model starting in PY 2027. This package ports the arithmetic of its
public methodology paper — "LEAD Model PY 2027 Alignment and Financial
Methodology Paper" v1.2, August 27, 2026 (cms.gov/priorities/innovation/
files/lead-alignment-finance-paper.pdf; bib item ``E4PEU3HV``) — as
generic engines. Parameters come from the paper (:mod:`lead.params`);
everything CMS computes per ACO or per year comes from the caller. It holds
no participant data and no organization-specific assumptions.
Modules:
- :mod:`lead.params` — Table 5.2.1 discounts, Quality Withhold,
retention incentive, Stop-Loss bands, risk corridors, sequestration,
base-year weights, ACPT guardrails, risk-score caps, REA/PSA shares and
caps, voluntary guardrail and minimum, Table 2.6.1 base-year minimums.
- :mod:`lead.settlement` — Final Financial Settlement, Table 5.6.1 line by
line (:func:`lead.settlement.settle`).
- :mod:`lead.stop_loss` — Stop-Loss Reinsurance (Box 5.3.3).
- :mod:`lead.monies_owed` — Total Monies Owed (Table 5.5.1).
- :mod:`lead.timing` — run-out, settlement basis, settlement-stage inputs.
- :mod:`lead.benchmark` — §3 benchmark formulas.
- :mod:`lead.express` — the same as narwhals expressions, so a frame of
scenarios settles at once on polars, DuckDB, or any narwhals backend.
Conventions:
- Dollar lines are rounded to the cent as they are produced
(:func:`lead.money.cents`, half away from zero); PBPMs, rates and
factors never are. That 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 to the cent.
- A missing input stays missing (None/null, or a refused input model),
never a silent zero; the zeros that appear are ones the paper defines.
- Docstrings cite "LEAD PY2027 methodology (bib E4PEU3HV) §x.y / Table
x.y.z".
"""

652
src/lead/benchmark.py Normal file
View File

@@ -0,0 +1,652 @@
"""LEAD Performance Year Benchmark formulas (§3), as published.
The order: Historical Baseline Expenditure PBPMs are risk standardized
(§3.3.1), trended to BY 3 with the national-regional blend (§3.3.2) and
weighted by base year (§3.3.3–3.3.4) into the Historical Benchmark; the
voluntary guardrail applies (§3.2.5); a Regional Efficiency Adjustment or
Prior Savings Adjustment is added (§3.4); the Adjusted Historical
Benchmark is trended with the three-way blend (§3.5.1), risk adjusted with
the growth-capped PY risk score (§3.5.2), multiplied out over full-year and
hybrid-aligned months (§3.5.3), and the categories summed (§3.5.4).
Only the arithmetic CMS publishes is here. Every data-derived input —
expenditures, risk scores, months, ACPT, modified USPCC amounts, hybrid
factors, CMS's alignment-standard and SSP-TIN determinations — is a caller
input. PBPMs, rates and factors are never rounded; dollar totals are
rounded to the cent (:func:`lead.money.cents`), which is what reproduces
Table 3.5.4 line 24.
Missing inputs give ``None``, never a stand-in value: a growth rate,
market share or PBPM without a positive denominator is None, and so is
anything computed from it.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.
"""
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass
from lead.money import cents
from lead.params import (
ACPT_GUARDRAILS,
ACPT_WEIGHT,
BASE_YEAR_ALIGNMENT_MINIMUMS,
BASE_YEAR_WEIGHTS,
BASE_YEARS,
BENCHMARK_ADJUSTMENT_CAP_RATE,
GLOBAL,
HIGHER_SPENDING,
LOWER_SPENDING,
NEWLY_ENTERING,
PRIOR_SAVINGS_PRORATION_CAP,
PRIOR_SAVINGS_SHARE,
REDUCED_BENCHMARK_ADJUSTMENT_CAP_RATE,
REGIONAL_EFFICIENCY_SHARE,
RISK_SCORE_GROWTH_CAPS,
VOLUNTARY_BENCHMARK_MINIMUM,
VOLUNTARY_GUARDRAIL_RATE,
AlignmentMinimumType,
BeneficiaryCategory,
EntryType,
RiskOption,
SpendingDesignation,
model_year,
)
# ── Shared arithmetic ─────────────────────────────────────────────────────
def ratio(numerator: float | None, denominator: float | None) -> float | None:
"""Return numerator ÷ denominator, or None unless both are known and
the denominator is positive.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3 (every ratio it
defines has a positive denominator).
"""
if numerator is None or denominator is None or denominator <= 0:
return None
return numerator / denominator
def month_weighted_average(
values_and_months: Iterable[tuple[float | None, float | None]],
) -> float | None:
"""Return the beneficiary month-weighted average of the values.
Pairs with a missing value or no positive months carry no weight; with
no weight at all the result is None. This is the paper's average risk
score, its claims/voluntary PBPM combination, its weighted REA for
excluded TINs and Box 5.3.3's month-weighted Stop-Loss values.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.1 / §3.5.2 /
§3.5.4 / Table 3.4.3.6 / Box 5.3.3.
"""
weighted = 0.0
months = 0.0
for value, weight in values_and_months:
if value is None or weight is None or weight <= 0:
continue
weighted += value * weight
months += weight
return ratio(weighted, months)
# ── §3.3 Risk standardize, trend, weight ──────────────────────────────────
def expenditure_pbpm(total_expenditure: float, eligible_months: float) -> float | None:
"""Return expenditure per beneficiary per month (Table 3.3.3 line 3).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3 / Table 3.3.3 line 3.
"""
return ratio(total_expenditure, eligible_months)
def risk_standardize(
pbpm: float | None, average_risk_score: float | None
) -> float | None:
"""Divide a PBPM by the same population's average risk score for the
same year (Table 3.3.3 line 5).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.1 / Table 3.3.3 line 5.
"""
return ratio(pbpm, average_risk_score)
def growth_rate(later: float | None, earlier: float | None) -> float | None:
"""Return a growth rate later ÷ earlier: BY 3 ÷ BY n FFS expenditures,
national (Step 1) or regional (Step 2). None unless both are positive.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Steps 1–2.
"""
if later is None or later <= 0:
return None
return ratio(later, earlier)
def national_trend_weight(
counties: Sequence[tuple[float, float]],
) -> float | None:
"""Return the weight on the national growth rate: the ACO's
county-weighted average market share.
Each county is (ACO-aligned months, alignment-eligible months). Market
share = aligned ÷ eligible; county weight = aligned ÷ ACO total aligned;
the national weight is Σ share × weight. None without aligned months or
when a county has no eligible months.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Step 3.
"""
total_aligned = sum(aligned for aligned, _ in counties)
weight_sum = 0.0
for aligned, eligible in counties:
share = ratio(aligned, eligible)
weight = ratio(aligned, total_aligned)
if share is None or weight is None:
return None
weight_sum += share * weight
return weight_sum if counties else None
def two_way_blended_trend(
national_growth: float | None,
regional_growth: float | None,
national_weight: float | None,
) -> float | None:
"""Blend the growth rates: national × weight + regional × (1 − weight).
Box 3.3.2's example gives 1.120 × 18% + 1.100 × 82% = 1.1036 (printed
1.104; its next sentence says 1.103, a slip in the paper).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Step 4.
"""
if national_growth is None or regional_growth is None or national_weight is None:
return None
return national_growth * national_weight + regional_growth * (1.0 - national_weight)
def base_year_weights(
entry_type: EntryType, sufficient_base_years: Iterable[str]
) -> dict[str, float]:
"""Return the weight of each sufficient base year.
Three sufficient years: 10/30/60 (Newly Entering) or equal thirds
(Renewing). Two: two-thirds recent / one-third older (Newly Entering)
or equal halves (Renewing). One: 100 percent. None sufficient: an empty
mapping — no Historical Benchmark can be built.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.3 / §3.3.4.
"""
if entry_type not in BASE_YEAR_WEIGHTS:
raise ValueError(f"unknown entry type {entry_type!r}")
sufficient = set(sufficient_base_years)
unknown = sufficient - set(BASE_YEARS)
if unknown:
raise ValueError(f"unknown base years {sorted(unknown)}")
years = [year for year in BASE_YEARS if year in sufficient]
if len(years) == 3:
return dict(BASE_YEAR_WEIGHTS[entry_type])
if len(years) == 2:
older, recent = years
if entry_type == NEWLY_ENTERING:
return {older: 1 / 3, recent: 2 / 3}
return {older: 0.5, recent: 0.5}
return {year: 1.0 for year in years}
def base_year_alignment_minimum(
standard: AlignmentMinimumType, performance_year: int
) -> int:
"""Return the claims-based alignment minimum a base year must meet, for
the alignment standard CMS assigned the ACO and the PY.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §2.6 / Table 2.6.1.
"""
return BASE_YEAR_ALIGNMENT_MINIMUMS[standard][model_year(performance_year) - 1]
def base_year_is_sufficient(
claims_aligned_beneficiaries: int,
standard: AlignmentMinimumType,
performance_year: int,
) -> bool:
"""Whether a base year has sufficient claims history: ACO-level
claims-based alignment meets or exceeds the Table 2.6.1 minimum.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.4.
"""
return claims_aligned_beneficiaries >= base_year_alignment_minimum(
standard, performance_year
)
def historical_benchmark(
risk_standardized_pbpm: Mapping[str, float],
trend_to_by3: Mapping[str, float],
weights: Mapping[str, float],
) -> float | None:
"""Return the Historical Benchmark PBPM: Σ risk-standardized PBPM ×
trend to BY 3 × base-year weight over the weighted base years.
BY 3's trend factor is 1.000. None when a weighted year lacks its PBPM
or trend, or when no year is weighted.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.3 / Table 3.3.3
lines 5–9.
"""
if not weights:
return None
total = 0.0
for year, weight in weights.items():
pbpm = risk_standardized_pbpm.get(year)
trend = trend_to_by3.get(year)
if pbpm is None or trend is None:
return None
total += pbpm * trend * weight
return total
# ── §3.2.5 Voluntarily aligned benchmarks ─────────────────────────────────
WITHIN_GUARDRAIL = "within_voluntary_guardrail"
CAPPED_AT_UPPER = "capped_at_110_percent_of_claims_aligned"
RAISED_TO_LOWER = "raised_to_90_percent_of_claims_aligned"
def voluntary_guardrail(
voluntary_historical_pbpm: float,
claims_historical_pbpm: float,
rate: float = VOLUNTARY_GUARDRAIL_RATE,
) -> tuple[float, str]:
"""Hold the voluntarily aligned Historical Benchmark within ±10 percent
of the claims-aligned one for the same category, after base-year
blending and before any REA or PSA. Returns (benchmark, status).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5 ($1,000 claims →
$900–$1,100).
"""
lower = claims_historical_pbpm * (1.0 - rate)
upper = claims_historical_pbpm * (1.0 + rate)
if voluntary_historical_pbpm > upper:
return upper, CAPPED_AT_UPPER
if voluntary_historical_pbpm < lower:
return lower, RAISED_TO_LOWER
return voluntary_historical_pbpm, WITHIN_GUARDRAIL
def contributes_to_voluntary_benchmark(
category: BeneficiaryCategory, by3_category: BeneficiaryCategory | None
) -> bool:
"""Whether a voluntarily aligned beneficiary contributes base-year
expenditures (and counts toward the 500 minimum): every A&D and ESRD
beneficiary, and a High Needs one only if High Needs in BY 3.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
return category != "High Needs" or by3_category == "High Needs"
def voluntary_benchmark_minimum_met(contributing_beneficiaries: int) -> bool:
"""Whether at least 500 contributing voluntarily aligned beneficiaries
exist; otherwise the claims-aligned benchmarks apply to them.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
return contributing_beneficiaries >= VOLUNTARY_BENCHMARK_MINIMUM
# ── §3.4 ACO-specific benchmark adjustments ───────────────────────────────
def regional_ffs_expenditure(
counties: Sequence[tuple[float | None, float | None, float]],
) -> float | None:
"""Return regional FFS expenditure: Σ county weight × risk-standardized
county FFS PBPM.
Each county is (county PBPM FFS expenditure, county average risk score,
ACO-aligned months in the county); the weight is the county's share of
the ACO's aligned months. None if any county lacks a PBPM or score, or
the ACO has no aligned months.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.1.
"""
standardized = [risk_standardize(pbpm, risk) for pbpm, risk, _ in counties]
if None in standardized:
return None
return month_weighted_average(
(value, months)
for value, (_, _, months) in zip(standardized, counties, strict=True)
)
@dataclass(frozen=True)
class SpendingDesignationResult:
"""The §3.4.2 ACO-level spending difference and the designation."""
weighted_difference: float
designation: SpendingDesignation | None
def spending_designation(
categories: Sequence[tuple[float, float, float]],
) -> SpendingDesignationResult:
"""Designate the ACO higher or lower spending.
Each category is (BY 3 regional FFS PBPM, Historical Benchmark PBPM,
BY 3 aligned months). The ACO-level difference is Σ (regional −
historical) × the category's share of BY 3 aligned months: positive is
lower spending, negative higher. The paper names no tie, so an exact
zero is left undetermined (``designation=None``).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.2 / Table 3.4.2.
"""
total_months = sum(months for _, _, months in categories)
if total_months <= 0:
raise ValueError("the spending designation needs BY 3 aligned months")
difference = sum(
(regional - historical) * months / total_months
for regional, historical, months in categories
)
designation: SpendingDesignation | None = (
LOWER_SPENDING
if difference > 0
else HIGHER_SPENDING
if difference < 0
else None
)
return SpendingDesignationResult(difference, designation)
def benchmark_adjustment_cap_rate(reduced: bool) -> float:
"""Return the benchmark adjustment cap rate: 5 percent, or 3 percent
when CMS finds a lower-spending ACO has more than 40 percent of its
Participant TINs from a Shared Savings Program ACO (``reduced``).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4 / §3.4.3.
"""
return (
REDUCED_BENCHMARK_ADJUSTMENT_CAP_RATE
if reduced
else BENCHMARK_ADJUSTMENT_CAP_RATE
)
def benchmark_adjustment_cap(
uspcc_amount: float, py_average_risk_score: float, cap_rate: float
) -> float:
"""Return the risk-adjusted cap: modified-USPCC amount × PY average risk
score × cap rate ($3,500 × 1.030 × 5% = $180.25).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Table 3.4.3.3.
"""
return uspcc_amount * py_average_risk_score * cap_rate
def regional_efficiency_eligible(
spending: SpendingDesignation, risk_option: RiskOption
) -> bool:
"""Whether the Regional Efficiency Adjustment applies: lower-spending
ACOs in the Global Risk track only.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3.
"""
return spending == LOWER_SPENDING and risk_option == GLOBAL
@dataclass(frozen=True)
class RegionalEfficiencyAdjustment:
"""Tables 3.4.3.1 and 3.4.3.3 for one category and alignment type."""
difference: float
uncapped: float
cap: float | None
applied: float | None
def regional_efficiency_adjustment(
regional_ffs_pbpm: float,
historical_benchmark_pbpm: float,
*,
eligible: bool,
uspcc_amount: float | None,
py_average_risk_score: float | None,
cap_rate: float,
) -> RegionalEfficiencyAdjustment:
"""Return the REA for one category and alignment type.
Uncapped = 50 percent of the positive regional − historical difference;
applied = the lesser of it and the cap. An ineligible ACO, or one at or
above regional spending, gets zero. A positive uncapped REA whose cap
inputs are missing is None — never the uncapped amount.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Tables
3.4.3.1–3.4.3.4.
"""
difference = regional_ffs_pbpm - historical_benchmark_pbpm
uncapped = max(0.0, difference) * REGIONAL_EFFICIENCY_SHARE
cap = (
None
if uspcc_amount is None or py_average_risk_score is None
else benchmark_adjustment_cap(uspcc_amount, py_average_risk_score, cap_rate)
)
if not eligible or uncapped == 0.0:
applied: float | None = 0.0
else:
applied = None if cap is None else min(uncapped, cap)
return RegionalEfficiencyAdjustment(difference, uncapped, cap, applied)
def prior_savings_pbpm(
py_benchmark: float, py_expenditure: float, aligned_months: float
) -> float | None:
"""Return a prior year's gross savings (losses) PBPM: (benchmark −
expenditure) ÷ aligned beneficiary months.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.4 / Table 3.4.3.7.
"""
return ratio(py_benchmark - py_expenditure, aligned_months)
@dataclass(frozen=True)
class PriorSavingsAdjustment:
"""§3.4.4 Examples 1–3."""
average_savings_pbpm: float
eligible: bool
proration_factor: float
prorated_average_savings: float
uncapped: float
cap: float | None
applied: float | None
def prior_savings_adjustment(
average_savings_pbpm: float,
predecessor_months: float,
py_months: float,
cap: float | None,
) -> PriorSavingsAdjustment:
"""Return the Prior Savings Adjustment PBPM.
Eligible only when the average PBPM savings is positive. Proration =
predecessor average months ÷ PY months, capped at 100 percent;
uncapped = 50 percent of the prorated average; applied = the lesser of
it and the ACO-level cap (None when an eligible ACO's cap is missing).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.4 (Examples 1–3).
"""
if py_months <= 0:
raise ValueError("the proration factor needs PY beneficiary months")
eligible = average_savings_pbpm > 0
proration = min(predecessor_months / py_months, PRIOR_SAVINGS_PRORATION_CAP)
prorated = average_savings_pbpm * proration
uncapped = prorated * PRIOR_SAVINGS_SHARE if eligible else 0.0
if not eligible:
applied: float | None = 0.0
else:
applied = None if cap is None else min(uncapped, cap)
return PriorSavingsAdjustment(
average_savings_pbpm, eligible, proration, prorated, uncapped, cap, applied
)
def prior_savings_cap(
subpopulations: Sequence[tuple[float, float, float]], cap_rate: float
) -> float:
"""Return the ACO-level Prior Savings Adjustment cap: Σ sub-population
cap × its share of PY alignment, each sub-population being (share,
USPCC-based amount, PY average risk score).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.4 / Table 3.4.3.9.
"""
return sum(
share * benchmark_adjustment_cap(uspcc, risk, cap_rate)
for share, uspcc, risk in subpopulations
)
# ── §3.5 Update to the Performance Year ───────────────────────────────────
@dataclass(frozen=True)
class ThreeWayTrend:
"""§3.5.1: the raw three-way factor, its guardrail band and the factor
applied."""
raw: float
lower_bound: float
upper_bound: float
applied: float
def three_way_blended_trend(
two_way_trend: float, acpt: float, performance_year: int
) -> ThreeWayTrend:
"""Blend two-thirds two-way trend and one-third ACPT, then hold the
result within the PY's guardrail around the two-way trend.
Both factors are cumulative from BY 3 to the PY.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.1 / Table 3.5.1.
"""
raw = (1.0 - ACPT_WEIGHT) * two_way_trend + ACPT_WEIGHT * acpt
low, high = ACPT_GUARDRAILS[model_year(performance_year)]
lower, upper = two_way_trend + low, two_way_trend + high
return ThreeWayTrend(raw, lower, upper, min(max(raw, lower), upper))
def cumulative_acpt(annual_rates: Sequence[float | None]) -> float | None:
"""Return the cumulative BY 3 → PY ACPT factor: Π (1 + each year's
ACPT). Each year keeps its original prospective value even when a
guardrail bound an earlier year. One missing year leaves it None.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.1 (ACPT Guardrail
Policy).
"""
if not annual_rates:
return None
factor = 1.0
for rate in annual_rates:
if rate is None:
return None
factor *= 1.0 + rate
return factor
WITHIN_CAP = "within_risk_score_growth_cap"
CAPPED = "capped_at_risk_score_growth_cap"
FLOORED = "raised_to_risk_score_growth_floor"
def capped_risk_score(
by3_risk_score: float, py_risk_score: float, category: BeneficiaryCategory
) -> tuple[float, str]:
"""Hold the PY average risk score within BY 3 × (1 ± cap): 3 percent
for A&D and ESRD, 4 percent for High Needs. Returns (score, status).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.2 (Risk Score
Growth Cap).
"""
cap = RISK_SCORE_GROWTH_CAPS[category]
upper = by3_risk_score * (1.0 + cap)
lower = by3_risk_score * (1.0 - cap)
if py_risk_score > upper:
return upper, CAPPED
if py_risk_score < lower:
return lower, FLOORED
return py_risk_score, WITHIN_CAP
def performance_year_benchmark_pbpm(
historical_benchmark_pbpm: float,
benchmark_adjustment: float | None,
trend_factor: float,
capped_py_risk_score: float,
) -> float | None:
"""Return the PY Benchmark PBPM: (Historical Benchmark + REA or PSA) ×
three-way trend × capped PY risk score ($1,100 × 1.030 = $1,133).
A missing adjustment (None, e.g. an uncomputable cap) stays missing.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5 / §3.5.2.
"""
if benchmark_adjustment is None:
return None
adjusted = historical_benchmark_pbpm + benchmark_adjustment
return adjusted * trend_factor * capped_py_risk_score
def hybrid_adjustment_factor(
partial_period_pbpm: float, full_year_pbpm: float
) -> float | None:
"""Return a hybrid alignment adjustment factor: average PBPM over the
partial-year period ÷ average full-year PBPM.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.3 / Table 3.5.3.1.
"""
return ratio(partial_period_pbpm, full_year_pbpm)
@dataclass(frozen=True)
class BenchmarkDollars:
"""Table 3.5.3.2 lines 6–8 for one category and alignment type."""
full_year: float
hybrid: float
total: float
def benchmark_dollars(
py_benchmark_pbpm: float,
full_year_months: float,
hybrid_months_and_factors: Sequence[tuple[float, float]] = (),
) -> BenchmarkDollars:
"""Multiply a PY Benchmark PBPM out to dollars over full-year months
and hybrid-aligned months at their adjustment factors.
``total`` = cents(PBPM × [full months + Σ hybrid months × factor]), one
product as Table 3.5.4 lines 8 and 22 state it; ``full_year`` and
``hybrid`` are the Table 3.5.3.2 lines 6 and 7 components.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.3 / Table 3.5.3.2 /
Table 3.5.4 lines 8 and 22.
"""
hybrid_months = sum(months * factor for months, factor in hybrid_months_and_factors)
return BenchmarkDollars(
full_year=cents(py_benchmark_pbpm * full_year_months),
hybrid=cents(py_benchmark_pbpm * hybrid_months),
total=cents(py_benchmark_pbpm * (full_year_months + hybrid_months)),
)
def total_benchmark(category_dollars: Iterable[float]) -> float:
"""Sum aggregate benchmark dollars (claims + voluntary per category,
then A&D + ESRD + High Needs) into the PY Benchmark.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.4 / Table 3.5.4
line 24.
"""
return cents(sum(category_dollars))

View File

@@ -0,0 +1,62 @@
"""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)

View File

@@ -0,0 +1,350 @@
"""Performance Year Benchmark formulas (§3) as narwhals expressions.
Row-wise builders for a frame with one row per benchmark cell (category ×
alignment type, county, base year…), plus :func:`month_weighted_mean` for
``group_by(...).agg(...)``. Scalar twins: ``lead.benchmark``. A null or
non-positive denominator gives null, never a stand-in value.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.
"""
from __future__ import annotations
from collections.abc import Sequence
import narwhals as nw
from lead.benchmark import (
CAPPED,
CAPPED_AT_UPPER,
FLOORED,
RAISED_TO_LOWER,
WITHIN_CAP,
WITHIN_GUARDRAIL,
)
from lead.express import IntoExpr, cents, col, lit_or_col, ratio
from lead.params import (
ACPT_GUARDRAILS,
ACPT_WEIGHT,
FIRST_PERFORMANCE_YEAR,
GLOBAL,
HIGH_NEEDS,
LOWER_SPENDING,
REGIONAL_EFFICIENCY_SHARE,
RISK_SCORE_GROWTH_CAPS,
VOLUNTARY_GUARDRAIL_RATE,
)
RISK_CAP_REFERENCE_MISSING = "risk_growth_cap_reference_missing"
_NULL = nw.lit(None, dtype=nw.Float64())
# ── §3.3 ──────────────────────────────────────────────────────────────────
def risk_standardized_pbpm(
expenditure: IntoExpr, eligible_months: IntoExpr, average_risk_score: IntoExpr
) -> nw.Expr:
"""Expenditure ÷ eligible months ÷ average risk score (Table 3.3.3
lines 3 and 5); null without positive months and score.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.1 / Table 3.3.3.
"""
return ratio(ratio(expenditure, eligible_months), average_risk_score)
def growth_rate(later: IntoExpr, earlier: IntoExpr) -> nw.Expr:
"""later ÷ earlier FFS expenditures; null unless both are positive.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Steps 1–2.
"""
return nw.when(col(later) > 0).then(ratio(later, earlier))
def county_market_share(aligned_months: IntoExpr, eligible_months: IntoExpr) -> nw.Expr:
"""ACO-aligned ÷ alignment-eligible months in a county.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Step 3.1.
"""
return ratio(aligned_months, eligible_months)
def county_weight(aligned_months: IntoExpr, aco_aligned_months: IntoExpr) -> nw.Expr:
"""A county's share of the ACO's aligned months (trend weights and
regional FFS expenditures alike).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Step 3.2 / §3.4.1.
"""
return ratio(aligned_months, aco_aligned_months)
def two_way_blended_trend(
national_growth: IntoExpr, regional_growth: IntoExpr, national_weight: IntoExpr
) -> nw.Expr:
"""national × weight + regional × (1 − weight).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 3.3.2 Step 4.
"""
weight = col(national_weight)
return col(national_growth) * weight + col(regional_growth) * (1.0 - weight)
def trended_weighted_pbpm(
risk_standardized: IntoExpr, trend_to_by3: IntoExpr, base_year_weight: IntoExpr
) -> nw.Expr:
"""One base year's contribution to the Historical Benchmark: PBPM ×
trend to BY 3 × weight (Table 3.3.3 line 9); sum it over base years.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.3 / Table 3.3.3.
"""
return col(risk_standardized) * col(trend_to_by3) * col(base_year_weight)
# ── §3.2.5 ────────────────────────────────────────────────────────────────
def voluntary_guardrail(
voluntary_historical_pbpm: IntoExpr,
claims_historical_pbpm: IntoExpr,
rate: float = VOLUNTARY_GUARDRAIL_RATE,
) -> nw.Expr:
"""The voluntary Historical Benchmark held within ±10 percent of the
claims-aligned one; null when either is null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
claims = col(claims_historical_pbpm)
return nw.when(~claims.is_null()).then(
col(voluntary_historical_pbpm).clip(
lower_bound=claims * (1.0 - rate), upper_bound=claims * (1.0 + rate)
)
)
def voluntary_guardrail_status(
voluntary_historical_pbpm: IntoExpr,
claims_historical_pbpm: IntoExpr,
rate: float = VOLUNTARY_GUARDRAIL_RATE,
) -> nw.Expr:
"""Which side of the guardrail the voluntary benchmark landed on; null
when either input is null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
voluntary, claims = col(voluntary_historical_pbpm), col(claims_historical_pbpm)
return (
nw.when(voluntary.is_null() | claims.is_null())
.then(nw.lit(None, dtype=nw.String()))
.when(voluntary > claims * (1.0 + rate))
.then(nw.lit(CAPPED_AT_UPPER))
.when(voluntary < claims * (1.0 - rate))
.then(nw.lit(RAISED_TO_LOWER))
.otherwise(nw.lit(WITHIN_GUARDRAIL))
)
def contributes_to_voluntary_benchmark(
category: IntoExpr, by3_category: IntoExpr
) -> nw.Expr:
"""A voluntarily aligned beneficiary contributes unless High Needs in
the PY without being High Needs in BY 3 (a null BY 3 category is not
High Needs).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
was_high_needs = (col(by3_category) == HIGH_NEEDS).fill_null(False)
return (col(category) != HIGH_NEEDS) | was_high_needs
# ── §3.4 ──────────────────────────────────────────────────────────────────
def regional_efficiency_eligible(
spending_designation: IntoExpr, risk_option: IntoExpr
) -> nw.Expr:
"""Lower-spending ACOs in the Global Risk track.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3.
"""
return (col(spending_designation) == LOWER_SPENDING) & (col(risk_option) == GLOBAL)
def uncapped_regional_efficiency_adjustment(
regional_ffs_pbpm: IntoExpr, historical_benchmark_pbpm: IntoExpr
) -> nw.Expr:
"""50 percent of the positive regional − historical difference.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Table 3.4.3.1.
"""
difference = col(regional_ffs_pbpm) - col(historical_benchmark_pbpm)
return difference.clip(lower_bound=0.0) * REGIONAL_EFFICIENCY_SHARE
def benchmark_adjustment_cap(
uspcc_amount: IntoExpr, py_average_risk_score: IntoExpr, cap_rate: IntoExpr | float
) -> nw.Expr:
"""USPCC-based amount × PY average risk score × cap rate.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Table 3.4.3.3.
"""
return col(uspcc_amount) * col(py_average_risk_score) * lit_or_col(cap_rate)
def regional_efficiency_adjustment(
regional_ffs_pbpm: IntoExpr,
historical_benchmark_pbpm: IntoExpr,
*,
eligible: IntoExpr,
uspcc_amount: IntoExpr,
py_average_risk_score: IntoExpr,
cap_rate: IntoExpr | float,
) -> nw.Expr:
"""Applied REA: zero when ineligible or not positive, else the lesser
of the uncapped REA and the cap; null when a needed input is null (a
positive REA never falls back to its uncapped amount).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Tables
3.4.3.1–3.4.3.4.
"""
uncapped = uncapped_regional_efficiency_adjustment(
regional_ffs_pbpm, historical_benchmark_pbpm
)
cap = benchmark_adjustment_cap(uspcc_amount, py_average_risk_score, cap_rate)
flag = col(eligible)
return (
nw.when(uncapped.is_null() | flag.is_null())
.then(_NULL)
.when(~flag | (uncapped == 0))
.then(nw.lit(0.0))
.when(cap.is_null())
.then(_NULL)
.otherwise(nw.min_horizontal(uncapped, cap))
)
# ── §3.5 ──────────────────────────────────────────────────────────────────
def _guardrail(performance_year: IntoExpr | int, side: int) -> nw.Expr:
year = lit_or_col(performance_year)
bound = None
for model_year, bounds in ACPT_GUARDRAILS.items():
condition = year == FIRST_PERFORMANCE_YEAR + model_year - 1
bound = (nw.when if bound is None else bound.when)(condition).then(
nw.lit(bounds[side])
)
return bound.otherwise(_NULL)
def three_way_blended_trend(
two_way_trend: IntoExpr, acpt: IntoExpr, performance_year: IntoExpr | int
) -> nw.Expr:
"""Two-thirds two-way trend + one-third ACPT, held within the PY's
guardrail around the two-way trend; null for a PY outside the model.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.1 / Table 3.5.1.
"""
two_way = col(two_way_trend)
raw = (1.0 - ACPT_WEIGHT) * two_way + ACPT_WEIGHT * col(acpt)
lower = two_way + _guardrail(performance_year, 0)
upper = two_way + _guardrail(performance_year, 1)
return nw.when(~lower.is_null()).then(
raw.clip(lower_bound=lower, upper_bound=upper)
)
def risk_score_growth_cap(category: IntoExpr) -> nw.Expr:
"""The category's growth cap: 3 percent A&D and ESRD, 4 percent High
Needs; null for anything else.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.2.
"""
return col(category).replace_strict(
dict(RISK_SCORE_GROWTH_CAPS), default=None, return_dtype=nw.Float64()
)
def capped_risk_score(
by3_risk_score: IntoExpr, py_risk_score: IntoExpr, category: IntoExpr
) -> nw.Expr:
"""PY risk score held within BY 3 × (1 ± cap). Null without a BY 3
score or a known category: the uncapped score never passes through.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.2.
"""
by3, cap = col(by3_risk_score), risk_score_growth_cap(category)
return nw.when(~by3.is_null() & ~cap.is_null()).then(
col(py_risk_score).clip(
lower_bound=by3 * (1.0 - cap), upper_bound=by3 * (1.0 + cap)
)
)
def risk_growth_cap_status(
by3_risk_score: IntoExpr, py_risk_score: IntoExpr, category: IntoExpr
) -> nw.Expr:
"""Whether the PY risk score was capped, raised to the floor, kept, or
could not be capped for a missing reference.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.2.
"""
by3, py, cap = (
col(by3_risk_score),
col(py_risk_score),
risk_score_growth_cap(category),
)
return (
nw.when(by3.is_null() | py.is_null() | cap.is_null())
.then(nw.lit(RISK_CAP_REFERENCE_MISSING))
.when(py > by3 * (1.0 + cap))
.then(nw.lit(CAPPED))
.when(py < by3 * (1.0 - cap))
.then(nw.lit(FLOORED))
.otherwise(nw.lit(WITHIN_CAP))
)
def performance_year_benchmark_pbpm(
historical_benchmark_pbpm: IntoExpr,
benchmark_adjustment: IntoExpr,
trend_factor: IntoExpr,
capped_py_risk_score: IntoExpr,
) -> nw.Expr:
"""(Historical Benchmark + REA or PSA) × three-way trend × capped PY
risk score; a null adjustment stays null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5 / §3.5.2.
"""
adjusted = col(historical_benchmark_pbpm) + col(benchmark_adjustment)
return adjusted * col(trend_factor) * col(capped_py_risk_score)
def benchmark_dollars(
py_benchmark_pbpm: IntoExpr,
full_year_months: IntoExpr,
hybrid_months_and_factors: Sequence[tuple[IntoExpr, IntoExpr]] = (),
) -> nw.Expr:
"""cents(PBPM × [full-year months + Σ hybrid months × factor]).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.3 / Table 3.5.3.2 /
Table 3.5.4 lines 8 and 22.
"""
hybrid = nw.lit(0.0)
for months, factor in hybrid_months_and_factors:
hybrid = hybrid + col(months) * col(factor)
return cents(col(py_benchmark_pbpm) * (col(full_year_months) + hybrid))
def month_weighted_mean(value: IntoExpr, months: IntoExpr) -> nw.Expr:
"""Aggregation: Σ value × months ÷ Σ months, over rows whose value is
known and months positive; null without such rows. Use inside
``group_by(...).agg(...)`` (average risk scores, the claims/voluntary
PBPM combination, regional FFS expenditure).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.1 / §3.4.1 /
§3.5.2 / §3.5.4.
"""
amount, weight = col(value), col(months)
counted = ~amount.is_null() & (weight > 0)
numerator = nw.when(counted).then(amount * weight).otherwise(nw.lit(0.0)).sum()
denominator = nw.when(counted).then(weight).otherwise(nw.lit(0.0)).sum()
return ratio(numerator, denominator)

View File

@@ -0,0 +1,290 @@
"""Final Financial Settlement (Table 5.6.1) as narwhals expressions.
:func:`settle` settles a frame of scenarios at once — one row per ACO,
risk option or what-if — and returns the same lines as
:func:`lead.settlement.settle`, on any narwhals backend. The line builders
are public so a caller can assemble a partial chain.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2–§5.6 / Table 5.6.1.
"""
from __future__ import annotations
import narwhals as nw
from narwhals.typing import FrameT
from lead.express import IntoExpr, cents, col
from lead.params import (
DISCOUNT_RATES,
FIRST_PERFORMANCE_YEAR,
GLOBAL,
LAST_PERFORMANCE_YEAR,
PROFESSIONAL,
QUALITY_WITHHOLD_RATE,
RETENTION_INCENTIVE_RATE,
RISK_CORRIDORS,
SEQUESTRATION_RATE,
)
from lead.settlement import SettlementInputs, SettlementLines
INPUT_COLUMNS = tuple(SettlementInputs.model_fields)
"""Columns :func:`settle` reads — the fields of ``SettlementInputs``."""
LINE_COLUMNS = tuple(SettlementLines.__dataclass_fields__)
"""Columns :func:`settle` writes — the fields of ``SettlementLines``."""
_NULL = nw.lit(None, dtype=nw.Float64())
def discount_rate(
risk_option: IntoExpr, spending_designation: IntoExpr, performance_year: IntoExpr
) -> nw.Expr:
"""Look up the benchmark discount rate (Table 5.6.1 line 2).
Professional: 0.0. Global: Table 5.2.1 by designation and PY. Anything
else — an unknown option or designation, a PY outside 2027–2036 — is
null, and so is every line computed from it.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.1 / Table 5.2.1.
"""
option, designation, year = (
col(risk_option),
col(spending_designation),
col(performance_year),
)
in_model = year.is_between(FIRST_PERFORMANCE_YEAR, LAST_PERFORMANCE_YEAR)
rate = nw.when((option == PROFESSIONAL) & in_model).then(nw.lit(0.0))
for name, by_year in DISCOUNT_RATES.items():
for py, value in by_year.items():
rate = rate.when(
(option == GLOBAL) & (designation == name) & (year == py)
).then(nw.lit(value))
return rate.otherwise(_NULL)
def discount_amount(py_benchmark: IntoExpr, rate: IntoExpr) -> nw.Expr:
"""Total Discount = PY Benchmark × rate (Table 5.6.1 line 3).
A null rate is the paper's "N/A" (the Professional option's Total
Discount) and removes nothing. Use it on a rate column that writes the
Professional option as null; :func:`settle` instead derives the rate
with :func:`discount_rate` and lets a null rate null the chain.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.1 / Table 5.2.2 line 3.
"""
return cents(col(py_benchmark) * nw.coalesce(col(rate), nw.lit(0.0)))
def quality_withhold(py_benchmark: IntoExpr) -> nw.Expr:
"""Quality Withhold = 3 percent of the PY Benchmark before the discount
(Table 5.6.1 line 5).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.2 / Table 5.2.2 line 5.
"""
return cents(col(py_benchmark) * QUALITY_WITHHOLD_RATE)
def quality_earn_back(withhold: IntoExpr, quality_score: IntoExpr) -> nw.Expr:
"""Earned Quality Withhold = withhold × quality score (line 7); null
when the score is missing or outside [0, 1].
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.2 / Table 5.2.2 line 7.
"""
score = col(quality_score)
return nw.when(score.is_between(0.0, 1.0)).then(cents(col(withhold) * score))
def retention_incentive_adjustment(
py_benchmark: IntoExpr, applies: IntoExpr
) -> nw.Expr:
"""2 percent of the PY Benchmark when the ACO terminated after its
first PY, else 0.0; null when ``applies`` is null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.3.
"""
flag = col(applies)
return (
nw.when(flag)
.then(cents(col(py_benchmark) * RETENTION_INCENTIVE_RATE))
.when(~flag)
.then(nw.lit(0.0))
.otherwise(_NULL)
)
def adjusted_stop_loss_payout(payout: IntoExpr, factor: IntoExpr) -> nw.Expr:
"""Payout × neutrality factor (Table 5.6.1 line 20). A null factor is
accepted only with a zero payout (no payouts model-wide); otherwise null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Application of
Stop-Loss, line 20).
"""
amount, scale = col(payout), col(factor)
return (
nw.when(scale.is_null() & (amount == 0))
.then(nw.lit(0.0))
.otherwise(cents(amount * scale))
)
def _corridor_exposure(
magnitude: nw.Expr, benchmark: nw.Expr, lower: float, upper: float | None
) -> nw.Expr:
capped = (
magnitude if upper is None else nw.min_horizontal(magnitude, benchmark * upper)
)
return (capped - benchmark * lower).clip(lower_bound=0.0)
def retained_in_corridor(
gross: IntoExpr,
benchmark_after_adjustments: IntoExpr,
risk_option: IntoExpr,
corridor: int,
) -> nw.Expr:
"""Savings (losses) retained in one risk corridor, signed like ``gross``
(the "Retained ... in Corridor n" lines of Table 5.6.1); null for an
unknown risk option or a null input.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.1 /
Table 5.4.3.
"""
amount, benchmark, option = (
col(gross),
col(benchmark_after_adjustments),
col(risk_option),
)
sign = nw.when(amount < 0).then(nw.lit(-1.0)).otherwise(nw.lit(1.0))
expr = None
for (name, scenario), corridors in RISK_CORRIDORS.items():
band = corridors[corridor - 1]
in_scenario = (amount >= 0) if scenario == "savings" else (amount < 0)
value = cents(
sign
* _corridor_exposure(amount.abs(), benchmark, band.lower, band.upper)
* band.rate
)
condition = (option == name) & in_scenario & ~benchmark.is_null()
expr = (nw.when if expr is None else expr.when)(condition).then(value)
return expr.otherwise(_NULL)
def sequestration_amount(shared: IntoExpr) -> nw.Expr:
"""2 percent of Shared Savings; zero on Shared Losses (line 27).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.2 line 27.
"""
return cents(col(shared).clip(lower_bound=0.0) * SEQUESTRATION_RATE)
@nw.narwhalify
def settle(scenarios: FrameT) -> FrameT:
"""Settle a frame of scenarios: add every Table 5.6.1 line as a column.
Reads the ``SettlementInputs`` fields (``INPUT_COLUMNS``) and writes the
``SettlementLines`` fields (``LINE_COLUMNS``), overwriting the Stop-Loss
input columns with lines 17–19 (zero charge and payout, null factor,
for a row that did not elect Stop-Loss). Other columns pass through.
A missing input column raises. Unlike ``SettlementInputs``, no row is
refused: a null or invalid input nulls the lines that depend on it. A
row the scalar accepts settles to exactly what
:func:`lead.settlement.settle` returns.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.6 / Table 5.6.1.
"""
present = set(scenarios.collect_schema().names())
missing = [c for c in INPUT_COLUMNS if c not in present]
if missing:
raise ValueError(f"settle() needs columns {missing}")
c = nw.col
elected = c("stop_loss_elected")
return (
scenarios.with_columns(
discount_rate=discount_rate(
"risk_option", "spending_designation", "performance_year"
),
retention_incentive_adjustment=retention_incentive_adjustment(
"py_benchmark", "retention_incentive_applies"
),
quality_withhold=quality_withhold("py_benchmark"),
total_ffs_payments=cents(
c("participant_provider_claims")
+ c("preferred_provider_claims")
+ c("non_aco_provider_claims")
),
adjusted_stop_loss_payout=nw.when(elected)
.then(
adjusted_stop_loss_payout(
"stop_loss_payout", "stop_loss_neutrality_factor"
)
)
.when(~elected)
.then(nw.lit(0.0)),
)
.with_columns(
discount_amount=cents(c("py_benchmark") * c("discount_rate")),
quality_earn_back=quality_earn_back("quality_withhold", "quality_score"),
py_expenditure=cents(c("capitation_payments") + c("total_ffs_payments")),
stop_loss_charge=nw.when(elected)
.then(c("stop_loss_charge"))
.when(~elected)
.then(nw.lit(0.0)),
stop_loss_payout=nw.when(elected)
.then(c("stop_loss_payout"))
.when(~elected)
.then(nw.lit(0.0)),
stop_loss_neutrality_factor=nw.when(elected).then(
c("stop_loss_neutrality_factor")
),
)
.with_columns(
benchmark_after_discount=cents(c("py_benchmark") - c("discount_amount")),
net_quality_withhold=cents(c("quality_withhold") - c("quality_earn_back")),
net_stop_loss_impact=cents(
c("stop_loss_charge") - c("adjusted_stop_loss_payout")
),
)
.with_columns(
benchmark_after_adjustments=cents(
c("benchmark_after_discount")
- c("retention_incentive_adjustment")
- c("net_quality_withhold")
),
py_expenditure_after_stop_loss=cents(
c("py_expenditure") + c("net_stop_loss_impact")
),
)
.with_columns(
gross_savings_losses=cents(
c("benchmark_after_adjustments") - c("py_expenditure_after_stop_loss")
)
)
.with_columns(
**{
f"retained_corridor_{n}": retained_in_corridor(
"gross_savings_losses",
"benchmark_after_adjustments",
"risk_option",
n,
)
for n in (1, 2, 3, 4)
}
)
.with_columns(
shared_savings_losses=cents(
c("retained_corridor_1")
+ c("retained_corridor_2")
+ c("retained_corridor_3")
+ c("retained_corridor_4")
)
)
.with_columns(
sequestration_amount=sequestration_amount("shared_savings_losses")
)
.with_columns(
shared_savings_losses_after_sequestration=cents(
c("shared_savings_losses") - c("sequestration_amount")
)
)
)

View File

@@ -0,0 +1,89 @@
"""Beneficiary-grain Stop-Loss (Box 5.3.3) as narwhals expressions.
One row per beneficiary: Predicted Expenditure, Residual Expenditure, the
distance to the Attachment Point, the banded payout and the band reached.
Scalar twins: ``lead.stop_loss``.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3.
"""
from __future__ import annotations
import narwhals as nw
from lead.express import IntoExpr, cents, col
from lead.params import STOP_LOSS_BANDS
from lead.stop_loss import BELOW_ATTACHMENT_POINT
def predicted_expenditure(
regional_expenditure_pbpm: IntoExpr, risk_score: IntoExpr, months_aligned: IntoExpr
) -> nw.Expr:
"""Predicted Expenditure = regional expenditure PBPM × risk score ×
months of alignment in the PY.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Residual Expenditure).
"""
return cents(col(regional_expenditure_pbpm) * col(risk_score) * col(months_aligned))
def residual_expenditure(actual_spend: IntoExpr, predicted_spend: IntoExpr) -> nw.Expr:
"""Residual Expenditure = PY expenditure − Predicted Expenditure.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3.
"""
return cents(col(actual_spend) - col(predicted_spend))
def distance_to_attachment_point(
attachment_point: IntoExpr, residual: IntoExpr
) -> nw.Expr:
"""Attachment Point − Residual: positive is headroom before Stop-Loss
attaches, negative is how far past it the beneficiary is.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 (protection begins
once the Residual passes the Attachment Point).
"""
return cents(col(attachment_point) - col(residual))
def stop_loss_payout(residual: IntoExpr, attachment_point: IntoExpr) -> nw.Expr:
"""Banded payout on the Residual above the Attachment Point: 80 percent
up to 200 percent of it, 100 percent beyond; null if either is null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payment Schedule; illustration: $400,000 residual, $150,000 AP → $220,000).
"""
amount, point = col(residual), col(attachment_point)
paid = []
for band in STOP_LOSS_BANDS:
capped = (
amount
if band.end_multiple is None
else nw.min_horizontal(amount, point * band.end_multiple)
)
exposure = (capped - point * band.start_multiple).clip(lower_bound=0.0)
paid.append(cents(exposure * band.payout_rate))
total = paid[0]
for band_payout in paid[1:]:
total = total + band_payout
return nw.when(~amount.is_null() & ~point.is_null()).then(cents(total))
def band_status(residual: IntoExpr, attachment_point: IntoExpr) -> nw.Expr:
"""The band the Residual reaches: ``band_2``, ``band_1`` or
``below_attachment_point``; null if either input is null.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payment Schedule).
"""
amount, point = col(residual), col(attachment_point)
status = nw.when(amount.is_null() | point.is_null()).then(
nw.lit(None, dtype=nw.String())
)
for band in reversed(STOP_LOSS_BANDS):
status = status.when(amount > point * band.start_multiple).then(
nw.lit(f"band_{band.band}")
)
return status.otherwise(nw.lit(BELOW_ATTACHMENT_POINT))

36
src/lead/money.py Normal file
View File

@@ -0,0 +1,36 @@
"""Cent rounding for LEAD dollar lines.
The paper prints every dollar line rounded to the cent and carries PBPMs,
rates and factors at full precision; its worked examples only reproduce
that way (Table 3.5.4 line 24 sums the cent-rounded category totals of
lines 8 and 22; Table 3.3.3 and Table 3.5.3.2 multiply unrounded PBPMs).
So every function in this package that produces a dollar line rounds it
with :func:`cents`, and nothing else is rounded.
Rounding is half away from zero on the amount scaled to cents
(``floor(|x| × 100 + 0.5)``). ``lead.express.cents`` performs the same
float operations, so scalar and frame results agree bit for bit on every
backend — unlike the engines' own ``round``, which differ (polars rounds
half to even, DuckDB half away from zero).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 5.6.1 / Table 3.5.4.
"""
from __future__ import annotations
import math
def cents(amount: float) -> float:
"""Round a dollar amount to the cent, half away from zero.
>>> cents(184_593.6544)
184593.65
>>> cents(-0.125)
-0.13
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 5.6.1.
"""
scaled = math.floor(abs(amount) * 100.0 + 0.5)
# ``+ 0.0`` turns a negative zero into zero.
return math.copysign(scaled, amount) / 100.0 + 0.0

108
src/lead/monies_owed.py Normal file
View File

@@ -0,0 +1,108 @@
"""LEAD Total Monies Owed (§5.5, Table 5.5.1).
Final Shared Savings/Losses settle as an adjustment to what Provisional
Settlement already paid or collected; the payment-arrangement under (over)
payments and the High-Performers Pool incentive are added. Every amount is
signed the way Table 5.5.1 prints it: positive is owed to the ACO,
negative is owed to CMS.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.5 / Table 5.5.1.
"""
from __future__ import annotations
from dataclasses import dataclass
from lead.money import cents
def apo_adjustment(fee_reductions: float, apo_payments: float) -> float:
"""Reconcile the Advanced Payment Option against actual claims
reductions: reductions above the APO payments are credited to the ACO
(positive), reductions below them are recouped by CMS (negative).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.5 (Adjustments for
ACOs participating in EPCC and/or APO).
"""
return cents(fee_reductions - apo_payments)
@dataclass(frozen=True)
class PaymentArrangementAdjustments:
"""Table 5.5.1 line 4 and its three component lines."""
capitation_under_over_payment: float
enhanced_pcc_repayment: float
apo_adjustment: float
payment_arrangement_adjustments: float
def payment_arrangement_adjustments(
*,
capitation_under_over_payment: float,
enhanced_pcc_paid: float,
apo_adjustment: float,
) -> PaymentArrangementAdjustments:
"""Return Under (Over) Payments from Payment Arrangements (line 4).
``capitation_under_over_payment`` is positive when CMS underpaid the
ACO's capitation. Enhanced PCC is recouped in full, so its repayment is
``-enhanced_pcc_paid``. An ACO that elected neither EPCC nor APO (the
TCC ACO of Table 5.5.1, footnote 1) passes zero for both.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.5 / Table 5.5.1 line 4.
"""
repayment = cents(-enhanced_pcc_paid)
return PaymentArrangementAdjustments(
capitation_under_over_payment=capitation_under_over_payment,
enhanced_pcc_repayment=repayment,
apo_adjustment=apo_adjustment,
payment_arrangement_adjustments=cents(
capitation_under_over_payment + repayment + apo_adjustment
),
)
@dataclass(frozen=True)
class TotalMoniesOwed:
"""Table 5.5.1 lines 1–7."""
provisional_shared_savings_losses: float
final_shared_savings_losses: float
shared_savings_losses_owed: float
payment_arrangement_adjustments: float
high_performers_pool_incentive: float
adjustments_owed: float
total_monies_owed: float
def total_monies_owed(
*,
provisional_shared_savings_losses: float,
final_shared_savings_losses: float,
payment_arrangement_adjustments: float,
high_performers_pool_incentive: float,
) -> TotalMoniesOwed:
"""Return Total Monies Owed at Final Settlement (lines 1–7).
Line 3 = final − provisional Shared Savings (Losses); line 6 = payment
arrangement adjustments + High-Performers Pool incentive; line 7 =
line 3 + line 6. Every input is required: an ACO that did not select
Provisional Settlement, or earned no High-Performers Pool incentive,
passes zero for it.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.5 / Table 5.5.1.
"""
owed = cents(final_shared_savings_losses - provisional_shared_savings_losses)
adjustments = cents(
payment_arrangement_adjustments + high_performers_pool_incentive
)
return TotalMoniesOwed(
provisional_shared_savings_losses=provisional_shared_savings_losses,
final_shared_savings_losses=final_shared_savings_losses,
shared_savings_losses_owed=owed,
payment_arrangement_adjustments=payment_arrangement_adjustments,
high_performers_pool_incentive=high_performers_pool_incentive,
adjustments_owed=adjustments,
total_monies_owed=cents(owed + adjustments),
)

332
src/lead/params.py Normal file
View File

@@ -0,0 +1,332 @@
"""LEAD PY2027 financial-methodology parameters, transcribed from the public paper.
Every constant here is printed in the CMS "LEAD Model PY 2027 Alignment and
Financial Methodology Paper" v1.2 (bib ``E4PEU3HV``). Values that CMS
computes per year or per ACO and does not print — the Stop-Loss Attachment
Point, ACPT values, hybrid alignment factors, modified USPCC amounts, the
Stop-Loss neutrality factor — are caller inputs, never constants. Worked-
example figures are test fixtures, never parameters.
The mappings are read-only (``MappingProxyType``) so a caller cannot change
a published rate for everyone in the process.
"""
from __future__ import annotations
from types import MappingProxyType
from typing import Final, Literal
from pydantic import BaseModel, ConfigDict, Field
# ── Labels ────────────────────────────────────────────────────────────────
RiskOption = Literal["Global", "Professional"]
GLOBAL: Final = "Global"
PROFESSIONAL: Final = "Professional"
RISK_OPTIONS: Final = (GLOBAL, PROFESSIONAL)
"""LEAD risk-sharing options. Source: LEAD PY2027 methodology (bib E4PEU3HV) §1.3."""
SpendingDesignation = Literal["Lower-spending", "Higher-spending"]
LOWER_SPENDING: Final = "Lower-spending"
HIGHER_SPENDING: Final = "Higher-spending"
SPENDING_DESIGNATIONS: Final = (LOWER_SPENDING, HIGHER_SPENDING)
"""Spending designations. Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.2."""
BeneficiaryCategory = Literal["A&D", "ESRD", "High Needs"]
AGED_DISABLED: Final = "A&D"
ESRD: Final = "ESRD"
HIGH_NEEDS: Final = "High Needs"
BENEFICIARY_CATEGORIES: Final = (AGED_DISABLED, ESRD, HIGH_NEEDS)
"""Beneficiary categories. Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.2."""
EntryType = Literal["Newly Entering", "Renewing"]
NEWLY_ENTERING: Final = "Newly Entering"
RENEWING: Final = "Renewing"
ENTRY_TYPES: Final = (NEWLY_ENTERING, RENEWING)
"""ACO entry types. Source: LEAD PY2027 methodology (bib E4PEU3HV) §1.1."""
BASE_YEARS: Final = ("BY1", "BY2", "BY3")
"""The three historical base years, oldest first.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2 / §3.3.3.
"""
# ── Model calendar ────────────────────────────────────────────────────────
FIRST_PERFORMANCE_YEAR: Final = 2027
LAST_PERFORMANCE_YEAR: Final = 2036
"""LEAD runs ten Performance Years, PY 2027 through PY 2036.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 5.2.1 (2032−2036) /
Table 2.6.1 (PY 1–10).
"""
def model_year(performance_year: int) -> int:
"""Return the model PY index (PY 2027 = 1, PY 2036 = 10).
Raises ``ValueError`` outside the model's ten Performance Years.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 2.6.1 / Table 3.5.1.
"""
if not FIRST_PERFORMANCE_YEAR <= performance_year <= LAST_PERFORMANCE_YEAR:
raise ValueError(
f"performance year {performance_year} is outside the LEAD model "
f"({FIRST_PERFORMANCE_YEAR}-{LAST_PERFORMANCE_YEAR})"
)
return performance_year - FIRST_PERFORMANCE_YEAR + 1
# ── §5.2 Performance Year Benchmark adjustments ───────────────────────────
DISCOUNT_RATES: Final = MappingProxyType(
{
LOWER_SPENDING: MappingProxyType({year: 0.03 for year in range(2027, 2037)}),
HIGHER_SPENDING: MappingProxyType(
{
2027: 0.0175,
2028: 0.02,
2029: 0.0225,
2030: 0.025,
2031: 0.0275,
**{year: 0.03 for year in range(2032, 2037)},
}
),
}
)
"""Global Risk Option benchmark discount by spending designation and PY.
Lower-spending ACOs: a static 3.0 percent. Higher-spending ACOs: 1.75 percent
in 2027, rising 0.25 points a year to 3.0 percent in 2032−2036. The
Professional option carries no discount (Table 5.2.2 line 2: 0.00%).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.1 / Table 5.2.1.
"""
QUALITY_WITHHOLD_RATE: Final = 0.03
"""Quality Withhold: 3 percent of the PY Benchmark before the discount.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.2 / Table 5.2.2 line 5.
"""
RETENTION_INCENTIVE_RATE: Final = 0.02
"""Retention incentive: 2 percent benchmark reduction for an ACO that
terminates after its first Performance Year.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.3.
"""
# ── §5.3.3 Stop-Loss Reinsurance ──────────────────────────────────────────
class StopLossBand(BaseModel):
"""One Stop-Loss payout band, in multiples of the Attachment Point.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3
(Stop-Loss Payment Schedule).
"""
model_config = ConfigDict(frozen=True)
band: int = Field(ge=1)
start_multiple: float = Field(ge=0.0, description="Band start, × Attachment Point.")
end_multiple: float | None = Field(
default=None, description="Band end, × Attachment Point; None = no upper limit."
)
payout_rate: float = Field(ge=0.0, le=1.0)
STOP_LOSS_BANDS: Final = (
StopLossBand(band=1, start_multiple=1.0, end_multiple=2.0, payout_rate=0.80),
StopLossBand(band=2, start_multiple=2.0, end_multiple=None, payout_rate=1.00),
)
"""Residual Expenditure between 100 and 200 percent of the Attachment Point
pays at 80 percent; above 200 percent it pays at 100 percent.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3
(Stop-Loss Payment Schedule).
"""
STOP_LOSS_ATTACHMENT_PAYOUT_SHARE: Final = 0.02
"""CMS sets each reference year's Attachment Point so that total model
payout equals 2 percent of total Medicare expenditures. Recorded for
reference: the Attachment Point itself is a CMS input to this package.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3
(Stop-Loss Attachment Point).
"""
STOP_LOSS_REFERENCE_YEAR_COUNT: Final = 3
STOP_LOSS_REFERENCE_YEAR_GAP: Final = 1
"""Three Stop-Loss reference years, with a one-year gap before the PY
(2023, 2024, 2025 for PY 2027).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3
(Stop-Loss Attachment Point).
"""
# ── §5.4 Risk corridors and sequestration ─────────────────────────────────
CorridorScenario = Literal["savings", "losses"]
class Corridor(BaseModel):
"""One risk corridor: a band of gross savings or losses, as a share of
the PY Benchmark after adjustments, shared at ``rate``.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.1 /
Table 5.4.3.
"""
model_config = ConfigDict(frozen=True)
corridor: int = Field(ge=1)
lower: float = Field(ge=0.0, description="Band start, share of benchmark.")
upper: float | None = Field(
default=None, description="Band end, share of benchmark; None = no limit."
)
rate: float = Field(ge=0.0, le=1.0)
def _corridors(
bounds: tuple[float, ...], rates: tuple[float, ...]
) -> tuple[Corridor, ...]:
edges = (0.0, *bounds, None)
return tuple(
Corridor(corridor=i + 1, lower=edges[i], upper=edges[i + 1], rate=rate)
for i, rate in enumerate(rates)
)
_GLOBAL_BOUNDS = (0.15, 0.35, 0.50)
_PROFESSIONAL_BOUNDS = (0.10, 0.15, 0.20)
RISK_CORRIDORS: Final = MappingProxyType(
{
(GLOBAL, "savings"): _corridors(_GLOBAL_BOUNDS, (1.00, 0.50, 0.25, 0.10)),
(GLOBAL, "losses"): _corridors(_GLOBAL_BOUNDS, (1.00, 0.50, 0.25, 0.10)),
(PROFESSIONAL, "savings"): _corridors(
_PROFESSIONAL_BOUNDS, (0.60, 0.35, 0.15, 0.05)
),
(PROFESSIONAL, "losses"): _corridors(
_PROFESSIONAL_BOUNDS, (0.50, 0.35, 0.15, 0.05)
),
}
)
"""Risk corridors by (risk option, savings/losses).
Global: up to 15% / 15–35% / 35–50% / over 50% of benchmark at 100 / 50 /
25 / 10 percent, savings and losses alike. Professional: up to 10% / 10–15% /
15–20% / over 20% at 60 (savings) or 50 (losses) / 35 / 15 / 5 percent.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4.1 / Table 5.4.1;
§5.4.2 / Table 5.4.3.
"""
SEQUESTRATION_RATE: Final = 0.02
"""Sequestration: 2 percent of the Shared Savings retained by the ACO
(Budget Control Act of 2011); it never applies to Shared Losses.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.2 line 27.
"""
# ── §3 Benchmark ─────────────────────────────────────────────────────────
BASE_YEAR_WEIGHTS: Final = MappingProxyType(
{
NEWLY_ENTERING: MappingProxyType({"BY1": 0.10, "BY2": 0.30, "BY3": 0.60}),
RENEWING: MappingProxyType({"BY1": 1 / 3, "BY2": 1 / 3, "BY3": 1 / 3}),
}
)
"""Base-year weights when all three base years are sufficient.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.3.3 / Table 3.3.3 line 8.
"""
ACPT_WEIGHT: Final = 1 / 3
"""Share of the three-way blended trend set by the ACPT; the observed
national-regional blend carries the other two-thirds.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.1.
"""
ACPT_GUARDRAILS: Final = MappingProxyType(
{
1: (-0.002, 0.003),
2: (-0.004, 0.006),
3: (-0.006, 0.009),
4: (-0.008, 0.012),
**{year: (-0.010, 0.015) for year in range(5, 11)},
}
)
"""(lower, upper) guardrail on the three-way trend factor, in factor units
(percentage points / 100) around the two-way blend, by model PY.
The §3.5.1 prose widens the lower bound one year later than Table 3.5.1
("-0.4 percentage points in 2029"); the table is followed here.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.1 / Table 3.5.1.
"""
RISK_SCORE_GROWTH_CAPS: Final = MappingProxyType(
{AGED_DISABLED: 0.03, ESRD: 0.03, HIGH_NEEDS: 0.04}
)
"""Symmetric PY risk-score growth cap relative to BY 3, by category.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.5.2 (Risk Score Growth Cap).
"""
REGIONAL_EFFICIENCY_SHARE: Final = 0.50
"""Regional Efficiency Adjustment: 50 percent of the positive gap between
BY 3 regional FFS expenditures and the Historical Benchmark.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.3 / Table 3.4.3.1.
"""
PRIOR_SAVINGS_SHARE: Final = 0.50
PRIOR_SAVINGS_PRORATION_CAP: Final = 1.0
"""Prior Savings Adjustment: 50 percent of the prorated average PBPM
savings; the proration factor is capped at 100 percent.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4.4.
"""
BENCHMARK_ADJUSTMENT_CAP_RATE: Final = 0.05
REDUCED_BENCHMARK_ADJUSTMENT_CAP_RATE: Final = 0.03
"""Benchmark adjustment cap: 5 percent of the modified USPCC amount, risk
adjusted; 3 percent for lower-spending ACOs with more than 40 percent of
Participant TINs in a Shared Savings Program ACO in the prior two years
(CMS determines that eligibility; it is a caller input here).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.4 / §3.4.3.
"""
VOLUNTARY_GUARDRAIL_RATE: Final = 0.10
"""The voluntarily aligned Historical Benchmark stays within ±10 percent of
the claims-aligned Historical Benchmark of the same category.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
VOLUNTARY_BENCHMARK_MINIMUM: Final = 500
"""Fewer than 500 contributing voluntarily aligned beneficiaries in BY 3:
the claims-aligned benchmarks apply to the voluntarily aligned population.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §3.2.5.
"""
AlignmentMinimumType = Literal["standard", "newly_entering", "high_needs_esrd"]
BASE_YEAR_ALIGNMENT_MINIMUMS: Final = MappingProxyType(
{
"standard": (3000,) * 10,
"newly_entering": (600, 1200, 1800, 2400) + (3000,) * 6,
"high_needs_esrd": (500, 625, 750, 825) + (1000,) * 6,
}
)
"""Claims-based alignment minimum a base year must meet to count toward
the Historical Benchmark, by the CMS-determined alignment standard and
model PY 1–10.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §2.6 / Table 2.6.1; §3.3.4.
"""

476
src/lead/settlement.py Normal file
View File

@@ -0,0 +1,476 @@
"""LEAD Final Financial Settlement: Table 5.6.1, line by line.
The chain is: PY Benchmark (line 1) → Global discount (lines 2–4) →
retention incentive (§5.2.3) → Quality Withhold and Earn Back (lines 5–9)
→ PY expenditure (lines 10–15) → Stop-Loss (lines 16–22) → gross
savings/losses (lines 23–25) → risk corridors (line 26 and its corridor
lines) → sequestration (lines 27–28). :func:`settle` runs it for one
scenario; ``lead.express.settlement.settle`` runs it for a frame of
scenarios on any narwhals backend and returns the same numbers.
Every dollar line is rounded to the cent as it is produced
(:func:`lead.money.cents`); rates and the quality score are not rounded.
The paper prints reductions in parentheses in Table 5.6.1 (lines 5 and 8);
here they are positive amounts that the chain subtracts.
Missing inputs are never silent zeros: :class:`SettlementInputs` refuses
an incomplete scenario. The zeros that do appear are the paper's own: the
Professional option's discount ("N/A", Table 5.6.1 line 3), the Stop-Loss
lines of an ACO that did not elect Stop-Loss (§5.3.3), and sequestration of
Shared Losses (§5.4).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2–§5.6 / Table 5.6.1.
"""
from __future__ import annotations
from dataclasses import dataclass
from pydantic import BaseModel, ConfigDict, Field, model_validator
from lead.money import cents
from lead.params import (
DISCOUNT_RATES,
GLOBAL,
PROFESSIONAL,
QUALITY_WITHHOLD_RATE,
RETENTION_INCENTIVE_RATE,
RISK_CORRIDORS,
SEQUESTRATION_RATE,
CorridorScenario,
RiskOption,
SpendingDesignation,
model_year,
)
from lead.stop_loss import apply_stop_loss, without_stop_loss
# ── §5.2 Performance Year Benchmark adjustments ───────────────────────────
def benchmark_discount_rate(
risk_option: RiskOption,
spending_designation: SpendingDesignation | None,
performance_year: int,
) -> float:
"""Return the benchmark discount rate for a risk option, designation and PY.
The Professional option has no discount (0.0). The Global option reads
Table 5.2.1; an unknown designation or a year outside the model raises,
because a Global benchmark with no scheduled discount is not computable.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.1 / Table 5.2.1 /
Table 5.2.2 line 2.
"""
model_year(performance_year)
if risk_option == PROFESSIONAL:
return 0.0
if risk_option != GLOBAL:
raise ValueError(f"unknown risk option {risk_option!r}")
if spending_designation not in DISCOUNT_RATES:
raise ValueError(
"the Global discount needs a spending designation, "
f"got {spending_designation!r}"
)
return DISCOUNT_RATES[spending_designation][performance_year]
@dataclass(frozen=True)
class Discount:
"""Table 5.6.1 lines 2–4."""
discount_rate: float
discount_amount: float
benchmark_after_discount: float
def apply_discount(py_benchmark: float, discount_rate: float | None) -> Discount:
"""Reduce the PY Benchmark by the discount (Table 5.6.1 lines 2–4).
A missing rate (``None``) is the paper's "N/A" — the Professional
option's Total Discount — and removes nothing.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.1 / Table 5.2.2
lines 2–4.
"""
rate = 0.0 if discount_rate is None else discount_rate
amount = cents(py_benchmark * rate)
return Discount(rate, amount, cents(py_benchmark - amount))
def quality_withhold(
py_benchmark: float, withhold_rate: float = QUALITY_WITHHOLD_RATE
) -> float:
"""Return the Quality Withhold: 3 percent of the PY Benchmark before
the discount (Table 5.6.1 line 5).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.2 / Table 5.2.2 line 5.
"""
return cents(py_benchmark * withhold_rate)
def quality_earn_back(withhold: float, quality_score: float) -> float:
"""Return the Earned Quality Withhold: withhold × quality score
(Table 5.6.1 line 7). The score is a share between 0 and 1.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.2 / Table 5.2.2 line 7.
"""
if not 0.0 <= quality_score <= 1.0:
raise ValueError(f"quality score must be within [0, 1], got {quality_score}")
return cents(withhold * quality_score)
def retention_incentive_adjustment(py_benchmark: float, applies: bool) -> float:
"""Return the retention incentive reduction: 2 percent of the PY
Benchmark when the ACO terminated after its first Performance Year,
otherwise zero (the ACO earns it back).
The paper says the PY Benchmark is "reduced by 2 percent" and orders the
reduction after the discount and before the Quality Withhold; like the
Quality Withhold it is taken here on the PY Benchmark before the
discount.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2.3.
"""
return cents(py_benchmark * RETENTION_INCENTIVE_RATE) if applies else 0.0
@dataclass(frozen=True)
class BenchmarkAdjustments:
"""Table 5.6.1 lines 1–9 (plus the §5.2.3 retention incentive)."""
py_benchmark: float
discount_rate: float
discount_amount: float
benchmark_after_discount: float
retention_incentive_adjustment: float
quality_withhold: float
quality_score: float
quality_earn_back: float
net_quality_withhold: float
benchmark_after_adjustments: float
def adjust_benchmark(
py_benchmark: float,
discount_rate: float | None,
quality_score: float,
*,
retention_incentive_applies: bool = False,
) -> BenchmarkAdjustments:
"""Apply the discount, retention incentive and net Quality Withhold to
the PY Benchmark (Table 5.6.1 lines 1–9).
Line 9 = line 4 − retention incentive − (line 5 − line 7).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.2 / Table 5.2.2 /
Table 5.6.1 lines 1–9.
"""
discount = apply_discount(py_benchmark, discount_rate)
retention = retention_incentive_adjustment(
py_benchmark, retention_incentive_applies
)
withhold = quality_withhold(py_benchmark)
earned = quality_earn_back(withhold, quality_score)
net_quality = cents(withhold - earned)
return BenchmarkAdjustments(
py_benchmark=py_benchmark,
discount_rate=discount.discount_rate,
discount_amount=discount.discount_amount,
benchmark_after_discount=discount.benchmark_after_discount,
retention_incentive_adjustment=retention,
quality_withhold=withhold,
quality_score=quality_score,
quality_earn_back=earned,
net_quality_withhold=net_quality,
benchmark_after_adjustments=cents(
discount.benchmark_after_discount - retention - net_quality
),
)
# ── §5.3 Performance Year expenditure ─────────────────────────────────────
@dataclass(frozen=True)
class PYExpenditure:
"""Table 5.6.1 lines 10–15."""
capitation_payments: float
participant_provider_claims: float
preferred_provider_claims: float
non_aco_provider_claims: float
total_ffs_payments: float
py_expenditure: float
def py_expenditure(
capitation_payments: float,
participant_provider_claims: float,
preferred_provider_claims: float,
non_aco_provider_claims: float,
) -> PYExpenditure:
"""Sum capitation and the three FFS claim components into the PY
expenditure, on a pre-sequestration basis (Table 5.6.1 lines 10–15).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3 / Table 5.3.2.
"""
total_ffs = cents(
participant_provider_claims
+ preferred_provider_claims
+ non_aco_provider_claims
)
return PYExpenditure(
capitation_payments=capitation_payments,
participant_provider_claims=participant_provider_claims,
preferred_provider_claims=preferred_provider_claims,
non_aco_provider_claims=non_aco_provider_claims,
total_ffs_payments=total_ffs,
py_expenditure=cents(capitation_payments + total_ffs),
)
# ── §5.4 Gross savings/losses, risk corridors, sequestration ──────────────
def gross_savings_losses(
benchmark_after_adjustments: float, py_expenditure_after_stop_loss: float
) -> float:
"""Return gross savings (positive) or losses (negative): benchmark after
adjustments less PY expenditure after Stop-Loss (Table 5.6.1 line 25).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.2 line 25.
"""
return cents(benchmark_after_adjustments - py_expenditure_after_stop_loss)
@dataclass(frozen=True)
class SharedSavingsLosses:
"""Table 5.6.1 line 26 and its "Retained ... in Corridor n" lines."""
risk_option: RiskOption
scenario: CorridorScenario
retained_by_corridor: tuple[float, ...]
shared_savings_losses: float
def corridor_exposure(
amount: float, benchmark: float, lower: float, upper: float | None
) -> float:
"""Return the part of ``|amount|`` that falls in one corridor, whose
bounds are shares of ``benchmark``.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.1 /
Table 5.4.3.
"""
magnitude = abs(amount)
capped = magnitude if upper is None else min(magnitude, benchmark * upper)
return max(0.0, capped - benchmark * lower)
def shared_savings_losses(
gross: float, benchmark_after_adjustments: float, risk_option: RiskOption
) -> SharedSavingsLosses:
"""Apply the risk corridors to gross savings or losses (Table 5.6.1
line 26).
Corridor bounds are shares of the PY Benchmark after the discount,
Quality Withhold and Earn Back. Each corridor's retained amount keeps
the sign of ``gross`` and is rounded to the cent; line 26 is their sum,
so the printed corridor lines add up to it.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.1 /
Table 5.4.3 / Table 5.4.2 / Table 5.4.4.
"""
if risk_option not in (GLOBAL, PROFESSIONAL):
raise ValueError(f"unknown risk option {risk_option!r}")
scenario: CorridorScenario = "savings" if gross >= 0 else "losses"
sign = 1.0 if gross >= 0 else -1.0
retained = tuple(
cents(
sign
* corridor_exposure(gross, benchmark_after_adjustments, c.lower, c.upper)
* c.rate
)
for c in RISK_CORRIDORS[(risk_option, scenario)]
)
return SharedSavingsLosses(risk_option, scenario, retained, cents(sum(retained)))
def sequestration_amount(shared: float, rate: float = SEQUESTRATION_RATE) -> float:
"""Return sequestration: 2 percent of Shared Savings; zero on Shared
Losses, which are not sequestered (Table 5.6.1 line 27).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.4 / Table 5.4.2 line 27.
"""
return cents(max(shared, 0.0) * rate)
# ── The whole chain ───────────────────────────────────────────────────────
class SettlementInputs(BaseModel):
"""One settlement scenario: the Table 5.6.1 inputs.
``spending_designation`` is required for the Global option (it selects
the discount) and unused for the Professional option. The Stop-Loss
charge and payout are required exactly when Stop-Loss was elected; the
neutrality factor is required unless the payout is zero.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Table 5.6.1 lines 1, 6,
10–13, 17–19; §5.2.3.
"""
model_config = ConfigDict(frozen=True)
py_benchmark: float = Field(description="Line 1: PY Benchmark, all aligned.")
risk_option: RiskOption
spending_designation: SpendingDesignation | None = None
performance_year: int
quality_score: float = Field(ge=0.0, le=1.0, description="Line 6, as a share.")
capitation_payments: float = Field(description="Line 10.")
participant_provider_claims: float = Field(description="Line 11.")
preferred_provider_claims: float = Field(description="Line 12.")
non_aco_provider_claims: float = Field(description="Line 13.")
stop_loss_elected: bool
stop_loss_charge: float | None = Field(default=None, description="Line 17.")
stop_loss_payout: float | None = Field(default=None, description="Line 18.")
stop_loss_neutrality_factor: float | None = Field(
default=None, description="Line 19 (model-wide)."
)
retention_incentive_applies: bool = Field(
default=False,
description="True only when the ACO terminated after its first PY (§5.2.3).",
)
@model_validator(mode="after")
def _complete(self) -> SettlementInputs:
model_year(self.performance_year)
if self.risk_option == GLOBAL and self.spending_designation is None:
raise ValueError("the Global option needs a spending designation")
amounts = (self.stop_loss_charge, self.stop_loss_payout)
if self.stop_loss_elected:
if None in amounts:
raise ValueError(
"Stop-Loss was elected: charge and payout are required"
)
if self.stop_loss_neutrality_factor is None and self.stop_loss_payout:
raise ValueError("a Stop-Loss payout needs the neutrality factor")
elif amounts != (None, None) or self.stop_loss_neutrality_factor is not None:
raise ValueError("Stop-Loss amounts given but Stop-Loss was not elected")
return self
@dataclass(frozen=True)
class SettlementLines:
"""Every line of Table 5.6.1 (line 16 = line 15, line 23 = line 22,
line 24 = line 9), plus the §5.2.3 retention incentive."""
py_benchmark: float # 1
discount_rate: float # 2
discount_amount: float # 3
benchmark_after_discount: float # 4
retention_incentive_adjustment: float # §5.2.3
quality_withhold: float # 5
quality_score: float # 6
quality_earn_back: float # 7
net_quality_withhold: float # 8
benchmark_after_adjustments: float # 9, 24
capitation_payments: float # 10
participant_provider_claims: float # 11
preferred_provider_claims: float # 12
non_aco_provider_claims: float # 13
total_ffs_payments: float # 14
py_expenditure: float # 15, 16
stop_loss_charge: float # 17
stop_loss_payout: float # 18
stop_loss_neutrality_factor: float | None # 19
adjusted_stop_loss_payout: float # 20
net_stop_loss_impact: float # 21
py_expenditure_after_stop_loss: float # 22, 23
gross_savings_losses: float # 25
shared_savings_losses: float # 26
retained_corridor_1: float
retained_corridor_2: float
retained_corridor_3: float
retained_corridor_4: float
sequestration_amount: float # 27
shared_savings_losses_after_sequestration: float # 28
def settle(inputs: SettlementInputs) -> SettlementLines:
"""Run Final Financial Settlement for one scenario (Table 5.6.1).
An ACO that did not elect Stop-Loss has no charge, payout or net
impact: its PY expenditure after Stop-Loss is its PY expenditure.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.6 / Table 5.6.1.
"""
rate = benchmark_discount_rate(
inputs.risk_option, inputs.spending_designation, inputs.performance_year
)
benchmark = adjust_benchmark(
inputs.py_benchmark,
rate,
inputs.quality_score,
retention_incentive_applies=inputs.retention_incentive_applies,
)
expenditure = py_expenditure(
inputs.capitation_payments,
inputs.participant_provider_claims,
inputs.preferred_provider_claims,
inputs.non_aco_provider_claims,
)
# SettlementInputs guarantees the charge and payout exist when elected.
stop_loss = (
apply_stop_loss(
expenditure.py_expenditure,
inputs.stop_loss_charge,
inputs.stop_loss_payout,
inputs.stop_loss_neutrality_factor,
)
if inputs.stop_loss_elected
else without_stop_loss(expenditure.py_expenditure)
)
gross = gross_savings_losses(
benchmark.benchmark_after_adjustments,
stop_loss.py_expenditure_after_stop_loss,
)
shared = shared_savings_losses(
gross, benchmark.benchmark_after_adjustments, inputs.risk_option
)
sequestration = sequestration_amount(shared.shared_savings_losses)
c1, c2, c3, c4 = shared.retained_by_corridor
return SettlementLines(
py_benchmark=benchmark.py_benchmark,
discount_rate=benchmark.discount_rate,
discount_amount=benchmark.discount_amount,
benchmark_after_discount=benchmark.benchmark_after_discount,
retention_incentive_adjustment=benchmark.retention_incentive_adjustment,
quality_withhold=benchmark.quality_withhold,
quality_score=benchmark.quality_score,
quality_earn_back=benchmark.quality_earn_back,
net_quality_withhold=benchmark.net_quality_withhold,
benchmark_after_adjustments=benchmark.benchmark_after_adjustments,
capitation_payments=expenditure.capitation_payments,
participant_provider_claims=expenditure.participant_provider_claims,
preferred_provider_claims=expenditure.preferred_provider_claims,
non_aco_provider_claims=expenditure.non_aco_provider_claims,
total_ffs_payments=expenditure.total_ffs_payments,
py_expenditure=expenditure.py_expenditure,
stop_loss_charge=stop_loss.stop_loss_charge,
stop_loss_payout=stop_loss.stop_loss_payout,
stop_loss_neutrality_factor=stop_loss.stop_loss_neutrality_factor,
adjusted_stop_loss_payout=stop_loss.adjusted_stop_loss_payout,
net_stop_loss_impact=stop_loss.net_stop_loss_impact,
py_expenditure_after_stop_loss=stop_loss.py_expenditure_after_stop_loss,
gross_savings_losses=gross,
shared_savings_losses=shared.shared_savings_losses,
retained_corridor_1=c1,
retained_corridor_2=c2,
retained_corridor_3=c3,
retained_corridor_4=c4,
sequestration_amount=sequestration,
shared_savings_losses_after_sequestration=cents(
shared.shared_savings_losses - sequestration
),
)

324
src/lead/stop_loss.py Normal file
View File

@@ -0,0 +1,324 @@
"""LEAD Stop-Loss Reinsurance (§5.3.3, Box 5.3.3).
Per beneficiary: Predicted Expenditure = regional expenditure PBPM × risk
score × months of alignment; Residual Expenditure = PY expenditure −
Predicted Expenditure; once the Residual passes the Attachment Point it
pays out by the banded schedule. Per ACO: the charge is the trended,
risk-adjusted reference expenditure × the 3-year average payout
percentage; at settlement the neutrality-adjusted payout nets against the
charge, and the net is added to PY expenditure (Table 5.6.1 lines 16–22).
CMS supplies the Attachment Point, the reference-year expenditure and
payout percentages, and the model-wide neutrality factor; none of them is
a constant here.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3.
"""
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from lead.money import cents
from lead.params import (
STOP_LOSS_BANDS,
STOP_LOSS_REFERENCE_YEAR_COUNT,
STOP_LOSS_REFERENCE_YEAR_GAP,
StopLossBand,
)
BELOW_ATTACHMENT_POINT = "below_attachment_point"
def reference_years(performance_year: int) -> tuple[int, ...]:
"""Return the three Stop-Loss reference years: the most recent three
before the PY, with a one-year gap (2023–2025 for PY 2027).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Attachment Point).
"""
last = performance_year - STOP_LOSS_REFERENCE_YEAR_GAP - 1
return tuple(range(last - STOP_LOSS_REFERENCE_YEAR_COUNT + 1, last + 1))
def trended_attachment_point(
reference_attachment_point: float, national_trend_factor: float
) -> float:
"""Trend the most recent reference year's Attachment Point to PY
dollars with the national trend factor.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Attachment Point).
"""
return cents(reference_attachment_point * national_trend_factor)
def predicted_expenditure(
regional_expenditure_pbpm: float, risk_score: float, months_aligned: float
) -> float:
"""Return Predicted Expenditure = LEAD regional expenditure PBPM ×
beneficiary risk score × months of alignment in the PY.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Residual Expenditure).
"""
return cents(regional_expenditure_pbpm * risk_score * months_aligned)
def residual_expenditure(actual_spend: float, predicted_spend: float) -> float:
"""Return Residual Expenditure = PY expenditure − Predicted Expenditure.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 / Box 5.3.3
(illustration line 4).
"""
return cents(actual_spend - predicted_spend)
@dataclass(frozen=True)
class BandPayout:
"""One band of a beneficiary's Stop-Loss payout."""
band: int
exposure: float
payout_rate: float
payout: float
@dataclass(frozen=True)
class StopLossPayout:
"""The illustration table of Box 5.3.3 for one beneficiary."""
residual_expenditure: float
attachment_point: float
below_attachment_point: float
bands: tuple[BandPayout, ...]
total_payout: float
def band_exposure(
residual: float, attachment_point: float, band: StopLossBand
) -> float:
"""Return the Residual Expenditure that falls inside one band.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payment Schedule).
"""
capped = (
residual
if band.end_multiple is None
else min(residual, attachment_point * band.end_multiple)
)
return max(0.0, capped - attachment_point * band.start_multiple)
def stop_loss_payout(
residual: float,
attachment_point: float,
bands: Sequence[StopLossBand] = STOP_LOSS_BANDS,
) -> StopLossPayout:
"""Pay the Residual Expenditure above the Attachment Point by band.
Nothing is covered below the Attachment Point; band 1 (100–200 percent
of it) pays 80 percent and band 2 (above 200 percent) pays 100 percent.
A $400,000 Residual against a $150,000 Attachment Point pays $220,000.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payout; illustration lines 1–5).
"""
exposures = [band_exposure(residual, attachment_point, band) for band in bands]
paid = tuple(
BandPayout(
band=band.band,
exposure=cents(exposure),
payout_rate=band.payout_rate,
payout=cents(exposure * band.payout_rate),
)
for band, exposure in zip(bands, exposures, strict=True)
)
return StopLossPayout(
residual_expenditure=residual,
attachment_point=attachment_point,
below_attachment_point=cents(min(max(residual, 0.0), attachment_point)),
bands=paid,
total_payout=cents(sum(band.payout for band in paid)),
)
def band_status(residual: float, attachment_point: float) -> str:
"""Name the band the Residual Expenditure reaches: ``band_2``,
``band_1`` or ``below_attachment_point``.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payment Schedule).
"""
for band in reversed(STOP_LOSS_BANDS):
if residual > attachment_point * band.start_multiple:
return f"band_{band.band}"
return BELOW_ATTACHMENT_POINT
def payout_percentage(
total_payout: float, reference_expenditure: float
) -> float | None:
"""Return a reference year's payout percentage: total Stop-Loss payout
÷ total reference-year expenditure (None without expenditure).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Charge).
"""
return total_payout / reference_expenditure if reference_expenditure > 0 else None
def average_payout_percentage(percentages: Sequence[float | None]) -> float | None:
"""Return the simple average of the reference years' payout
percentages, or None if any year is missing.
Not rounded: Box 5.3.3 prints 2.03% but its charge (line 6) only
reproduces with the unrounded mean of 1.96%, 2.09% and 2.05%.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Calculation of
Stop-Loss Charge, line 5).
"""
known = [p for p in percentages if p is not None]
if not known or len(known) != len(percentages):
return None
return sum(known) / len(known)
@dataclass(frozen=True)
class StopLossCharge:
"""Box 5.3.3, Calculation of Stop-Loss Charge, lines 1–6."""
average_reference_expenditure_pbpm_trended: float
aligned_beneficiary_months: float
average_risk_score: float
trended_risk_adjusted_reference_expenditure: float
average_payout_percentage: float
stop_loss_charge: float
def stop_loss_charge(
average_reference_expenditure_pbpm_trended: float,
aligned_beneficiary_months: float,
average_risk_score: float,
average_payout_pct: float,
) -> StopLossCharge:
"""Return the PY Stop-Loss charge: trended reference PBPM × aligned
months × average PY risk score (line 4), × average payout percentage
(line 6).
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Calculation of
Stop-Loss Charge).
"""
reference = cents(
average_reference_expenditure_pbpm_trended
* aligned_beneficiary_months
* average_risk_score
)
return StopLossCharge(
average_reference_expenditure_pbpm_trended=average_reference_expenditure_pbpm_trended,
aligned_beneficiary_months=aligned_beneficiary_months,
average_risk_score=average_risk_score,
trended_risk_adjusted_reference_expenditure=reference,
average_payout_percentage=average_payout_pct,
stop_loss_charge=cents(reference * average_payout_pct),
)
def charge_pbpm(charge: float, aligned_beneficiary_months: float) -> float | None:
"""Express the charge per aligned beneficiary month (None without months).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 (the charge is a
per-beneficiary per-month amount).
"""
return charge / aligned_beneficiary_months if aligned_beneficiary_months else None
def neutrality_factor(
total_model_charges: float, total_model_payouts: float
) -> float | None:
"""Return the Stop-Loss Neutrality Factor: all LEAD Stop-Loss charges ÷
all LEAD Stop-Loss payouts in the PY.
With no payouts model-wide the ratio is undefined and None is returned;
every ACO's payout is then zero, so nothing needs scaling.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Stop-Loss
Payout Adjustment Factor).
"""
return total_model_charges / total_model_payouts if total_model_payouts else None
@dataclass(frozen=True)
class StopLossApplication:
"""Table 5.6.1 lines 16–22."""
py_expenditure: float
stop_loss_charge: float
stop_loss_payout: float
stop_loss_neutrality_factor: float | None
adjusted_stop_loss_payout: float
net_stop_loss_impact: float
py_expenditure_after_stop_loss: float
def adjusted_payout(payout: float, factor: float | None) -> float:
"""Scale the ACO's payout by the neutrality factor (Table 5.6.1 line 20).
A None factor is accepted only with a zero payout.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Application of
Stop-Loss, line 20).
"""
if factor is None:
if payout:
raise ValueError("a Stop-Loss payout needs the neutrality factor")
return 0.0
return cents(payout * factor)
def apply_stop_loss(
py_expenditure: float,
charge: float,
payout: float,
factor: float | None,
) -> StopLossApplication:
"""Add the net of charge and neutrality-adjusted payout to the PY
expenditure (Table 5.6.1 lines 16–22).
Stop-Loss is virtual: the charge increases PY expenditure and the
payout reduces it.
Source: LEAD PY2027 methodology (bib E4PEU3HV) Box 5.3.3 (Application of
Stop-Loss).
"""
adjusted = adjusted_payout(payout, factor)
net = cents(charge - adjusted)
return StopLossApplication(
py_expenditure=py_expenditure,
stop_loss_charge=charge,
stop_loss_payout=payout,
stop_loss_neutrality_factor=factor,
adjusted_stop_loss_payout=adjusted,
net_stop_loss_impact=net,
py_expenditure_after_stop_loss=cents(py_expenditure + net),
)
def without_stop_loss(py_expenditure: float) -> StopLossApplication:
"""Lines 16–22 for an ACO that did not elect Stop-Loss: no charge, no
payout, and PY expenditure unchanged.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.3.3 (only ACOs that
elected Stop-Loss have PY expenditures adjusted).
"""
return StopLossApplication(
py_expenditure=py_expenditure,
stop_loss_charge=0.0,
stop_loss_payout=0.0,
stop_loss_neutrality_factor=None,
adjusted_stop_loss_payout=0.0,
net_stop_loss_impact=0.0,
py_expenditure_after_stop_loss=py_expenditure,
)

139
src/lead/timing.py Normal file
View File

@@ -0,0 +1,139 @@
"""LEAD settlement timing and which basis a settlement run states.
Final Financial Settlement includes claims run out through March 31 of the
year after the PY; Provisional Settlement, before then, applies a
completion factor instead (§5.1 / Table 5.1.1). So a settlement computed
before the run-out closes states a projection, and partial-year actual
claims are labelled as such, never the headline.
A run declares one of three modes: ``projection`` (may state a labelled
projection), ``actual`` (every PY number must come from PY data; a run
before the run-out closes fails), or ``rewind_test`` (development only,
behaves as ``projection``). Which basis a run may use before the run-out
closes is a run-configuration choice, not a CMS rule; every result names
the basis it used.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 / Table 5.1.1.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import date
from typing import Final
PROJECTION: Final = "projection"
ACTUAL: Final = "actual"
REWIND_TEST: Final = "rewind_test"
RUN_MODES: Final = (PROJECTION, ACTUAL, REWIND_TEST)
PROJECTED_BASIS: Final = "projected_from_base_year_experience"
ACTUAL_CLAIMS_BASIS: Final = "actual_performance_year_claims"
PROJECTED_STATUS: Final = "projected"
PARTIAL_YEAR_STATUS: Final = "actual_to_date_partial_year"
COMPUTED_STATUS: Final = "computed_from_calculated_inputs"
PY_CLAIMS_INPUT: Final = "performance_year_claims_by_provider_class"
RUNOUT_REMEDY: Final = (
"wait for the Performance Year claims run-out (through Q1 of the following "
"year) or run in projection mode"
)
CMS_SETTLEMENT_REPORT: Final = "cms_settlement_report"
CMS_SETTLEMENT_REPORT_MISSING: Final = "cms_settlement_report_source_missing"
PROVISIONAL_NOT_YET_MADE: Final = "provisional_settlement_not_yet_made"
FINAL_NOT_YET_MADE: Final = "final_settlement_not_yet_made"
def validate_run_mode(run_mode: str) -> str:
"""Return the run mode, or raise naming the three modes.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 (run-configuration
choice, not a CMS rule).
"""
if run_mode not in RUN_MODES:
raise ValueError(
f"run_mode must be one of {', '.join(RUN_MODES)}; got {run_mode!r}"
)
return run_mode
def require_projection_allowed(run_mode: str, input_name: str, remedy: str) -> None:
"""Raise in ``actual`` mode, where a PY input without rows may not be
replaced by a projection; ``projection`` and ``rewind_test`` continue.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 (run-configuration
choice, not a CMS rule).
"""
if validate_run_mode(run_mode) == ACTUAL:
raise ValueError(
f"{input_name} has no complete performance-year rows as of the as-of "
f"date and run_mode={ACTUAL!r} forbids a projection; {remedy}"
)
def settlement_runout_end(performance_year: int) -> date:
"""Return the last day of the Final Settlement claims run-out: March 31
of the year after the PY.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 / Table 5.1.1
(Claims Run-Out).
"""
return date(performance_year + 1, 3, 31)
@dataclass(frozen=True)
class SettlementBasis:
"""The basis a run states, the status of its headline rows and of its
actual-claims rows."""
basis: str
headline_status: str
actual_rows_status: str
as_of_date: date
def settlement_basis_for_run(
run_mode: str, as_of_date: date, performance_year: int, py_claim_rows: int
) -> SettlementBasis:
"""Decide the basis of one settlement run.
Actual once PY claim rows exist and the run-out has closed; before that
a projection (refused in ``actual`` mode), with any partial-year actual
rows labelled ``actual_to_date_partial_year``. PY claim rows observed
before the PY starts are data from the future: a feed error.
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 / Table 5.1.1.
"""
if py_claim_rows and as_of_date < date(performance_year, 1, 1):
raise ValueError(
f"{PY_CLAIMS_INPUT}: {py_claim_rows} performance-period rows dated after "
f"the as-of date {as_of_date.isoformat()}; data from the future is a "
"feed error"
)
if py_claim_rows and as_of_date > settlement_runout_end(performance_year):
return SettlementBasis(
ACTUAL_CLAIMS_BASIS, COMPUTED_STATUS, COMPUTED_STATUS, as_of_date
)
require_projection_allowed(run_mode, PY_CLAIMS_INPUT, RUNOUT_REMEDY)
rows_status = PARTIAL_YEAR_STATUS if py_claim_rows else PROJECTED_STATUS
return SettlementBasis(PROJECTED_BASIS, PROJECTED_STATUS, rows_status, as_of_date)
def settlement_stage_input(
as_of_date: date, stage_start: date, reported: float | None, not_yet_status: str
) -> tuple[float | None, str]:
"""Return a settlement-stage amount and its status.
The amount is CMS's settlement-report figure when supplied; zero while
the as-of date precedes the stage (nothing has been settled yet); and
otherwise missing (None) until the report arrives. Used for what
Provisional Settlement distributed (Table 5.5.1 line 1) and the
High-Performers Pool incentive (line 5).
Source: LEAD PY2027 methodology (bib E4PEU3HV) §5.1 / §5.5.
"""
if reported is not None:
return float(reported), CMS_SETTLEMENT_REPORT
if as_of_date < stage_start:
return 0.0, not_yet_status
return None, CMS_SETTLEMENT_REPORT_MISSING

6
tests/lead/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
"""Tests for lead — the LEAD Model PY2027 financial methodology.
The expected values are the worked examples CMS printed in the LEAD PY2027
methodology paper (bib E4PEU3HV), transcribed in ``tests/lead/paper.py``;
every Expr test runs on polars and on DuckDB through narwhals.
"""

44
tests/lead/conftest.py Normal file
View File

@@ -0,0 +1,44 @@
"""Backends for the lead Expr tests: every frame test runs on polars and on
DuckDB through narwhals."""
from __future__ import annotations
from collections.abc import Iterator
from typing import Any
import duckdb
import narwhals as nw
import polars as pl
import pytest
class Backend:
"""Build a native frame on one backend and read results back as rows."""
def __init__(self, name: str) -> None:
self.name = name
self.con = duckdb.connect() if name == "duckdb" else None
def frame(self, data: Any, schema: dict[str, Any] | None = None) -> Any:
df = pl.DataFrame(data, schema=schema)
return df if self.con is None else self.con.from_arrow(df.to_arrow())
def rows(self, native: Any) -> list[dict[str, Any]]:
return nw.from_native(native).lazy().collect("polars").to_native().to_dicts()
def select(self, data: Any, schema: dict[str, Any] | None = None, **exprs: nw.Expr):
frame = nw.from_native(self.frame(data, schema))
return self.rows(frame.select(**exprs).to_native())
def close(self) -> None:
if self.con is not None:
self.con.close()
@pytest.fixture(params=["polars", "duckdb"])
def backend(request: pytest.FixtureRequest) -> Iterator[Backend]:
engine = Backend(request.param)
try:
yield engine
finally:
engine.close()

250
tests/lead/paper.py Normal file
View File

@@ -0,0 +1,250 @@
"""Worked examples printed in the LEAD PY2027 methodology (bib E4PEU3HV).
Test fixtures only — never runtime parameters. Each value is copied from
the paper as printed; percentages are written as shares.
"""
from __future__ import annotations
# ── Table 5.6.1: Full Settlement Calculation ──────────────────────────────
TABLE_5_6_1_INPUTS = {
"py_benchmark": 150_000_000.00, # line 1
"spending_designation": "Lower-spending", # 3.00% discount (line 2)
"performance_year": 2027,
"quality_score": 0.95, # line 6
"capitation_payments": 10_000_000.00, # line 10
"participant_provider_claims": 1_003_442.00, # line 11
"preferred_provider_claims": 33_435_084.00, # line 12
"non_aco_provider_claims": 91_355_457.00, # line 13
"stop_loss_elected": True,
"stop_loss_charge": 2_948_334.28, # line 17
"stop_loss_payout": 2_900_000.00, # line 18
"stop_loss_neutrality_factor": 0.930, # line 19
"retention_incentive_applies": False,
}
# (paper line, SettlementLines field). Lines 16, 23 and 24 restate 15, 22
# and 9; "26.1"–"26.4" are the "Retained ... in Corridor n" lines.
TABLE_5_6_1_LINES = (
("1", "py_benchmark"),
("2", "discount_rate"),
("3", "discount_amount"),
("4", "benchmark_after_discount"),
("5", "quality_withhold"),
("6", "quality_score"),
("7", "quality_earn_back"),
("8", "net_quality_withhold"),
("9", "benchmark_after_adjustments"),
("10", "capitation_payments"),
("11", "participant_provider_claims"),
("12", "preferred_provider_claims"),
("13", "non_aco_provider_claims"),
("14", "total_ffs_payments"),
("15", "py_expenditure"),
("16", "py_expenditure"),
("17", "stop_loss_charge"),
("18", "stop_loss_payout"),
("19", "stop_loss_neutrality_factor"),
("20", "adjusted_stop_loss_payout"),
("21", "net_stop_loss_impact"),
("22", "py_expenditure_after_stop_loss"),
("23", "py_expenditure_after_stop_loss"),
("24", "benchmark_after_adjustments"),
("25", "gross_savings_losses"),
("26", "shared_savings_losses"),
("26.1", "retained_corridor_1"),
("26.2", "retained_corridor_2"),
("26.3", "retained_corridor_3"),
("26.4", "retained_corridor_4"),
("27", "sequestration_amount"),
("28", "shared_savings_losses_after_sequestration"),
)
_COMMON = {
"1": 150_000_000.00,
"5": 4_500_000.00, # printed ($4,500,000.00): a reduction
"6": 0.95,
"7": 4_275_000.00,
"8": 225_000.00, # printed ($225,000.00): a reduction
"10": 10_000_000.00,
"11": 1_003_442.00,
"12": 33_435_084.00,
"13": 91_355_457.00,
"14": 125_793_983.00,
"15": 135_793_983.00,
"16": 135_793_983.00,
"17": 2_948_334.28,
"18": 2_900_000.00,
"19": 0.930,
"20": 2_697_000.00,
"21": 251_334.28,
"22": 136_045_317.28,
"23": 136_045_317.28,
"26.2": 0.00,
"26.3": 0.00,
"26.4": 0.00,
}
TABLE_5_6_1 = {
"Global": {
**_COMMON,
"2": 0.03,
"3": 4_500_000.00,
"4": 145_500_000.00,
"9": 145_275_000.00,
"24": 145_275_000.00,
"25": 9_229_682.72,
"26": 9_229_682.72,
"26.1": 9_229_682.72,
"27": 184_593.65,
"28": 9_045_089.07,
},
"Professional": {
**_COMMON,
"2": 0.00,
"3": 0.00, # printed "N/A"
"4": 150_000_000.00,
"9": 149_775_000.00,
"24": 149_775_000.00,
"25": 13_729_682.72,
"26": 8_237_809.63,
"26.1": 8_237_809.63,
"27": 164_756.19,
"28": 8_073_053.44,
},
}
# ── Table 5.5.1: Calculation of Total Monies Owed (Professional, TCC) ─────
TABLE_5_5_1 = {
"1": 6_456_540.00, # Provisional Settlement Shared Savings (Losses)
"2": 8_073_053.43, # Final Settlement Shared Savings (Losses)
"3": 1_616_513.43, # Shared Savings (Losses) Owed
"4": 160_700.00, # Under (Over) Payments from Payment Arrangements
"4.capitation": 160_700.00,
"5": 100_000.00, # High-Performers Pool Incentive
"6": 260_700.00, # Adjustments Owed
"7": 1_877_213.43, # Total Monies Owed
}
# ── Box 5.3.3: Stop-Loss charge and payout illustration ───────────────────
STOP_LOSS_CHARGE = {
"1": 946.97, # Average Reference Year Expenditure PBPM, Trended
"2": 132_000, # ACO-Aligned Beneficiary Months in PY
"3": 1.160, # ACO Average Risk Score in PY
"4": 145_000_046.40, # Total Trended, Risk-Adjusted Reference Expenditure
"5": 0.0203, # 3-Year Average Payout Percentage, printed 2.03%
"RY": (0.0196, 0.0209, 0.0205),
"6": 2_948_334.28, # PY Stop-Loss Charge
}
STOP_LOSS_PAYOUT = {
"attachment_point": 150_000.00,
"predicted": 100_000.00,
"actual": 500_000.00,
"residual": 400_000.00,
"below_attachment_point": 150_000.00,
"band_1_exposure": 150_000.00,
"band_2_exposure": 100_000.00,
"total_payout": 220_000.00,
"band_1_payout": 120_000.00,
"band_2_payout": 100_000.00,
}
# ── Table 3.3.3: Historical Benchmark Calculation (Newly Entering, A&D) ───
TABLE_3_3_3 = {
# base year: (payments, months, risk, line 3, line 5, trend, line 7, weight, line 9)
"BY1": (87_856_003.26, 91_366, 1.122, 961.58, 857.03, 1.104, 946.16, 0.100, 94.62),
"BY2": (93_375_409.42, 94_577, 1.115, 987.30, 885.47, 1.076, 952.76, 0.300, 285.83),
"BY3": (
106_794_359.82,
104_671,
1.087,
1_020.29,
938.63,
1.000,
938.63,
0.600,
563.18,
),
"historical_benchmark": 943.62,
}
# ── Table 3.4.2 / Tables 3.4.3.1–3.4.3.4 ──────────────────────────────────
# category: (regional FFS PBPM, Historical Benchmark PBPM, BY 3 weight,
# uncapped REA, USPCC, PY risk score, REA cap, applied REA,
# Adjusted Historical Benchmark)
REA_TABLES = {
"A&D": (1_050, 1_000, 0.80, 25, 1_000, 1.02, 51, 25, 1_025),
"ESRD": (8_000, 8_500, 0.05, 0, 8_000, 0.99, 396, 0, 8_500),
"High Needs": (3_700, 3_200, 0.15, 250, 3_500, 1.03, 180.25, 180.25, 3_380.25),
}
TABLE_3_4_2_DIFFERENCE = 90.0
# ── Table 3.5.3.2: Hybrid Alignment Benchmark Adjustment ──────────────────
TABLE_3_5_3_2 = {
"1": 1_125.00,
"2": 60_000,
"3": 4_500,
"4": 1.0099,
"5": 1_136.14,
"6": 67_500_000.00,
"7": 5_112_618.75,
"8": 72_612_618.75,
}
# ── Table 3.5.4: Combined Benchmark Calculation ───────────────────────────
# category: (adjusted HB PBPM, trend, PY risk, full months, hybrid months,
# hybrid factor, line 8)
TABLE_3_5_4_CLAIMS = {
"ESRD": (10_175.00, 1.034, 1.007, 981, 120, 1.0099, 11_677_237.29),
"A&D": (796.00, 1.042, 1.045, 56_677, 11_570, 1.0085, 59_238_767.92),
"High Needs": (3_739.00, 1.037, 1.020, 9_806, 2_864, 1.0111, 50_234_182.06),
}
# category: (adjusted HB PBPM, trend, PY risk, full+Q1 months,
# (Q2, Q3, Q4 months), (Q2, Q3, Q4 factors), line 22)
TABLE_3_5_4_VOLUNTARY = {
"ESRD": (
9_543.00,
1.034,
0.990,
421,
(76, 62, 51),
(1.0099, 1.0236, 1.0352),
5_998_140.95,
),
"A&D": (
716.00,
1.042,
0.866,
5_514,
(825, 753, 538),
(1.0085, 1.0247, 1.0431),
4_961_259.64,
),
"High Needs": (
4_109.00,
1.037,
1.061,
2_393,
(734, 549, 240),
(1.0111, 1.0233, 1.0527),
17_855_909.55,
),
}
TABLE_3_5_4_TOTALS = {
"8": 121_150_187.27,
"22": 28_815_310.14,
"24": {
"ESRD": 17_675_378.24,
"A&D": 64_200_027.56,
"High Needs": 68_090_091.61,
"TOTAL": 149_965_497.41,
},
}

View File

@@ -0,0 +1,412 @@
"""lead.benchmark reproduces the §3 worked examples: Box 3.3.2, Table 3.3.3,
Table 3.4.2, Tables 3.4.3.1–3.4.3.9, §3.5.1–3.5.2, Table 3.5.3.2 and
Table 3.5.4."""
from __future__ import annotations
import pytest
from lead.benchmark import (
CAPPED,
CAPPED_AT_UPPER,
FLOORED,
RAISED_TO_LOWER,
WITHIN_CAP,
WITHIN_GUARDRAIL,
base_year_alignment_minimum,
base_year_is_sufficient,
base_year_weights,
benchmark_adjustment_cap,
benchmark_adjustment_cap_rate,
benchmark_dollars,
capped_risk_score,
contributes_to_voluntary_benchmark,
cumulative_acpt,
expenditure_pbpm,
growth_rate,
historical_benchmark,
hybrid_adjustment_factor,
month_weighted_average,
national_trend_weight,
performance_year_benchmark_pbpm,
prior_savings_adjustment,
prior_savings_cap,
prior_savings_pbpm,
ratio,
regional_efficiency_adjustment,
regional_efficiency_eligible,
regional_ffs_expenditure,
risk_standardize,
spending_designation,
three_way_blended_trend,
total_benchmark,
two_way_blended_trend,
voluntary_benchmark_minimum_met,
voluntary_guardrail,
)
from lead.money import cents
from tests.lead.paper import (
REA_TABLES,
TABLE_3_3_3,
TABLE_3_4_2_DIFFERENCE,
TABLE_3_5_3_2,
TABLE_3_5_4_CLAIMS,
TABLE_3_5_4_TOTALS,
TABLE_3_5_4_VOLUNTARY,
)
# ── Box 3.3.2 and Table 3.3.3 ─────────────────────────────────────────────
def test_box_3_3_2_national_regional_blended_trend():
national = growth_rate(1_120, 1_000)
regional = growth_rate(1_155, 1_050)
weight = national_trend_weight([(20_000, 100_000), (5_000, 50_000)])
assert (national, round(regional, 3), round(weight, 6)) == (1.12, 1.1, 0.18)
trend = two_way_blended_trend(national, regional, weight)
assert round(trend, 3) == 1.104 # printed 1.104; the text's "1.103" is a slip
def test_trend_inputs_without_positive_denominators_are_missing():
assert growth_rate(0, 1_000) is None
assert growth_rate(1_000, 0) is None
assert national_trend_weight([]) is None
assert national_trend_weight([(10, 0)]) is None
assert two_way_blended_trend(None, 1.1, 0.2) is None
assert ratio(None, 1.0) is None
@pytest.mark.parametrize("year", ["BY1", "BY2", "BY3"])
def test_table_3_3_3_lines_3_to_9(year):
payments, months, risk, line3, line5, trend, line7, weight, line9 = TABLE_3_3_3[
year
]
pbpm = expenditure_pbpm(payments, months)
standardized = risk_standardize(pbpm, risk)
# PBPMs are printed to the cent but carried unrounded: line 5 is
# $857.03 from the unrounded line 3, $857.02 from the printed $961.58.
assert cents(pbpm) == line3
assert cents(standardized) == line5
assert cents(standardized * trend) == line7
assert cents(standardized * trend * weight) == line9
def test_table_3_3_3_historical_benchmark():
years = ("BY1", "BY2", "BY3")
standardized = {
y: risk_standardize(expenditure_pbpm(*TABLE_3_3_3[y][:2]), TABLE_3_3_3[y][2])
for y in years
}
trends = {y: TABLE_3_3_3[y][5] for y in years}
weights = base_year_weights("Newly Entering", years)
benchmark = historical_benchmark(standardized, trends, weights)
assert cents(benchmark) == TABLE_3_3_3["historical_benchmark"]
# The printed line 9 cents sum to 943.63: the total is unrounded too.
assert sum(TABLE_3_3_3[y][8] for y in years) == pytest.approx(943.63)
def test_historical_benchmark_needs_every_weighted_year():
assert historical_benchmark({"BY3": 900.0}, {"BY3": 1.0}, {}) is None
assert historical_benchmark({}, {"BY3": 1.0}, {"BY3": 1.0}) is None
assert historical_benchmark({"BY3": 900.0}, {"BY3": 1.0}, {"BY3": 1.0}) == 900.0
def test_risk_standardize_needs_a_positive_score():
assert risk_standardize(1_000.0, 0.0) is None
assert expenditure_pbpm(1_000.0, 0) is None
# ── §3.3.3–3.3.4 base-year weights and sufficiency ────────────────────────
@pytest.mark.parametrize(
"entry,years,weights",
[
("Newly Entering", ("BY1", "BY2", "BY3"), {"BY1": 0.1, "BY2": 0.3, "BY3": 0.6}),
("Renewing", ("BY1", "BY2", "BY3"), {"BY1": 1 / 3, "BY2": 1 / 3, "BY3": 1 / 3}),
("Newly Entering", ("BY1", "BY3"), {"BY1": 1 / 3, "BY3": 2 / 3}),
("Renewing", ("BY2", "BY3"), {"BY2": 0.5, "BY3": 0.5}),
("Renewing", ("BY2",), {"BY2": 1.0}),
("Newly Entering", (), {}),
],
)
def test_base_year_weights(entry, years, weights):
assert base_year_weights(entry, years) == weights
def test_base_year_weights_refuse_unknown_labels():
with pytest.raises(ValueError, match="unknown entry type"):
base_year_weights("New", ("BY3",))
with pytest.raises(ValueError, match="unknown base years"):
base_year_weights("Renewing", ("BY4",))
def test_table_2_6_1_base_year_minimums():
assert base_year_alignment_minimum("standard", 2027) == 3_000
assert base_year_alignment_minimum("newly_entering", 2029) == 1_800
assert base_year_alignment_minimum("high_needs_esrd", 2030) == 825
assert base_year_alignment_minimum("high_needs_esrd", 2036) == 1_000
assert base_year_is_sufficient(600, "newly_entering", 2027)
assert not base_year_is_sufficient(599, "newly_entering", 2027)
# ── §3.2.5 voluntary benchmarks ───────────────────────────────────────────
@pytest.mark.parametrize(
"voluntary,expected",
[
(1_150.0, (1_100.0, CAPPED_AT_UPPER)),
(850.0, (900.0, RAISED_TO_LOWER)),
(1_050.0, (1_050.0, WITHIN_GUARDRAIL)),
],
)
def test_voluntary_guardrail_example(voluntary, expected):
benchmark, status = voluntary_guardrail(voluntary, 1_000.0)
assert (round(benchmark, 6), status) == expected
def test_voluntary_contribution_and_minimum():
assert contributes_to_voluntary_benchmark("A&D", None)
assert contributes_to_voluntary_benchmark("High Needs", "High Needs")
assert not contributes_to_voluntary_benchmark("High Needs", "A&D")
assert not contributes_to_voluntary_benchmark("High Needs", None)
assert voluntary_benchmark_minimum_met(500)
assert not voluntary_benchmark_minimum_met(499)
# ── §3.4.1–3.4.2 regional FFS and spending designation ────────────────────
def test_regional_ffs_expenditure_weights_risk_standardized_counties():
# County A: $1,100 / 1.10 = $1,000 at 75%; county B: $1,200 / 1.00 at 25%.
assert regional_ffs_expenditure(
[(1_100.0, 1.10, 750.0), (1_200.0, 1.0, 250.0)]
) == pytest.approx(1_050.0)
assert regional_ffs_expenditure([(1_100.0, None, 750.0)]) is None
assert regional_ffs_expenditure([(1_100.0, 1.1, 0.0)]) is None
def test_table_3_4_2_spending_designation():
rows = [(r[0], r[1], r[2]) for r in REA_TABLES.values()]
result = spending_designation(rows)
assert result.weighted_difference == pytest.approx(TABLE_3_4_2_DIFFERENCE)
assert result.designation == "Lower-spending"
assert (
spending_designation([(900.0, 1_000.0, 1.0)]).designation == "Higher-spending"
)
# The paper names no tie.
assert spending_designation([(1_000.0, 1_000.0, 1.0)]).designation is None
with pytest.raises(ValueError, match="BY 3 aligned months"):
spending_designation([(1_000.0, 900.0, 0.0)])
# ── §3.4.3 Regional Efficiency Adjustment ─────────────────────────────────
@pytest.mark.parametrize("category", list(REA_TABLES))
def test_tables_3_4_3_1_to_3_4_3_4(category):
regional, historical, _, uncapped, uspcc, risk, cap, applied, adjusted = REA_TABLES[
category
]
rea = regional_efficiency_adjustment(
regional,
historical,
eligible=regional_efficiency_eligible("Lower-spending", "Global"),
uspcc_amount=uspcc,
py_average_risk_score=risk,
cap_rate=benchmark_adjustment_cap_rate(reduced=False),
)
assert rea.uncapped == uncapped
assert cents(rea.cap) == cap
assert cents(rea.applied) == applied
assert cents(historical + rea.applied) == adjusted
def test_rea_eligibility_and_missing_cap_inputs():
assert not regional_efficiency_eligible("Higher-spending", "Global")
assert not regional_efficiency_eligible("Lower-spending", "Professional")
kwargs = dict(uspcc_amount=None, py_average_risk_score=1.0, cap_rate=0.05)
# A positive REA never falls back to its uncapped amount.
assert (
regional_efficiency_adjustment(1_050, 1_000, eligible=True, **kwargs).applied
is None
)
assert (
regional_efficiency_adjustment(950, 1_000, eligible=True, **kwargs).applied
== 0.0
)
assert (
regional_efficiency_adjustment(1_050, 1_000, eligible=False, **kwargs).applied
== 0.0
)
def test_reduced_cap_rate():
assert benchmark_adjustment_cap_rate(reduced=True) == 0.03
assert benchmark_adjustment_cap(1_000, 1.02, 0.03) == pytest.approx(30.6)
def test_table_3_4_3_6_weighted_rea_with_an_excluded_tin():
rea = regional_efficiency_adjustment(
3_700,
3_000,
eligible=True,
uspcc_amount=3_500,
py_average_risk_score=1.03,
cap_rate=0.05,
)
assert (rea.difference, rea.uncapped, cents(rea.applied)) == (700, 350, 180.25)
assert cents(month_weighted_average([(rea.applied, 8_000), (0.0, 2_000)])) == 144.20
# ── §3.4.4 Prior Savings Adjustment ───────────────────────────────────────
def test_table_3_4_3_7_and_example_1():
rows = [
(120_000_000, 114_250_000, 115_000),
(125_000_000, 126_200_000, 120_000),
(130_000_000, 121_250_000, 125_000),
]
pbpm = [prior_savings_pbpm(*row) for row in rows]
assert pbpm == [50.0, -10.0, 70.0]
average = sum(pbpm) / 3
assert cents(average) == 36.67
psa = prior_savings_adjustment(average, 120_000, 240_000, cap=None)
assert psa.eligible and psa.proration_factor == 0.5
assert cents(psa.prorated_average_savings) == 18.33
assert cents(psa.uncapped) == 9.17
assert psa.applied is None # eligible, but no cap supplied
def test_table_3_4_3_8_9_examples_2_and_3():
average = month_weighted_average([(30.0, 80_000), (70.0, 20_000)])
assert average == pytest.approx(38.0)
cap = prior_savings_cap(
[(0.80, 1_000, 1.02), (0.05, 8_000, 0.99), (0.15, 3_500, 1.03)], cap_rate=0.05
)
assert cents(cap) == 87.64
psa = prior_savings_adjustment(average, 100_000, 125_000, cap)
assert psa.proration_factor == 0.8
assert cents(psa.prorated_average_savings) == 30.40
assert cents(psa.applied) == 15.20
def test_prior_savings_eligibility_and_proration_cap():
not_eligible = prior_savings_adjustment(-5.0, 100_000, 100_000, cap=50.0)
assert (not_eligible.eligible, not_eligible.applied) == (False, 0.0)
shrank = prior_savings_adjustment(40.0, 200_000, 100_000, cap=5.0)
assert shrank.proration_factor == 1.0
assert shrank.applied == 5.0 # capped
with pytest.raises(ValueError, match="PY beneficiary months"):
prior_savings_adjustment(40.0, 1, 0, cap=None)
assert prior_savings_pbpm(1.0, 0.0, 0) is None
# ── §3.5.1 three-way trend and ACPT ───────────────────────────────────────
@pytest.mark.parametrize(
"year,acpt,applied",
[
(2027, 1.070, 1.053), # raw 1.0567 capped at two-way + 0.3 points
(2027, 1.020, 1.048), # raw 1.04 raised to two-way − 0.2 points
(2027, 1.055, (2 / 3) * 1.05 + 1.055 / 3), # within the band
(2028, 1.070, 1.056), # PY 2 caps at +0.6 points
(2028, 1.065, 1.055), # within PY 2's wider band
(2032, 0.950, 1.04), # PY 6: −1.0 points
],
)
def test_three_way_blended_trend_guardrail(year, acpt, applied):
trend = three_way_blended_trend(1.05, acpt, year)
assert trend.applied == pytest.approx(applied)
assert trend.lower_bound <= trend.applied <= trend.upper_bound
def test_cumulative_acpt():
assert cumulative_acpt([0.04, 0.05]) == pytest.approx(1.092)
assert cumulative_acpt([0.04, None]) is None
assert cumulative_acpt([]) is None
# ── §3.5.2 risk adjustment and the growth cap ─────────────────────────────
def test_section_3_5_2_examples():
assert performance_year_benchmark_pbpm(1_100.0, 0.0, 1.0, 1.02) == pytest.approx(
1_122.0
)
score, status = capped_risk_score(1.000, 1.050, "A&D")
assert (score, status) == (1.03, CAPPED)
assert performance_year_benchmark_pbpm(1_100.0, 0.0, 1.0, score) == pytest.approx(
1_133.0
)
def test_risk_score_cap_by_category():
assert capped_risk_score(1.0, 0.90, "ESRD") == (0.97, FLOORED)
assert capped_risk_score(1.0, 1.035, "High Needs") == (1.035, WITHIN_CAP)
assert capped_risk_score(1.0, 1.05, "High Needs") == (1.04, CAPPED)
def test_missing_adjustment_stays_missing():
assert performance_year_benchmark_pbpm(1_000.0, None, 1.03, 1.0) is None
# ── §3.5.3 hybrid alignment and §3.5.4 combination ────────────────────────
def test_table_3_5_3_2_hybrid_alignment():
t = TABLE_3_5_3_2
assert cents(t["1"] * t["4"]) == t["5"]
dollars = benchmark_dollars(t["1"], t["2"], [(t["3"], t["4"])])
assert (dollars.full_year, dollars.hybrid, dollars.total) == (
t["6"],
t["7"],
t["8"],
)
assert hybrid_adjustment_factor(1_009.9, 1_000.0) == pytest.approx(1.0099)
assert hybrid_adjustment_factor(1.0, 0.0) is None
@pytest.mark.parametrize("category", list(TABLE_3_5_4_CLAIMS))
def test_table_3_5_4_claims_line_8(category):
pbpm, trend, risk, full, hybrid, factor, line8 = TABLE_3_5_4_CLAIMS[category]
py_pbpm = performance_year_benchmark_pbpm(pbpm, 0.0, trend, risk)
assert benchmark_dollars(py_pbpm, full, [(hybrid, factor)]).total == line8
@pytest.mark.parametrize("category", list(TABLE_3_5_4_VOLUNTARY))
def test_table_3_5_4_voluntary_line_22(category):
pbpm, trend, risk, full, months, factors, line22 = TABLE_3_5_4_VOLUNTARY[category]
py_pbpm = performance_year_benchmark_pbpm(pbpm, 0.0, trend, risk)
hybrid = list(zip(months, factors, strict=True))
assert benchmark_dollars(py_pbpm, full, hybrid).total == line22
def test_table_3_5_4_totals_sum_the_cent_rounded_cells():
claims = {c: v[-1] for c, v in TABLE_3_5_4_CLAIMS.items()}
voluntary = {c: v[-1] for c, v in TABLE_3_5_4_VOLUNTARY.items()}
assert total_benchmark(claims.values()) == TABLE_3_5_4_TOTALS["8"]
assert total_benchmark(voluntary.values()) == TABLE_3_5_4_TOTALS["22"]
line24 = {c: total_benchmark([claims[c], voluntary[c]]) for c in claims}
for category, expected in TABLE_3_5_4_TOTALS["24"].items():
if category != "TOTAL":
assert line24[category] == expected
assert total_benchmark(line24.values()) == TABLE_3_5_4_TOTALS["24"]["TOTAL"]
def test_table_3_5_4_esrd_line_24_needs_cent_rounded_cells():
# Unrounded, ESRD line 8 + line 22 = 17,675,378.2467 → .25, not the printed .24.
pbpm, trend, risk, full, hybrid, factor, _ = TABLE_3_5_4_CLAIMS["ESRD"]
claims = pbpm * trend * risk * (full + hybrid * factor)
pbpm, trend, risk, full, months, factors, _ = TABLE_3_5_4_VOLUNTARY["ESRD"]
voluntary = (
pbpm
* trend
* risk
* (full + sum(m * f for m, f in zip(months, factors, strict=True)))
)
assert cents(claims + voluntary) == 17_675_378.25
assert TABLE_3_5_4_TOTALS["24"]["ESRD"] == 17_675_378.24

View File

@@ -0,0 +1,335 @@
"""lead.express.benchmark: the §3 worked examples as frames on polars and
DuckDB, plus nulls for missing inputs."""
from __future__ import annotations
import narwhals as nw
import polars as pl
import pytest
from lead import benchmark as scalar
from lead.express import cents
from lead.express.benchmark import (
RISK_CAP_REFERENCE_MISSING,
benchmark_adjustment_cap,
benchmark_dollars,
capped_risk_score,
contributes_to_voluntary_benchmark,
county_market_share,
county_weight,
growth_rate,
month_weighted_mean,
performance_year_benchmark_pbpm,
regional_efficiency_adjustment,
regional_efficiency_eligible,
risk_growth_cap_status,
risk_standardized_pbpm,
three_way_blended_trend,
trended_weighted_pbpm,
two_way_blended_trend,
uncapped_regional_efficiency_adjustment,
voluntary_guardrail,
voluntary_guardrail_status,
)
from lead.money import cents as scalar_cents
from tests.lead.paper import (
REA_TABLES,
TABLE_3_3_3,
TABLE_3_5_4_CLAIMS,
TABLE_3_5_4_TOTALS,
TABLE_3_5_4_VOLUNTARY,
)
def test_box_3_3_2_as_a_county_frame(backend):
counties = {
"aligned": [20_000.0, 5_000.0],
"eligible": [100_000.0, 50_000.0],
"total": [25_000.0, 25_000.0],
}
rows = backend.select(
counties,
contribution=county_market_share("aligned", "eligible")
* county_weight("aligned", "total"),
)
weight = sum(r["contribution"] for r in rows)
assert weight == pytest.approx(0.18)
(trend,) = backend.select(
{
"nat_by3": [1_120.0],
"nat_by1": [1_000.0],
"reg_by3": [1_155.0],
"reg_by1": [1_050.0],
"w": [weight],
},
trend=two_way_blended_trend(
growth_rate("nat_by3", "nat_by1"), growth_rate("reg_by3", "reg_by1"), "w"
),
)
assert round(trend["trend"], 3) == 1.104
def test_growth_and_shares_are_null_without_positive_denominators(backend):
data = {"a": [0.0, 10.0, None], "b": [5.0, 0.0, 5.0]}
rows = backend.select(
data,
{"a": pl.Float64, "b": pl.Float64},
g=growth_rate("a", "b"),
s=county_market_share("a", "b"),
)
assert [(r["g"], r["s"]) for r in rows] == [(None, 0.0), (None, None), (None, None)]
def test_table_3_3_3_as_a_base_year_frame(backend):
years = ("BY1", "BY2", "BY3")
data = {
"year": list(years),
"paid": [TABLE_3_3_3[y][0] for y in years],
"months": [float(TABLE_3_3_3[y][1]) for y in years],
"risk": [TABLE_3_3_3[y][2] for y in years],
"trend": [TABLE_3_3_3[y][5] for y in years],
"weight": [TABLE_3_3_3[y][7] for y in years],
}
rs = risk_standardized_pbpm("paid", "months", "risk")
rows = backend.select(
data,
year=nw.col("year"),
line5=cents(rs),
line9=cents(trended_weighted_pbpm(rs, "trend", "weight")),
raw=trended_weighted_pbpm(rs, "trend", "weight"),
)
by_year = {r["year"]: r for r in rows}
for year in years:
assert by_year[year]["line5"] == TABLE_3_3_3[year][4]
assert by_year[year]["line9"] == TABLE_3_3_3[year][8]
assert (
scalar_cents(sum(r["raw"] for r in rows)) == TABLE_3_3_3["historical_benchmark"]
)
def test_rea_tables_as_a_category_frame(backend):
data = {
"category": list(REA_TABLES),
"regional": [float(v[0]) for v in REA_TABLES.values()],
"historical": [float(v[1]) for v in REA_TABLES.values()],
"uspcc": [float(v[4]) for v in REA_TABLES.values()],
"risk": [v[5] for v in REA_TABLES.values()],
"designation": ["Lower-spending"] * 3,
"option": ["Global"] * 3,
}
rea = regional_efficiency_adjustment(
"regional",
"historical",
eligible=regional_efficiency_eligible("designation", "option"),
uspcc_amount="uspcc",
py_average_risk_score="risk",
cap_rate=0.05,
)
rows = backend.select(
data,
uncapped=uncapped_regional_efficiency_adjustment("regional", "historical"),
cap=cents(benchmark_adjustment_cap("uspcc", "risk", 0.05)),
applied=cents(rea),
adjusted=cents(nw.col("historical") + rea),
)
for values, result in zip(REA_TABLES.values(), rows, strict=True):
assert (
result["uncapped"],
result["cap"],
result["applied"],
result["adjusted"],
) == (values[3], values[6], values[7], values[8])
def test_rea_nulls_and_zeros(backend):
data = {
"regional": [1_050.0, 1_050.0, 950.0, 1_050.0, None],
"historical": [1_000.0] * 5,
"uspcc": [None, 1_000.0, None, 1_000.0, 1_000.0],
"eligible": [True, None, True, False, True],
}
schema = {
"regional": pl.Float64,
"historical": pl.Float64,
"uspcc": pl.Float64,
"eligible": pl.Boolean,
}
rea = regional_efficiency_adjustment(
"regional",
"historical",
eligible="eligible",
uspcc_amount="uspcc",
py_average_risk_score=nw.lit(1.0),
cap_rate="cap_rate",
)
frame_rows = backend.select(
{**data, "cap_rate": [0.05] * 5}, {**schema, "cap_rate": pl.Float64}, rea=rea
)
# missing cap on a positive REA, unknown eligibility, at/above regional,
# ineligible, missing regional
assert [r["rea"] for r in frame_rows] == [None, None, 0.0, 0.0, None]
def test_three_way_trend_per_row_performance_year(backend):
data = {
"two_way": [1.05, 1.05, 1.05, 1.05],
"acpt": [1.07, 1.02, 1.07, 1.07],
"py": [2027, 2027, 2028, 2040],
}
rows = backend.select(data, t=three_way_blended_trend("two_way", "acpt", "py"))
expected = [
scalar.three_way_blended_trend(1.05, a, y).applied
for a, y in ((1.07, 2027), (1.02, 2027), (1.07, 2028))
]
assert [r["t"] for r in rows] == [*expected, None]
(literal,) = backend.select(
{"two_way": [1.05], "acpt": [1.07]},
t=three_way_blended_trend("two_way", "acpt", 2027),
)
assert literal["t"] == expected[0]
def test_capped_risk_score_and_status(backend):
data = {
"by3": [1.0, 1.0, 1.0, None, 1.0],
"py": [1.05, 0.90, 1.035, 1.2, 1.2],
"cat": ["A&D", "ESRD", "High Needs", "A&D", "Other"],
}
rows = backend.select(
data,
{"by3": pl.Float64, "py": pl.Float64, "cat": pl.String},
score=capped_risk_score("by3", "py", "cat"),
status=risk_growth_cap_status("by3", "py", "cat"),
)
expected = [
scalar.capped_risk_score(1.0, py, cat)
for py, cat in ((1.05, "A&D"), (0.90, "ESRD"), (1.035, "High Needs"))
]
assert [(r["score"], r["status"]) for r in rows[:3]] == expected
# No BY 3 reference or unknown category: no silent pass-through.
assert [(r["score"], r["status"]) for r in rows[3:]] == [
(None, RISK_CAP_REFERENCE_MISSING)
] * 2
def test_voluntary_guardrail_and_contribution(backend):
data = {
"vol": [1_150.0, 850.0, 1_050.0, 1_000.0],
"claims": [1_000.0, 1_000.0, 1_000.0, None],
}
rows = backend.select(
data,
{"vol": pl.Float64, "claims": pl.Float64},
b=voluntary_guardrail("vol", "claims"),
s=voluntary_guardrail_status("vol", "claims"),
)
expected = [
scalar.voluntary_guardrail(v, 1_000.0) for v in (1_150.0, 850.0, 1_050.0)
]
assert [(r["b"], r["s"]) for r in rows[:3]] == expected
assert (rows[3]["b"], rows[3]["s"]) == (None, None)
cats = {
"cat": ["A&D", "High Needs", "High Needs", "High Needs"],
"by3": ["A&D", "High Needs", "A&D", None],
}
flags = backend.select(
cats,
{"cat": pl.String, "by3": pl.String},
f=contributes_to_voluntary_benchmark("cat", "by3"),
)
assert [r["f"] for r in flags] == [True, True, False, False]
def test_table_3_5_4_as_a_frame(backend):
rows = []
for category, (
pbpm,
trend,
risk,
full,
hybrid,
factor,
_,
) in TABLE_3_5_4_CLAIMS.items():
rows.append(
{
"category": category,
"pbpm": pbpm,
"trend": trend,
"risk": risk,
"full": float(full),
"m2": float(hybrid),
"f2": factor,
"m3": 0.0,
"f3": 1.0,
"m4": 0.0,
"f4": 1.0,
}
)
for category, (
pbpm,
trend,
risk,
full,
months,
factors,
_,
) in TABLE_3_5_4_VOLUNTARY.items():
rows.append(
{
"category": category,
"pbpm": pbpm,
"trend": trend,
"risk": risk,
"full": float(full),
**{f"m{q}": float(m) for q, m in zip((2, 3, 4), months, strict=True)},
**{f"f{q}": f for q, f in zip((2, 3, 4), factors, strict=True)},
}
)
py_pbpm = performance_year_benchmark_pbpm("pbpm", nw.lit(0.0), "trend", "risk")
dollars = benchmark_dollars(
py_pbpm, "full", [("m2", "f2"), ("m3", "f3"), ("m4", "f4")]
)
cells = backend.select(rows, category=nw.col("category"), dollars=dollars)
expected = [v[-1] for v in TABLE_3_5_4_CLAIMS.values()] + [
v[-1] for v in TABLE_3_5_4_VOLUNTARY.values()
]
assert [c["dollars"] for c in cells] == expected
line24 = {}
for cell in cells:
line24[cell["category"]] = scalar.total_benchmark(
[line24.get(cell["category"], 0.0), cell["dollars"]]
)
assert line24 == {k: v for k, v in TABLE_3_5_4_TOTALS["24"].items() if k != "TOTAL"}
def test_benchmark_dollars_without_hybrid_months(backend):
(result,) = backend.select(
{"pbpm": [1_125.0], "months": [60_000.0]}, d=benchmark_dollars("pbpm", "months")
)
assert result["d"] == 67_500_000.0
def test_month_weighted_mean_in_a_group_by(backend):
data = {
"cat": ["A&D", "A&D", "A&D", "ESRD", "HN"],
"score": [1.0, 1.2, None, 0.9, 1.5],
"months": [10.0, 30.0, 50.0, 12.0, 0.0],
}
frame = nw.from_native(
backend.frame(
data, {"cat": pl.String, "score": pl.Float64, "months": pl.Float64}
)
)
out = (
frame.group_by("cat")
.agg(avg=month_weighted_mean("score", "months"))
.sort("cat")
)
rows = backend.rows(out.to_native())
assert [r["cat"] for r in rows] == ["A&D", "ESRD", "HN"]
assert rows[0]["avg"] == pytest.approx(
scalar.month_weighted_average([(1.0, 10), (1.2, 30), (None, 50)])
)
assert rows[1]["avg"] == pytest.approx(0.9)
assert rows[2]["avg"] is None

View File

@@ -0,0 +1,230 @@
"""lead.express.settlement settles frames of scenarios on polars and DuckDB:
Table 5.6.1 to the cent, bit-for-bit parity with the scalar engine over a
grid of scenarios, and nulls — not zeros — for missing inputs."""
from __future__ import annotations
import random
from dataclasses import asdict
import polars as pl
import pytest
from lead.express import cents
from lead.express.settlement import (
INPUT_COLUMNS,
LINE_COLUMNS,
discount_amount,
discount_rate,
settle,
)
from lead.money import cents as scalar_cents
from lead.params import DISCOUNT_RATES
from lead.settlement import SettlementInputs
from lead.settlement import settle as settle_scalar
from tests.lead.paper import TABLE_5_6_1, TABLE_5_6_1_INPUTS, TABLE_5_6_1_LINES
SCHEMA = {
"py_benchmark": pl.Float64,
"risk_option": pl.String,
"spending_designation": pl.String,
"performance_year": pl.Int64,
"quality_score": pl.Float64,
"capitation_payments": pl.Float64,
"participant_provider_claims": pl.Float64,
"preferred_provider_claims": pl.Float64,
"non_aco_provider_claims": pl.Float64,
"stop_loss_elected": pl.Boolean,
"stop_loss_charge": pl.Float64,
"stop_loss_payout": pl.Float64,
"stop_loss_neutrality_factor": pl.Float64,
"retention_incentive_applies": pl.Boolean,
}
def row(**changes):
return {**TABLE_5_6_1_INPUTS, "risk_option": "Global", **changes}
def settled(backend, rows):
return backend.rows(settle(backend.frame(rows, SCHEMA)))
def test_schema_matches_the_input_model():
assert tuple(SCHEMA) == INPUT_COLUMNS
assert "shared_savings_losses_after_sequestration" in LINE_COLUMNS
def test_table_5_6_1_every_line_on_each_backend(backend):
rows = settled(
backend, [row(risk_option="Global"), row(risk_option="Professional")]
)
for result in rows:
expected = TABLE_5_6_1[result["risk_option"]]
for line, field in TABLE_5_6_1_LINES:
assert result[field] == expected[line], (result["risk_option"], line)
def scenario_grid() -> list[dict]:
"""Valid scenarios across options, designations, years, quality,
elections, and gross results from deep losses to deep savings."""
rng = random.Random(2027)
scenarios = []
for i in range(160):
benchmark = rng.choice([150_000_000.0, 48_250_731.17, 9_999_999.99])
option = rng.choice(["Global", "Professional"])
elected = rng.random() < 0.7
share = rng.choice(
[
-0.62,
-0.5,
-0.35,
-0.2,
-0.15,
-0.1,
-0.0001,
0.0,
0.07,
0.1,
0.15,
0.2,
0.35,
0.5,
0.71,
]
)
payout = rng.choice([0.0, 1_234_567.89, 2_900_000.0])
scenarios.append(
row(
py_benchmark=benchmark,
risk_option=option,
spending_designation=rng.choice(
[
"Lower-spending",
"Higher-spending",
None if option == "Professional" else "Lower-spending",
]
),
performance_year=rng.choice([2027, 2028, 2030, 2031, 2036]),
quality_score=rng.choice([0.0, 0.333, 0.95, 1.0]),
capitation_payments=round(benchmark * rng.uniform(0.0, 0.2), 2),
participant_provider_claims=round(benchmark * rng.uniform(0.0, 0.1), 2),
preferred_provider_claims=round(benchmark * rng.uniform(0.0, 0.2), 2),
non_aco_provider_claims=round(benchmark * (0.5 - share), 2),
stop_loss_elected=elected,
stop_loss_charge=round(benchmark * 0.02, 2) if elected else None,
stop_loss_payout=payout if elected else None,
stop_loss_neutrality_factor=(
rng.choice([0.93, 1.07, None])
if payout == 0.0
else rng.choice([0.93, 1.07])
)
if elected
else None,
retention_incentive_applies=i % 7 == 0,
)
)
return scenarios
def test_frame_matches_scalar_bit_for_bit(backend):
scenarios = scenario_grid()
rows = settled(backend, scenarios)
assert len(rows) == len(scenarios)
signs = {
(r["shared_savings_losses"] > 0) - (r["shared_savings_losses"] < 0)
for r in rows
}
assert signs == {1, -1} # savings and losses both exercised
for scenario, result in zip(scenarios, rows, strict=True):
expected = asdict(settle_scalar(SettlementInputs(**scenario)))
assert {k: result[k] for k in expected} == expected
def test_other_columns_pass_through(backend):
frame = backend.frame([{**row(), "aco": "A1"}], {**SCHEMA, "aco": pl.String})
assert backend.rows(settle(frame))[0]["aco"] == "A1"
def test_not_elected_rows_get_the_papers_zeros(backend):
(result,) = settled(
backend,
[
row(
stop_loss_elected=False,
stop_loss_charge=None,
stop_loss_payout=None,
stop_loss_neutrality_factor=None,
)
],
)
assert (result["stop_loss_charge"], result["stop_loss_payout"]) == (0.0, 0.0)
assert result["stop_loss_neutrality_factor"] is None
assert result["net_stop_loss_impact"] == 0.0
assert result["py_expenditure_after_stop_loss"] == result["py_expenditure"]
@pytest.mark.parametrize(
"changes,first_null",
[
({"quality_score": None}, "quality_earn_back"),
({"quality_score": 1.2}, "quality_earn_back"),
({"spending_designation": None}, "discount_rate"),
({"spending_designation": "lower-spending"}, "discount_rate"),
({"performance_year": 2037}, "discount_rate"),
({"risk_option": "global"}, "discount_rate"),
({"stop_loss_charge": None}, "net_stop_loss_impact"),
({"stop_loss_neutrality_factor": None}, "adjusted_stop_loss_payout"),
({"stop_loss_elected": None}, "adjusted_stop_loss_payout"),
({"retention_incentive_applies": None}, "retention_incentive_adjustment"),
({"non_aco_provider_claims": None}, "total_ffs_payments"),
],
)
def test_missing_inputs_null_the_chain_never_zero_it(backend, changes, first_null):
(result,) = settled(backend, [row(**changes)])
assert result[first_null] is None
assert result["shared_savings_losses_after_sequestration"] is None
assert result["sequestration_amount"] is None
def test_missing_columns_are_named(backend):
frame = backend.frame([{"py_benchmark": 1.0}])
with pytest.raises(ValueError, match="quality_score"):
settle(frame)
def test_discount_rate_lookup(backend):
data = [
{"o": "Global", "d": d, "y": y}
for d, by_year in DISCOUNT_RATES.items()
for y in by_year
] + [
{"o": "Professional", "d": None, "y": 2030},
{"o": "Professional", "d": "Lower-spending", "y": 2026},
{"o": "Global", "d": "Lower-spending", "y": 2026},
]
rates = [r["rate"] for r in backend.select(data, rate=discount_rate("o", "d", "y"))]
assert rates[:-3] == [
r for by_year in DISCOUNT_RATES.values() for r in by_year.values()
]
assert rates[-3:] == [0.0, None, None]
def test_missing_discount_rate_is_zero_in_the_na_helper(backend):
data = {"benchmark": [150_000_000.0, 150_000_000.0], "rate": [None, 0.03]}
result = backend.select(
data,
{"benchmark": pl.Float64, "rate": pl.Float64},
amount=discount_amount("benchmark", "rate"),
)
assert [r["amount"] for r in result] == [0.0, 4_500_000.0]
def test_cents_expr_matches_the_scalar_bit_for_bit(backend):
rng = random.Random(5)
values = [rng.uniform(-1e9, 1e9) for _ in range(500)]
values += [k / 1000 for k in range(-2000, 2001, 5)] # half-cent ties
values += [0.125, -0.125, 2.675, 1.005, -0.001, 0.0, None]
result = backend.select({"x": values}, {"x": pl.Float64}, y=cents("x"))
for value, got in zip(values, (r["y"] for r in result), strict=True):
assert got == (None if value is None else scalar_cents(value)), value

View File

@@ -0,0 +1,87 @@
"""lead.express.stop_loss: beneficiary-grain Stop-Loss on polars and DuckDB,
matching Box 5.3.3 and the scalar engine."""
from __future__ import annotations
import random
import polars as pl
from lead.express.stop_loss import (
band_status,
distance_to_attachment_point,
predicted_expenditure,
residual_expenditure,
stop_loss_payout,
)
from lead.stop_loss import band_status as scalar_band_status
from lead.stop_loss import stop_loss_payout as scalar_payout
from tests.lead.paper import STOP_LOSS_PAYOUT
SCHEMA = {"actual": pl.Float64, "predicted": pl.Float64, "ap": pl.Float64}
def test_box_5_3_3_illustration(backend):
data = [
{
"actual": STOP_LOSS_PAYOUT["actual"],
"predicted": STOP_LOSS_PAYOUT["predicted"],
"ap": STOP_LOSS_PAYOUT["attachment_point"],
}
]
residual = residual_expenditure("actual", "predicted")
(result,) = backend.select(
data,
SCHEMA,
residual=residual,
payout=stop_loss_payout(residual, "ap"),
status=band_status(residual, "ap"),
distance=distance_to_attachment_point("ap", residual),
)
assert result == {
"residual": 400_000.0,
"payout": 220_000.0,
"status": "band_2",
"distance": -250_000.0,
}
def test_predicted_expenditure(backend):
data = {"regional": [1_000.0], "risk": [1.2], "months": [12.0]}
(result,) = backend.select(
data, predicted=predicted_expenditure("regional", "risk", "months")
)
assert result["predicted"] == 14_400.0
def test_payout_and_band_match_the_scalar(backend):
rng = random.Random(3)
data = [
{
"actual": rng.uniform(0, 900_000),
"predicted": rng.uniform(0, 200_000),
"ap": rng.choice([150_000.0, 98_765.43]),
}
for _ in range(200)
]
data += [
{"actual": None, "predicted": 1.0, "ap": 150_000.0},
{"actual": 1.0, "predicted": 0.0, "ap": None},
]
residual = residual_expenditure("actual", "predicted")
rows = backend.select(
data,
SCHEMA,
residual=residual,
payout=stop_loss_payout(residual, "ap"),
status=band_status(residual, "ap"),
)
for inputs, result in zip(data, rows, strict=True):
if inputs["actual"] is None or inputs["ap"] is None:
assert (result["payout"], result["status"]) == (None, None)
continue
assert (
result["payout"]
== scalar_payout(result["residual"], inputs["ap"]).total_payout
)
assert result["status"] == scalar_band_status(result["residual"], inputs["ap"])

View File

@@ -0,0 +1,71 @@
"""lead.monies_owed reproduces Table 5.5.1 and keeps who-owes-whom signs."""
from __future__ import annotations
from lead.monies_owed import (
apo_adjustment,
payment_arrangement_adjustments,
total_monies_owed,
)
from lead.settlement import SettlementInputs, settle
from tests.lead.paper import TABLE_5_5_1, TABLE_5_6_1_INPUTS
def test_table_5_5_1_every_line():
# The example ACO elected TCC: no Enhanced PCC or APO (footnote 1).
arrangements = payment_arrangement_adjustments(
capitation_under_over_payment=TABLE_5_5_1["4.capitation"],
enhanced_pcc_paid=0.0,
apo_adjustment=0.0,
)
assert arrangements.capitation_under_over_payment == TABLE_5_5_1["4.capitation"]
assert arrangements.payment_arrangement_adjustments == TABLE_5_5_1["4"]
owed = total_monies_owed(
provisional_shared_savings_losses=TABLE_5_5_1["1"],
final_shared_savings_losses=TABLE_5_5_1["2"],
payment_arrangement_adjustments=arrangements.payment_arrangement_adjustments,
high_performers_pool_incentive=TABLE_5_5_1["5"],
)
assert owed.shared_savings_losses_owed == TABLE_5_5_1["3"]
assert owed.high_performers_pool_incentive == TABLE_5_5_1["5"]
assert owed.adjustments_owed == TABLE_5_5_1["6"]
assert owed.total_monies_owed == TABLE_5_5_1["7"]
def test_table_5_5_1_line_2_is_a_cent_below_table_5_6_1_line_28():
# The paper's own inconsistency: Table 5.5.1 carries the Professional
# final Shared Savings as $8,073,053.43, Table 5.6.1 line 28 as .44
# (60% × 13,729,682.72 = 8,237,809.632; less 2% = 8,073,053.439).
final = settle(SettlementInputs(**TABLE_5_6_1_INPUTS, risk_option="Professional"))
assert final.shared_savings_losses_after_sequestration == 8_073_053.44
owed = total_monies_owed(
provisional_shared_savings_losses=TABLE_5_5_1["1"],
final_shared_savings_losses=final.shared_savings_losses_after_sequestration,
payment_arrangement_adjustments=TABLE_5_5_1["4"],
high_performers_pool_incentive=TABLE_5_5_1["5"],
)
assert owed.total_monies_owed == 1_877_213.44 # Table 5.5.1 line 7: .43
def test_signs_follow_who_owes_whom():
# Overpaid capitation and the Enhanced PCC recoupment are owed to CMS.
lines = payment_arrangement_adjustments(
capitation_under_over_payment=-5_000.0,
enhanced_pcc_paid=120_000.0,
apo_adjustment=0.0,
)
assert lines.enhanced_pcc_repayment == -120_000.0
assert lines.payment_arrangement_adjustments == -125_000.0
owed = total_monies_owed(
provisional_shared_savings_losses=500_000.0,
final_shared_savings_losses=-200_000.0,
payment_arrangement_adjustments=lines.payment_arrangement_adjustments,
high_performers_pool_incentive=0.0,
)
assert owed.total_monies_owed == -825_000.0
def test_apo_reconciliation():
# Reductions above the APO payment are credited; below it, recouped.
assert apo_adjustment(110_000.0, 100_000.0) == 10_000.0
assert apo_adjustment(90_000.0, 100_000.0) == -10_000.0

View File

@@ -0,0 +1,113 @@
"""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"

View File

@@ -0,0 +1,284 @@
"""lead.settlement reproduces Table 5.6.1 line by line and keeps the
paper's edge cases: no Professional discount, the "N/A" discount, losses,
corridor boundaries, the retention incentive, and Stop-Loss elections."""
from __future__ import annotations
from dataclasses import asdict, replace
import pytest
from pydantic import ValidationError
from lead.params import DISCOUNT_RATES, RISK_CORRIDORS
from lead.settlement import (
SettlementInputs,
adjust_benchmark,
apply_discount,
benchmark_discount_rate,
corridor_exposure,
gross_savings_losses,
py_expenditure,
quality_earn_back,
quality_withhold,
retention_incentive_adjustment,
sequestration_amount,
settle,
shared_savings_losses,
)
from tests.lead.paper import TABLE_5_6_1, TABLE_5_6_1_INPUTS, TABLE_5_6_1_LINES
def table_5_6_1(risk_option: str, **changes) -> SettlementInputs:
return SettlementInputs(
**{**TABLE_5_6_1_INPUTS, "risk_option": risk_option, **changes}
)
# ── Table 5.6.1 ───────────────────────────────────────────────────────────
@pytest.mark.parametrize("risk_option", ["Global", "Professional"])
@pytest.mark.parametrize(
"line,field", TABLE_5_6_1_LINES, ids=[f"line {n}" for n, _ in TABLE_5_6_1_LINES]
)
def test_table_5_6_1_every_line_to_the_cent(risk_option, line, field):
lines = asdict(settle(table_5_6_1(risk_option)))
assert lines[field] == TABLE_5_6_1[risk_option][line]
def test_table_5_2_2_is_lines_1_to_9_of_table_5_6_1():
# Table 5.2.2 prints the same benchmark adjustments, reductions positive.
for option, rate in (("Global", 0.03), ("Professional", 0.0)):
adjusted = adjust_benchmark(150_000_000.0, rate, 0.95)
assert adjusted.benchmark_after_adjustments == TABLE_5_6_1[option]["9"]
assert adjusted.net_quality_withhold == 225_000.00
def test_table_5_3_2_py_expenditure():
expenditure = py_expenditure(10_000_000, 1_003_442, 33_435_084, 91_355_457)
assert (expenditure.total_ffs_payments, expenditure.py_expenditure) == (
125_793_983.00,
135_793_983.00,
)
def test_tables_5_4_2_and_5_4_4_corridor_lines():
global_ = shared_savings_losses(9_229_682.72, 145_275_000.0, "Global")
professional = shared_savings_losses(13_729_682.72, 149_775_000.0, "Professional")
assert global_.retained_by_corridor == (9_229_682.72, 0.0, 0.0, 0.0)
assert professional.retained_by_corridor == (8_237_809.63, 0.0, 0.0, 0.0)
assert (global_.scenario, professional.scenario) == ("savings", "savings")
# ── §5.2 benchmark adjustments ────────────────────────────────────────────
@pytest.mark.parametrize(
"designation,year,rate",
[(d, y, r) for d, by_year in DISCOUNT_RATES.items() for y, r in by_year.items()],
)
def test_table_5_2_1_discount_schedule(designation, year, rate):
assert benchmark_discount_rate("Global", designation, year) == rate
def test_discount_schedule_values():
higher = DISCOUNT_RATES["Higher-spending"]
assert [higher[y] for y in range(2027, 2033)] == [
0.0175,
0.02,
0.0225,
0.025,
0.0275,
0.03,
]
assert set(DISCOUNT_RATES["Lower-spending"].values()) == {0.03}
def test_professional_option_has_no_discount_whatever_the_designation():
for designation in ("Lower-spending", "Higher-spending", None):
assert benchmark_discount_rate("Professional", designation, 2031) == 0.0
def test_higher_spending_2027_discount_dollars():
lines = settle(table_5_6_1("Global", spending_designation="Higher-spending"))
assert (lines.discount_rate, lines.discount_amount) == (0.0175, 2_625_000.00)
assert lines.benchmark_after_adjustments == 147_150_000.00
def test_discount_rate_refuses_what_it_cannot_look_up():
with pytest.raises(ValueError, match="spending designation"):
benchmark_discount_rate("Global", None, 2027)
with pytest.raises(ValueError, match="outside the LEAD model"):
benchmark_discount_rate("Global", "Lower-spending", 2037)
with pytest.raises(ValueError, match="unknown risk option"):
benchmark_discount_rate("global", "Lower-spending", 2027)
def test_missing_discount_rate_is_the_papers_na_and_removes_nothing():
discount = apply_discount(150_000_000.0, None)
assert (discount.discount_rate, discount.discount_amount) == (0.0, 0.0)
assert discount.benchmark_after_discount == 150_000_000.0
def test_quality_withhold_is_on_the_benchmark_before_the_discount():
assert quality_withhold(150_000_000.0) == 4_500_000.00
assert quality_earn_back(4_500_000.0, 0.0) == 0.0
assert quality_earn_back(4_500_000.0, 1.0) == 4_500_000.0
with pytest.raises(ValueError, match="quality score"):
quality_earn_back(4_500_000.0, 1.2)
def test_retention_incentive_reduces_the_benchmark_by_two_percent():
assert retention_incentive_adjustment(150_000_000.0, False) == 0.0
lines = settle(table_5_6_1("Global", retention_incentive_applies=True))
assert lines.retention_incentive_adjustment == 3_000_000.00
assert lines.benchmark_after_adjustments == 145_275_000.00 - 3_000_000.00
# ── §5.4 corridors, losses, sequestration ─────────────────────────────────
B = 100_000_000.0
@pytest.mark.parametrize(
"option,share,expected",
[
# Global: 100% to 15%, 50% to 35%, 25% to 50%, 10% beyond.
("Global", 0.15, 15_000_000.00),
("Global", 0.35, 15_000_000.00 + 10_000_000.00),
("Global", 0.50, 25_000_000.00 + 3_750_000.00),
("Global", 0.60, 28_750_000.00 + 1_000_000.00),
# Professional savings: 60% to 10%, 35% to 15%, 15% to 20%, 5% beyond.
("Professional", 0.10, 6_000_000.00),
("Professional", 0.15, 6_000_000.00 + 1_750_000.00),
("Professional", 0.20, 7_750_000.00 + 750_000.00),
("Professional", 0.30, 8_500_000.00 + 500_000.00),
# Professional losses: corridor 1 shares 50%.
("Professional", -0.10, -5_000_000.00),
(
"Professional",
-0.30,
-(5_000_000.00 + 1_750_000.00 + 750_000.00 + 500_000.00),
),
("Global", -0.60, -29_750_000.00),
],
)
def test_corridor_boundaries(option, share, expected):
shared = shared_savings_losses(B * share, B, option)
assert shared.shared_savings_losses == expected
assert shared.scenario == ("savings" if share > 0 else "losses")
assert sum(shared.retained_by_corridor) == pytest.approx(expected)
def test_each_corridor_takes_only_its_band():
shared = shared_savings_losses(B * 0.60, B, "Global")
assert shared.retained_by_corridor == (
15_000_000.0,
10_000_000.0,
3_750_000.0,
1_000_000.0,
)
bands = RISK_CORRIDORS[("Global", "savings")]
assert [corridor_exposure(B * 0.2, B, c.lower, c.upper) for c in bands] == [
15_000_000.0,
5_000_000.0,
0.0,
0.0,
]
def test_unknown_risk_option_is_refused():
with pytest.raises(ValueError, match="unknown risk option"):
shared_savings_losses(1.0, B, "Hybrid")
def test_shared_losses_are_not_sequestered():
assert sequestration_amount(-1_000_000.0) == 0.0
assert sequestration_amount(0.0) == 0.0
assert sequestration_amount(9_229_682.72) == 184_593.65
def test_loss_scenario_end_to_end():
# Expenditure 20 percent above a Global benchmark after adjustments.
lines = settle(
table_5_6_1(
"Global",
non_aco_provider_claims=91_355_457.00
+ 0.20 * 145_275_000.00
+ 9_229_682.72,
)
)
assert lines.gross_savings_losses == -29_055_000.00
# 15% at 100% (21,791,250) + 5% at 50% (3,631,875).
assert lines.retained_corridor_1 == -21_791_250.00
assert lines.retained_corridor_2 == -3_631_875.00
assert lines.shared_savings_losses == -25_423_125.00
assert lines.sequestration_amount == 0.0
assert lines.shared_savings_losses_after_sequestration == -25_423_125.00
def test_break_even_shares_nothing():
assert gross_savings_losses(100.0, 100.0) == 0.0
shared = shared_savings_losses(0.0, B, "Professional")
assert (shared.scenario, shared.shared_savings_losses) == ("savings", 0.0)
# ── Stop-Loss elections and input completeness ───────────────────────────
def test_no_stop_loss_election_leaves_expenditure_unchanged():
lines = settle(
table_5_6_1(
"Global",
stop_loss_elected=False,
stop_loss_charge=None,
stop_loss_payout=None,
stop_loss_neutrality_factor=None,
)
)
assert (lines.stop_loss_charge, lines.adjusted_stop_loss_payout) == (0.0, 0.0)
assert lines.stop_loss_neutrality_factor is None
assert lines.net_stop_loss_impact == 0.0
assert (
lines.py_expenditure_after_stop_loss == lines.py_expenditure == 135_793_983.00
)
assert lines.gross_savings_losses == 145_275_000.00 - 135_793_983.00
def test_zero_payout_needs_no_neutrality_factor():
lines = settle(
table_5_6_1("Global", stop_loss_payout=0.0, stop_loss_neutrality_factor=None)
)
assert lines.adjusted_stop_loss_payout == 0.0
assert lines.net_stop_loss_impact == 2_948_334.28
@pytest.mark.parametrize(
"changes,message",
[
({"spending_designation": None}, "spending designation"),
({"performance_year": 2026}, "outside the LEAD model"),
({"stop_loss_charge": None}, "charge and payout are required"),
({"stop_loss_neutrality_factor": None}, "needs the neutrality factor"),
({"stop_loss_elected": False}, "not elected"),
({"quality_score": 1.5}, "less than or equal to 1"),
],
)
def test_incomplete_scenarios_are_refused(changes, message):
with pytest.raises(ValidationError, match=message):
table_5_6_1("Global", **changes)
def test_professional_needs_no_designation():
lines = settle(table_5_6_1("Professional", spending_designation=None))
assert (
lines.shared_savings_losses_after_sequestration
== TABLE_5_6_1["Professional"]["28"]
)
def test_settlement_lines_are_frozen():
lines = settle(table_5_6_1("Global"))
with pytest.raises(AttributeError):
lines.py_benchmark = 0.0 # type: ignore[misc]
assert replace(lines, py_benchmark=1.0).py_benchmark == 1.0

View File

@@ -0,0 +1,134 @@
"""lead.stop_loss reproduces Box 5.3.3: the charge calculation, the payout
illustration, and the application of Stop-Loss to PY expenditure."""
from __future__ import annotations
import pytest
from lead.money import cents
from lead.params import STOP_LOSS_ATTACHMENT_PAYOUT_SHARE, STOP_LOSS_BANDS
from lead.stop_loss import (
adjusted_payout,
apply_stop_loss,
average_payout_percentage,
band_status,
charge_pbpm,
neutrality_factor,
payout_percentage,
predicted_expenditure,
reference_years,
residual_expenditure,
stop_loss_charge,
stop_loss_payout,
trended_attachment_point,
without_stop_loss,
)
from tests.lead.paper import STOP_LOSS_CHARGE, STOP_LOSS_PAYOUT
def test_box_5_3_3_charge_calculation():
pct = average_payout_percentage(STOP_LOSS_CHARGE["RY"])
charge = stop_loss_charge(
STOP_LOSS_CHARGE["1"], STOP_LOSS_CHARGE["2"], STOP_LOSS_CHARGE["3"], pct
)
assert charge.trended_risk_adjusted_reference_expenditure == STOP_LOSS_CHARGE["4"]
assert round(charge.average_payout_percentage, 4) == STOP_LOSS_CHARGE["5"]
assert charge.stop_loss_charge == STOP_LOSS_CHARGE["6"]
def test_box_5_3_3_charge_uses_the_unrounded_average_payout_percentage():
# Line 5 prints 2.03%; multiplying line 4 by the printed rate misses line 6.
assert cents(STOP_LOSS_CHARGE["4"] * STOP_LOSS_CHARGE["5"]) == 2_943_500.94
assert cents(STOP_LOSS_CHARGE["4"] * STOP_LOSS_CHARGE["5"]) != STOP_LOSS_CHARGE["6"]
def test_box_5_3_3_payout_illustration():
residual = residual_expenditure(
STOP_LOSS_PAYOUT["actual"], STOP_LOSS_PAYOUT["predicted"]
)
assert residual == STOP_LOSS_PAYOUT["residual"]
payout = stop_loss_payout(residual, STOP_LOSS_PAYOUT["attachment_point"])
band_1, band_2 = payout.bands
assert payout.below_attachment_point == STOP_LOSS_PAYOUT["below_attachment_point"]
assert (band_1.exposure, band_2.exposure) == (
STOP_LOSS_PAYOUT["band_1_exposure"],
STOP_LOSS_PAYOUT["band_2_exposure"],
)
assert (band_1.payout, band_2.payout) == (
STOP_LOSS_PAYOUT["band_1_payout"],
STOP_LOSS_PAYOUT["band_2_payout"],
)
assert payout.total_payout == STOP_LOSS_PAYOUT["total_payout"]
@pytest.mark.parametrize(
"residual,status,total",
[
(-5_000.0, "below_attachment_point", 0.0),
(150_000.0, "below_attachment_point", 0.0),
(150_000.01, "band_1", 0.01),
(300_000.0, "band_1", 120_000.0),
(300_000.01, "band_2", 120_000.01),
],
)
def test_band_boundaries(residual, status, total):
assert band_status(residual, 150_000.0) == status
assert stop_loss_payout(residual, 150_000.0).total_payout == total
def test_negative_residual_is_nothing_below_the_attachment_point():
assert stop_loss_payout(-5_000.0, 150_000.0).below_attachment_point == 0.0
def test_payment_schedule_and_attachment_share():
assert [
(b.start_multiple, b.end_multiple, b.payout_rate) for b in STOP_LOSS_BANDS
] == [
(1.0, 2.0, 0.80),
(2.0, None, 1.00),
]
assert STOP_LOSS_ATTACHMENT_PAYOUT_SHARE == 0.02
def test_reference_years_leave_a_one_year_gap():
assert reference_years(2027) == (2023, 2024, 2025)
assert reference_years(2030) == (2026, 2027, 2028)
def test_predicted_expenditure_and_trended_attachment_point():
assert predicted_expenditure(1_000.0, 1.2, 12) == 14_400.00
assert trended_attachment_point(150_000.0, 1.05) == 157_500.00
def test_payout_percentages():
assert payout_percentage(196.0, 10_000.0) == 0.0196
assert payout_percentage(1.0, 0.0) is None
assert average_payout_percentage([0.02, None, 0.03]) is None
assert average_payout_percentage([]) is None
def test_charge_pbpm_and_neutrality_factor():
assert charge_pbpm(2_948_334.28, 132_000) == pytest.approx(22.3359, abs=1e-4)
assert charge_pbpm(1.0, 0) is None
assert neutrality_factor(93.0, 100.0) == 0.93
# No payouts model-wide: the ratio is undefined, not 1.0.
assert neutrality_factor(93.0, 0.0) is None
def test_application_of_stop_loss_lines_16_to_22():
applied = apply_stop_loss(135_793_983.00, 2_948_334.28, 2_900_000.00, 0.930)
assert applied.adjusted_stop_loss_payout == 2_697_000.00
assert applied.net_stop_loss_impact == 251_334.28
assert applied.py_expenditure_after_stop_loss == 136_045_317.28
def test_adjusted_payout_without_a_factor():
assert adjusted_payout(0.0, None) == 0.0
with pytest.raises(ValueError, match="neutrality factor"):
adjusted_payout(10.0, None)
def test_without_stop_loss():
applied = without_stop_loss(100.0)
assert applied.py_expenditure_after_stop_loss == 100.0
assert applied.stop_loss_neutrality_factor is None

100
tests/lead/test_timing.py Normal file
View File

@@ -0,0 +1,100 @@
"""lead.timing: the run-out, the settlement basis rule and settlement-stage
inputs."""
from __future__ import annotations
from datetime import date
import pytest
from lead.timing import (
ACTUAL_CLAIMS_BASIS,
CMS_SETTLEMENT_REPORT,
CMS_SETTLEMENT_REPORT_MISSING,
COMPUTED_STATUS,
FINAL_NOT_YET_MADE,
PARTIAL_YEAR_STATUS,
PROJECTED_BASIS,
PROJECTED_STATUS,
PROVISIONAL_NOT_YET_MADE,
SettlementBasis,
require_projection_allowed,
settlement_basis_for_run,
settlement_runout_end,
settlement_stage_input,
validate_run_mode,
)
def test_runout_ends_march_31_of_the_following_year():
assert settlement_runout_end(2027) == date(2028, 3, 31)
def test_before_the_py_a_projection_is_projected():
basis = settlement_basis_for_run("projection", date(2026, 10, 20), 2027, 0)
assert basis == SettlementBasis(
PROJECTED_BASIS, PROJECTED_STATUS, PROJECTED_STATUS, date(2026, 10, 20)
)
def test_inside_the_py_rows_are_partial_actuals_under_a_projected_headline():
basis = settlement_basis_for_run("projection", date(2027, 6, 1), 2027, 10)
assert (basis.basis, basis.headline_status, basis.actual_rows_status) == (
PROJECTED_BASIS,
PROJECTED_STATUS,
PARTIAL_YEAR_STATUS,
)
def test_after_the_runout_rows_are_actual():
for mode in ("projection", "actual"):
basis = settlement_basis_for_run(mode, date(2028, 4, 1), 2027, 10)
assert basis.basis == ACTUAL_CLAIMS_BASIS
assert basis.headline_status == basis.actual_rows_status == COMPUTED_STATUS
def test_after_the_runout_without_rows_is_still_a_projection():
assert (
settlement_basis_for_run("rewind_test", date(2028, 4, 1), 2027, 0).basis
== PROJECTED_BASIS
)
def test_rows_before_the_py_are_a_feed_error():
with pytest.raises(ValueError, match="data from the future"):
settlement_basis_for_run("projection", date(2026, 10, 20), 2027, 3)
def test_actual_mode_before_the_runout_fails_naming_the_input():
for as_of, rows in ((date(2026, 10, 20), 0), (date(2027, 6, 1), 10)):
with pytest.raises(
ValueError, match="performance_year_claims_by_provider_class"
):
settlement_basis_for_run("actual", as_of, 2027, rows)
def test_run_modes_are_validated():
assert validate_run_mode("rewind_test") == "rewind_test"
with pytest.raises(ValueError, match="run_mode must be one of"):
validate_run_mode("Projection")
require_projection_allowed("projection", "x", "y")
def test_settlement_stage_inputs_follow_the_as_of_date():
start = date(2028, 1, 1)
assert settlement_stage_input(
date(2027, 12, 31), start, None, PROVISIONAL_NOT_YET_MADE
) == (
0.0,
PROVISIONAL_NOT_YET_MADE,
)
assert settlement_stage_input(start, start, None, FINAL_NOT_YET_MADE) == (
None,
CMS_SETTLEMENT_REPORT_MISSING,
)
assert settlement_stage_input(
start, start, 6_456_540, PROVISIONAL_NOT_YET_MADE
) == (
6_456_540.0,
CMS_SETTLEMENT_REPORT,
)

13
uv.lock generated
View File

@@ -14,7 +14,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-10-20T16:53:32.422989299Z"
exclude-newer = "2026-10-20T17:26:12.492023543Z"
exclude-newer-span = "P7D"
[[package]]
@@ -4660,6 +4660,12 @@ lake = [
{ name = "pydantic" },
{ name = "sqlglot" },
]
lead = [
{ name = "duckdb" },
{ name = "httpx" },
{ name = "narwhals" },
{ name = "pydantic" },
]
llm = [
{ name = "duckdb" },
{ name = "fastapi" },
@@ -4809,6 +4815,7 @@ requires-dist = [
{ name = "narwhals", marker = "extra == 'aco'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'bcda'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'cms'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'lead'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'opps'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'pfs'", specifier = ">=2.17.0" },
{ name = "narwhals", marker = "extra == 'rec'", specifier = ">=2.17.0" },
@@ -4862,6 +4869,7 @@ requires-dist = [
{ name = "stack", extras = ["conf"], marker = "extra == 'api'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'bcda'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'bib'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'lead'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'llm'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'nlm'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'opps'" },
@@ -4872,6 +4880,7 @@ requires-dist = [
{ name = "stack", extras = ["conf"], marker = "extra == 'rec'" },
{ name = "stack", extras = ["conf"], marker = "extra == 'rex'" },
{ name = "stack", extras = ["lake"], marker = "extra == 'all'" },
{ name = "stack", extras = ["lead"], marker = "extra == 'all'" },
{ name = "stack", extras = ["llm"], marker = "extra == 'all'" },
{ name = "stack", extras = ["llm"], marker = "extra == 'cli'" },
{ name = "stack", extras = ["mail"], marker = "extra == 'cli'" },
@@ -4893,7 +4902,7 @@ requires-dist = [
{ name = "uvicorn", marker = "extra == 'llm'", specifier = ">=0.41.0" },
{ name = "xlrd", specifier = ">=2.0.2" },
]
provides-extras = ["conf", "aco", "api", "bcda", "bib", "bls", "llm", "ccw", "cli", "nlm", "mail", "cms", "opps", "pfs", "qpp", "rec", "rex", "prisma", "perf", "sem", "lake", "aws", "gcp", "azure", "all"]
provides-extras = ["conf", "aco", "api", "bcda", "bib", "bls", "llm", "ccw", "lead", "cli", "nlm", "mail", "cms", "opps", "pfs", "qpp", "rec", "rex", "prisma", "perf", "sem", "lake", "aws", "gcp", "azure", "all"]
[package.metadata.requires-dev]
dev = [