Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/irmaa-lookback-fallback.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added gov.simulation.medicare_irmaa_uses_current_year_income, which sets Medicare Part B and Part D IRMAA from the benefit year's own MAGI (medicare_irmaa_magi, medicare_irmaa_tax_year_magi) for marginal tax rate and behavioral response analysis.
1 change: 1 addition & 0 deletions changelog.d/irmaa-lookback-fallback.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Medicare IRMAA no longer drops to zero when the year two years before the benefit year precedes every input (single-year datasets in their first two years, and households entered for one year): the earliest input year's MAGI stands in, deflated to that year by per-capita CBO AGI. For a household entered for one year this imputes the lookback income from that year's income, so its IRMAA now moves with that income.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
description: PolicyEngine sets Medicare income-related monthly adjustment amounts from the benefit year's own modified adjusted gross income, rather than the income from two years earlier that the law uses, if this is true.
# The law sets IRMAA on income from two years before the benefit year, so a
# change in this year's earnings meets the surcharge two years later. Marginal
# tax rate and behavioral response analyses that treat a lasting earnings
# change as meeting the surcharge in the same year turn this on. Even when it
# is off, a simulation whose earliest input year is the benefit year (a
# household entered for one year) imputes the income two years prior from the
# benefit year's income, deflated, so its charges already move with that
# income; see medicare_irmaa_magi_two_years_prior.
values:
0000-01-01: false
metadata:
unit: bool
period: year
label: Set Medicare IRMAA from current-year income
reference:
- title: 42 U.S.C. 1395r(i)(4)(B)
href: https://www.law.cornell.edu/uscode/text/42/1395r#i_4_B
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# The IRMAA brackets apply to MAGI from two years before the benefit year
# unless gov.simulation.medicare_irmaa_uses_current_year_income is true, when
# they apply to the benefit year's own MAGI.

- name: By default the 2025 charges use 2023 income.
period: 2025
input:
age: 66
employment_income:
2023: 50_000
2025: 300_000
output:
medicare_irmaa_magi: 50_000
# Below 106,001: the $185 standard premium only.
gross_medicare_part_b_premium: 2_220
income_adjusted_part_d_premium_surcharge: 0

- name: With the switch on the 2025 charges use 2025 income.
period: 2025
input:
gov.simulation.medicare_irmaa_uses_current_year_income: true
age: 66
employment_income:
2023: 50_000
2025: 300_000
output:
medicare_irmaa_magi: 300_000
# 2025 single, 200,001 up to 500,000: 12 x (185 + 406.90) = 7,102.80.
gross_medicare_part_b_premium: 7_102.8
# Part D: 12 x 78.60.
income_adjusted_part_d_premium_surcharge: 943.2

- name: With the switch on a provided lag MAGI does not set the charges.
period: 2025
input:
gov.simulation.medicare_irmaa_uses_current_year_income: true
people:
person:
age: 66
employment_income: 50_000
tax_units:
tax_unit:
members: [person]
medicare_irmaa_magi_two_years_prior: 300_000
households:
household:
members: [person]
output:
medicare_irmaa_magi: 50_000
gross_medicare_part_b_premium: 2_220

- name: A joint return's MAGI applies to both spouses with the switch on.
period: 2026
input:
gov.simulation.medicare_irmaa_uses_current_year_income: true
people:
head:
age: 70
employment_income: 150_000
spouse:
age: 68
employment_income: 150_000
tax_units:
tax_unit:
members: [head, spouse]
filing_status: JOINT
marital_units:
marital_unit:
members: [head, spouse]
households:
household:
members: [head, spouse]
output:
medicare_irmaa_magi: 300_000
# 2026 joint, 274,001 up to 342,001: $202.90 + $202.90 a month each.
gross_medicare_part_b_premium: [4_869.6, 4_869.6]
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
# IRMAA uses MAGI from two years before the benefit year. PolicyEngine models
# 2015 onward, so for 2015 and 2016 benefit years the income year (2013, 2014)
# is outside the model: only income provided for that year counts, and the
# MAGI is otherwise zero.
# IRMAA uses MAGI from two years before the benefit year. When the simulation
# holds no input for that year or any earlier one, the MAGI of the earliest
# year with inputs (no earlier than 2015, the first modeled year) stands in,
# deflated to the year two years prior by
# calibration.gov.cbo.income_by_source.adjusted_gross_income_per_capita (CBO
# AGI divided by population). That index starts in 2015 and is held at its
# 2015 value before then:
# 2015: 9,033,840,000,000 / 326,078,000 = 27,704.54
# 2016: 9,033,840,000,000 / 328,964,000 = 27,461.49
# 2022: 14,821,800,000,000 / 339,654,000 = 43,637.94
# 2023: 15,347,400,000,000 / 342,835,000 = 44,766.14
# 2024: 16,685,900,000,000 / 346,588,000 = 48,143.33
# For 2015 and 2016 benefit years the income year (2013, 2014) precedes the
# model: income provided for it counts, and only that.

- name: Case 1, 2015 benefit year reads no 2013 income.
- name: Case 1, 2015 benefit year with only 2015 inputs uses 2015 income.
period: 2015
input:
age: 66
employment_income: 500_000
output:
medicare_irmaa_magi_two_years_prior: 0
# 500,000 x 27,704.54 (2013, held at 2015) / 27,704.54 (2015).
medicare_irmaa_magi_two_years_prior: 500_000

- name: Case 2, 2016 benefit year reads no 2014 income.
- name: Case 2, 2016 benefit year with only 2016 inputs uses 2016 income.
period: 2016
absolute_error_margin: 0.01
input:
age: 66
employment_income: 500_000
output:
medicare_irmaa_magi_two_years_prior: 0
# 500,000 x 27,704.54 (2014, held at 2015) / 27,461.49 (2016).
medicare_irmaa_magi_two_years_prior: 504_425.34

- name: Case 3, 2015 benefit year counts 2013 income that is provided.
period: 2015
Expand Down Expand Up @@ -52,28 +65,153 @@
output:
medicare_irmaa_magi_two_years_prior: 155_000

- name: Case 6, 2015 Part B premium is the base premium with no IRMAA.
- name: Case 6, 2015 Part B premium has no IRMAA for 2015 income below the first threshold.
period: 2015
input:
age: 66
employment_income: 500_000
employment_income: 100_000
is_medicare_eligible: true
# 2015 standard Part B premium of $104.90 per month, supplied directly so
# the case tests only the IRMAA amount.
base_part_b_premium: 1_258.8
output:
medicare_irmaa_magi_two_years_prior: 100_000
gross_medicare_part_b_premium: 1_258.8

- name: Case 7, 2016 Part D IRMAA is zero for an enrolled high earner.
- name: Case 7, 2016 Part D IRMAA is zero for an enrolled earner below the first threshold.
period: 2016
absolute_error_margin: 0.01
input:
age: 66
employment_income: 500_000
employment_income: 100_000
is_medicare_eligible: true
medicare_enrolled: true
output:
# 100,000 x 27,704.54 / 27,461.49, under the first threshold of 103,001.
medicare_irmaa_magi_two_years_prior: 100_885.07
income_adjusted_part_d_premium_surcharge: 0

- name: A 2025 household with only 2024 inputs has IRMAA set on deflated 2024 income.
period: 2025
absolute_error_margin: 0.01
input:
people:
person:
age:
2024: 66
employment_income:
2024: 200_000
tax_units:
tax_unit:
members: [person]
households:
household:
members: [person]
state_code:
2024: TX
output:
# 2024 MAGI of 200,000 x 44,766.14 (2023) / 48,143.33 (2024).
medicare_irmaa_magi_two_years_prior: 185_970.28
medicare_irmaa_magi: 185_970.28
# 2025 single brackets: 167,001 up to 200,001 adds $295.90 a month to the
# $185 standard premium: 12 x (185 + 295.90) = 5,770.80.
gross_medicare_part_b_premium: 5_770.8
# Part D adds $57.00 a month in the same bracket.
income_adjusted_part_d_premium_surcharge: 684

- name: The same household in 2026 reads its 2024 income without deflation.
period: 2026
absolute_error_margin: 0.01
input:
people:
person:
age:
2024: 66
employment_income:
2024: 200_000
tax_units:
tax_unit:
members: [person]
households:
household:
members: [person]
state_code:
2024: TX
output:
medicare_irmaa_magi_two_years_prior: 200_000
# 2026 single brackets: 171,001 up to 205,001 adds $324.60 a month to the
# $202.90 standard premium: 12 x (202.90 + 324.60) = 6,330.
gross_medicare_part_b_premium: 6_330
income_adjusted_part_d_premium_surcharge: 724.8

- name: The data year itself as the benefit year looks back to deflated income too.
period: 2024
# MAGI is stored as float32, so allow for its rounding.
absolute_error_margin: 0.1
input:
people:
person:
age:
2024: 66
employment_income:
2024: 200_000
tax_units:
tax_unit:
members: [person]
households:
household:
members: [person]
state_code:
2024: TX
output:
# 200,000 x 43,637.94 (2022) / 48,143.33 (2024).
medicare_irmaa_magi_two_years_prior: 181_283.42
# 2024 single brackets: 161,001 up to 193,001 adds $279.50 a month to the
# $174.70 standard premium: 12 x (174.70 + 279.50) = 5,450.40.
gross_medicare_part_b_premium: 5_450.4

- name: Income provided for the year two years prior is used as given.
period: 2025
input:
people:
person:
age:
2024: 66
employment_income:
2024: 200_000
tax_units:
tax_unit:
members: [person]
adjusted_gross_income:
2023: 50_000
households:
household:
members: [person]
output:
medicare_irmaa_magi_two_years_prior: 50_000
gross_medicare_part_b_premium: 2_220

- name: A provided IRMAA MAGI overrides the fallback.
period: 2025
input:
people:
person:
age:
2024: 66
employment_income:
2024: 200_000
tax_units:
tax_unit:
members: [person]
medicare_irmaa_magi_two_years_prior:
2025: 90_000
households:
household:
members: [person]
output:
medicare_irmaa_magi: 90_000
gross_medicare_part_b_premium: 2_220

- name: Amounts excluded under IRC sections 135, 911, 931 and 933 are added back.
period: 2026
input:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Households with a member aged 65 or older in 2015 and 2016. Medicare IRMAA
# looks two years back, to 2013 and 2014, which precede the first modeled year;
# these calculations raised ParameterNotFoundError before (issue #9613).
# these calculations raised ParameterNotFoundError before (issue #9613). With
# inputs only for the benefit year, the IRMAA MAGI is that year's MAGI deflated
# to two years earlier, far below the first IRMAA threshold here.
#
# Single 66-year-old in Texas with $10,000 of wages:
# - Payroll tax: 7.65% x $10,000 = $765.
Expand Down Expand Up @@ -30,7 +32,8 @@
members: [person]
state_code: TX
output:
medicare_irmaa_magi_two_years_prior: 0
# 10,000 x 27,704.54 (2013, held at 2015) / 27,704.54 (2015).
medicare_irmaa_magi_two_years_prior: 10_000
income_tax: 0
ssi: 4_306
snap: 192
Expand All @@ -49,7 +52,8 @@
members: [person]
state_code: TX
output:
medicare_irmaa_magi_two_years_prior: 0
# 10,000 x 27,704.54 (2014, held at 2015) / 27,461.49 (2016).
medicare_irmaa_magi_two_years_prior: 10_088.51
income_tax: 0
ssi: 4_306
snap: 192
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Issue #9613: a 2015 or 2016 household with a member aged 65 or older raised
ParameterNotFoundError because the IRMAA MAGI read 2013 or 2014 income, before
the first year the parameters are backdated to.
the first year the parameters are backdated to. With inputs only for the
benefit year, the IRMAA MAGI is that year's MAGI deflated to two years
earlier; with inputs for 2013 or 2014, only the provided income counts.
"""

import pytest
Expand Down Expand Up @@ -62,7 +64,7 @@ def test_first_modeled_year_is_the_backdating_floor():
@pytest.mark.parametrize("year", UNMODELED_LAG_YEARS)
@pytest.mark.parametrize("filing_status", FILING_STATUSES)
@pytest.mark.parametrize("employment_income", INCOMES)
def test_magi_is_zero_when_lag_precedes_the_model(
def test_benefit_year_income_stands_in_when_lag_precedes_the_model(
year, filing_status, employment_income
):
sim = senior_household(
Expand All @@ -74,13 +76,24 @@ def test_magi_is_zero_when_lag_precedes_the_model(
},
tax_unit_inputs={"filing_status": {year: filing_status}},
)
assert sim.calculate("medicare_irmaa_magi_two_years_prior", year)[0] == 0
# With a zero MAGI, no IRMAA is added to the base Part B premium and no
# Part D surcharge applies.
index = SYSTEM.parameters.calibration.gov.cbo.income_by_source.adjusted_gross_income_per_capita
# The index starts in 2015 and is held at that value for 2013 and 2014.
deflator = index(f"{FIRST_MODELED_YEAR}-01-01") / index(f"{year}-01-01")
benefit_year_magi = sim.calculate("medicare_irmaa_tax_year_magi", year)[0]
magi = sim.calculate("medicare_irmaa_magi_two_years_prior", year)[0]
assert magi == pytest.approx(benefit_year_magi * deflator, rel=1e-6)
# The brackets apply to that MAGI: the Part B premium adds the IRMAA
# amount to the base premium, and Part D charges its surcharge.
p = SYSTEM.parameters(f"{year}-01-01").gov.hhs.medicare
scale = filing_status.lower()
gross = sim.calculate("gross_medicare_part_b_premium", year)[0]
base = sim.calculate("base_part_b_premium", year)[0]
assert gross == pytest.approx(base)
assert sim.calculate("income_adjusted_part_d_premium_surcharge", year)[0] == 0
assert gross == pytest.approx(
base + 12 * getattr(p.part_b.irmaa, scale).calc(magi), abs=0.01
)
assert sim.calculate("income_adjusted_part_d_premium_surcharge", year)[
0
] == pytest.approx(12 * getattr(p.part_d.irmaa, scale).calc(magi), abs=0.01)


@pytest.mark.parametrize("year", MODELED_LAG_YEARS)
Expand Down
Loading
Loading