Skip to content

Subtract the expected Part D premium from medicare_cost - #10111

Open
MaxGhenis wants to merge 11 commits into
mainfrom
medicare-cost-part-d-premiums
Open

MaxGhenis wants to merge 11 commits into
mainfrom
medicare-cost-part-d-premiums

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

medicare_cost, the net value of Medicare, subtracts the Part A premium and the gross Part B premium from calibration.gov.hhs.medicare.per_capita_cost. That series holds CMS National Health Expenditure Medicare spending per enrollee (from #10107), which covers Parts A through D. So medicare_cost left in the Part D spending that enrollees' Part D premiums finance, and it overstated Medicare's net value for every enrollee.

What changes

medicare_cost now also subtracts the following Part D premiums:

part_d.enrollment_share × part_d.premiums_per_enrollee   # average Part D premium per Medicare enrollee
+ income_adjusted_part_d_premium_surcharge                # the person's own Part D IRMAA
  • Spending per enrollee averages Part D over every Medicare enrollee, so this PR averages the premium the same way: premiums per Part D enrollee × the Part D enrollment share.
  • Since Charge the Part D income-related premium only to Part D enrollees #10115, income_adjusted_part_d_premium_surcharge reads zero for anyone not enrolled in Part D (medicare_part_d_enrolled), so medicare_cost subtracts it in full. Scaling it by the share would count non-enrollment twice and charge an enrollee only about 82% of the adjustment they pay. medicare_cost already subtracts the Part B IRMAA per person the same way.
  • Every enrollee nets the same average Part D premium, including low-income subsidy enrollees and people whose drug coverage isn't Part D. The average understates the net value for those people and overstates it for other Part D enrollees, and the variable's documentation says so.
  • medicare_gross_cost doesn't change.

New series under calibration.gov.hhs.medicare.part_d, from the 2026 Medicare Trustees Report:

Series Definition Years
premiums_per_enrollee (Table III.D3 premium income, file page 116, − Table V.E4 aggregate Part D IRMAA, file page 218) ÷ Table V.B3 Part D enrollment, file page 196 2015–2035. The series moves the Dec. 31 credits in III.D3's footnote 6 (file page 117) to the following year. From 2036 to 2100 it grows at its 2034–2035 rate (3.3%), and then it holds
enrollment_share Table V.B3 Part D enrollment ÷ total ("HI and/or SMI") enrollment 2015–2035 annually, then every fifth year to 2100

Table V.B3's Part D enrollment includes retiree drug subsidy beneficiaries, and takes_up_medicare_part_d_if_eligible leaves them out. medicare_cost reads only the product of the two series, so that denominator cancels. Both labels name the broader count.

Year Premiums per Part D enrollee Share Average premium per Medicare enrollee
2015 $277.60 0.7517 $208.67
2025 $220.25 0.8191 $180.41
2026 $289.79 0.8195 $237.48
2030 $637.06 0.8382 $533.98
2035 $722.05 0.8485 $612.66

The Trustees' own figures dip in 2025 and jump in 2030. The report attributes those moves to the 2025 Part D premium demonstration (file page 109) and the 2030 change in the base beneficiary premium percent (file page 154).

Definition

medicare_cost gives PolicyEngine's net-of-premiums Medicare value, and no published income measure defines it. #10118 quotes CBO's household-income measure, which counts Medicare at average cost per enrollee net of enrollee premiums, and implements that measure as its own variable. medicare_cost subtracts the person's own Part A premium, gross Part B premium (including the Part B IRMAA) and Part D IRMAA, and only the Part D base premium as an average over enrollees.

Household changes (medicare_cost.yaml, hand-computed)

Case Before After
2025, premium-free Part A, standard Part B $15,531 17,751 − 2,220 − 0.8191 × 220.25 = $15,350.59
2025, single, $500k MAGI (top IRMAA tiers) $10,204.20 17,751 − 7,546.80 − 180.41 − 85.80 × 12 = $8,994.19
2025, same, not enrolled in Part D $10,204.20 17,751 − 7,546.80 − 180.41 = $10,023.79
2030, Part B $3,000, Part D IRMAA $1,000 — 23,789 − 3,000 − 0.8382 × 637.06 − 1,000 = $19,255.02
2036, Part B $3,000 — 33,488.47 − 3,000 − 0.8485 × (722.05 × 722.05 ÷ 699.07) = $29,855.67

Invariants

policyengine_us/tests/core/test_medicare_cost_part_d_premium.py tests these in 85 cases, some of them Hypothesis properties:

  1. Transcription. Every stored value equals its formula applied to the Trustees cells.
  2. Conservation. Premiums per Part D enrollee × Part D enrollment + aggregate IRMAA reproduces Table III.D3 premium income in every year from 2015 to 2035.
  3. Bounds. The share lies strictly between 0 and 1, and the premium stays positive, from 2015 to 2200.
  4. Extension. Values from 2036 to 2100 follow the closed form at the 2034–2035 rate, and later years hold the 2100 value.
  5. Calendar years. Every date in a year reads the January value.
  6. Consumer. medicare_cost lies in [0, medicare_gross_cost] for any year and premiums, and the gross cost doesn't depend on the Part D premium.
  7. Accounting. Above the zero floor, the person's Part D IRMAA lowers medicare_cost dollar for dollar.

Tests

  • medicare_cost.yaml holds 13 cases, including a 2036 case that pins where the premium's extension starts.
  • test_medicare_cost_part_d_premium.py holds the 85 cases above.
  • Each of these mutants fails medicare_cost.yaml: scaling the IRMAA by the share, dropping the share, dropping the IRMAA, subtracting the average only for Part D enrollees, removing the floor, and extending the premium from 2034.

Interactions

impact: pending a comparison on the default dataset for 2026, main against this branch.
axiom: n/a: this PR subtracts the average Part D premium and the person's own Part D surcharge from PolicyEngine's Medicare net-benefit value, and leaves eligibility and premium-liability calculations alone.

🤖 Generated with Claude Code

MaxGhenis and others added 5 commits October 11, 2026 12:59
Replace KFF's 2021 traditional-Medicare figure and an unsourced $14,500
2025 estimate (held flat for every later year) with CMS National Health
Expenditure Table 17's Medicare spending per enrollee: historical
2013-2024, projected 2025-2034, extended to 2100 at the 2031-2032 rate
so the end of sequestration in 2033 does not compound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transcription checks against NHE Table 17 (and its spending over
enrollment), a cross-source level check against the Trustees Report's
per beneficiary cost, Hypothesis properties for order, the 2035-2100
closed form and calendar-year steps, and identities for
medicare_gross_cost, medicare_cost and qmb_cost_sharing. Existing
expectations move from $14,500/$11,080 to the CMS values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CMS Medicare spending per enrollee (calibration.gov.hhs.medicare.per_capita_cost,
#10107) includes Part D, which enrollee premiums
partly finance, but medicare_cost subtracted only Part A and Part B premiums.
It now also subtracts the Part D enrollment share times (Part D premiums per
Part D enrollee + the person's Part D IRMAA), both new calibration series from
the 2026 Medicare Trustees Report (Tables III.D3, V.E4 and V.B3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Transcription from the Trustees' cells, conservation back to Table III.D3
premium income, bounds, the 2036-2100 extension's closed form, and a
Hypothesis differential test of medicare_cost against a reference
implementation (0 <= net <= gross, non-increasing in Part D IRMAA).

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

Copy link
Copy Markdown
Contributor Author

Heads-up from #10115, which charges income_adjusted_part_d_premium_surcharge only to Part D enrollees. It adds medicare_part_d_enrolled, defined for medicare_enrolled and driven by a new takes_up_medicare_part_d_if_eligible input that defaults to true.

This PR subtracts part_d.enrollment_share × (premiums_per_enrollee + income_adjusted_part_d_premium_surcharge). That expected-value form is right while the surcharge reaches every Medicare enrollee. Once microdata set Part D take-up, the surcharge is already zero for non-enrollees, and multiplying it by enrollment_share would count non-enrollment twice.

Options, after both merge:

  • If medicare_part_d_enrolled is known per person, subtract medicare_part_d_enrolled × premiums_per_enrollee + income_adjusted_part_d_premium_surcharge.
  • Otherwise, keep the expected value on the base premium only, and subtract the surcharge as it stands.

With the default input the two PRs give the same numbers today: git merge-tree is clean, and base and #10115 match exactly on the certified file. So this is not blocking. It is a note for whichever PR merges second.

MaxGhenis added a commit that referenced this pull request Oct 11, 2026
YAML: the 2025, 2015 and 2035 values, and five people with different
premiums (Part A premium, income-related premiums, under 65, not
enrolled) who all get the same average. Python, parameters only:
transcription and conservation against the Trustees cells, the Part D
series against #10111's share x premium plus income-related premiums,
Part B premium income including income-related premiums, bounds through
2200, the post-2035 extension keeping each series' share of spending,
and January reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from medicare-per-enrollee-cms to main October 12, 2026 12:21
@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 #10107, which has since merged to main in revised
form, so its shared lines (the per capita cost series, its extension and its
tests) take main's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#10115 made income_adjusted_part_d_premium_surcharge zero for anyone not
enrolled in Part D (medicare_part_d_enrolled). Multiplying it by the Part D
enrollment share then counted non-enrollment twice once take-up is modeled,
and charged an enrolled person only 82 percent of the adjustment they pay.
The share now scales only the average Part D premium, which matches
spending per enrollee averaged over every Medicare enrollee; the person's
own adjustment amount is subtracted as computed, as the Part B one is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Part D series listed the report in several reference entries that
differed only by #page=; CLAUDE.md asks for one entry per source with the
pages in its title. The 2030 base beneficiary premium reset is on file page
154, not 152, and file page 219 says low-income subsidy enrollees pay
substantially reduced premiums, not none. The December 31 credits move to
the following year, not back a year.

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

Picking this up for the US hub, continuing an earlier session's work on this PR. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 5c24d3faca21e22bcf5d33e0e807e85143c33008 (still a draft).

This continues the earlier session's work: f75816d07e, 1d7e9f97f5 and 437e8e5993.

What I did

  1. Still needed? Yes. medicare_cost on origin/main (abc67b98de) subtracts only base_part_a_premium and gross_medicare_part_b_premium_if_enrolled from per_capita_cost (variables/gov/hhs/medicare/costs/medicare_cost.py on main). No other open PR adds a Part D offset. Charge the Part D income-related premium only to Part D enrollees #10115 has merged, and its coordination comment is handled in item 2.
  2. Earlier session's commits, checked:
  3. Merged origin/main in 5c24d3faca, with no conflicts. I made no code changes this session: every hand check and mutant below came out as expected.
  4. Body rewritten: what changes and why, the definition it follows, sources with page links, invariants, and the impact: and axiom: lines. The changelog fragment was already present.

No reviews or inline comments exist on this PR.

Definition followed

medicare_cost is PolicyEngine's net Medicare value, not a published measure. Its net-of-premiums concept is the one CBO's standard household-income measure uses (CBO, January 2026, https://www.cbo.gov/publication/61910). It differs from CBO by subtracting the person's own Part A premium and IRMAA. I could not fetch cbo.gov in this session, so I don't quote it here. #10118 carries the earlier session's quotes, unverified.

Hand computations

Every medicare_cost.yaml case changed by this PR, recomputed:

  • Unit test 1 (2025): 17,751 − 2,220 − 0.8191 × 220.25 = 15,350.59.
  • Unit test 3 (2025, $500k single): 17,751 − 7,546.80 − 180.41 − 85.80 × 12 = 8,994.19.
  • Unit test 12 (2025, $500k single, not in Part D): 17,751 − 7,546.80 − 180.41 = 10,023.79.
  • Unit test 9 (2030): 23,789 − 3,000 − 0.8382 × 637.06 − 1,000 = 19,255.02.
  • per_capita_cost on main is 17,751 in 2025 and 23,789 in 2030.

Tests run (local, one file per process)

File Result
tests/core/test_medicare_cost_part_d_premium.py 85 passed
tests/core/test_medicare_per_capita_cost.py 223 passed
test_part_b_msp_offset.py 14 passed
test_part_d_enrollment.py 1 passed
YAML costs/medicare_cost 12 passed
YAML eligibility/income_adjusted_part_d_premium_surcharge 12 passed
YAML eligibility/medicare_part_d_enrolled 5 passed
test_uprating_extensions.py 165 passed, 1 failed

The one failure is the same network-blocked test that fails on main here (test_retirement_contribution_limits_include_latest_explicit_irs_values, a 403 from the sandbox proxy).

I also ran this branch merged with #10103: test_medicare_cost_part_d_premium.py (85 passed), test_medicare_premium_projection.py (162 passed), test_medicare_per_capita_cost.py (223 passed) and medicare_cost.yaml (12 passed).

Partner tests. No file under tests/policy/baseline/partners/ references medicare_cost, and no variable in the model reads it.

Mutants (one process each, code restored)

Mutant Python YAML
Scale the IRMAA by the share (the pre-1d7e9f97f5 form) 1 failed 3 failed
Drop the share 1 failed 9 failed
Extend the premium series from 2034 instead of 2035 3 failed killed

CI

None has run. CI skips drafts.

Judgment calls

  1. IRMAA in full. The aggregate this subtracts depends on Part D take-up in the data. takes_up_medicare_part_d_if_eligible defaults to true, so unless the microdata set take-up, every high-income enrollee pays Part D IRMAA. The Trustees' aggregate IRMAA counts only Part D enrollees. This is the same treatment medicare_cost already gives the Part B IRMAA, and the impact run should show its size.
  2. Average premium for everyone. Enrollees without Part D (for example, those with retiree coverage) still have the average Part D premium subtracted. Spending per enrollee averages Part D over all enrollees, so this keeps the net value consistent in aggregate.
  3. Extension rate. The premium grows at its own 2034–2035 rate (3.3%) after 2035, not at per_capita_cost's rate.
  4. Low-income subsidy enrollees get the uniform average premium, though they pay little. Modeling the subsidy is out of scope here (Model the Medicare Part D low-income subsidy as a CBO means-tested transfer #10127 is a draft).

Sources I could not read in this session

This environment's network policy refused cms.gov, cbo.gov, law.cornell.edu and web.archive.org. I could not re-read the Trustees tables behind the two series. The conservation and transcription tests check internal consistency against the cells the earlier session recorded, not against the report.

Interactions with #10103 and #10118


Generated by Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 3, 2026 21:13
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Reviewing head 5c24d3faca21e22bcf5d33e0e807e85143c33008. Interim findings so far; the final review follows as a separate comment.

Mutants are running.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: APPROVE
5c24d3f
METHODOLOGY_CHOICE: yes

The head hasn't moved since 5c24d3faca. The code applies the rule it describes in every year from 2015 to 2200, and every hand value I computed matches. Each changed test file passes, and the Pull request CI run at this head finished with all 36 jobs successful (run 41719873923). I approve with one condition. The two new parameter series rest on Trustees Report cells that no session, including this one, has read against the report. The hub has to check those cells before landing (see "What I couldn't check").

The PR makes three methodology choices that no source settles, so METHODOLOGY_CHOICE is yes:

  1. It subtracts the average Part D premium from every Medicare enrollee, including people without Part D.
  2. It subtracts the person's own Part D IRMAA in full instead of an expected value.
  3. After 2035 it extends the premium at its own 2034–2035 growth rate.

The first two are consistent with the gross series: per_capita_cost averages Part D spending over every enrollee, and the IRMAA is already gated on enrollment. Taking them is still the hub's call; the brief said to stop at such a choice.

Findings

P2-1. The IRMAA term counts on Part D take-up that the data may not set (check before landing)

policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py:47

premiums_per_enrollee excludes the Trustees' aggregate Part D IRMAA (Table V.E4), which only actual Part D enrollees pay. In its place, medicare_cost subtracts each person's own income_adjusted_part_d_premium_surcharge. That variable is defined_for = "medicare_part_d_enrolled", which adds takes_up_medicare_part_d_if_eligible, and that input has default_value = True (variables/gov/hhs/medicare/takes_up_medicare_part_d_if_eligible.py). Its own documentation says people covered by a retiree drug subsidy plan, FEHB, TRICARE for Life or the VA aren't Part D enrollees.

Failure scenario: unless the microdata assign take-up, every high-income enrollee pays the Part D IRMAA in the model. The model's aggregate Part D IRMAA then exceeds the Trustees' aggregate that the average premium excludes, and medicare_cost is too low for high-income enrollees without Part D. Witness (2025, age 70, single, $500k AGI in 2023, medicare_quarters_of_coverage: 0, so full Part A premium and top-tier IRMAA):

take-up default (true):  income_adjusted_part_d_premium_surcharge 1,029.60  medicare_cost 2,778.19
take-up false:           income_adjusted_part_d_premium_surcharge 0         medicare_cost 3,807.79  (= 2,778.19 + 1,029.60)

On main the Part D IRMAA isn't subtracted at all, so this PR makes the gap reachable. Within the model's own rules the treatment is right: an enrollee pays the surcharge and a non-enrollee doesn't. The open question is the data. In the impact run, compare the weighted sum of income_adjusted_part_d_premium_surcharge with Table V.E4's aggregate ($2.4 billion in 2025, per the PR's transcription). If the model is well above that, the data need Part D take-up before this lands. No code change is needed if they agree.

P2-2. The brief's "quote the published definition" step isn't done, and the definition sentence leaves out the Part B premium

PR body, "Which definition this follows". The brief asked each PR to name the published definition it follows and quote it. The body names CBO's household-income measure (CBO, January 2026, publication 61910) but quotes nothing, and the hand-off says cbo.gov was unreachable. I couldn't reach cbo.gov either (proxy 403), so nobody has read the source behind the claim that "its concept matches" CBO's.

The body also says "Where this PR differs is that it subtracts the person's own Part A premium and IRMAA, not averages", but the code also subtracts the person's own gross Part B premium (medicare_cost.py:34). Suggested replacement text, once the hub has read the CBO page:

Where this PR differs is that it subtracts the person's own Part A premium, gross Part B premium (including the Part B IRMAA) and Part D IRMAA, and only the Part D base premium as an average over enrollees.

P3-1. "Part D enrollee" means two different populations

policyengine_us/parameters/calibration/gov/hhs/medicare/part_d/enrollment_share.yaml:1 and premiums_per_enrollee.yaml:1

The PR's own comment says Table V.B3's Part D enrollment "includes retiree drug subsidy beneficiaries" (it equals Table IV.B7's total). The model's takes_up_medicare_part_d_if_eligible says retiree drug subsidy beneficiaries are not enrolled in Part D. medicare_cost reads only the product of the two series, which equals premium income net of IRMAA over all Medicare enrollees, so the denominators cancel and no output here is wrong.

A later reader could still go wrong by using enrollment_share as a take-up target for medicare_part_d_enrolled, or by multiplying premiums_per_enrollee by modeled Part D enrollees for an aggregate: take-up would come out too high and premiums too low. If the hub's reading of V.B3 confirms the RDS inclusion, suggested label text:

  • enrollment_share.yaml, label: Medicare Part D enrollment share, including retiree drug subsidy beneficiaries
  • premiums_per_enrollee.yaml, label: Medicare Part D premiums per Part D enrollee, counting retiree drug subsidy beneficiaries as enrollees

P3-2. The hand-off's mutant table overstates the YAML kill for the extension mutant

The hand-off says "Extend the premium series from 2034 instead of 2035 | 3 failed | killed". The Python file kills it, but the YAML file doesn't: no medicare_cost.yaml case is dated after 2030. My run of that mutant on medicare_cost.yaml gave 12 passed. This is a claim about the tests, not a code defect.

P3-3. The Hypothesis consumer test re-implements the formula

policyengine_us/tests/core/test_medicare_cost_part_d_premium.py:239-272

test_medicare_cost_matches_reference_and_bounds checks medicare_cost against _reference_medicare_cost, which is the same expression as the variable. CLAUDE.md asks contributors not to "duplicate a policy formula in Python merely to test it against itself". Every mutant this test kills (M1, M2, M4, M6 below) is also killed by medicare_cost.yaml. The test does add two invariants YAML can't state over a range: net ≤ gross for random premiums, and the dollar-for-dollar IRMAA slope. So I'd keep it, but the hub may prefer to drop the reference-equality assertion.

Cost, measured locally on this VM rather than in Linux CI: the whole file took 19.3 s inside pytest, 71 s wall including uv run start-up, and 1.19 GB peak RSS. It ran green in CI's rest Python shards.

Mutants (one process each, on the working tree, restored after each; git status clean at the end)

Mutant test_medicare_cost_part_d_premium.py medicare_cost.yaml
M1 (requested): scale the IRMAA by the enrollment share 1 failed (test_medicare_cost_matches_reference_and_bounds) 3 failed
M2: drop the share (premium per Part D enrollee for everyone) 1 failed (same test) 9 failed
M3: extend the premium from 2034 instead of 2035 3 failed (test_premium_is_…[2035], test_premiums_and_irmaa_return_…[2035], test_extension_rate_follows_…) 0 failed (12 passed): survives
M4 (mine): drop the IRMAA term 1 failed (consumer test) 3 failed
M5 (mine): subtract the average premium only for medicare_part_d_enrolled 0 failed (85 passed): survives 1 failed
M6 (mine): remove the zero floor 1 failed (consumer test) 1 failed

The YAML runner's failure lines carry no case names. I inferred them from which cases have the input each mutant touches:

  • M1 and M4: the three cases with nonzero Part D IRMAA (unit tests 3, 7 and 9).
  • M5: unit test 12, the only non-enrollee.
  • M6: unit test 10, the floor case.

M5 is the alternative reading of methodology choice 1, and only YAML unit test 12 pins the PR's reading. That's enough, but it is the only guard.

Command per mutant: python3 mut.py <name>, then uv run pytest -q -p no:cacheprovider policyengine_us/tests/core/test_medicare_cost_part_d_premium.py, then uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml -c policyengine_us, then git checkout -- policyengine_us.

Checked and sound

  • The defect reproduces at main (6145ad4513). Main's medicare_cost subtracts no Part D premium. One enrollee with premium-free Part A and standard Part B, main against head:

    Year Main Head Difference Hand value
    2015 10,147.00 9,938.33 208.67 0.7517 × 277.60
    2020 11,765.00 11,543.98 221.02 0.7743 × 285.44
    2025 15,531.00 15,350.59 180.41 0.8191 × 220.25
    2026 16,548.20 16,310.72 237.48 0.8195 × 289.79
    2030 21,354.20 20,820.22 533.98 0.8382 × 637.06
    2035 29,329.55 28,716.89 612.66 0.8485 × 722.05
    2036 31,053.67 30,420.87 632.80 0.8485 × 722.05 × (722.05/699.07)
    2050 67,754.23 66,752.21 1,002.0 0.8543 × 722.05 × 1.03287^15
    2101 983,920.69 978,814.94 5,105.75 0.8639 × the 2100 premium, held

    Every difference matches the hand value.

  • The definition at the head (medicare_cost.py:35-50). medicare_cost = max(per_capita_cost − base_part_a_premium − gross_medicare_part_b_premium_if_enrolled − enrollment_share × premiums_per_enrollee − income_adjusted_part_d_premium_surcharge, 0). That matches the body. The IRMAA is subtracted in full and only the base premium is scaled.

  • Gating. income_adjusted_part_d_premium_surcharge is defined_for = "medicare_part_d_enrolled". That variable is defined_for = "medicare_enrolled" and adds = ["takes_up_medicare_part_d_if_eligible"]. Witness (2025, single, $500k AGI): the surcharge is $1,029.60 with take-up and $0 without, and medicare_cost moves by exactly $1,029.60. A non-enrollee pays no Part D IRMAA.

  • YAML hand values. I recomputed every changed case before reading the test comments:

    • Case 1: 17,751 − 2,220 − 180.41 = 15,350.59.
    • Case 2: 17,751 − 6,216 − 2,220 − 180.41 = 9,134.59.
    • Case 3: 17,751 − 7,546.80 − 180.41 − 85.80×12 = 8,994.19.
    • Case 5: 14,293 − 1,782 − 0.7811×306.15 = 12,271.87.
    • Case 6: 17,751 − 3,000 − 180.41 = 14,570.59.
    • Case 7: 17,751 − 2,220 − 180.41 − 35.30×12 = 14,926.99.
    • Case 8: 20,096 − 2,500 − 0.8255×373.53 = 17,287.65.
    • Case 9: 23,789 − 3,000 − 0.8382×637.06 − 1,000 = 19,255.02.
    • Case 12: 17,751 − 7,546.80 − 180.41 = 10,023.79.

    All match the expected outputs. The test_part_b_msp_offset.py values (15,350.59 and 4,440 → 13,130.59) match too.

  • Bounds. medicare_cost can't go negative because of the max_(…, 0) floor (main has it too), and it can't exceed medicare_gross_cost. With formula-derived premiums the floor never binds. My fully loaded case has full Part A premium (0 quarters), single filer, $5M AGI two years prior, so top-tier Part B and Part D IRMAA. At the head, medicare_cost is 2,369.24 in 2024 and 2,778.19 in 2025 (17,751 − 6,216 − 7,546.80 − 1,029.60 − 180.41 = 2,778.19). With Project Medicare premiums and IRMAA past the last announced year #10103 merged, its smallest value from 2024 to 2045 is 1,813.42 in 2030 (23,789 − 8,328 − 10,425.60 − 2,688 − 533.98).

    So premiums exceed spending only through direct input, as in YAML unit test 10 (Part B $30,000 → 0). I think the floor is right for a value-of-coverage measure; letting it go negative would only matter for aggregate premium accounting, and that path is unreachable in 2024–2045.

  • Extension. uprating_extensions.py extends premiums_per_enrollee from last_projected_year=2035 to END_YEAR. The 2036, 2050 and 2101 values above follow the closed form 722.05 × (722.05/699.07)^(y−2035), held after 2100. The share holds each five-year Trustees point, so 2036 reads 0.8485 and 2050 reads 0.8543.

  • Internal consistency of the series. The stored values reproduce the PR's own transcription of the cells. For example, 2025 is (14.9 − 2.4) × 10⁹ / 56,754,000 = 220.25; 2026 is (20.1 − 0.2 − 3.1) × 10⁹ / 57,973,000 = 289.79; and the 2025 share is 56,754 / 69,289 = 0.8191. The transcription and conservation tests (85 passed) check exactly this.

  • Tests run (head, one file per process; times are local wall clock including uv run start-up):

    File Result Time Peak RSS
    tests/core/test_medicare_cost_part_d_premium.py 85 passed 71 s 1.19 GB
    tests/core/test_medicare_per_capita_cost.py 223 passed 87 s 1.60 GB
    tests/policy/baseline/gov/hhs/medicare/test_part_b_msp_offset.py 14 passed 112 s 2.11 GB
    tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml 12 passed 128 s 2.30 GB
  • Readers. Nothing in policyengine_us/ reads medicare_cost besides its own tests. monthly_medicare_cost in qmb_cost_sharing.py is an unrelated local name. Only medicare_cost and uprating_extensions.py read the two new parameters. At baseline the diff moves medicare_cost for every Medicare enrollee, by −share × premium − own Part D IRMAA, and nothing else in this repo. The population check: the aggregate fall in medicare_cost should be close to Part D premium income (Table III.D3, $14.9 billion in 2025 per the PR's transcription) scaled to the model's enrollee count, plus the model's aggregate Part D IRMAA (see P2-1).

  • Count Medicare net of premiums in CBO household income #10118. Nothing in this PR depends on it. Count Medicare net of premiums in CBO household income #10118's cbo_medicare_benefit reads its own premium_income_per_enrollee series, not this PR's part_d parameters. Count Medicare net of premiums in CBO household income #10118's head (3d8bb6d643) sits on this branch's pre-merge commit 4024d2ec08, whose medicare_cost still scales the IRMAA by the share. When it rebases it should take this branch's version. Its trial merge conflicts only in cbo_social_insurance_benefits.yaml.

  • Project Medicare premiums and IRMAA past the last announced year #10103. A trial merge with this head (pr-10103 at 01cc340a38) is clean. On the merged tree, medicare_cost.yaml gave 12 passed and test_medicare_cost_part_d_premium.py gave 85 passed, which matches the body's claim.

  • Charge the Medicare Part B premium only to people who pay it #10634 (open, not draft) changes gross_medicare_part_b_premium_if_enrolled, which medicare_cost subtracts, and edits test_part_b_msp_offset.py. A trial merge is clean. On the merged tree, test_part_b_msp_offset.py gave 15 passed and medicare_cost.yaml gave 12 passed.

  • Body and docs. Each of these claims checks out:

    • the medicare_cost documentation string matches the code;
    • "No other variable reads medicare_cost";
    • "about 82%" (share 0.8191 in 2025);
    • the hand table, with "Before" values matching main's YAML;
    • "85 cases";
    • the seven invariants, each mapped to a test in the file.

    The impact: and axiom: lines are present. The changelog fragment is a top-level changelog.d/medicare-cost-part-d-premiums.fixed.md.

  • References. The reference format follows CLAUDE.md: one entry per source, the href opens the first cited page, and multi-page titles are quoted with #page ….

  • Scope. Nothing under .github/ or policyengine_us/tests/policy/baseline/partners/.

  • Overlap search. I searched open PRs for medicare_cost, premiums_per_enrollee, enrollment_share and uprating_extensions. Besides Project Medicare premiums and IRMAA past the last announced year #10103, Count Medicare net of premiums in CBO household income #10118 and Charge the Medicare Part B premium only to people who pay it #10634 above:

    Whichever of these lands second must take this PR's IRMAA-in-full form.

What I couldn't check

  • The Trustees cells. cms.gov, govinfo.gov, uscode.house.gov, ecfr.gov, cbo.gov and ssa.gov all returned 403 at this VM's proxy. I couldn't read the 2026 Trustees Report, so I couldn't check any of these against it:

    • premium income in Table III.D3, its footnotes 1 and 6, and the Dec. 31 credits for 2015, 2020 and 2026 (nor whether a Dec. 31, 2014 credit should be added to 2015);
    • aggregate IRMAA in Table V.E4;
    • enrollment in Table V.B3, and whether it includes RDS beneficiaries;
    • Table V.D1, and the page-109 and page-154 explanations of the 2025 dip and 2030 jump.

    The transcription and conservation tests compare the parameters to cells an earlier session recorded, not to the report. The hub needs to check these cells before landing.

  • Whether NHE Medicare spending is gross of Part D premiums paid directly to plans. The premium income here includes premiums "paid directly to Part D plans" (the PR quotes footnote 1). If the NHE Medicare row that per_capita_cost uses counts only federal payments for Part D, that part is subtracted twice. Main's per_capita_cost.yaml comment says premiums "finance part of that spending rather than reduce it" (NHE methodology, file pages 15, 23 and 30), but I couldn't open that paper to see whether it covers direct-to-plan Part D premiums.

  • The CBO definition in publication 61910 (P2-2), and the 42 U.S.C. 1395w-113(c)(4) citation.

  • Whether microdata assign takes_up_medicare_part_d_if_eligible (P2-1), and the population impact. Both need data I can't reach.


Generated by Claude Code

MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
…re-net-of-premiums

Brings in current main via #10111. Main renamed unemployment_compensation
to total_unemployment_compensation in the CBO social insurance list, so the
list now reads social_security, cbo_medicare_benefit,
total_unemployment_compensation, workers_compensation.

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

DTrim99 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Subtract the expected Part D premium from medicare_cost

Summary

medicare_cost is PolicyEngine's net value of Medicare coverage: calibration.gov.hhs.medicare.per_capita_cost (CMS National Health Expenditure Medicare spending per enrollee, covering Parts A–D) less the person's Part A premium and gross Part B premium, floored at zero. Nothing in the model reads it. It isn't in household_health_benefits, SPM resources, MOOP, the ACA calculation or any state program, and the CBO social-insurance list uses medicare_gross_cost. So the PR changes this one output for every Medicare enrollee and nothing downstream.

The PR also subtracts enrollment_share × premiums_per_enrollee, the average Part D base premium per Medicare enrollee, plus the person's own income_adjusted_part_d_premium_surcharge (Part D IRMAA). Both series come from the 2026 Medicare Trustees Report.

I checked the sources before the code:

  • Every cell matches the report. The author's comments say no session had read the 2026 Trustees Report against the stored values. I downloaded it; its sha256 (ffa56b91…48fa8) matches the parameter comments. Every premium-income, footnote-6, IRMAA and enrollment cell the PR uses is in the report, and all 21 premium values and 34 share values recompute exactly from them.
  • No double subtraction. CMS spending is gross of Part D premiums, including those paid directly to plans, so subtracting them doesn't take anything out twice (details under Verified correct).
  • The statute supports the IRMAA treatment. It is right to subtract the person's own IRMAA in full, gated on Part D enrollment: 42 U.S.C. 1395w-113(a)(7) and (c)(4).

The design is sound and every expectation I hand-checked is right. Findings: no critical findings and one should-address item. The axiom: line isn't a valid form. Because medicare_cost is a modeled valuation and not a legal provision, n/a: <reason> is the right form. The rest are suggestions. CI is green at the head: 36 pass, and the 3 skipped jobs come from an earlier run at the same head, made while the PR was a draft.

Critical

None.

Should address

1. The axiom: line isn't a valid form. This PR needs n/a, not a pe-parity issue (PR body, last lines)

The body reads axiom: (hub files pe-parity before landing). That isn't one of the forms in CLAUDE.md / CONTRIBUTING.md#axiom-parity (encoded-correct, <rulespec PR> encoded, <issue> queued, n/a: <reason>). It also points the wrong way, because no legal provision here needs encoding:

  • The measure. medicare_cost is PolicyEngine's valuation of Medicare coverage: average CMS spending per enrollee net of premiums. No statute or regulation defines it.
  • The new series. The two new series are empirical Trustees aggregates under calibration/ (premium income net of IRMAA ÷ Part D enrollment, and Part D ÷ total enrollment). They are not statutory amounts.
  • The one legal quantity. The only legal quantity involved is the Part D IRMAA (42 U.S.C. 1395w-113(a)(7)). The PR reads it from the existing, unchanged income_adjusted_part_d_premium_surcharge.

A pe-parity issue would give the encoder nothing to encode. Suggested line:

axiom: n/a: medicare_cost is a modeled valuation (CMS Medicare spending per enrollee net of premiums) built from 2026 Medicare Trustees Report aggregates in the calibration tree; no statutory provision changes, and the Part D IRMAA it subtracts comes from the existing, unchanged income_adjusted_part_d_premium_surcharge

This is a body-only edit.

Suggestions

1. Say in the variable documentation that Extra Help (LIS) enrollees and non-Part-D enrollees net the average premium.

  • Who pays less. Under 42 U.S.C. 1395w-114(a)(1)(A), a full-subsidy individual (up to 150% FPL from 2024) gets "a premium subsidy equal to 100 percent of" the low-income benchmark premium. Table IV.B7 (file page 153) shows 14.2 million LIS enrollees among 56.8 million Part D enrollees in 2025. RDS, FEHB, TRICARE and VA beneficiaries pay no Part D premium at all.
  • What the PR does. It subtracts the same average from each of them: $180.41 in 2025 and $533.98 in 2030. That is correct in aggregate. LIS premium subsidies are Part D spending inside per_capita_cost and are not in Table III.D3 premium income, so the average spreads them over every enrollee. Distributionally, though, it understates the net value for low-income enrollees and overstates it for non-LIS Part D enrollees, who pay about $12.46B ÷ (56.75M − 0.7M RDS − 14.2M LIS) ≈ $298 a year in 2025.
  • What to change. The author lists this as judgment call 4, and Model the Medicare Part D low-income subsidy as a CBO means-tested transfer #10127 (draft) is where LIS gets modeled. One sentence in the medicare_cost documentation would keep readers from treating it as a person-level premium. Also keep in mind that once Model the Medicare Part D low-income subsidy as a CBO means-tested transfer #10127 counts LIS as a separate transfer, any measure that sums it with medicare_cost would count the LIS premium subsidy twice.

2. Check the Part D IRMAA aggregate in the impact run (data, not code). income_adjusted_part_d_premium_surcharge is defined for medicare_part_d_enrolled, which adds takes_up_medicare_part_d_if_eligible (default True). The current microdata don't assign that input. So every Medicare enrollee above the IRMAA thresholds pays Part D IRMAA in the model.

  • The Trustees' counts. 4.5 million Part D IRMAA payers in 2025 (Table V.E4, file page 218), against 5.1 million Part B IRMAA payers (Table V.E3).
  • The likely size. The modeled Part D aggregate will likely sit near the Part B count, roughly $0.3B above the $2.4B that premiums_per_enrollee nets out. That is small against Medicare spending.
  • The formula is right. It follows (a)(7): only enrollees pay. Comparing the weighted surcharge sum with V.E4 will show whether the data need Part D take-up.

3. The Hypothesis consumer test re-implements the formula (tests/core/test_medicare_cost_part_d_premium.py, _reference_medicare_cost and test_medicare_cost_matches_reference_and_bounds). The reference-equality assertion repeats medicare_cost's own expression, which CLAUDE.md's test-design section asks contributors not to do ("Do not duplicate a policy formula in Python merely to test it against itself").

  • YAML already pins it. medicare_cost.yaml pins the same behavior: IRMAA in full (cases 3, 7, 9 and 12), the floor (case 10) and the unchanged gross cost (case 11).
  • What's worth keeping. The invariants net ≤ gross and the dollar-for-dollar IRMAA slope are worth keeping. The transcription, extension and calendar tests on the parameters can't be written in YAML, so they belong in Python.
  • What to change. Consider dropping the equality assertion and keeping the invariants.
  • Cost. The test builds 25 two-person situations on the shared system. It loads no dataset and constructs no model, and it passed in CI's Rest Python shards.

4. Reference and label nits.

  • premiums_per_enrollee.yaml's comment cites Table IV.B7 (file page 153) for RDS inclusion, but the reference title's page list (#page 109,114,116-117,151,154,196,210,218-219) leaves out 153.
  • Table V.B3's Part D enrollment includes RDS beneficiaries. It equals the Table IV.B7 total, for example 43,198 thousand against 43.2 million (1.9 million RDS) in 2016. The model's takes_up_medicare_part_d_if_eligible excludes them. A label such as "Medicare Part D enrollment share, including retiree drug subsidy beneficiaries" would keep someone from using enrollment_share as a take-up target. medicare_cost reads only the product, so the denominators cancel and nothing here is wrong.

5. Body wording.

  • The body still opens with "Draft.", but the PR is ready for review.
  • "Where this PR differs is that it subtracts the person's own Part A premium and IRMAA, not averages" leaves out the person's own gross Part B premium, which the code also subtracts (medicare_cost.py, gross_medicare_part_b_premium_if_enrolled).
  • The CBO 61910 definition is named but not quoted. cbo.gov refused automated fetches here too, so I couldn't verify it.

Verified correct

  • Trustees cells (2026 report, sha256 matches the parameter comments). Each was read from the report:

    • Premium income, Table III.D3 (file page 116), 2015–2035: 12.7, 13.8, 15.5, 15.9, 15.7, 15.8, 17.0, 17.6, 18.3, 19.2, 14.9, 20.1, 25.6, 28.8, 30.5, 51.7, 54.9, 56.4, 59.5, 62.0, 65.3.
    • Footnote 6 (file page 117): about $0.2B credited Dec. 31, 2015, $0.1B on Dec. 31, 2020 and an estimated $0.2B on Dec. 31, 2026. Each is moved to the following year, and no Dec. 31, 2014 credit exists.
    • Aggregate IRMAA, Table V.E4 (file page 218): 0.9 … 2.4 (2025), 3.1, 3.5, 4.0, 4.6, 10.8 … 15.9 (2035).
    • Enrollment, Table V.B3 (file page 196). The PR's Part D and total enrollment rows agree with Table IV.B7's totals (2016 43.2M, 2024 55.2M, 2025 56.8M), from 2015 (41,786 / 55,589) through 2100 (89,095 / 103,133).
  • Stored values. All 21 premiums_per_enrollee values (2015 $277.60 … 2035 $722.05) recompute exactly as (accrual premium income − IRMAA) ÷ Part D enrollment. All 34 enrollment_share values recompute exactly to 4 dp. The footnote-6 shift is what separates 2015 ($277.60 against $282.39 unshifted), 2016, 2020, 2021, 2026 ($289.79 against $293.24) and 2027.

  • IRMAA is inside premium income, so netting V.E4 out is right. Table III.D1 (file page 109) splits 2025's $14.86B of premiums into $6.01B withheld from benefits and $8.85B paid directly to plans. File page 151 says about 29% of non-LIS premium dollars are withheld: 0.29 × $12.46B = $3.61B. Add the $2.4B IRMAA and you get $6.0B. File page 114 says: "The extra premiums are credited to the Part D trust fund account and reduce the government contribution."

  • Spending is gross of Part D premiums, so nothing is subtracted twice.

    • Table III.D3 footnote 4: benefit payments include "Part D drug premiums collected from beneficiaries".
    • File page 114: "the Trustees supplement these expenditures to include the amount of enrollee premiums paid directly to Part D plans".
    • NHE Methodology Paper (sha256 matches per_capita_cost.yaml), file page 15: "incurred benefit spending data from the annual Medicare Trustees Report is used". File pages 29–30 treat SMI premiums (Part B and Part D) as household financing of Medicare, not as reductions in it.
    • This settles the open question in the author's review comment.
  • Statute.

    • 42 U.S.C. 1395w-113(a)(7) raises "the monthly amount of the beneficiary premium applicable under this section", so only Part D enrollees owe it. That matches defined_for = "medicare_part_d_enrolled", and subtracting the person's own IRMAA in full, not scaled by the share, is right.
    • (c)(4): IRMAA "shall be paid through withholding from benefit payments". This corroborates the new reference.
    • The 2030 jump is the IRA base-premium reset: "substantial premium increase from $46.44 in 2029 to $68.93 in 2030" (file page 154).
    • The 2025 dip is the PDP premium demonstration (file page 109).
    • The extension growth rate is 722.05/699.07 = 1.03287, against Table V.D1's Part D cost growth of 5,074/4,919 = 1.03151 (file page 210).
    • The test's average paid premiums, $23.95 (2025) and $27.22 (2026), are on file page 217. The series' monthly $18.35 and $24.15 sit below them.
  • Values by year (premium per Part D enrollee × share = average per Medicare enrollee):

    Year Calculation Average
    2015 277.60 × 0.7517 208.67
    2020 285.44 × 0.7743 221.02
    2024 307.70 × 0.8117 249.76
    2025 220.25 × 0.8191 180.41
    2026 289.79 × 0.8195 237.48
    2027 373.53 × 0.8255 308.35
    2030 637.06 × 0.8382 533.98
    2035 722.05 × 0.8485 612.66

    In 2025, base premiums plus IRMAA come to ($12.5B + $2.4B) ÷ 69.3M ≈ $215 per enrollee, 1.2% of $17,751, in line with Part D premiums' share of Medicare spending.

  • Hand checks (inputs: per_capita_cost 2021 $14,293, 2025 $17,751, 2027 $20,096, 2030 $23,789; 2025 Part B $185 × 12 = $2,220; Part A full $518 × 12 = $6,216; top-tier Part B $628.90 × 12 = $7,546.80; Part D IRMAA $85.80 × 12 = $1,029.60 at the top tier and $35.30 × 12 = $423.60 at the 50% tier, as in Table V.E4; 2021 Part B $148.50 × 12 = $1,782):

    Case Arithmetic Result Matches
    YAML 1 17,751 − 2,220 − 180.4068 15,350.59 yes
    YAML 2 17,751 − 6,216 − 2,220 − 180.41 9,134.59 yes
    YAML 3 17,751 − 7,546.80 − 180.41 − 1,029.60 8,994.19 yes
    YAML 5 14,293 − 1,782 − 0.7811 × 306.15 (239.13) 12,271.87 yes, within 0.01
    YAML 6 17,751 − 3,000 − 180.41 14,570.59 yes
    YAML 7 17,751 − 2,220 − 180.41 − 423.60 14,926.99 yes
    YAML 8 20,096 − 2,500 − 0.8255 × 373.53 (308.35) 17,287.65 yes
    YAML 9 23,789 − 3,000 − 0.8382 × 637.06 (533.98) − 1,000 19,255.02 yes
    YAML 10 17,751 − 30,000 − … < 0 0 yes
    YAML 11 medicare_gross_cost is unchanged 17,751 yes
    YAML 12 17,751 − 7,546.80 − 180.41, IRMAA 0 without take-up 10,023.79 yes
    test_part_b_msp_offset.py 17,751 − 2,220 − 180.41 15,350.59 yes
    test_part_b_msp_offset.py 17,751 − 4,440 − 180.41 13,130.59 yes
  • Mutation rule. Every new term is pinned:

    • the share factor, by YAML 1, 2, 5–9 and 12;
    • the IRMAA term, by YAML 3, 7 and 9;
    • non-enrollees still netting the average, by YAML 12;
    • every stored value and the footnote-6 shift, by the parametrized transcription tests;
    • the 2035 extension start, by the Python extension tests.
  • Readers and overlap.

    • Nothing reads medicare_cost.
    • The model has no other Part D premium variable or parameter, so nothing is reinvented.
    • SPM premiums add the Part D IRMAA as an expense. That is no double count, because medicare_cost isn't a resource anywhere.
  • Code. The parameters are scalars that broadcast over people. The calculation reads January values for a YEAR period. The zero floor is kept. The extend_parameter_values call grows each year from 2036 to 2100 at the 2034–2035 rate and holds the 2100 value. FIRST_MODELED_YEAR is 2015, so the Part D series starting in 2015 leaves no modeled year undefined.

  • Housekeeping.

    • The changelog fragment is a top-level changelog.d/medicare-cost-part-d-premiums.fixed.md, and fixed fits.
    • The YAML is in the variable-named medicare_cost.yaml.
    • The reference format follows CLAUDE.md: one entry per source, the href opens the first page, and the multi-page title is quoted.
    • No partner-test files are touched.
    • No state-gated key variables.
    • The diff adds no conflict-sounding comments.
  • Body claims check out: the before/after table, "85 cases", "No other variable reads medicare_cost", "about 82%" (0.8191), and the seven invariants, each mapped to a test.

Axiom line assessment

Invalid as written; n/a is the right form (see Should address 1). The line axiom: (hub files pe-parity before landing) isn't an accepted form. The change is a valuation built from calibration data, not a legal provision. The only statutory input, the Part D IRMAA under 42 U.S.C. 1395w-113(a)(7), comes from an existing, unchanged variable. So no rulespec-us module or pe-parity issue applies. Replace the line with an n/a: line like the one suggested above.

CI status

gh pr checks 10111, re-checked at the end, shows 36 pass and 3 skipping, all at head 5c24d3faca.

  • Full run. Run 41719873923 is green: every Baseline and Contrib shard, Rest Python shards 1–9, Microsimulation, Household API Partners, PR validation, and Package and compatibility.
  • Skipped jobs. The 3 come from run 41717222137 at the same head, made while the PR was a draft.

No failures.

Branch status

Verdict: REQUEST_CHANGES

DTrim99
DTrim99 previously requested changes Nov 4, 2026

@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: 0 critical, 1 should. The design and all values check out (21 premium and 34 share values recompute from the 2026 Trustees Report, no double subtraction). Open: the axiom line isn't a valid form; since medicare_cost is a valuation built from Trustees aggregates, an n/a line applies (suggested wording in the report). Full report in the comment above.

MaxGhenis and others added 2 commits November 5, 2026 11:29
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Say in `medicare_cost`'s documentation that every enrollee nets the same average premium.
- Name retiree drug subsidy beneficiaries in both labels, and add page 153 to the premium reference.
- Drop the test's reference equality, keeping the bounds and the IRMAA slope.
- Add a 2036 case that pins where the premium's extension starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis dismissed DTrim99’s stale review November 5, 2026 16:29

Answered at 1b77458c7a. The axiom line reads n/a (should 1). The medicare_cost documentation and the body say every enrollee nets the same average Part D premium (suggestion 1). The hub's comparison on the default dataset will set the weighted surcharge against Table V.E4's aggregate (suggestion 2). The test keeps the bounds and the IRMAA slope and drops the reference equality (suggestion 3). The premium reference lists page 153, and both labels name the retiree drug subsidy count (suggestion 4). The body drops "Draft.", its definition section names the gross Part B premium, and it points to #10118's CBO quotes (suggestion 5).

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