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.
325 lines
11 KiB
Python
325 lines
11 KiB
Python
"""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,
|
||
)
|