Skip to content

Charge the Part D income-related premium only to Part D enrollees - #10115

Merged
MaxGhenis merged 2 commits into
mainfrom
medicare-part-d-enrollment
Oct 12, 2026
Merged

MaxGhenis merged 2 commits into
mainfrom
medicare-part-d-enrollment

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

income_adjusted_part_d_premium_surcharge was defined_for = "medicare_enrolled", and medicare_enrolled covers Part A and/or Part B. So every Medicare enrollee above the IRMAA thresholds paid the Part D surcharge. That includes people whose drug coverage is not Part D (retiree drug subsidy plans, FEHB, TRICARE for Life, the VA) and people with no drug coverage. 42 U.S.C. 1395w-113(a)(7)(A) increases "the monthly amount of the beneficiary premium applicable under this section", and (a)(1)(A) defines that premium "for a prescription drug plan". Only Part D enrollees pay it.

Measured on the certified dataset. The run used the us-6.2.6 bundle (populace-us-2024-spm-20260915) on pe-us 2.2.1 with #10106 backported, through policyengine.py's managed microsimulation. Every figure below is a weighted MicroSeries sum. The model's Part D IRMAA payer count equals its Part B count by construction:

2026 Model payers Trustees 2026, "Beneficiaries affected" Model total Trustees "Aggregate premiums"
Part B (Table V.E3) 6.07M 6.1M $21.27B $18.8B
Part D (Table V.E4) 6.07M 5.3M $3.95B $3.1B

The Part B payer count matches. The Part D count is 14% high, because 1 in 8 Part B payers has no Part D income-related premium. The rest of both dollar gaps is a top-heavy tier mix, which comes from the dataset's AGI distribution (PolicyEngine/microcosm#958). That is a data issue and is out of scope here.

Change

  • takes_up_medicare_part_d_if_eligible (Person, bool input, default true): whether a Medicare enrollee takes up Part D. Microdata assign it, like the other take-up inputs.
  • medicare_part_d_enrolled (Person, bool): defined_for = "medicare_enrolled", adds the take-up input. 42 U.S.C. 1395w-101(a)(3)(A) limits Part D to individuals entitled to Part A or enrolled in Part B.
  • income_adjusted_part_d_premium_surcharge is now defined_for = "medicare_part_d_enrolled".

Impact: none until microdata set the input. On the certified h5, base (ae3782f4d7) and this PR give identical results. Each side ran as a frozen git archive snapshot through policyengine_us.Microsimulation, and the dataset sha256 begins 6496cc4393d4.

2026 2027
Part D surcharge, base and PR $4.0077B $4.6896B
Payers, base and PR 6.0947M 7.1990M
medicare_part_d_enrolled = medicare_enrolled 58.468M 58.800M

The data step is a Microcosm Part D take-up input (PolicyEngine/microcosm#1206), queued for Max as decision d1372. Program take-up is assigned in microdata, not at simulation time. The full diagnosis covers tiers, filing status, the AGI tail against SOI Tables 1.1 and 1.6, and prototype reweightings. It is in ~/PolicyEngine/crfb-m4a/research/irmaa-run/diagnosis/REPORT.md.

Invariants (Hypothesis, test_part_d_enrollment.py)

For every drawn set of people (ages 55–95, Medicare and Part D take-up, filing status, float32 MAGI from $0 to $1.5M, 2024–2026):

  1. The surcharge is never negative, and it is zero for anyone not enrolled in Part D.
  2. Differential: for Part D enrollees, the surcharge equals the surcharge when every Medicare enrollee takes up Part D. The gate removes charges and changes none.
  3. medicare_part_d_enrolled equals medicare_enrolled AND take-up. Part D enrollment implies Medicare enrollment, which implies eligibility.
  4. Part D take-up leaves gross_medicare_part_b_premium unchanged.

Mutation check. With the old medicare_enrolled gate restored, the property test fails on invariant 1, and so do YAML cases 4a and 4b.

Cost. One test of 25 examples, each building two small simulations on the shared model system. No extra CountryTaxBenefitSystem stays resident. Locally (macOS, not Linux CI) the call took 3.3 s, and the process peaked at 1.15 GB, mostly model import. It falls in the Rest policy group.

Tests

  • medicare_part_d_enrolled.yaml: default take-up, no take-up, not Medicare-enrolled, under 65 without disability entitlement, and SSDI entitlement under 65.
  • income_adjusted_part_d_premium_surcharge.yaml:
    • 4a: an enrollee above the threshold without Part D pays $0.
    • 4b: a 2026 joint couple in tier 3 where only one spouse has Part D. They pay [$724.80, $0], and the Part B premium stays [$6,330, $6,330].

Interactions

Plan

axiom: TheAxiomFoundation/rulespec-us#1677 queued (extended with the 1395w-113(a)(1)/(a)(7) enrollment gate and 1395w-101(a)(3)(A))

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 11, 2026 15:20
income_adjusted_part_d_premium_surcharge was defined for medicare_enrolled
(Part A and/or Part B), so every Medicare enrollee above the IRMAA
thresholds paid it, including people whose drug coverage is not Part D
(retiree drug subsidy plans, FEHB, TRICARE for Life, VA) and people with no
drug coverage. 42 U.S.C. 1395w-113(a)(7) raises the Part D beneficiary
premium, which only Part D enrollees pay.

Adds takes_up_medicare_part_d_if_eligible (input, default true) and
medicare_part_d_enrolled (Medicare enrollees who take up Part D; 42 U.S.C.
1395w-101(a)(3)(A)), and gates the surcharge on the latter. With the
default, results are unchanged until microdata supply the input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10115 at 0470a2567f36740d2c9716bedcd4e33348e27275 (squash): charge the Part D income-related premium only to Part D enrollees.

  • Adopted on 2026-10-12: the session that opened it could not be woken (Fleet ops' placement).
  • Review: independent GPT-6.1 Sol review APPROVE at this head. It found the legal gate correct (42 U.S.C. 1395w-113(a)(1) and (a)(7); 42 CFR 423.286(d)(4)), recomputed every YAML expectation, checked every reader of the surcharge and of Medicare enrollment, and matched the description's Trustees figures to Tables V.E3 and V.E4.
  • Default for the new take-up input: true. The review called the default a methodology choice. Max asked on 2026-10-12 whether true matches the model's other take-up flags; it does (all 17 takes_up_* inputs on main default to true, including takes_up_medicare_if_eligible), so the default stands.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head concluded success;
    • MERGEABLE;
    • not a draft;
    • no reviewer's latest review requests changes;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: none until microdata set Part D take-up. The description's run on the certified dataset shows identical results on base and PR for 2026 and 2027 (Part D surcharge $4.0077B and $4.6896B both sides). Microcosm's imputation of the input is approved separately (d1372).
  • CI note: the earlier run failed only on main's since-fixed person-adds ratchet; CI was re-run on current main.
  • Axiom: rulespec-us#1677, queued.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant