Skip to content

Glide the Medicare Part B premium and spending per enrollee upraters to the Trustees' long-range rates - #10657

Open
MaxGhenis wants to merge 2 commits into
mainfrom
medicare-long-range-glide
Open

MaxGhenis wants to merge 2 commits into
mainfrom
medicare-long-range-glide

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

The Medicare Part B premium and Medicare spending per enrollee upraters now glide to the 2026 Medicare Trustees' long-range rates through #10119's long_range_growth=. On main, each repeats a single projected year's growth through 2100.

#10111 adds the Part D premiums per enrollee series that #10656 also covers, and a follow-up PR will glide it once #10111 merges.

Changes

  • calibration.gov.hhs.cms.medicare_part_b_premium_per_enrollee glides after 2035 from its 2034–2035 growth of 6.529% to MEDICARE_PART_B_PER_ENROLLEE_LONG_RANGE_GROWTH. The Secretary of Health and Human Services sets the standard premium to cover about 25 percent of average expenditures for beneficiaries aged 65 and over (42 U.S.C. 1395r(a); 2026 Medicare Trustees Report, file page 97). So the premium grows with Part B spending per enrollee.
  • calibration.gov.hhs.medicare.per_capita_cost keeps growth_years=(2031, 2032) and glides after 2034 from that 5.428% rate to MEDICARE_PER_ENROLLEE_LONG_RANGE_GROWTH.
  • long_range_growth_rate rejects a NaN or infinite rate and any rate at or below −1, and extend_parameter_values rejects growth_years other than consecutive years ending by the last projected year. MappingProxyType keeps importers from changing the knots. No current caller trips a check, so no value moves.
  • The per capita comment quotes the Trustees' expectation that "the drivers of health spending will be similar for the overall health sector and for the Medicare program" (file page 176). The knot comment adds that each series starts its glide from its own last projected year, which for CMS's 2034 projections comes a year before the Trustees' 2035 start.
  • YAML comments, the SPM methodology page and the tests that asserted the constant rule describe the glide.
Series 2050 2100 2050 at the constant rate 2100 at the constant rate
Part B standard monthly premium $751.26 $4,612.46 $931.18 $21,998.04
Medicare spending per enrollee $60,994.64 $365,393.86 $70,189 $986,356

The Part B premium keeps every value through 2035 and Medicare spending per enrollee every value through 2034. Years after 2100 hold the new 2100 value.

Readers

On main, only medicare_part_b_premiums_reported reads the Part B series. medicare_gross_cost, medicare_cost and qmb_cost_sharing read Medicare spending per enrollee, and through them CBO household income and Medicare Savings Program costs move.

#10103 makes gov.hhs.medicare.part_b.base_premium follow the Part B series after 2026. Once both PRs merge, the projected Part B standard premium falls from #10103's $931.20 to $751.30 a month in 2050 and from $21,998.00 to $4,612.50 in 2100. On a trial merge of #10103's head 7cc881c with this branch, #10103's test_medicare_premium_projection.py (162 tests) and this PR's changed tests pass. The two branches conflict in uprating_extensions.py's typing import and where both add functions, and keeping both sides resolves each.

Long-run microsimulation

We age Microcosm's 2024 dataset a year at a time to 2050 and 2100 and estimate these nominal totals on main (67d4dde) and on this branch:

Total 2050, main 2050, this PR 2100, main 2100, this PR
Medicare spending (medicare_gross_cost) $4.33T $3.76T $60.90T $22.56T
Medicare Savings Program costs (msp_cost) $28.9B $25.9B $225.8B $86.0B
CBO household income $56.64T $56.07T $400.73T $362.39T
Household net income $39.13T $39.13T $231.98T $231.98T

medicare_gross_cost assigns each enrollee the year's Medicare spending per enrollee, so the total falls with that series, by 13.1% in 2050 and 63.0% in 2100. medicare_cost falls by the same dollars. Medicare Savings Program costs fall by less, 10.3% and 61.9%, because only QMB cost sharing reads the series. Their premium coverage, msp_benefit_value, reads base_premium and the Part A premiums, which keep their 2026 values on main. CBO household income falls by the change in Medicare spending, 1.0% and 9.6%, while household net income stays put, since include_health_benefits_in_net_income defaults to false. healthcare_benefit_value and household_net_income_including_health_benefits fall by the MSP change, within float32 rounding.

medicare_part_b_premiums_reported holds zero for every person on both branches: Microcosm's dataset stores reported Part B premiums under medicare_part_b_premiums, a name no policyengine-us variable defines. So none of these totals moves with the Part B series.

#10119's review items

Numbers refer to the hand-off table and the round-1 review.

Item Change
Hand-off 1, P3-3: each series starts its glide at its own last projected year The knot comment says so.
Hand-off 2: file page 176 supports the per capita rate more closely than 177 The per capita comment quotes page 176.
Hand-off 3, P3-4: the extend_parameter_values docstring #10119's last commit fixed the summary on main, and the argument docs now give the growth_years limits.
Hand-off 4, P3-5: the V.B5 tolerance comment counts two roundings The comment counts one: Table V.B3 gives enrollment in thousands.
Hand-off 5, P3-1: no test combines growth_years= with long_range_growth= test_growth_years_set_the_rate_the_glide_starts_from and test_growth_years_extension_matches_an_independent_implementation.
Hand-off 6: unchecked rates and mutable knots The checks above, with three new tests.
Hand-off 7: the II.D1 tolerance comment credits rounding for Part D's gap The comment now says the knots apply 2051–2100's demographic mix of −0.1 to 2036–2050, where Table II.D1 gives Part D −0.2.

P3-2 and P3-6 asked for edits to #10119's PR body.

Invariants

tests/core/test_long_range_health_growth.py checks these with Hypothesis and an independent np.interp/np.prod implementation:

  1. A knot year's rate equals that knot's rate.
  2. Every rate lies between the two knots around it, and the last knot's rate holds after it.
  3. The value in year y equals the last projected value times the product of (1 + rate), for synthetic series and every wired series, with and without growth_years.
  4. Raising any knot never lowers any extended value, and scaling the projected values by c scales every extended value by c.
  5. Projected years keep their values, and years after 2100 hold the 2100 value.
  6. With growth_years, the glide starts from the measured rate at the last projected year, whatever the growth after the measured pair.
  7. Every series that set_all_uprating_parameters glides appears in WIRED_SERIES.
  8. long_range_growth_rate rejects any rate that would make a growth factor zero, negative or not finite. Before it changes any value, extend_parameter_values rejects growth_years that skip a year, run backward or end after the last projected year. The knots reject assignment.

Plan

axiom: n/a: uprating extension of calibration series only (no policy rule)

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits November 7, 2026 13:22
The Secretary of Health and Human Services sets the Part B standard
premium to cover about 25 percent of average expenditures for
beneficiaries aged 65 and over (42 U.S.C. 1395r(a); 2026 Medicare
Trustees Report, file page 97), so the premium uprater glides from its
2034-2035 growth to the Trustees' long-range growth in Part B spending
per enrollee. CMS Medicare spending per enrollee keeps its 2031-2032
growth rate, which skips the end of sequestration in 2033, and glides
from 2034 to the Trustees' long-range growth in Medicare spending per
enrollee.

Tests check growth_years with long_range_growth in closed form and
against an independent implementation, start the Trustees-consistency
test from the 2034 anchor, compare the Part B premium with Table II.D1,
and scan set_all_uprating_parameters so every glided series appears in
WIRED_SERIES.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
long_range_growth_rate rejects NaN, infinite and at-or-below -1 rates,
extend_parameter_values rejects growth_years other than consecutive years
ending by the last projected year, and MappingProxyType keeps importers
from changing the knots. The per capita comment now quotes the Trustees'
file page 176 reason for applying that rate to non-Medicare series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the medicare-long-range-glide branch from 8d29632 to 189dde2 Compare November 7, 2026 18:22
@MaxGhenis
MaxGhenis marked this pull request as ready for review November 7, 2026 18:35

This branch has not been deployed

No deployments
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