Skip to content

Project Medicare premiums and IRMAA past the last announced year - #10103

Open
MaxGhenis wants to merge 29 commits into
mainfrom
medicare-part-b-premium-uprating
Open

MaxGhenis wants to merge 29 commits into
mainfrom
medicare-part-b-premium-uprating

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

On main, gov.hhs.medicare.part_b.base_premium, the Part A premiums, the Part A deductible, and the Part B and D IRMAA amounts and thresholds stop at their last CMS-announced year and carry no uprating. So every year from 2027 on holds the 2026 values: $202.90 a month for Part B, and the same IRMAA brackets. This PR projects each of them past the last announced year. CMS values in the YAML still take precedence for their own years.

What changes

Parameter Projection after the last announced year Rounding Where
Part B standard premium (part_b.base_premium) Uprated by the Trustees' projected standard premium, calibration.gov.hhs.cms.medicare_part_b_premium_per_enrollee (on main since #10085) Nearest 10 cents uprating: metadata
Part B IRMAA amounts, all five filing statuses Uprated by the same series, so each keeps its 2026 multiple of the standard premium Nearest 10 cents uprating: metadata on each bracket amount
Part A full premium Last published premium × the growth of the new Trustees standard-premium series (Table V.E1) Nearest $1, 50 cents rounded up extend_medicare_part_a_premiums
Part A reduced premium 55% of the full premium (the full premium less 45%) Nearest $1, 50 cents rounded up extend_medicare_part_a_premiums
Part A deductible Last published deductible × the growth of the new Trustees deductible series (Table V.E1) Nearest $4, a midpoint rounded up extend_medicare_part_a_deductible
IRMAA thresholds, Parts B and D Statutory dollar amounts × CPI-U for the 12 months ending August of the prior year over the base period: August 2018 for the four lower amounts, and August 2026 for the $500,000 amount from 2028 Nearest $1,000 extend_medicare_irmaa_thresholds
Part D IRMAA amounts Base beneficiary premium × (applicable percentage − base percent) / base percent, on the new Trustees base-premium series (Table V.E2). The base percent is 25.5%, then 20% from 2030 (the Trustees' expectation) Nearest 10 cents extend_medicare_part_d_irmaa_amounts

New parameters:

  • calibration.gov.hhs.cms.medicare_part_a_premium_per_enrollee, medicare_part_a_deductible and medicare_part_d_base_beneficiary_premium_per_enrollee: Trustees Tables V.E1 and V.E2 for 2015–2035. They grow at their 2034–2035 rate to 2100 and hold after that. The 2027 Part D base premium ($41.33) is also the CMS-announced value.
  • gov.hhs.medicare.part_d.base_beneficiary_premium_percent: 25.5% from 2006, and 20% from 2030.

The threshold rules follow each filing status's published 2024–2026 scales. Joint thresholds are twice the unmarried ones, and the joint top tier is 1.5 × the unmarried top. Married filing separately has two tiers, starting $1 above the unmarried threshold amount and at the top amount less that threshold.

Projected values (hand-checked)

Year Part B premium Part A full / reduced Deductible Single thresholds Part D IRMAA, single
2026 (published) $202.90 $565 / $311 $1,736 109,001 / 137,001 / 171,001 / 205,001 / 500,000 14.50 / 37.50 / 60.40 / 83.30 / 91.00
2027 $209.50 $600 / $330 $1,788 112,001 / 141,001 / 176,001 / 211,001 / 500,000 15.40 / 39.70 / 64.00 / 88.30 / 96.40
2028 $224.50 $629 / $346 $1,844 116,001 / 146,001 / 182,001 / 218,001 / 517,000 16.30 / 42.10 / 67.90 / 93.60 / 102.20
2030 $255.50 $694 / $382 $1,960 121,001 / 153,001 / 191,001 / 229,001 / 541,000 51.70 / 103.40 / 155.10 / 206.80 / 224.00
2036 $384.10 $940 / $517 $2,368 139,001 / 175,001 / 218,001 / 261,001 / 619,000 60.20 / 120.30 / 180.50 / 240.60 / 260.70

Examples:

  • 2027 single thresholds. 85,000 × 329.001 / 249.280 = 112,183, which rounds to $112,000. The 12-month CPI-U averages ending August 2026 and August 2018 come from the model's CPI-U series and its shared helper.
  • 2027 Part D tiers. 41.33 × (0.35 − 0.255) / 0.255 = 15.397, which rounds to $15.40.
  • 2030 Part D tiers. 68.93 × (0.35 − 0.20) / 0.20 = 51.70.
  • 2036 Part A premium. 900 × 900 / 862 = 939.67, which rounds to $940. The reduced premium is 0.55 × 940 = 517.
  • 2036 deductible. 2,296 × 2,296 / 2,228 = 2,366.08, which rounds to the nearest $4 at $2,368. 42 U.S.C. 1395e(b)(1) sends an amount midway between two multiples of $4 "to the next higher multiple of $4". Core's nearest uprating rounding sends it to the even multiple, so the deductible is projected in code. No year from 2015 to 2124 changes, since none lands on a midpoint.

Sources

Invariants

These are tested in policyengine_us/tests/core/test_medicare_premium_projection.py, with 165 cases, some of them Hypothesis properties:

  1. Source agreement. Projections match Trustees Tables V.E1–V.E4 for 2027–2035, to within cents for the Part B IRMAA amounts.
  2. Transcription. The new series equal the published table cells.
  3. Rounding. Every projected amount sits on its statutory or published grid. A deductible midway between two multiples of $4 goes to the higher one, shown on a small series.
  4. Proportionality. Each Part B IRMAA amount keeps its multiple of the standard premium. Each Part D amount equals the (a)(7)(B) formula. The reduced Part A premium is 55% of the full premium, rounded half up.
  5. Monotonicity. Premiums and thresholds never fall, and the tiers rise with income.
  6. Filing-status rules. Rebuilding each published scale from its unmarried amounts reproduces every status's 2024–2026 thresholds.
  7. Indexing. The CPI-U schedule reproduces CMS's published thresholds exactly through 2024, and within one $1,000 step in 2025–2026. From 2024 the model's CPI-U is seasonally adjusted, while CMS averages the published index. Each projected unmarried threshold equals its statutory amount indexed from its statutory base year. A fall in CPI-U leaves every amount unadjusted, the percentage "(if any)" in 1395r(i)(5)(A) and (C)(ii), shown on a small falling CPI-U path.
  8. Precedence. A CMS value added to the YAML replaces the projection for that year only. Each Part A premium is projected from its own last published year.
  9. Extension. The new series grow at their last Trustees rate to 2100 and then hold.

YAML cases in base_part_a_premium, base_part_b_premium, income_adjusted_part_d_premium_surcharge, medicare_part_b_premium and msp_part_b_premium_coverage cover the household effects in projected years.

Tests already on main that pinned the 2027 Part B premium at the frozen $202.90 now expect the projected $209.50 (12 × 209.50 = $2,514 a year):

  • nh_fap.yaml, nh_fap_countable_income.yaml and nh_fap_eligible.yaml (New Hampshire FAP): the 2027 premium and the countable income derived from it, for example 18,000 − 2,514 = 15,486. No band or eligibility outcome changes.
  • policyengine_us/tests/unit/test_ss_cola_default_invariance.py: the 80% IRMAA tier's 2027 premium is 12 × (209.50 + 460.80) = $8,043.60.
  • policyengine_us/tests/microsimulation/test_social_security_current_law.py: the ingress differential compares against a main that predates this projection, so it skips medicare_part_b_premium@2027. The medicare_irmaa_magi requests still pin the IRMAA ingress path. A comment says to remove the skip once REVIEWED_MAIN_SHA includes the projection.

Run one file per process at this head (c48ef5fb25): medicare_part_b_premium.yaml 22 passed, base_part_a_premium.yaml 24, base_part_b_premium.yaml 13, income_adjusted_part_d_premium_surcharge.yaml 47, msp_part_b_premium_coverage.yaml 4, nh_fap.yaml 88, nh_fap_countable_income.yaml 36, nh_fap_eligible.yaml 30, test_medicare_premium_projection.py 165, test_ss_cola_default_invariance.py 14, test_trustees_2026_economic_assumptions.py 120 and test_source_conflicts.py 103. CI's Microsimulation job runs test_social_security_current_law.py.

CI: the Pull request run at c48ef5fb25, 42641736699, is pending.

Interactions

impact: pending a comparison at the final head.
axiom: TheAxiomFoundation/rulespec-us#1677 queued, extended with a comment (IRMAA indexing and formulas, and Part B premium rounding); TheAxiomFoundation/rulespec-us#1679 queued, extended with a comment (Part A premium and deductible rules). Both await corpus re-pin and approved encoder dispatch. n/a: announced data, forecast amounts, calibration assumptions and extrapolation.

🤖 Generated with Claude Code

MaxGhenis and others added 13 commits October 11, 2026 06:48
employer_sponsored_insurance_premiums uprated by CBO employment income
(per capita after the per-capita conversion), and
pre_tax_health_insurance_premiums by CMS out-of-pocket spending per
capita, which by CMS's own definition excludes premiums. Both are shares
of employer plan premiums, so both now follow CMS NHE Table 17's
employer-sponsored insurance spending per enrollee (2013-2034, 2025-2034
projections release), extended past 2034 at the 2033-2034 growth rate.

The per-capita classifier now treats per_enrollee paths as per-person
rates, like per_capita ones, so they are neither divided by population
nor rejected as unclassified.

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

- Split the ESI tests: parameter and property checks stay in tests/core;
  the Simulation and dataset-extension checks move to
  tests/microsimulation (fewer Hypothesis examples, one vectorized
  simulation each).
- Assert the 22 published Table 17 values exactly, keeping the
  spending-over-enrollment consistency check.
- Per-person markers count only in a path's last segment, so a marker
  in an ancestor folder no longer hides a national total.
- Cite the methodology paper's file page 29 in the href, the ZIP as the
  Table 17 source, and the 2035-2100 extension endpoint.
- Describe the index as a premium-growth proxy at fixed coverage per
  policy and roughly constant employer share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
health_insurance_premiums_without_medicare_part_b and
other_health_insurance_premiums follow CMS ESI premium per enrollee, like
pre_tax_health_insurance_premiums; health_insurance_premiums follows a new
CMS direct purchase premium per enrollee series (NHE projections Table 17).
CMS out-of-pocket spending excludes premiums by definition.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No dataset fills long_term_health_insurance_premiums and no CMS series
measures long-term care insurance premiums; 26 U.S.C. 213(d)(10)(B)
indexes the deductible limits on them by the medical care CPI. Adds
gov.bls.cpi.cpi_u_medical_care: BLS annual means to 2022, CBO's February
2026 calendar-year series for 2023-2036, extended at its 2035-2036 rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds calibration.gov.hhs.cms.medicare_part_b_premium_per_enrollee: the
standard monthly premium for 2013-2026 (2026 Trustees Report Table III.C2)
and the Trustees' intermediate estimates for 2027-2035 (Table V.E2),
extended at the 2034-2035 rate. A premium with any income-related
adjustment is a fixed multiple of the standard premium (42 U.S.C.
1395r(i)(3)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core tests cover transcription, the Part B statutory components, agreement
with the model's Part B premium, Hypothesis bounds and extension rules for
each index; the microsimulation test covers all five inputs. The SPM
methodology page says which inputs follow which premium series.

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

# Conflicts:
#	docs-quarto/methodology/spm-poverty.qmd
#	policyengine_us/parameters/uprating_extensions.py
#	policyengine_us/tests/core/test_per_capita_uprating.py
#	policyengine_us/tests/core/test_uprating_series.py
#	policyengine_us/tests/policy/baseline/parameters/test_uprating_extensions.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Part B standard premium, Part A premiums and deductible, and the Part B
and Part D income-related adjustment amounts and thresholds held their 2026
values in every later year.

- Part B standard premium and Part B IRMAA amounts uprate by the Trustees'
  projected standard premium (calibration series from #10085), rounded to
  10 cents (42 U.S.C. 1395r(c)).
- Part A full and reduced premiums uprate by the Trustees' projected Part A
  premium (Table V.E1), rounded to $1; the Part A deductible gains its 2026
  value ($1,736) and uprates by the Trustees' projected deductible, rounded
  to $4 (1395e(b)(1)).
- IRMAA thresholds (Parts B and D) follow 1395r(i)(5)'s CPI-U schedule,
  computed from the statutory amounts; the $500,000 tier is indexed from
  2028.
- Part D IRMAA amounts follow 1395w-113(a)(7)(B) applied to the Trustees'
  projected base beneficiary premium, with the (a)(9) percent of 20% from
  2030; this reproduces Trustees Table V.E4 for 2027-2035.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Property tests (Hypothesis) for statutory rounding, proportionality,
monotonicity and threshold structure in every projected year; differential
tests against the 2026 Trustees Report Tables V.E1-V.E4 and CMS's published
2020-2026 IRMAA thresholds; a precedence test showing a CMS amount added to
the YAML replaces only its own year; and YAML household cases for 2027-2034.

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

Copy link
Copy Markdown
Contributor Author

Coordination note from #10112, which adds the 2015–2023 IRMAA schedules and the 2015–2020 standard Part B premium.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Heads-up from #10117, which adds the 2015–2020 Part A premiums and the 2015–2023 and 2026 Part A deductibles to the same three files.

Base automatically changed from premium-inputs-cms-uprating to main October 13, 2026 19:26
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

The branch was stacked on #10085, which has since merged to main in revised
form, so its shared lines take main's version. The Part A premium and
deductible files keep the 2015-2023 Federal Register references #10117 added
on main; this branch adds only the uprating metadata there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Part D base beneficiary premium series and percent each listed the
report twice, with hrefs that differed only by #page=. CLAUDE.md asks for
one entry per source with the cited pages in its title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMS announced the 2027 base beneficiary premium, $41.33, on July 28, 2026;
it equals the Trustees' estimate, so the value is unchanged. The 2030 jump
comes from the (a)(9) percent, which the Trustees expect at its 20 percent
floor; the statute does not itself set 20 percent. The unsubsidized Part B
premium in 1395r(i)(3)(A) adds 4 times the (a)(6) increase, and $311 is
0.08 percent above $310.75, not 0.04 percent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Uprating CMS's 2026 reduced premium ($311, which CMS rounded up from
$310.75) on its own carried that 0.08 percent excess into every projected
year, so from 2038 it sat $1 to $7 a month above the full premium less 45
percent that 42 U.S.C. 1395i-2(d)(4) sets. extend_medicare_part_a_premiums
now projects the full premium from the Trustees series and sets the reduced
premium at 55 percent of it, each rounded to the nearest dollar with 50-cent
amounts rounded up, as 1395i-2(d)(2) requires; core's nearest rounding sends
ties to even. CMS's published reduced premiums follow the same rule in
every year from 2015 to 2026, and 2027-2035 still equal Trustees Table V.E1.

Also reword the 2030 Part D base premium jump: it comes from the end of the
(a)(8) 6 percent cap, with the (a)(9) percent's 20 percent floor binding.

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

MaxGhenis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Finished. The final review is posted at #10103 (comment) (VERDICT: REQUEST_CHANGES).


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
01cc340
METHODOLOGY_CHOICE: yes

The head hasn't moved since the hand-off. The projections are right: every value I computed by hand from the statutory rule matches the model. But the PR leaves three CI jobs red. Tests already on main pin the 2027 Part B premium at the frozen 2026 value, and the PR doesn't update them. The fix only touches tests (P1 below).

Findings

P1. CI fails: three suites pin the 2027 Part B premium at $202.90 a month

The Pull request run at this head (run 41719868265) ends failure. Three jobs fail: Baseline (states-shard-4), Rest (Python shard 1) and Microsimulation. All three test files are already at this head, inherited from main. All 25 failures are the same change: 2027 medicare_part_b_premium becomes 12 × $209.50 = $2,514, where the test expects 12 × $202.90 = $2,434.80. The hand-off's test list didn't include these files.

(a) NH FAP YAML (states-shard-4, 8 cases). I reproduced it at this head, one file per process:

uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/<file> -c policyengine_us
nh_fap.yaml                   1 failed, 87 passed   (Case 86)
nh_fap_countable_income.yaml  6 failed, 30 passed   (Cases 28, 29, 30, 31, 32, 36)
nh_fap_eligible.yaml          1 failed, 29 passed   (Case 26)
  medicare_part_b_premium@2027: [2514.] differs from 2434.800048828125

Core stops at a case's first mismatch, so I patched scratch copies and reran until they passed. The full set of replacements is below. With them, all 88 + 36 + 30 cases pass, and no band or eligibility outcome changes:

File:line Old New
nh_fap.yaml:2839 medicare_part_b_premium: 2_434.8 2_514
nh_fap.yaml:2840 nh_fap_countable_income: 25_565.2 25_486
nh_fap_countable_income.yaml:845, 873, 899, 927 2_434.8 2_514
nh_fap_countable_income.yaml:846 15_565.2 15_486
nh_fap_countable_income.yaml:874 19_565.2 19_486
nh_fap_countable_income.yaml:928 21_565.2 21_486
nh_fap_countable_income.yaml:988 [2_434.8, 2_434.8, 0] [2_514, 2_514, 0]
nh_fap_countable_income.yaml:989 [37_130.4, 12_000] [36_972, 12_000]
nh_fap_countable_income.yaml:1143 [2_434.8, 2_434.8] [2_514, 2_514]
nh_fap_eligible.yaml:964 [0, 2_434.8] [0, 2_514]
nh_fap_eligible.yaml:965 [50_000, 47_565.2] [50_000, 47_486]

The comments that compute "202.90 * 12 = 2,434.80" need the same change: nh_fap.yaml:2812, nh_fap_countable_income.yaml:822, 905 and nh_fap_eligible.yaml:918. They should read "the projected 2027 premium of 209.50 * 12 = 2,514". For example, 18,000 − 2,514 = 15,486.

(b) policyengine_us/tests/unit/test_ss_cola_default_invariance.py:90 (Python shard 1, 11 errors). The irmaa_control fixture expects [7790.40], which is 12 × ($202.90 + $446.30). The single filer has $463,100.31 of MAGI, which falls in the 80% tier (211,001 up to 500,000 in 2027). The 2027 premium is therefore 12 × ($209.50 + $460.80) = $8,043.60.

uv run pytest -q -p no:cacheprovider policyengine_us/tests/unit/test_ss_cola_default_invariance.py
3 passed, 47 warnings, 11 errors      # at this head
14 passed, 57 warnings                # with [7790.40] -> [8043.60] (scratch edit, reverted)

Replacement: values["medicare_part_b_premium"], [8043.60], rtol=0, atol=0.01.

(c) policyengine_us/tests/microsimulation/test_social_security_current_law.py:1078 (Microsimulation, 6 failures). test_legacy_public_ingress_matches_unmodified_main compares this tree with REVIEWED_MAIN_SHA (an older main) on DOWNSTREAM outcomes, which include medicare_part_b_premium. The CI log's mismatch lines name exactly one request: medicare_part_b_premium@2027, current [2514.0] against main's [2434.8]. It appears in 6 of the routes. Any change to the 2027 premium fails this test by construction, so it needs a decision, not new numbers. One option keeps the Social Security ingress check intact; the medicare_irmaa_magi* requests still pin the IRMAA ingress path:

# The reference main predates the Medicare premium projection, so the Part B
# premium after 2026 legitimately differs; the medicare_irmaa_magi requests
# still pin the IRMAA ingress path.
CHANGED_SINCE_REFERENCE = {"medicare_part_b_premium@2027"}
...
    for request in main[route]:
        if request in CHANGED_SINCE_REFERENCE:
            continue
        np.testing.assert_array_equal(...)

The keys() equality assert stays as it is. I didn't run this file: it builds microsimulations, and this review runs none.

P2. The 2027 projections depend on CPI-U months the model doesn't have, and CMS announces 2027 soon

gov/bls/cpi/cpi_u.yaml holds monthly CPI-U through June 2026. The 12-month window ending August 2026 therefore carries June's 332.568 flat through July and August, and averages 329.00125. That window sets the 2027 thresholds, and it is also the (i)(5)(C) base for the top amount from 2028. The 2027 unmarried amounts come out 112,183 / 141,219 / 176,194 / 211,169 before rounding. The second and fourth are $281 and $331 below a rounding boundary (0.20% and 0.16%), so the observed July and August values, or CMS's not-seasonally-adjusted averaging, could move either one up a step. The model already misses CMS by one step in 2025 (166k/199k against 167k/200k) and in 2026 (170k against 171k), as the PR discloses. This is main's data, not this PR's defect. It does mean the 2027 YAML cases (medicare_part_b_premium.yaml, the 141,001 to 176,000 tier) and the 2027 numbers in the body are provisional until CMS publishes. Recent search results (secondary sites only) say CMS hasn't announced the 2027 Part B premium or the IRMAA thresholds. They usually come in mid-November.

P3. Smaller items

  1. uprating_extensions.py, the comment in get_medicare_irmaa_thresholds on the joint scale. It reads "(C)(ii): twice the unmarried amounts, but 150 percent of the $500,000 amount." From 2028 the code takes 150% of the indexed top amount (517,000 → 775,500), so the comment understates it. Replacement: # (C)(ii): twice the unmarried amounts for the calendar year, but 150 percent of the unmarried top amount for the calendar year (the $500,000 amount as adjusted under (5)(C)); (5)(B) rounds only amounts increased under (A) or (C).
  2. test_medicare_premium_projection.py, test_separate_top_threshold_falls_until_2028. It takes its range from MEDICARE_IRMAA_TOP_FIRST_INDEXED_YEAR, the constant under test. Moving the constant to 2027 removes the 2027 case instead of failing it (161 passed). The mutant is equivalent here, because the 2027 window is the base period. Writing range(2025, 2028) would still pin it.
  3. Float noise in projected amounts. Core's round(x / 0.1) * 0.1 stores values such as 83.80000000000001 and 538.8000000000001 (2027 and 2028 joint Part B amounts). This is cosmetic, but the parameter metadata API serves it. It's core behaviour, so fixing it belongs in policyengine-core.

Judgment calls

  • The joint top tier (judgment call 1). The text supports the PR. I couldn't fetch 1395r(i), so this rests on the quotations in the review brief. (3)(C)(ii) makes the joint top "150 percent of such dollar amounts for the calendar year", where "such dollar amounts" are the clause (i) amounts "for the calendar year". Those are the unmarried amounts after (5) adjustment and rounding, the same base as the lower rows' "twice". (5)(B) rounds only an amount "increased under subparagraph (A) or (C)". The 150% figure isn't increased under either: the statute has no $750,000 amount to index. So 1.5 × the rounded unmarried top ($775,500 in 2028) is the textual reading. Indexing $750,000 on its own gives a result that differs by $500 or $1,000 in most years: 2028 776,000 against 775,500, 2031 830,000 against 831,000, 2036 928,000 against 928,500. No CMS figure, form or instruction contradicts the PR's reading yet. CMS's first post-2028 joint top comes out in fall 2027. Under CONTRIBUTING's definition this is a statutory reading, not a source conflict, so it doesn't belong in the register now. If CMS publishes a whole-thousand joint top, that would be a conflict to register.
  • Ties (judgment call 5). This is a modelling default. Rounding half-down instead survives every test (162 passed) because no exact tie occurs. I scanned 2020–2124 and found none; the closest is the 2029 second amount at 149,502.87, $2.87 from a tie. My hand computations of the 2020–2026 published thresholds show no tie either (e.g. 2023: 97,469.1 / 122,696.4 / 153,083.8 / 183,471.2).
  • The Part D base percent of 20% from 2030 is the Trustees' expectation, not a statutory constant. Search-result snippets (not a fetched source) show that P.L. 117-169 §11201(d)(3)(C) inserted "(or, for 2030 and each subsequent year, the percent specified under paragraph (9))" after "25.5 percent" in 1395w-113(a)(7)(B)(i), with a 20% floor in (a)(9). That supports the code's use of the variable percent. The 2029 → 2030 jump in Part D IRMAA ($17.30 → $51.70 for the 35% tier) follows from it.
  • Part B IRMAA amounts uprated by the standard premium (keeping CMS's 2026 ratios) instead of being recomputed from (i)(3)(A). The difference is at most $0.10, as the hand-off says.

Checked and sound

  • The defect, on main (6145ad4513). I loaded the parameters in both trees for every year from 2015 to 2040, plus 2060 and 2100. On main, every year from 2027 holds the 2026 values: $202.90; 109,001 / 137,001 / 171,001 / 205,001 / 500,000; Part A $565 / $311; deductible $1,736. The PR changes no value in 2015–2026. Its first changed year is 2027.
  • Thresholds, by hand before reading the code. I computed the 12-month CPI-U averages ending in August from cpi_u.yaml instants: 2018 = 249.28017, 2025 = 319.23667, 2026 = 329.00125. Indexing $85,000 / $107,000 / $133,500 / $160,000 from the August 2018 window reproduces CMS's thresholds exactly for 2020, 2021, 2022, 2023, 2024 and 2026, and within one step in 2025. 2027 gives 112,000 / 141,000 / 176,000 / 211,000 with the top held at 500,000. 2028 gives 116,000 / 146,000 / 182,000 / 218,000 / 517,000, with top = 500,000 × 340.3 / 329.00125 = 517,171 rounding to 517,000. The model matches all of these, plus 2030 and 2036, as do the joint values (2 × amount + 1, 1.5 × top) and separate ones (lower + 1, top − lower: 388,000 in 2027, 401,000 in 2028, 391,000 in 2026 as CMS published). 2018 and 2019 carry no increase: indexing starts in 2020 and raises before that.
  • Base years. The four lower amounts index from August 2018. The top amount stays fixed through 2027 and indexes from August 2026 starting in 2028 (get_medicare_irmaa_unmarried_amounts). The new test test_projected_unmarried_thresholds_use_their_statutory_base_years rebuilds both independently.
  • Premiums. For Part D in 2027, 41.33 × (0.35 − 0.255)/0.255 = 15.40, then 39.70, 64.00, 88.30 and 96.40. In 2030, 68.93 × 0.15/0.20 = 51.70. For Part B in 2027, 81.20 × 209.50/202.90 = 83.84, rounding to 83.80, and 487 × 209.50/202.90 = 502.84, rounding to 502.80. For Part A, 2027 is 600 with 0.55 × 600 = 330, and 2028 is 629 with 0.55 × 629 = 345.95, rounding to 346. The deductible is $1,788 in 2027. The model matches every figure.
  • Tests, one file per process. test_medicare_premium_projection.py: 162 passed. YAML: base_part_a_premium 24, base_part_b_premium 7, income_adjusted_part_d_premium_surcharge 14, medicare_part_b_premium 16, msp_part_b_premium_coverage 4 and medicare_cost 6, all passed. Each YAML case's comment matches my arithmetic.
  • Mutants, each in its own process with the code restored and git status clean.
Mutant Result Killed by
Reduced share 0.55 → 0.56 11 failed test_part_a_premiums_are_the_trustees_projection
Joint top 1.5× → 2× 5 failed test_threshold_rules_reproduce_the_published_scales, test_thresholds_follow_the_filing_status_rules
Part D ignores the 2030 percent 25 failed test_part_d_irmaa_is_the_trustees_projection, test_part_d_irmaa_follows_the_statute
Drop the +1 on the "more than" thresholds 7 failed test_threshold_rules_reproduce_the_published_scales and others
Top CPI base 2026 → 2025 1 failed test_projected_unmarried_thresholds_use_their_statutory_base_years
Lower CPI base 2018 → 2019 8 failed test_cpi_schedule_reproduces_published_thresholds
Top indexed from 2027 survived (161 passed) equivalent: the 2027 window is the base period. See P3.2
Joint top indexed on its own (adjust $750,000 from August 2026 and round) 4 failed test_thresholds_follow_the_filing_status_rules (joint top = 1.5 × unmarried top). The 2024–2026 failures in test_threshold_rules_reproduce_the_published_scales are partly an artifact of how I wired the mutant
Third statutory amount 133,500 → 133,000 (mine) 1 failed test_projected_unmarried_thresholds_use_their_statutory_base_years
Ties round half-down (mine) survived (162 passed) equivalent: no exact tie occurs
Separate top = top − threshold − 1 (mine) 5 failed test_threshold_rules_reproduce_the_published_scales and others
  • Readers. base_premium and irmaa feed base_part_b_premium, gross_medicare_part_b_premium, medicare_part_b_premium and msp_part_b_premium_coverage. From there they reach medicare_cost, medical_expense_health_insurance_premiums (tax medical deductions), spm_unit_health_insurance_premiums (SPM resources), Medi-Cal countable income and 30 state-program variable files (mostly LIHEAP and energy-assistance income definitions) that read medicare_part_b_premium or medical_expense_health_insurance_premiums. The Part D IRMAA feeds income_adjusted_part_d_premium_surcharge; the Part A premiums feed base_part_a_premium and medicare_part_a_premium, which reach SPM premiums. No variable reads part_a.deductible, so its projection moves no output. The PR makes every reader more accurate from 2027 and touches nothing before 2027.
  • Baseline impact. Outputs move in every year from 2027: higher Part B and Part A premiums, and IRMAA thresholds that rise instead of staying frozen, which lowers IRMAA incidence against main. The hub's population check should cover these, 2027–2035: aggregate medicare_part_b_premium against Trustees SMI premium income, medicare_cost, SPM poverty for people 65 and over, and the medical expense deduction.
  • Body and docs. The claims check out: the projected-values table (all rows reproduced from the parameter tree), the 2027 single-threshold and Part D examples, invariants 3–8, the 162 cases and "CMS values take precedence". "Subtract the expected Part D premium from medicare_cost #10111 merges cleanly" holds: git merge-tree of this head with Subtract the expected Part D premium from medicare_cost #10111 has no conflicts, and landing them in either order is clean. On the merged tree, medicare_cost.yaml gets 12 passed, test_medicare_cost_part_d_premium.py 85 and this file 162. Subtract the expected Part D premium from medicare_cost #10111's projected-year medicare_cost cases set gross_medicare_part_b_premium and income_adjusted_part_d_premium_surcharge as inputs, as the body says. The impact: and axiom: lines are present.
  • Overlap. Subtract the expected Part D premium from medicare_cost #10111 reads base_part_a_premium, gross_medicare_part_b_premium_if_enrolled and income_adjusted_part_d_premium_surcharge, all of which this PR changes from 2027, so its medicare_cost drops further in projected years. Count Medicare net of premiums in CBO household income #10118's cbo_medicare_benefit reads only calibration.gov.hhs.medicare.* aggregates, none of them this PR's. Add 2015-2023 Medicare IRMAA brackets and 2015-2020 Part B premiums #10112 conflicts in the six Part B files and two test YAMLs, as the body says. Charge the Medicare Part B premium only to people who pay it #10634 (open, "Charge the Medicare Part B premium only to people who pay it") edits medicare_part_b_premium.yaml and test_part_b_msp_offset.py. The first is a file this PR appends to, so whichever lands second needs a mechanical merge. Glide CMS health spending upraters to the Trustees' long-range growth rates #10119 also edits uprating_extensions.py, but different series.
  • Scope. Nothing under .github/ or tests/policy/baseline/partners/. Every partner test period is 2026 or earlier, and the Household API Partners job passed.

What I couldn't check

  • Primary sources. This VM couldn't reach law.cornell.edu, govinfo.gov, uscode.house.gov, cms.gov, ssa.gov, ecfr.gov or web.archive.org: the proxy refused each one. On 1395r(i) I rely on the hub's reading quoted in the brief. I couldn't read (i)(3)(C)(iii) for the married-filing-separately scale, 1395r(a)(3) for the 10-cent rounding, 1395i-2(d) or 1395e(b). I also couldn't check the Trustees transcriptions (Tables V.E1–V.E4, footnote 63) or the CMS fact sheet giving the 2027 base beneficiary premium of $41.33. The code's separate-scale rule does reproduce CMS's 2024–2026 published separate thresholds.
  • Whether CMS has published the 2027 Part A/B amounts. Secondary sources found by web search say it hasn't. I couldn't open cms.gov to confirm.
  • The Microsimulation fix in P1(c), which I didn't run, and the population-level impact.
  • The # 2028 value. label on cpi_u.yaml's 2027-02-01: 340.3 (main's file, not this PR's). The helper treats that point as the calendar-2027 average. If the label is right and it's CBO's 2028 figure, every projected window from 2028 reads a year ahead. The observed 2026 levels make 340.3 look like calendar 2027, so the label is probably stale. Proposed follow-up: "Correct the year labels on CPI-U projection points". Confirm which CBO calendar year each February point holds, and fix the comments or the instants.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

The 2027 Part B premium is now projected at $209.50 a month instead of
the frozen 2026 $202.90, so the three NH FAP suites that pinned
12 x 202.90 = 2,434.80 failed. Pin 12 x 209.50 = 2,514 and recompute
the countable income each case derives from it. No band or eligibility
outcome changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Kdz5jWLbAMmyYu3CF
The COLA invariance fixture now expects 12 x (209.50 + 460.80) = 8,043.60
for the 80% IRMAA tier in 2027. The Social Security ingress differential
compares against a main that predates the premium projection, so skip
medicare_part_b_premium@2027 there; the IRMAA MAGI requests still pin the
ingress path, and the route keys are still compared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Kdz5jWLbAMmyYu3CF
The joint top is 150 percent of the indexed unmarried top, not of the
fixed $500,000. The separate-top test took its range from the constant
under test, so moving that constant silently dropped 2027; use a fixed
range(2025, 2028).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Kdz5jWLbAMmyYu3CF
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
3683146

Round 1 fix round: the P1 from the review, plus P3-1 and P3-2. The branch hadn't moved since 01cc340a38, and no one else pushed while I worked.

Commits

  • d96dce73c2 Merge origin/main (0315452583) into the branch. No conflicts. (The merge message names my local branch, not this PR's branch. The tree is the same either way.)
  • a5bf45568b Update New Hampshire FAP tests for the projected 2027 Part B premium
  • 5b4dbd0aed Update Python tests that pin the 2027 Part B premium
  • 368314616c Clarify the joint IRMAA top comment and pin 2027 in the top test

Items

  1. Pick-up, merge and search. I merged origin/main with no conflicts. Then I grepped policyengine_us/tests for 2_434.8|2434.8|7790.4|202.90|202.9 and for the IRMAA total 7_790, and read the period of every case containing a hit:

    • 2027 or later, changed: only the three NH FAP files and test_ss_cola_default_invariance.py:90, the same set as the review.
    • 2027 or later, already right: medicare_part_b_premium.yaml:235 and :254. These are this PR's own projection comments (× 209.50 / 202.90, × 338.50 / 202.90), and they're correct.
    • 2026 or earlier, left alone: healthcare_benefit_value.yaml, household_health_benefits.yaml, federal_and_state_benefit_cost.yaml, spm_unit_medical_out_of_pocket_expenses_medicare_part_b.yaml, the Medicaid optional senior/disabled files, the PA, OK and AZ LIHEAP files, medicare_part_b_premium.yaml:179 (Case 12, 2026 separate filer, 7_790.4), and tests/core/test_premium_input_uprating.py:325 (the Trustees' published series, which has 202.9 in 2026 and 209.5 in 2027). My period heuristic first flagged several LIHEAP comments as 2027. I read each one, and every case is period: 2026.
    • Partner file: partners/.../msp/federal.yaml mentions 202.90 only in a header comment, and all its cases are 2025 or 2026. I didn't touch it, and no partner case moves.
  2. P1(a): NH FAP. The review's line numbers still matched after the merge. I changed every line in its table:

    • nh_fap.yaml:2839-2840
    • nh_fap_countable_income.yaml:845-846, 873-874, 899, 927-928, 988-989, 1143
    • nh_fap_eligible.yaml:964-965

    The four comments now read "the projected 2027 premium of 209.50 * 12 = 2,514": nh_fap.yaml:2812, nh_fap_countable_income.yaml:822, 905 and nh_fap_eligible.yaml:918. I also fixed the arithmetic that follows each of them, plus some derived comments the review didn't list:

    • nh_fap.yaml:2813: 28,000 − 2,514 = 25,486, which is 159.7% of 15,960 (was 160.2%). That's still above 150% and within 175%, so it stays in band E.
    • nh_fap_countable_income.yaml:850 (Case 29): 24,000 − 2,000 − 2,514 = 19,486.
    • :876 (Case 30): it still floors at 0.
    • :933 (Case 32): 42,000 − 2 × 2,514 = 36,972.
    • :1095-1096 (Case 36): min(2,514, 1,200), so it stays 30,000.
    • nh_fap_eligible.yaml:919: 50,000 − 2,514 = 47,486, still within 48,713.
  3. P1(b). test_ss_cola_default_invariance.py:90 now expects [8043.60] with rtol=0, atol=0.01. That's 12 × (209.50 + 460.80) = 12 × 670.30 = 8,043.60. No comment in the file showed the old arithmetic.

  4. P1(c). test_social_security_current_law.py:65-69 adds CHANGED_SINCE_REFERENCE = {"medicare_part_b_premium@2027"} with the brief's comment. Keys are built as f"{variable}@{period}" (:121). Lines :1086 and :1104 skip those requests in the mismatch printout loop and in the assertion loop. The keys() assertion stays. I didn't run this file, because it builds microsimulations; CI's Microsimulation job runs it.

  5. P3-1. uprating_extensions.py:871-874 (get_medicare_irmaa_thresholds) replaces the two-line comment with the brief's four lines, at the same indentation.

  6. P3-2. test_medicare_premium_projection.py:417 now uses range(2025, 2028), which gives three cases (2025, 2026 and 2027). MEDICARE_IRMAA_TOP_FIRST_INDEXED_YEAR is still imported and used at :409 and :482.

  7. Body. I added a paragraph under the YAML-cases line naming the three test files, what changed in each, and the run counts. The readback matches what I sent apart from a trailing newline. Two stale points I left for the hub, because my changes didn't cause them:

    I left the impact: and axiom: lines as they were. The CI sentence is below.

Tests at 368314616c, one file per process

File Result
nh_fap.yaml 88 passed
nh_fap_countable_income.yaml 36 passed
nh_fap_eligible.yaml 30 passed
medicare_part_b_premium.yaml 16 passed
test_ss_cola_default_invariance.py 14 passed
test_medicare_premium_projection.py 162 passed

The first attempt at the two Python files failed to collect because hypothesis wasn't installed. I ran uv sync --extra dev and reran both. That was a local environment gap, not a test failure.

Mutant

I set MEDICARE_IRMAA_TOP_FIRST_INDEXED_YEAR = 2027 in its own process, then restored the file and confirmed git status was clean. The result was 162 passed, so the mutant survived. That's expected: the review's point is that the mutant is equivalent, because the August 2026 window is the base period, so the 2027 top is 500,000 either way. Before this change, the same mutant gave 161 passed, because it removed the 2027 case. Now the count stays 162: test_separate_top_threshold_falls_until_2028[2027] still runs and passes, so 2027 stays checked.

CI

Pull request run 41958440318 passed all 36 jobs at 368314616c695c6fc2839e7e0420596ad3345483. The run concluded success. Python tests / Full Suite - Microsimulation: success, so the CHANGED_SINCE_REFERENCE skip resolves P1(c). Baseline (states-shard-4) and Rest (Python shard 1), which failed at 01cc340a38, also pass. I added this sentence to the body, after the test-run paragraph.

Every job's conclusion:

  • PR validation (lock, guard tests, lint, changelog): success
  • Quick Feedback (Selective Tests + Coverage): success
  • Household API Partners: success
  • Package and compatibility: success
  • Python tests / Full Suite - Microsimulation: success
  • Baseline tests / Full Suite - Baseline (contrib-hhs): success
  • Baseline tests / Full Suite - Baseline (ssa-usda): success
  • Baseline tests / Full Suite - Baseline (states-shard-2): success
  • Python tests / Plan Rest Python shards: success
  • Baseline tests / Full Suite - Baseline (rest-b): success
  • Baseline tests / Full Suite - Baseline (irs): success
  • Baseline tests / Full Suite - Baseline (reform): success
  • Baseline tests / Full Suite - Baseline (states-shard-1): success
  • Baseline tests / Full Suite - Baseline (states-shard-4): success
  • Baseline tests / Full Suite - Baseline (rest-a): success
  • Baseline tests / Full Suite - Baseline (household): success
  • Baseline tests / Full Suite - Baseline (states-shard-3): success
  • Python tests / Full Suite - Rest (Python shard 7): success
  • Python tests / Full Suite - Rest (Python shard 8): success
  • Python tests / Full Suite - Rest (Python shard 9): success
  • Python tests / Full Suite - Rest (Python shard 5): success
  • Python tests / Full Suite - Rest (Python shard 6): success
  • Python tests / Full Suite - Rest (Python shard 1): success
  • Python tests / Full Suite - Rest (Python shard 2): success
  • Python tests / Full Suite - Rest (Python shard 4): success
  • Python tests / Full Suite - Rest (Python shard 3): success
  • Python tests / Check Rest Python shards: success
  • Contrib tests / Full Suite - Contrib (other-shard-1): success
  • Contrib tests / Full Suite - Contrib (states-shard-3): success
  • Contrib tests / Full Suite - Contrib (states-shard-2): success
  • Contrib tests / Full Suite - Contrib (congress): success
  • Contrib tests / Full Suite - Contrib (other-shard-2a): success
  • Contrib tests / Full Suite - Contrib (other-shard-3): success
  • Contrib tests / Full Suite - Contrib (states-shard-4): success
  • Contrib tests / Full Suite - Contrib (other-shard-2b): success
  • Contrib tests / Full Suite - Contrib (states-shard-1): success

Body readback (after the final PATCH, identical to what I sent)

Full body
Draft. On `main`, `gov.hhs.medicare.part_b.base_premium`, the Part A premiums, the Part A deductible, and the Part B and D IRMAA amounts and thresholds stop at their last CMS-announced year and carry no uprating. So every year from 2027 on holds the 2026 values: $202.90 a month for Part B, and the same IRMAA brackets. This PR projects each of them past the last announced year. CMS values in the YAML still take precedence for their own years.

## What changes

| Parameter | Projection after the last announced year | Rounding | Where |
|---|---|---|---|
| Part B standard premium (`part_b.base_premium`) | Uprated by the Trustees' projected standard premium, `calibration.gov.hhs.cms.medicare_part_b_premium_per_enrollee` (on `main` since #10085) | Nearest 10 cents | `uprating:` metadata |
| Part B IRMAA amounts, all five filing statuses | Uprated by the same series, so each keeps its 2026 multiple of the standard premium | Nearest 10 cents | `uprating:` metadata on each bracket amount |
| Part A full premium | Last published premium × the growth of the new Trustees standard-premium series (Table V.E1) | Nearest $1, 50 cents rounded up | `extend_medicare_part_a_premiums` |
| Part A reduced premium | 55% of the full premium (the full premium less 45%) | Nearest $1, 50 cents rounded up | `extend_medicare_part_a_premiums` |
| Part A deductible | Uprated by the new Trustees deductible series (Table V.E1) | Nearest $4 | `uprating:` metadata |
| IRMAA thresholds, Parts B and D | Statutory dollar amounts × CPI-U for the 12 months ending August of the prior year over the base period: August 2018 for the four lower amounts, and August 2026 for the $500,000 amount from 2028 | Nearest $1,000 | `extend_medicare_irmaa_thresholds` |
| Part D IRMAA amounts | Base beneficiary premium × (applicable percentage − base percent) / base percent, on the new Trustees base-premium series (Table V.E2). The base percent is 25.5%, then 20% from 2030 (the Trustees' expectation) | Nearest 10 cents | `extend_medicare_part_d_irmaa_amounts` |

New parameters:
- `calibration.gov.hhs.cms.medicare_part_a_premium_per_enrollee`, `medicare_part_a_deductible` and `medicare_part_d_base_beneficiary_premium_per_enrollee`: Trustees Tables V.E1 and V.E2 for 2015–2035. They grow at their 2034–2035 rate to 2100 and hold after that. The 2027 Part D base premium ($41.33) is also the CMS-announced value.
- `gov.hhs.medicare.part_d.base_beneficiary_premium_percent`: 25.5% from 2006, and 20% from 2030.

The threshold rules follow each filing status's published 2024–2026 scales. Joint thresholds are twice the unmarried ones, and the joint top tier is 1.5 × the unmarried top. Married filing separately has two tiers, starting $1 above the unmarried threshold amount and at the top amount less that threshold.

### Projected values (hand-checked)

| Year | Part B premium | Part A full / reduced | Deductible | Single thresholds | Part D IRMAA, single |
|---|---:|---:|---:|---|---|
| 2026 (published) | $202.90 | $565 / $311 | $1,736 | 109,001 / 137,001 / 171,001 / 205,001 / 500,000 | 14.50 / 37.50 / 60.40 / 83.30 / 91.00 |
| 2027 | $209.50 | $600 / $330 | $1,788 | 112,001 / 141,001 / 176,001 / 211,001 / 500,000 | 15.40 / 39.70 / 64.00 / 88.30 / 96.40 |
| 2028 | $224.50 | $629 / $346 | $1,844 | 116,001 / 146,001 / 182,001 / 218,001 / 517,000 | 16.30 / 42.10 / 67.90 / 93.60 / 102.20 |
| 2030 | $255.50 | $694 / $382 | $1,960 | 121,001 / 153,001 / 191,001 / 229,001 / 541,000 | 51.70 / 103.40 / 155.10 / 206.80 / 224.00 |
| 2036 | $384.10 | $940 / $517 | $2,368 | 139,001 / 175,001 / 218,001 / 261,001 / 619,000 | 60.20 / 120.30 / 180.50 / 240.60 / 260.70 |

Examples:
- **2027 single thresholds.** 85,000 × 329.001 / 249.280 = 112,184, which rounds to $112,000. The 12-month CPI-U averages ending August 2026 and August 2018 come from the model's CPI-U series and its shared helper.
- **2027 Part D tiers.** 41.33 × (0.35 − 0.255) / 0.255 = 15.397, which rounds to $15.40.
- **2030 Part D tiers.** 68.93 × (0.35 − 0.20) / 0.20 = 51.70.
- **2036 Part A premium.** 900 × 900 / 862 = 939.67, which rounds to $940. The reduced premium is 0.55 × 940 = 517.
- **2036 deductible.** 2,296 × 2,296 / 2,228 = 2,366.08, which rounds to the nearest $4 at $2,368.

## Sources

- 42 U.S.C. [1395r](https://www.law.cornell.edu/uscode/text/42/1395r#i): Part B premium and IRMAA, including the (i)(5) CPI-U adjustment.
- 42 U.S.C. [1395w-113](https://www.law.cornell.edu/uscode/text/42/1395w-113#a): Part D IRMAA and the base beneficiary premium percent.
- 42 U.S.C. [1395i-2(d)](https://www.law.cornell.edu/uscode/text/42/1395i-2#d): Part A premium and its rounding.
- 42 U.S.C. [1395e(b)](https://www.law.cornell.edu/uscode/text/42/1395e#b_1): Part A deductible.
- [2026 Medicare Trustees Report](https://www.cms.gov/oact/tr/2026#page=212): Tables V.E1 (file page 212) and V.E2 (file page 213), plus the Part D premium discussion (file pages 113 and 154).
- CMS, [Medicare Part D 2027 national average monthly bid amount](https://www.cms.gov/newsroom/fact-sheets/medicare-part-d-2027-national-average-monthly-bid-amount-information): the 2027 base beneficiary premium.

## Invariants

These are tested in `policyengine_us/tests/core/test_medicare_premium_projection.py`, with 162 cases, some of them Hypothesis properties:
1. **Source agreement.** Projections match Trustees Tables V.E1–V.E4 for 2027–2035, to within cents for the Part B IRMAA amounts.
2. **Transcription.** The new series equal the published table cells.
3. **Rounding.** Every projected amount sits on its statutory or published grid.
4. **Proportionality.** Each Part B IRMAA amount keeps its multiple of the standard premium. Each Part D amount equals the (a)(7)(B) formula. The reduced Part A premium is 55% of the full premium, rounded half up.
5. **Monotonicity.** Premiums and thresholds never fall, and the tiers rise with income.
6. **Filing-status rules.** Rebuilding each published scale from its unmarried amounts reproduces every status's 2024–2026 thresholds.
7. **Indexing.** The CPI-U schedule reproduces CMS's published thresholds exactly through 2024, and within one $1,000 step in 2025–2026. From 2024 the model's CPI-U is seasonally adjusted, while CMS averages the published index. Each projected unmarried threshold equals its statutory amount indexed from its statutory base year.
8. **Precedence.** A CMS value added to the YAML replaces the projection for that year only.
9. **Extension.** The new series grow at their last Trustees rate to 2100 and then hold.

YAML cases in `base_part_a_premium`, `base_part_b_premium`, `income_adjusted_part_d_premium_surcharge`, `medicare_part_b_premium` and `msp_part_b_premium_coverage` cover the household effects in projected years.

Tests already on `main` that pinned the 2027 Part B premium at the frozen $202.90 now expect the projected $209.50 (12 × 209.50 = $2,514 a year):
- `nh_fap.yaml`, `nh_fap_countable_income.yaml` and `nh_fap_eligible.yaml` (New Hampshire FAP): the 2027 premium and the countable income derived from it, for example 18,000 − 2,514 = 15,486. No band or eligibility outcome changes.
- `policyengine_us/tests/unit/test_ss_cola_default_invariance.py`: the 80% IRMAA tier's 2027 premium is 12 × (209.50 + 460.80) = $8,043.60.
- `policyengine_us/tests/microsimulation/test_social_security_current_law.py`: the ingress differential compares against a `main` that predates this projection, so it skips `medicare_part_b_premium@2027`. The `medicare_irmaa_magi` requests still pin the IRMAA ingress path.

Run one file per process at this head: `nh_fap.yaml` 88 passed, `nh_fap_countable_income.yaml` 36, `nh_fap_eligible.yaml` 30, `medicare_part_b_premium.yaml` 16, `test_ss_cola_default_invariance.py` 14 and `test_medicare_premium_projection.py` 162. CI's Microsimulation job runs `test_social_security_current_law.py`.

Pull request run [41958440318](https://github.com/PolicyEngine/policyengine-us/actions/runs/41958440318) passed all 36 jobs at `368314616c695c6fc2839e7e0420596ad3345483`.

## Interactions

- #10111 (`medicare_cost` Part D premium) merges cleanly with this PR, and both test sets pass on the merged tree. Its projected-year `medicare_cost` cases take the Part B premium and Part D IRMAA as inputs.
- #10112 (2015–2023 IRMAA schedules) conflicts in the six Part B parameter files (`part_b/base_premium.yaml` and the five `part_b/irmaa/*.yaml`) and in two test YAMLs (`base_part_b_premium.yaml`, `income_adjusted_part_d_premium_surcharge.yaml`). This PR moves each Part B IRMAA amount under a `values:` key, so whichever PR lands second moves #10112's earlier dates under that key.

impact: the hub will measure
axiom: (hub files pe-parity before landing)

https://claude.ai/code/session_01LwQq6GEuvw3bKC1dT7mUe2

Judgment calls

  • I updated the derived comment arithmetic in Cases 29, 30, 32 and 36 and the Case 86 percentage, beyond the four comments the review listed. Otherwise those comments would contradict the new expected values.
  • I didn't edit the merge commit's message. It names my local branch, and amending it would have meant rewriting commits before the push.

Proposed follow-up

None beyond the review's own CPI-U label follow-up ("Correct the year labels on CPI-U projection points").


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Round 2 review of head 368314616c695c6fc2839e7e0420596ad3345483. The head hasn't moved, and main is still 0315452583, which the branch already contains.

Findings so far:

  • The merge. The PR's net change before the merge (01cc340a38 against its merge base) and after it (d96dce73c2 against 0315452583) are identical. The only difference is the blob hash on the index line of programs.yaml, because main edited another part of that file. No hunk content differs.
  • CI. Pull request run 41958440318 at this head concluded success, and all 36 jobs succeeded. The Microsimulation job's log runs test_social_security_current_law.py as its own step: 82 passed.
  • Round 1's P1. Every grep hit for 2_434.8|2434.8|7790.4|7_790|202.90|202.9 under policyengine_us/tests is in a period: 2026 or 2026-01 case, apart from two of this PR's own 2027/2034 projection comments in medicare_part_b_premium.yaml and the Trustees series in test_premium_input_uprating.py.
  • Hand values. 12 × 209.50 = 2,514, and 12 × (209.50 + 460.80) = 8,043.60. All NH FAP comment arithmetic checks out.
  • Hub mutant (MEDICARE_IRMAA_TOP_FIRST_INDEXED_YEAR = 2027): 162 passed, so it survives.

Test runs, more mutants and the #10111 merged-tree runs are still going.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: APPROVE
3683146
METHODOLOGY_CHOICE: yes

Round 2 review. The head hasn't moved since 368314616c, and main is still 0315452583, which the branch already contains. The four new commits fix round 1's P1 and both P3s that the hub took. The merge leaves the PR's net change byte-for-byte the same, CI is green at this head, and I found no new defect. The findings below are body text and comment nits only.

METHODOLOGY_CHOICE stays yes, as in round 1. The PR picks readings where the law leaves room: the joint top tier is 1.5 × the rounded unmarried top, ties round half up, the Part D base percent is 20% from 2030, and the Part B IRMAA amounts keep their 2026 multiples of the standard premium. None of the new commits changes any of these.

Findings

P3-1. The body's 2027 threshold example is off by $1 before rounding

Body, "Examples": "85,000 × 329.001 / 249.280 = 112,184". It's 112,183.4, whether you use the rounded indices or the full averages:

python3 -c "print(85000*329.00125/249.2801666666667, 85000*329.001/249.280)"
112183.4385139612 112183.42827342746

The rounded result ($112,000) is unaffected. Replacement: 85,000 × 329.001 / 249.280 = 112,183, which rounds to $112,000.

P3-2. The body's "Interactions" section leaves out #10634, which now conflicts

git merge-tree --write-tree --name-only pr-10103 pr-10634 reports CONFLICT (content): Merge conflict in policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml. #10634 ("Charge the Medicare Part B premium only to people who pay it") is open and changes who pays the premium. So whichever PR lands second has to merge that file and rerun this PR's 2027 and 2034 medicare_part_b_premium cases. Add to "Interactions":

- #10634 (who pays the Part B premium) conflicts in medicare_part_b_premium.yaml. Whichever lands second merges that file and reruns its projected-year cases.

P3-3. CHANGED_SINCE_REFERENCE has no exit condition

policyengine_us/tests/microsimulation/test_social_security_current_law.py:65-69. The skip is correct today (see "Checked and sound"). But once someone moves REVIEWED_MAIN_SHA past this PR, the 2027 premium would match again, and the skip would then quietly hide a Social Security ingress regression that shows up only in the 2027 premium. Suggested final comment line:

# Remove this entry once REVIEWED_MAIN_SHA includes the projection.

This is optional, and I'd land without it.

Checked and sound

  • The merge. I ran git diff $(git merge-base 01cc340a38 d96dce73c2^2) 01cc340a38 > old.diff, then git diff d96dce73c2^2 d96dce73c2 > new.diff, then diff old.diff new.diff. The output is 4 lines, and it's only the index line of programs.yaml (4cb94f1a03..580126503d against 15c6105f9b..ddd7d8bd9b): main edited another part of that file. Every hunk's content is the same, so the merge made no conflict resolutions. The 9 commits on main since the round 1 base (abc67b98de..0315452583: California state supplement, EITC for separate filers, version bumps) add no line that reads a Part A, B or D premium, an IRMAA amount or threshold, medical_expense_health_insurance_premiums or health_insurance_premiums. I checked with a git diff -U0 grep on the added lines.

  • The defect, on main (0315452583). I loaded the parameter tree in a worktree at origin/main. 2027, 2028 and 2030 all show 2026's values: Part B $202.90, Part A $565 / $311, deductible $1,736, single thresholds 109,001 / 137,001 / 171,001 / 205,001 / 500,000, joint top 750,000, separate top 391,000. At this head:

    Year Part B Part A Deductible Single thresholds 80% tier Joint top Separate top
    2027 209.5 600 / 330 1,788 112,001 / 141,001 / 176,001 / 211,001 / 500,000 460.8 750,000 388,000
    2028 224.5 629 / 346 1,844 116,001 / 146,001 / 182,001 / 218,001 / 517,000 493.8 775,500 401,000
    2030 255.5 694 / 382 1,960 121,001 / 153,001 / 191,001 / 229,001 / 541,000 562.0 811,500 420,000
  • Hand values, computed before reading the code.

    • The 12-month CPI-U averages ending August, from cpi_u.yaml. August 2018 is 249.28017. August 2026 is 329.00125: October 2025 carries September's 324.245 (the file notes BLS cancelled that release), and July–August carry June's 332.568.
    • The 2027 unmarried amounts are 112,183.4 / 141,219.2 / 176,194.0 / 211,168.8, which round to 112,000 / 141,000 / 176,000 / 211,000. The 2028 top is 500,000 × 340.3 / 329.00125 = 517,171, which rounds to 517,000. Then joint 1.5 × 517,000 = 775,500, and separate 517,000 − 116,000 = 401,000.
    • The Part B premium is 202.90 × 209.5 / 202.9 = 209.50, from the Trustees series in medicare_part_b_premium_per_enrollee.yaml (2026: 202.9, 2027: 209.5). The 80% tier is 446.30 × 209.5 / 202.9 = 460.82, which rounds to 460.80.
    • The model matches every value.
  • Round 1's P1: the old premium in tests. I grepped policyengine_us/tests for 2_434\.8|2434\.8|7790\.4|7_790|202\.90|202\.9\b and found 242 hits. A script assigned each hit to its YAML case and read that case's period:. 231 hits are 2026 and 8 are 2026-01. The other three:

    • medicare_part_b_premium.yaml:235 (2027) and :254 (2034), this PR's own projection comments ($487 × 209.50 / 202.90 and 338.50 / 202.90);
    • test_premium_input_uprating.py:325, the Trustees series.

    No 2027-or-later case still expects $202.90. The partner hits under tests/policy/baseline/partners/ are all 2026 cases, and this PR doesn't touch them. A second pass loaded each test YAML and listed every case dated 2027 or later whose outputs include a premium reader. They are only this PR's own cases and the three NH FAP files. The Python tests that touch IRMAA in 2027 or later (test_irmaa_first_modeled_year.py, test_irmaa_lookback_and_switch.py) check against the parameter tree, not fixed numbers.

  • New Hampshire arithmetic. Each changed value and comment:

    • Case 86: 28,000 − 2,514 = 25,486, which is 159.7% of 15,960 (1.59687). That's above 23,940 (150%) and below 27,930 (175%), so it stays in band E, and nh_fap stays 1,100.
    • Case 28: 18,000 − 2,514 = 15,486.
    • Case 29: 24,000 − 2,000 − 2,514 = 19,486.
    • Case 30: 24,000 − 23,000 − 2,514 < 0, which floors at 0.
    • Case 31: 24,000 − 2,514 = 21,486.
    • Case 32: 42,000 − 2 × 2,514 = 36,972, and unit 2 stays 12,000.
    • Case 36: min(2,514, 1,200) = 1,200, so it stays 30,000.
    • nh_fap_eligible Case 26: 50,000 − 2,514 = 47,486 ≤ 48,713, so it stays eligible.

    No band or eligibility outcome moves.

  • test_ss_cola_default_invariance.py. The irmaa_control fixture is a single 2027 filer with MAGI two years prior of $463,100.31. That falls between 211,001 and 500,000, the 80% tier, so the premium is 12 × (209.50 + 460.80) = 8,043.60, as the test now expects (rtol=0, atol=0.01).

  • CHANGED_SINCE_REFERENCE. Requests are keyed f"{variable}@{period}" (:126). The skip matches the single exact key medicare_part_b_premium@2027, so medicare_part_b_premium@2024 and @2026, and every medicare_irmaa_* request at every period, are still compared. assert current[route].keys() == main[route].keys() runs before the skipped loop over the full key sets, so a missing request still fails. The Microsimulation job's log (job 127018966157 in run 41958440318) runs this file in its own step, pytest policyengine_us/tests/microsimulation/test_social_security_current_law.py ..., and ends 82 passed in 529.41s. The 82 cases include all 74 test_legacy_public_ingress_matches_unmodified_main[...] routes, each PASSED.

  • Round 1's P3s. The new comment in get_medicare_irmaa_thresholds (uprating_extensions.py:871-874) matches the code below it. The joint scale uses 1.5 * top, where top comes from get_medicare_irmaa_unmarried_amounts: the fixed $500,000 before 2028 and the indexed amount from 2028. It isn't passed through round_medicare_irmaa_amount, as the comment says. test_separate_top_threshold_falls_until_2028 now uses range(2025, 2028). With -k separate_top_threshold -v, it runs [2025], [2026] and [2027], all PASSED, both at the head and with the hub's mutant applied.

  • Tests at the head, one file per process:

    File Result
    nh_fap.yaml 88 passed
    nh_fap_countable_income.yaml 36 passed
    nh_fap_eligible.yaml 30 passed
    medicare_part_b_premium.yaml 16 passed
    base_part_a_premium.yaml 24 passed
    base_part_b_premium.yaml 7 passed
    income_adjusted_part_d_premium_surcharge.yaml 14 passed
    msp_part_b_premium_coverage.yaml 4 passed
    test_ss_cola_default_invariance.py 14 passed
    test_medicare_premium_projection.py 162 passed

    These agree with the body's counts.

  • Mutants. Each ran in its own process in a scratch worktree, against test_medicare_premium_projection.py. I restored the file after each, and git status was clean.

    Mutant Result Killed by
    MEDICARE_IRMAA_TOP_FIRST_INDEXED_YEAR = 2027 (hub) survived, 162 passed equivalent
    Joint top rounded to $1,000, round_medicare_irmaa_amount(1.5 * top) (mine; the competing reading of (i)(3)(C)(ii)) 1 failed, 161 passed test_thresholds_follow_the_filing_status_rules
    Part D amounts truncated (ROUND_DOWN) in place of ROUND_HALF_UP (mine) 31 failed test_part_d_formula_reproduces_cms_published_amounts[2024-2026], test_part_d_irmaa_is_the_trustees_projection[...]
    Part A scale taken from the year before the last published one (mine) 1 failed test_published_part_a_premiums_take_precedence_and_later_years_grow_from_them

    The hub's mutant is equivalent by construction. For 2027, adjust divides the window ending August 2026 by the base period, also the window ending August 2026. The factor is exactly 1, so the top stays $500,000 whichever year the constant names. The model gives the same answer whether 1395r(i)(5)(C) begins indexing in 2027 or 2028. What does matter is that the base is August 2026 (MEDICARE_IRMAA_TOP_CPI_BASE_YEAR), and round 1's "top CPI base 2026 → 2025" mutant kills a change to that. I couldn't read the statute itself to confirm that (5)(C) names August 2026 (see below).

  • Subtract the expected Part D premium from medicare_cost #10111 on the merged tree. git merge-tree --write-tree pr-10103 pr-10111 exits 0 with tree 870195a54d. A local merge in a scratch worktree reproduces that tree. On it, one file per process:

    File Result
    medicare_cost.yaml 12 passed
    medicare_part_b_premium.yaml 16 passed
    base_part_a_premium.yaml 24 passed
    base_part_b_premium.yaml 7 passed
    income_adjusted_part_d_premium_surcharge.yaml 14 passed
    msp_part_b_premium_coverage.yaml 4 passed
    nh_fap.yaml 88 passed
    nh_fap_countable_income.yaml 36 passed
    nh_fap_eligible.yaml 30 passed
    test_medicare_premium_projection.py 162 passed
    test_medicare_cost_part_d_premium.py 85 passed
    test_medicare_per_capita_cost.py 223 passed
    test_part_b_msp_offset.py 14 passed
    test_ss_cola_default_invariance.py 14 passed

    That's 729 passed and none failed.

    The body's claim that "Subtract the expected Part D premium from medicare_cost #10111 merges cleanly with this PR, and both test sets pass on the merged tree" holds at both current heads.

  • Other open work. The search for open PRs on the changed file names found:

  • CI. Pull request run 41958440318 is the only run at this head. It concluded success, attempt 1, and all 36 jobs concluded success, none cancelled or skipped. That includes Python tests / Full Suite - Microsimulation, Baseline (states-shard-4) and Rest (Python shard 1), the three jobs that failed at 01cc340a38.

  • Body. It no longer opens with "Draft.". Its projected-values rows for 2026, 2027, 2028 and 2030 match the parameter tree. The CI sentence, the test-run counts and the three files it names under "Tests already on main" all hold. The impact: and axiom: lines are present. Apart from P3-1 and P3-2, the new commits made no body statement stale.

  • Baseline impact. The diff moves outputs in every year from 2027 and none before. The population check round 1 proposed still applies, 2027–2035:

    • aggregate medicare_part_b_premium against Trustees SMI premium income;
    • medicare_cost;
    • SPM poverty at 65 and over;
    • the medical expense deduction.

    The new commits change only tests and a comment, so they add nothing to it.

  • Scope. Nothing under .github/ or policyengine_us/tests/policy/baseline/partners/ is in the PR's file list. The changelog fragment changelog.d/medicare-part-b-premium-uprating.fixed.md is top-level.

What I couldn't check

  • Primary law. This VM couldn't reach www.govinfo.gov (DNS refused), uscode.house.gov, www.law.cornell.edu, www.ecfr.gov or www.ssa.gov. I couldn't read 42 U.S.C. 1395r(i)(3)(C) or (i)(5) to confirm the August 2026 base for the top amount, or the 150% joint rule. My reading of the top-year mutant therefore rests on the model's arithmetic, which is equivalent under either start year, not on the statute's text. The other legal points rest on round 1 and the PR's own citations.
  • Whether CMS has announced the 2027 Part B premium and IRMAA thresholds. cms.gov is unreachable from here. Round 1's P2 still stands: the 2027 values stay provisional until CMS publishes, and the hub said it would check before landing.
  • test_social_security_current_law.py locally. It builds microsimulations, so I relied on the CI log above.
  • Population-level impact. That needs data this VM doesn't have.

Generated by Claude Code

Keep the 2015-2023 IRMAA dates and references from main and the uprating and Trustees values from this branch.

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

DTrim99 commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Project Medicare premiums and IRMAA past the last announced year

Summary

The PR replaces the 2026 freeze with projections: Medicare premiums, the Part A deductible, and the Part B and D IRMAA amounts and thresholds now move after CMS's last announced year. I checked each rule against the statute and found the projection logic follows it:

  • Part B premium. Rounded to 10 cents under 42 U.S.C. 1395r(c).
  • IRMAA thresholds. Indexed under 1395r(i)(5) by CPI-U for the 12 months ending August. The four lower amounts use an August 2018 base. The $500,000 amount stays fixed through 2027 and is indexed against August 2026 from 2028. Each is rounded to $1,000.
  • Joint and separate scales. They follow 1395r(i)(3)(C)(ii) and (iii) and the (i)(1) threshold rule.
  • Part D adjustment. Uses the 1395w-113(a)(7)(B) formula, with the paragraph (9) percent from 2030.
  • Part A premium. Rounded to $1 with 50-cent amounts rounded up, under 1395i-2(d)(2). The reduced premium is 55% of the rounded full premium, under (d)(4).
  • Part A deductible. Rounded to $4 under 1395e(b)(1).

I checked every Trustees cell the PR copies against the 2026 Trustees Report PDF (sha256 ffa56b91…8fa8, the same file the YAML cites): Tables V.E1, V.E2, V.E3 and V.E4 on file pages 212, 213, 215 and 218, plus footnote 63 on page 113 and the Part D premium text on page 154. All of them match.

CMS had not announced the 2027 Part A and Part B amounts or the 2027 IRMAA brackets as of 2026-11-07:

  • the 2027 fact-sheet URL returns 404;
  • the Federal Register has no 2027 premium notice.

So 2027 is still a projection. The exception is the 2027 Part D base beneficiary premium of $41.33, which CMS announced on 2026-07-28 and which the PR uses.

CI is green at the head (36/36). The PR is 36 commits behind main. The tree merged with main has a conflict in medicare_part_b_premium.yaml. Once that conflict is resolved, the PR's three new projected-year cases in that file fail, because #10634 (merged 2026-11-06) now nets out a state's Part B buy-in for SSI recipients. That is one should-address item. I found nothing critical.

Critical (must fix)

None.

Should address

1. Three new projected-year cases fail on the tree merged with main (merge blocker)

Suggestions

1. CMS's 2027 announcement is about a week away.

  • Background. CMS announced the 2026 amounts on 2025-11-14. The 2027 Part A, Part B and IRMAA amounts should arrive in mid-November. Consider adding them, and moving LAST_ANNOUNCED_YEAR, before merging or immediately after. The test module's docstring already describes that step. The Trustees rows for 2028–2035 in test_medicare_premium_projection.py will then need updating, because later years grow from the announced amounts.
  • Why the 2027 thresholds are uncertain.
    • What the model uses. Its CPI-U is seasonally adjusted from 2024 and ends at June 2026, which it carries flat through July and August. The statute and CMS use the published index, which is not seasonally adjusted.
    • What the published data give. BLS CUUR0000SA0 has September 2025 through August 2026, except October 2025, which BLS cancelled. The result depends on how CMS fills that October gap:
      • If October is interpolated, the 12-month average is about 329.22, and the 2027 amounts are $112,000, $141,000, $176,000 and $211,000, the same as the PR's.
      • If CMS averages the 11 published months, the average is 329.66. That gives 141,500 → $142,000, 176,544 → $177,000 and 211,589 → $212,000.
    • What it means for the PR. Three of the four 2027 tiers sit close to a rounding step, so treat the 2027 thresholds as provisional until CMS publishes them.

2. The deductible's tie-breaking rule.

  • The mismatch. part_a/deductible.yaml rounds with rounding: {type: nearest, interval: 4}. Core implements "nearest" with Python's round, which sends ties to the even multiple. 42 U.S.C. 1395e(b)(1) instead sends an amount "midway between two multiples of $4, to the next higher multiple of $4".
  • Reach. No real year hits it. The Trustees values are multiples of $4 through 2035, and the extended values after that never land exactly midway.
  • The comment overstates it. The parameter comment says the rounding is "as 42 U.S.C. 1395e(b)(1) requires". The PR's own rulespec-us#1679 extension also says "the deductible's midpoint rule rounds upward even where a generic nearest-rounding helper would round to even."
  • Options. Project the deductible with round_half_up, as extend_medicare_part_a_premiums does for the Part A premium, or reword the comment.

3. Untested guards in uprating_extensions.py.

  • The "(if any)" floor. max(window / base - 1, 0) at line 836 implements the statute's "(if any)": thresholds never fall when CPI-U falls. Removing it fails no test, because CPI-U never falls below either base in the current data. A small-tree test with a falling CPI-U path would pin it, built like the existing precedence trees.
  • The reduced premium's own last year. At line 754 the reduced premium tracks its own last published year (last_reduced_year). It is tested only where that year equals the full premium's last year.

4. Long-run growth after 2035.

  • The rule. Each Trustees series grows to 2100 at its own 2034–2035 rate. That rule comes from main (Uprate premium inputs by premium series, not CMS out-of-pocket spending #10085) and is disclosed. Through this PR, the law parameters now follow it too:
    • the Part B series grows 6.5% a year;
    • the new Part A series grows 4.4% a year.
  • The result. The projected Part B premium is $931.20 a month in 2050 and about $22,000 a month in 2100 (series value 21,998). Over the same period the IRMAA thresholds and Social Security grow with CPI.
  • Suggestion. This now drives household results in every year after 2035. A follow-up could document the sensitivity or adopt a long-run convergence rule.

5. Optional exit condition. The self-review proposed an exit condition for CHANGED_SINCE_REFERENCE in test_social_security_current_law.py:65-69, and it was not added: "Remove this entry once REVIEWED_MAIN_SHA includes the projection." It's still worth adding, so the skip doesn't outlive its reason.

Verified correct

The law, checked against the statute text (LII):

  • Part B premium. 1395r(c) rounds to the nearest 10 cents.
  • IRMAA scales.
    • Unmarried. The 1395r(i)(3)(C)(i)(III) table is $85,000 / $107,000 / $133,500 / $160,000 / $500,000, with percentages 35, 50, 65, 80 and 85.
    • Joint. (ii) doubles each amount, except that the last row (and the second amount in the row before it) becomes 150% "for the calendar year". So 1.5 × the adjusted top, giving 775,500 in 2028, is the literal reading. (5)(B) rounds only amounts increased under (A) or (C).
    • Separate. (iii) reduces each amount by the unmarried threshold, and (i)(1) still requires MAGI above that threshold. That leaves two tiers, starting at 109,001 and 391,000 in 2026 and at 112,001 and 388,000 in 2027.
  • Indexing. (5)(A) indexes against August 2018 for 2020 and later. (5)(C) holds the $500,000 amount through 2027 and indexes it against August 2026 from 2028.
  • Part D.
    • Adjustment formula. 1395w-113(a)(7)(B) uses 25.5%, or the (a)(9) percent from 2030.
    • The 2030 percent. (a)(9) sets it subject to a 20% floor, and Trustees footnote 63 expects the floor to bind. The parameter labels the 20% as that expectation.
  • Part A.
    • Premium. 1395i-2(d)(2) rounds to $1 with 50 cents rounded up.
    • Reduced premium. (d)(4) applies a 45% reduction. CMS applies it to the rounded full premium (86 FR 64214), and the PR's test confirms that holds for 2015–2026.

Transcription.

  • Every Trustees cell matches the PDF:
    • Part A premium and deductible: 2015–2035;
    • Part D base premium: 2015–2035;
    • the Part B, Part B IRMAA and Part D IRMAA tables: 2027–2035.
  • The $41.33 2027 base premium matches CMS's fact sheet of 2026-07-28, and it equals the 6% cap: 38.99 × 1.06.

References.

  • They follow the CLAUDE.md rules:
    • one entry per document;
    • quoted #page 154,213 and #page 113,154 titles, with each href opening the first page;
    • file pages are correct.
  • Each reference corroborates its values.

Order of operations. set_all_uprating_parameters runs inside set_irs_uprating_parameter, before core's uprate_parameters, so the series reach 2100 before the uprating: metadata uses them. No new reader is added to gov.ssa.uprating or gov.ssa.nawi.

Projected values (local probe of the merged tree).

Year Part B premium Part A full / reduced Deductible Single thresholds Joint top Separate top
2027 209.50 600 / 330 1,788 112,001 / 141,001 / 176,001 / 211,001 / 500,000 750,000 388,000
2028 224.50 629 / 346 1,844 116,001 / … / 517,000 775,500 401,000
2030 255.50 694 / 382 1,960 121,001 / … / 541,000 811,500 420,000
2036 384.10 940 / 517 2,368 139,001 / … / 619,000 928,500 480,000

These agree with the PR body's table.

Hand-checks (15).

# Expectation Arithmetic Result
1 base_part_b_premium 2027 = 2,514 202.90 × 209.5 / 202.9 = 209.50 (Table V.E2); × 12 ✓
2 base_part_b_premium 2034 = 4,062 338.50 × 12 ✓
3 base_part_a_premium 2027 = 3,960 (35 quarters) and 7,200 (20 quarters) 0.55 × 600 = 330 (Table V.E1); × 12. 600 × 12 ✓
4 base_part_a_premium 2038 = 12,288 and 6,756 900 × (900 / 862)³ = 1,024.35 → 1,024; × 12. 0.55 × 1,024 = 563.20 → 563; × 12 ✓
5 2027 unmarried thresholds 329.00125 / 249.28017 = 1.31981. × 85,000 = 112,184; × 107,000 = 141,220; × 133,500 = 176,195; × 160,000 = 211,170 → 112k / 141k / 176k / 211k ✓
6 Projected bracket with rounding: 2028 top, joint top and separate top 500,000 × 340.3 / 329.00125 = 517,171 → 517,000. Joint 1.5 × 517,000 = 775,500. 85,000 × 340.3 / 249.28017 = 116,036 → 116,000, so separate = 517,000 − 116,000 = 401,000 ✓
7 Part D 2029, joint, 35% tier = 207.60 Thresholds 85,000 × 348.3 / 249.28 = 118,764 → 119,000, joint 238,001. 107,000 → 149,503 → 150,000, joint 300,000. MAGI 270,000 is in the tier. 46.44 × 0.095 / 0.255 = 17.301 → 17.30; × 12 ✓
8 Part D 2030, joint, 35% tier = 620.40 85,000 × 356.3 / 249.28 = 121,492 → 121,000, joint 242,001. 107,000 → 152,937 → 153,000, joint 306,000. 68.93 × 0.15 / 0.20 = 51.6975 → 51.70; × 12 ✓
9 2027 single, 50% tier = 5,028 MAGI 160,000 is in 141,001–176,000. Adjustment 202.90 × 209.5 / 202.9 = 209.50. (209.50 + 209.50) × 12. At the head; see Should 1 for the merged tree ✓
10 2027 separate, 85% tier = 8,547.60 390,000 ≥ 388,000. 487 × 209.5 / 202.9 = 502.84 → 502.80. (209.50 + 502.80) × 12 ✓
11 2034 joint, 65% tier = 10,560 August 2033 CPI-U 389.6. 133,500 × 1.56290 = 208,647 → 209,000, joint 418,001. 160,000 → 250,064 → 250,000, joint 500,001. Top 500,000 × 389.6 / 329.00125 = 592,095 → 592,000, joint 888,000. 324.60 × 338.5 / 202.9 = 541.53 → 541.50. (338.50 + 541.50) × 12 ✓
12 Part D 2027 single tiers equal Table V.E4 41.33 × 0.095 / 0.255 = 15.40; × 0.245 / 0.255 = 39.70; × 0.395 / 0.255 = 64.00; × 0.545 / 0.255 = 88.30; × 0.595 / 0.255 = 96.40 ✓
13 2036 deductible = 2,368; 2036 Part B premium = 384.10 2,296 × 2,296 / 2,228 = 2,366.08 → 2,368. 360.6 × 360.6 / 338.5 = 384.14 → 384.10 ✓
14 msp_part_b_premium_coverage 2027 = 2,514 209.50 × 12 ✓
15 NH FAP Case 86, and test_ss_cola_default_invariance 28,000 − 2,514 = 25,486, which is 159.7% of 15,960, so band E. 446.30 × 209.5 / 202.9 = 460.82 → 460.80, and 12 × (209.50 + 460.80) = 8,043.60 ✓

Self-review claims.

Tests, scope and conventions.

  • The new Python file reads parameters through the shared system, plus a few small ParameterNodes. It builds no simulation and copies no full tree, so it needs no cost report.

  • Household cases are in variable-named YAML files.

  • No partner test file is touched, and no partner test has a period of 2027 or later.

  • The changelog fragment changelog.d/medicare-part-b-premium-uprating.fixed.md is top-level.

  • No state-gated key variable is involved.

  • The PR adds no conflict-sounding comment, so the source-conflict ratchet is unaffected.

  • No reading here sets a statute against a form or an agency practice:

    • the Part D 10-cent rounding and the half-up tie on IRMAA amounts fill gaps where the statute is silent;
    • the joint top is the literal reading.

    So nothing needs a register entry.

Axiom line assessment

The line is valid:

rulespec-us#1677 queued (extended); rulespec-us#1679 queued (extended); n/a: announced data, forecast amounts, calibration assumptions and extrapolation.

Status. Both issues are OPEN and labelled pe-parity.

Dispatch-readiness. The 2026-11-05 extension comments on both issues have:

  • target module paths;
  • the corpus citation: scope 2026-09-13-medicare-statute-eligibility-premiums-title-42, provisions blob 212839ff…;
  • the verbatim law;
  • required outputs and invariants. For Refactor SNAP emergency allotment to decompose accurately in the app #1677 these include the August 2018 and August 2026 bases, the 1.5 × adjusted top, and the paragraph (9) percent kept as an input. For Fix inaccurate NY supplemental tax calculations #1679 they include half-up dollar rounding, the 45% reduction of the rounded premium, and midpoint-up deductible rounding.
  • companion tests:
    • Historical rows come from the Federal Register and CMS sources.
    • Projected rows use labelled fixtures. External sources can't exist for future years, so that is acceptable.

Blocker. Each issue records it:

  • the corpus re-pin: the current pin, us-rulespec-2026-10-29-ecfr-section-text, leaves out the Medicare scopes;
  • separate approval for each encoder run.

Match with the head. Both extensions cite head 368314616c. The current head, 7cc881c66c, only merges main: comparing the two over the PR's files shows nothing but main's #10112 content. So the issues still match the head.

The n/a part. It is a valid reason for what it covers:

  • the Trustees forecast series;
  • the announced 2027 Part D base premium, which is data;
  • the 20% assumption for 2030;
  • the extrapolation to 2100 and the hold after it.

None of these sets a statutory amount, and #1677's comment excludes them explicitly.

CI status

gh pr checks 10103 at head 7cc881c66c: all 36 checks pass, and none is pending. That includes Microsimulation, every Baseline and Contrib shard, and the Rest Python shards.

Branch status

Position. The PR is 24 commits ahead of main and 36 behind (main at c03a44d498).

Merge result. git merge-tree --write-tree PolicyEngine/main 7cc881c66c:

  • Conflict. CONFLICT (content) in policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml, caused by Charge the Medicare Part B premium only to people who pay it #10634.
  • Merged-tree runs. After a keep-both resolution:
    • medicare_part_b_premium.yaml: 19 passed, 3 failed (Should 1). With the ssi: 0 fix it passes 22 of 22.
    • base_part_a_premium.yaml, base_part_b_premium.yaml, income_adjusted_part_d_premium_surcharge.yaml and msp_part_b_premium_coverage.yaml: 88 passed.
    • nh_fap_countable_income.yaml and nh_fap_eligible.yaml: 66 passed.
    • The Trustees readers test, test_trustees_2026_economic_assumptions.py::test_the_tree_has_the_expected_readers: passed.
  • Local environment. These runs used policyengine-core 3.33.12, older than CI's.

What main changed since the merge base.

  • Nothing in uprating_extensions.py, the CPI-U series or any Medicare parameter.
  • Main does add new test cases dated 2027 or later: CT CEAP, WV LIEAP, CA LIHEAP, and the contributed ACA and California reforms. None of them puts a Medicare enrollee's premium through the model; one CT case enters the premium as an input.

Related PRs.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes per the review above: 0 critical, 1 should (merge conflict with #10634 in medicare_part_b_premium.yaml; after resolving it the 3 new projected cases fail because #10634's SSI buy-in nets the premium — add ssi: 0 to each), 5 suggestions. The law, Trustees values and hand-checks all check out.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Two files conflicted, and both keep both sides.
- uprating_extensions.py: this branch's Medicare projection functions and
  main's long_range_growth_rate (#10119) were added at the same place.
- medicare_part_b_premium.yaml: this branch's three projected-year cases
  and main's Part B payer cases (#10634) were appended at the same place.

On the merged tree the three projected-year cases fail, because #10634 nets
a state's Part B buy-in for SSI recipients out of the premium. The next
commit fixes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#10634 made medicare_part_b_premium net out medicaid_part_b_buy_in_premium_coverage.
In the Section 1634 and SSI-criteria states that buy-in pays the standard
premium for every month a person receives SSI. The three projected-year
cases give income only for the IRMAA lookback year, so the model found each
person an SSI recipient and the buy-in paid the standard premium. Setting
ssi to 0, as #10634 did for the file's other cases, restores the premiums the
cases test: 5,028, 8,547.60 and 10,560.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
42 U.S.C. 1395e(b)(1) rounds the Part A deductible to the nearest $4 and
sends an amount midway between two multiples to the higher one. The
deductible's uprating metadata used core's nearest rounding, which calls
Python's round and sends a midpoint to the even multiple.
extend_medicare_part_a_deductible now projects it with round_half_up,
like the Part A premiums. No year from 2015 to 2124 changes, because no
projected deductible lands on a midpoint; a small-series test pins the
rule.

Two more small-tree tests pin guards that no test reached. A falling CPI-U
path leaves the IRMAA amounts at their statutory values, the "(if any)"
in 1395r(i)(5)(A) and (C)(ii). A 2027 announcement of only one Part A
premium keeps it, and the other premium is projected from its own last
published year.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Social Security ingress test skips medicare_part_b_premium@2027
because its reference main predates the projection. The comment now says
to remove the entry once REVIEWED_MAIN_SHA includes the projection, so the
skip doesn't outlive its reason.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review
c48ef5f

This round merges main and resolves DTrim99's review 5813327763 (comment 6709160341) at 7cc881c66c. No one else pushed to the branch while I worked.

1. Main

  • 40875f112a merges main at ba9bfdea75, with both sides kept in each of two conflicts:
  • The review's failure, reproduced first. On the merged tree before any fix, medicare_part_b_premium.yaml gave 3 failed, 19 passed, with the review's exact values: [2514.] for 5028.0, [6033.5996] for 8547.6 and [6498.] for 10560.0.
  • Why. I read medicaid_part_b_buy_in_premium_coverage: in Section 1634 and SSI-criteria states it pays the monthly standard premium for each month with SSI. A direct run of the 2027 single case without ssi puts the person in CA with $12,348 of SSI, a $2,514 buy-in and a $2,514 premium. With ssi: 0 the premium is $5,028.
  • Fix. fca23cd2d0 adds ssi: 0 # Not an SSI recipient, so no state buy-in. after msp_asset_eligible: false in each case (medicare_part_b_premium.yaml:226, :244, :265). The file now passes 22 of 22.

2. Findings

  • Should 1: the merge blocker. Fixed above. The body's Interactions section now names Glide the Medicare Part B premium and spending per enrollee upraters to the Trustees' long-range rates #10657, and it drops the notes that called Add 2015-2023 Medicare IRMAA brackets and 2015-2020 Part B premiums #10112 and Charge the Medicare Part B premium only to people who pay it #10634 open or conflicting.
  • Suggestion 1: the 2027 CMS announcement. Skipped, because it hasn't happened. On 2026-11-09 the 2027 Parts A and B fact sheet URL returns 404, and the Federal Register search shows no 2027 premium notice. The 2027 thresholds stay provisional until CMS publishes them, as the review says.
  • Suggestion 2: the deductible's midpoint.
    • I fetched 42 U.S.C. 1395e(b)(1) from LII. A midway amount goes "to the next higher multiple of $4".
    • I read core's round_uprated_value: nearest calls Python's round, which sends a tie to the even multiple.
    • Fixed in 98c2adf96a. extend_medicare_part_a_deductible (uprating_extensions.py:925-950) projects the deductible with round_half_up(..., 4) like the Part A premiums, and set_all_uprating_parameters calls it (:1674). part_a/deductible.yaml drops its uprating: block, and its comment names the function.
    • The deductible is identical in every year from 2015 to 2124, old code against new; no projected year lands on a midpoint.
  • Suggestion 3: the untested guards. Two small-tree tests, both in 98c2adf96a:
    • test_amounts_do_not_fall_when_cpi_u_falls (test_medicare_premium_projection.py:581) uses a CPI-U path averaging 300, 270 and 250 for the 12 months ending August 2018, 2026 and 2027. The 2027 and 2028 amounts stay within the $1,000 rounding step of their statutory values, and the top amount stays at $500,000. I fetched 1395r(i)(5) from LII: (A)(ii) and (C)(ii)(II) both say "the percentage (if any) by which" the average exceeds its base.
    • test_reduced_part_a_premium_is_projected_from_its_own_last_published_year (:728) announces only one of the two Part A premiums for 2027, each way round. _part_a_tree_with_2027_values (:686) gained full_2027 and reduced_2027 arguments, with the old defaults.
  • Suggestion 4: long-run growth after 2035. No change here. Glide the Medicare Part B premium and spending per enrollee upraters to the Trustees' long-range rates #10657 glides this series and merges cleanly with this head. On a scratch merge, every base premium through 2035 is unchanged, and later ones fall well below this PR's: $751.30 against $931.20 in 2050, and $4,612.50 against $21,998.00 in 2100. test_medicare_premium_projection.py (165) and test_long_range_health_growth.py (37) pass on that merge.
  • Suggestion 5: the exit condition. Done in c48ef5fb25. test_social_security_current_law.py:68-69 now says to remove the entry once REVIEWED_MAIN_SHA includes the projection.

3. Source conflicts

No finding turns on two sources that disagree, so I added no candidates.yaml entry.

  • The deductible midpoint was the code departing from the statute, with no official source behind the departure. CONTRIBUTING.md calls that a bug, and the fix follows the statute.
  • The 2027 threshold question (CMS's handling of the missing October 2025 CPI-U, and the seasonally adjusted series) is an agency gap-filling choice CMS hasn't made yet, not a conflict.

Rulings for the hub: none. No change reverses a reading in the body, candidates.yaml or an earlier review.

4. Axiom line

The review found the line valid, and I kept it unchanged. Optional comment for rulespec-us#1679, if the hub wants the extension to cite the new head:

#10103 at c48ef5fb25 projects the Part A deductible with midpoint-up rounding to $4 (extend_medicare_part_a_deductible), matching this issue's required output. No projected year from 2027 to 2124 changes. The companion fixtures stand.

5. Mutants

M0 is the merged-tree run from section 1. M1 to M3 ran one process each on the projection test file, restoring the code after each (git status clean).

Mutant Undoes Result Killed by
M0: the merged tree without ssi: 0 Should 1 3 failed, 19 passed (medicare_part_b_premium.yaml) the three projected-year cases
M1: round(scale * series(period) / 4) * 4 at uprating_extensions.py:945 Suggestion 2 1 failed, 164 passed test_projected_deductible_rounds_a_midpoint_up (1736 vs 1740)
M2: amount * (window / base) at :1013 Suggestion 3, the floor 1 failed, 164 passed test_amounts_do_not_fall_when_cpi_u_falls (77,000 below 85,000)
M3: last_reduced_year = last_full_year at :903 Suggestion 3, own last year 1 failed, 164 passed test_reduced_part_a_premium_is_projected_from_its_own_last_published_year (311 vs 336)

Suggestion 5 is a comment, so no mutant applies. M1 changes no real year, so only the small-series test can catch it, and it does.

6. Tests at c48ef5fb25

One file per process.

File Result
medicare_part_b_premium.yaml 22 passed
base_part_a_premium.yaml 24 passed
base_part_b_premium.yaml 13 passed
income_adjusted_part_d_premium_surcharge.yaml 47 passed
msp_part_b_premium_coverage.yaml 4 passed
nh_fap.yaml 88 passed
nh_fap_countable_income.yaml 36 passed
nh_fap_eligible.yaml 30 passed
test_medicare_premium_projection.py 165 passed
test_source_conflicts.py 103 passed
test_trustees_2026_economic_assumptions.py (whole file, readers test included) 120 passed
test_ss_cola_default_invariance.py 14 passed
Main's test_long_range_health_growth.py 23 passed
Main's test_uprating_extensions.py 166 passed
Main's test_medicaid_home_equity_limit_indexing.py 22 passed
Main's test_cms_moop_uprating.py 59 passed
Main's test_esi_premium_uprating.py 50 passed
Main's test_premium_input_uprating.py 155 passed

I didn't run test_social_security_current_law.py: it's a microsimulation test, and this round only edits its comment. I ran no full suites and no microsimulations.

CI: Pull request run 42641736699 at c48ef5fb25 is in progress. I didn't wait for it, so I can't yet say whether it skipped or cancelled any job.

7. Body

Every section now matches this head:

  • What changes: the deductible row names the function and the midpoint rule.
  • Examples: the 2036 deductible example gains the statute quote and the unchanged-years check.
  • Invariants: 165 cases, and items 3, 7 and 8 describe the new tests.
  • Tests: counts at this head and the CI line.
  • Interactions: rewritten as above.
  • impact: impact: the hub will measure became impact: pending a comparison at the final head.
  • axiom: unchanged.
  • Session URL: I removed it, since it's an internal id.

Judgment calls

  • Suggestion 2: code, not wording. The cheaper option was rewording the comment. I fixed the code because the departure is from the statute, and Fix inaccurate NY supplemental tax calculations #1679's extension already requires midpoint-up rounding. Dropping the uprating: metadata means core no longer projects the deductible. The function writes 2027-2100 and holds the 2100 value after it, as core did.
  • Suggestion 3: Python, not YAML. The pins are Python tests, because no year's real data reaches either guard. CLAUDE.md allows Python for behavior YAML can't reach.
  • The (if any) test's tolerance. The floor test checks each amount stays within $1,000 above its statutory value, rather than pinning $134,000 for $133,500. Whether a zero increase still triggers (5)(B)'s rounding is moot until CPI-U falls.
  • Scratch work. The checks against Glide the Medicare Part B premium and spending per enrollee upraters to the Trustees' long-range rates #10657 ran on a throwaway merge, and nothing from it is in this branch.

Follow-up issue proposed

  • Add CMS's 2027 Parts A and B amounts and IRMAA brackets when CMS publishes them. Move LAST_ANNOUNCED_YEAR to 2027 and update the 2028-2035 Trustees rows in test_medicare_premium_projection.py. Check the 2027 thresholds against CMS's, since three sit near a rounding step.

DISMISSAL MAP


Generated by Claude Code

@DTrim99

DTrim99 commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Project Medicare premiums and IRMAA past the last announced year

Summary

Round 1 (head 7cc881c66c) found 0 critical, 1 should and 5 suggestions. Since then the author has pushed three commits of his own and one merge of main, which needed two conflicts resolved by hand. The head is now c48ef5fb25.

CI is green at the head (36/36). The branch merges cleanly with current main. I found no new critical or should-address issue.

Prior findings

Finding Status Evidence
Should 1. Three projected-year cases fail on the merged tree because #10634's SSI buy-in nets out the standard premium; the body's Interactions section is stale Fixed Merge. 40875f112a keeps both blocks. git diff <merge-base ba9bfdea75> c48ef5fb25 on medicare_part_b_premium.yaml adds 64 lines and removes none. The file has 22 cases: 13 pre-existing, the PR's 3, and #10634's 6 (lines 272–341), unchanged.
Fix. fca23cd2d0 adds ssi: 0 # Not an SSI recipient, so no state buy-in. to each projected case (lines 226, 244 and 265), after msp_asset_eligible: false, as #10634 did for the file's other cases.
Mutation. Without ssi: 0 the three cases fail with [2514.], [6033.60] and [6498.]. Round 1's merged-tree run showed this and the author's M0 run reproduces it, so the fix is pinned.
Run. Locally at the head, through the lock, medicare_part_b_premium.yaml gives 22 passed (policyengine-core 3.33.12). CI's Baseline (contrib-hhs) shard runs policy/baseline/gov/hhs and passes.
Body. Interactions now says that #10112 and #10634 have merged, describes the ssi: 0 change and adds #10657.
Suggestion 1. Add CMS's 2027 Part A, Part B and IRMAA amounts when they're announced Still open, not yet actionable CMS had not published them as of 2026-11-09:
• The 2026 fact sheet is at /newsroom/fact-sheets/2026-medicare-parts-b-premiums-deductibles (HTTP 200). The same address for 2027, and three other variants, return 404.
• The Federal Register API's newest Part B actuarial-rate and Part A premium notices are the 2026 ones of 2025-11-19. Nothing for CY 2027 is published or on public inspection.
• Medicare.gov's costs page still shows the $202.90 standard premium.
The PR claims 2027 only as projected, apart from the announced $41.33 Part D base premium, so this stays a suggestion. The author has proposed the follow-up: move LAST_ANNOUNCED_YEAR and update the 2028–2035 Trustees rows. The model's CPI-U series hasn't changed since round 1, so the 2027 thresholds are still $112,000 / $141,000 / $176,000 / $211,000. Round 1's caveat stands: three of them sit near a $1,000 rounding step.
Suggestion 2. The deductible's tie-breaking: core's nearest rounds half to even; 1395e(b)(1) rounds a midpoint up Fixed 98c2adf96a adds extend_medicare_part_a_deductible (uprating_extensions.py:925-950). It uses round_half_up(scale * series, 4) and is called at :1674, after the series loop extends the Trustees deductible to 2100. part_a/deductible.yaml drops its uprating: block, so core no longer projects it twice, and its comment now names the function.
test_projected_deductible_rounds_a_midpoint_up pins the rule; hand-check 4 below. The author's M1 mutant (Python round) fails it.
No real year moves. The scale is 1,736 / 1,736 = 1, and every Trustees value for 2027–2035 is a multiple of $4. After 2035 the series grows by the non-terminating ratio 2,296 / 2,228.
Suggestion 3. Untested guards: the "(if any)" floor, and the reduced premium's own last year Fixed test_amounts_do_not_fall_when_cpi_u_falls (hand-check 5) and test_reduced_part_a_premium_is_projected_from_its_own_last_published_year (hand-check 6). The mutants M2 (amount * (window / base)) and M3 (last_reduced_year = last_full_year) each fail exactly one new test, and my traces below agree.
Suggestion 4. Long-run growth after 2035 (Part B 6.5% a year; Part A 4.4%) Deferred, reason accepted #10657 (open, head 189dde2d38) glides the Part B premium series. I ran git merge-tree of #10657 with this head and it exits 0. By the author's scratch-merge figures, 2050 falls from $931.20 to $751.30 and 2100 from $21,998 to $4,612.50, and every year through 2035 is unchanged.
The PR's own new series still grow at their 2034–2035 rates: Part A premium 4.4%, deductible 3.1% (2,296 / 2,228) and Part D base premium 3.1% (77.81 / 75.48). Those are close to the Trustees' long-range 3.7–4.3%, so the main outlier was the Part B series, which #10657 handles.
Suggestion 5. Exit condition for CHANGED_SINCE_REFERENCE Fixed c48ef5fb25: test_social_security_current_law.py:68-69 now ends "Remove this entry once REVIEWED_MAIN_SHA includes the projection." This is a comment, so no mutant applies.

New findings

Critical: none.

Should address: none.

Suggestions

  1. The body's CI line is stale. The body says the Pull request run at c48ef5fb25 (42641736699) "is pending". It has since finished with 36 of 36 jobs passing. Update the line when the body is next edited.

  2. Optionally refresh rulespec-us#1679. The 2026-11-05 extension still cites head 368314616c. The deductible change brings the code into line with what the issue already requires:

    • line 77: "rounded to the nearest $4 with midpoint amounts rounded up";
    • the midpoint row 1,786 → 1,788;
    • line 84: "Encode the law, not the PE-US value."

    So the issue is not stale in substance. Posting the comment the author drafted in the hand-off would point the extension at the current head.

Verified

Hand-resolved block 1: medicare_part_b_premium.yaml. I covered this under Should 1. Both blocks are kept, the boundary between them is clean, and no case is duplicated.

Hand-resolved block 2: uprating_extensions.py. The merge put the PR's Medicare constants and functions (lines 866–1162) before main's long_range_growth_rate (#10119, line 1164), with the blank lines correct and no conflict markers left.

Hand-checks of every new or changed expectation.

# Expectation Arithmetic Result
1 2027 single, 50% tier = 5,028 (now with ssi: 0) MAGI 160,000 is in the 141,001–176,000 tier. Standard premium 202.90 × 209.5 / 202.9 = 209.50, and the adjustment is the same: (209.50 + 209.50) × 12 ✓
2 2027 separate, 85% tier = 8,547.60 The top starts at 500,000 − 112,000 = 388,000, and MAGI 390,000 is above it. Adjustment 487 × 209.5 / 202.9 = 502.84 → 502.80. (209.50 + 502.80) × 12 = 8,547.60 ✓
3 2034 joint, 65% tier = 10,560 Thresholds 133,500 × 389.6 / 249.28 = 208,647 → 209,000, joint 418,001. 160,000 → 250,064 → 250,000, joint 500,000. MAGI 450,000 is in the tier. Adjustment 324.60 × 338.5 / 202.9 = 541.53 → 541.50. (338.50 + 541.50) × 12 = 10,560 ✓
4 Deductible midpoint test: 2026 = 1,736, 2027 = 1,740, 2028 = 1,744, 2100 = 1,744 Scale 1,736 / 1,736 = 1. In 2027, 1,738 / 4 = 434.5, which ROUND_HALF_UP takes to 435, so 1,740; half to even would give 434, so 1,736. In 2028, 1,745 / 4 = 436.25 → 436, so 1,744. The final update(start=2028) holds 1,744 thereafter ✓
5 CPI-U floor test: amounts unadjusted in 2027 and 2028; top 500,000 The windows average 300 (Sep 2017–Aug 2018, all at 300), 270 (Sep 2025–Aug 2026) and 250. The last observation is 2026-09, carried through the unobserved tail of the window ending August 2027.
2027: 270 / 300 < 1, so the factor is 1 and the amounts are 85,000 / 107,000 / 134,000 (133,500 rounded) / 160,000, each within [amount, amount + 1,000). The top is fixed before 2028.
2028: 250 / 300 and 250 / 270 are both below 1, so the top stays 500,000.
M2: 85,000 × 0.9 = 76,500 → 77,000, which fails
✓
6 Reduced Part A premium from its own last year: 611 and 336; then 337 kept, with 2028 and 2030 at 0.55 × full Only the full premium announced for 2027. The reduced premium's last published year is 2026, so 2027 = round_half_up(0.55 × 611 = 336.05) = 336.
Only the reduced premium announced for 2027. The reduced loop starts in 2028, so 337 is kept. The full premium is projected from 565.
M3 makes the first case keep 2026's 311 in place of 336, so it fails
✓
7 Body: 2036 deductible = 2,368 2,296 × 2,296 / 2,228 = 2,366.08. 2,366.08 / 4 = 591.52 → 592, so 2,368 ✓

New tests: conventions and cost.

  • Cost. The three new Python tests build small ParameterNode trees under a SimpleNamespace root. They build no simulation and copy no full tree, so CLAUDE.md asks for no cost report.
  • Why Python. The tests cover behavior that no real year's data reaches, which CLAUDE.md allows. The author says so.
  • Zero increase. The CPI-U floor test tolerates 133,500 being rounded to 134,000 when the amount isn't increased. (5)(B) rounds an amount "after being increased", and rulespec-us#1677 specifies amount × max(ratio, 1), then rounding. The question is moot until CPI-U falls below its 2018 base.

Law. I checked the 1395e(b)(1) text in round 1: an amount "midway between two multiples of $4, to the next higher multiple of $4". The new code now matches it. The parameter comment and the round_half_up docstring cite it correctly.

No regressions in the fix commits.

Axiom

The line is unchanged and still valid:

rulespec-us#1677 queued, extended; rulespec-us#1679 queued, extended; n/a: announced data, forecast amounts, calibration assumptions and extrapolation.

  • Status. Both issues are OPEN and labelled pe-parity, and both were last updated on 2026-11-05.

  • Readiness and blocker. Both stay dispatch-ready as round 1 found. The blocker is recorded: the corpus re-pin, plus approval for each encoder run.

  • Match with the head. The fix commits change no required output:

    So the issues match the head. A comment citing the new head is optional (Suggestion 2).

CI

gh pr checks 10103, re-checked at the end of the review: 36 of 36 pass, none pending. Run 42641736699 (pull_request, head c48ef5fb25) concluded success. Every job succeeded, including:

  • Microsimulation;
  • Rest Python shards 1–9;
  • every Baseline shard, including contrib-hhs, which runs gov/hhs;
  • every Contrib shard;
  • Household API Partners;
  • PR validation.

Branch

Position. The PR is 28 commits ahead of main and 6 behind (main at 61d9e8520c).

Merge result. git merge-tree --write-tree PolicyEngine/main c48ef5fb25 exits 0 (tree af431ae8de) with no conflict.

Main's six new commits. They are:

They overlap the PR only in uprating_extensions.py, in the separate ACA benchmark hunk. None of their 2027 or later test cases reads a Medicare premium: they are ACA cases with is_medicare_eligible: false. They leave the CPI-U series unchanged.

Siblings. git merge-tree of the head with each of these exits 0:

Verdict: APPROVE

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2: the #10634 merge keeps both blocks and the three projected cases now set ssi: 0 (22/22 pass); the uprating merge resolution is correct; the deductible midpoint and both guards are pinned; CI is green. CMS hasn't announced 2027 yet. Three optional suggestions remain.

test_social_security_current_law.py conflicted, and the result keeps both
sides. It keeps this branch's CHANGED_SINCE_REFERENCE skip for the 2027
Part B premium in both loops of the ingress comparison, and main's
INCOME_TAX_CHANGE_2027 pins from Rev. Proc. 2026-35 (#10683), which the
second loop checks after that skip. The two cover different requests, so
each check runs as before.

uprating_extensions.py merged without a conflict. It keeps this branch's
Medicare projection functions and calls and main's 2027 ACA benchmark
extension (#10682), each once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis closed this Nov 10, 2026
@MaxGhenis MaxGhenis reopened this Nov 10, 2026
@MaxGhenis MaxGhenis closed this Nov 10, 2026
@MaxGhenis MaxGhenis reopened this Nov 10, 2026

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.

3 participants