diff --git a/changelog.d/irmaa-lookback-fallback.added.md b/changelog.d/irmaa-lookback-fallback.added.md new file mode 100644 index 00000000000..67101266cb2 --- /dev/null +++ b/changelog.d/irmaa-lookback-fallback.added.md @@ -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. diff --git a/changelog.d/irmaa-lookback-fallback.fixed.md b/changelog.d/irmaa-lookback-fallback.fixed.md new file mode 100644 index 00000000000..7d20969f703 --- /dev/null +++ b/changelog.d/irmaa-lookback-fallback.fixed.md @@ -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. diff --git a/policyengine_us/parameters/gov/simulation/medicare_irmaa_uses_current_year_income.yaml b/policyengine_us/parameters/gov/simulation/medicare_irmaa_uses_current_year_income.yaml new file mode 100644 index 00000000000..ead9b664b0d --- /dev/null +++ b/policyengine_us/parameters/gov/simulation/medicare_irmaa_uses_current_year_income.yaml @@ -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 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi.yaml new file mode 100644 index 00000000000..b40599ff477 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi.yaml @@ -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] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml index 4fb58ad3f70..bcda1dddd8a 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.yaml @@ -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 @@ -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: diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/senior_household_2015_2016_integration.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/senior_household_2015_2016_integration.yaml index a4837d3c5c5..c940dcd51f4 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/senior_household_2015_2016_integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/senior_household_2015_2016_integration.yaml @@ -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. @@ -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 @@ -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 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py index 3c3c41f93f9..d2a94141895 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_first_modeled_year.py @@ -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 @@ -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( @@ -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) diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_lookback_and_switch.py b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_lookback_and_switch.py new file mode 100644 index 00000000000..14eb48e698c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/test_irmaa_lookback_and_switch.py @@ -0,0 +1,401 @@ +"""Medicare IRMAA's income lookback fallback and same-year income switch. + +The law sets IRMAA on MAGI from two years before the benefit year. When a +simulation holds no input for that year or any earlier one (a single-year +dataset in its data year and the year after, or a household entered for one +year), the MAGI of the earliest year with inputs stands in, deflated to the +lookback year. gov.simulation.medicare_irmaa_uses_current_year_income sets the +charges from the benefit year's own MAGI instead. + +Invariants checked here, for every input drawn: +- the Part B IRMAA amount and the Part D surcharge never fall as MAGI rises; +- both are zero below the first IRMAA threshold and positive from it on; +- the fallback is the earliest input year's MAGI times one positive factor, + the same for every tax unit, so it preserves order and zero; +- provided inputs for the lookback year, or a provided IRMAA MAGI, win; +- the result does not depend on what else was calculated first; +- with the switch on, the charges follow the benefit year's income, so a + counterfactual earnings change in that year moves them, and with it off a + known lag income keeps them fixed. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.reforms import Reform + +from policyengine_us import CountryTaxBenefitSystem, Simulation +from policyengine_us.data.economic_assumptions import DEFAULT_MICRODATA_UPRATING +from policyengine_us.tools.general import earliest_input_year +from policyengine_us.tools.per_capita_uprating import per_capita_path +from policyengine_us.variables.gov.hhs.medicare.eligibility.medicare_irmaa_magi_two_years_prior import ( + LOOKBACK_DEFLATOR, +) +from policyengine_us.variables.household.marginal_tax_rate_helpers import ( + user_set_variables, +) + +SYSTEM = CountryTaxBenefitSystem() +SWITCH = "gov.simulation.medicare_irmaa_uses_current_year_income" +SAME_YEAR = Reform.from_dict({SWITCH: {"2015-01-01.2100-12-31": True}}, "us") +FILING_STATUSES = [ + "SINGLE", + "JOINT", + "HEAD_OF_HOUSEHOLD", + "SURVIVING_SPOUSE", + "SEPARATE", +] +# Years with brackets of their own; earlier years are backdated from 2024. +BRACKET_YEARS = [2024, 2025, 2026] +DEFLATOR = SYSTEM.parameters.calibration.gov.cbo.income_by_source.adjusted_gross_income_per_capita + + +def deflator(lag_year, source_year): + return DEFLATOR(f"{lag_year}-01-01") / DEFLATOR(f"{source_year}-01-01") + + +def senior(person_inputs, tax_unit_inputs=None, reform=None): + return Simulation( + situation={ + "people": {"person": {**person_inputs}}, + "tax_units": { + "tax_unit": {"members": ["person"], **(tax_unit_inputs or {})} + }, + "households": {"household": {"members": ["person"]}}, + }, + reform=reform, + ) + + +def part_b_irmaa(sim, year): + return sim.calculate("gross_medicare_part_b_premium", year) - sim.calculate( + "base_part_b_premium", year + ) + + +def part_d_irmaa(sim, year): + return sim.calculate("income_adjusted_part_d_premium_surcharge", year) + + +# Fallback + + +def test_deflator_is_the_dataset_extension_index(): + # Deflating runs the single-year dataset extension backward only while + # both use the same index. + assert LOOKBACK_DEFLATOR == per_capita_path(DEFAULT_MICRODATA_UPRATING) + + +def test_household_with_only_prior_year_inputs_pays_irmaa(): + sim = senior({"age": {2024: 66}, "employment_income": {2024: 200_000}}) + assert earliest_input_year(sim) == 2024 + magi = sim.calculate("medicare_irmaa_magi_two_years_prior", 2025)[0] + assert magi == pytest.approx(200_000 * deflator(2023, 2024), rel=1e-6) + assert part_b_irmaa(sim, 2025)[0] > 0 + assert part_d_irmaa(sim, 2025)[0] > 0 + + +def test_multi_year_inputs_use_the_fallback_only_before_the_first_year(): + # As in a dataset extended from 2024: inputs for 2024 and 2025. + sim = senior( + { + "age": {2024: 66, 2025: 67}, + "employment_income": {2024: 200_000, 2025: 250_000}, + } + ) + + def lag(year): + return sim.calculate("medicare_irmaa_magi_two_years_prior", year)[0] + + def tax_year(year): + return sim.calculate("medicare_irmaa_tax_year_magi", year)[0] + + assert lag(2025) == pytest.approx(tax_year(2024) * deflator(2023, 2024), rel=1e-6) + assert lag(2026) == pytest.approx(tax_year(2024)) + assert lag(2027) == pytest.approx(tax_year(2025)) + assert tax_year(2025) == pytest.approx(250_000) + + +def test_fallback_reads_nothing_before_the_first_input_year(): + sim = senior({"age": {2024: 66}, "employment_income": {2024: 200_000}}) + sim.calculate("gross_medicare_part_b_premium", 2025) + sim.calculate("income_adjusted_part_d_premium_surcharge", 2025) + for variable in ["adjusted_gross_income", "medicare_irmaa_tax_year_magi"]: + years = {period.start.year for period in sim.get_known_periods(variable)} + assert 2023 not in years, variable + + +@pytest.mark.parametrize( + "calculated_first", + [ + [], + [("adjusted_gross_income", 2023)], + [("medicare_irmaa_tax_year_magi", 2023)], + [("income_tax", 2023), ("income_tax", 2021)], + [("household_net_income", 2026), ("medicare_irmaa_magi", 2027)], + ], +) +def test_fallback_does_not_depend_on_calculation_order(calculated_first): + sim = senior({"age": {2024: 66}, "employment_income": {2024: 200_000}}) + for variable, year in calculated_first: + sim.calculate(variable, year) + assert earliest_input_year(sim) == 2024 + assert sim.calculate("medicare_irmaa_magi_two_years_prior", 2025)[ + 0 + ] == pytest.approx(200_000 * deflator(2023, 2024), rel=1e-6) + + +@pytest.mark.parametrize( + "tax_unit_inputs, expected", + [ + ({"adjusted_gross_income": {2023: 50_000}}, 50_000), + ({"medicare_irmaa_tax_year_magi": {2023: 60_000}}, 60_000), + ({"medicare_irmaa_magi_two_years_prior": {2025: 70_000}}, 70_000), + ], +) +def test_provided_lookback_income_is_authoritative(tax_unit_inputs, expected): + sim = senior( + {"age": {2024: 66}, "employment_income": {2024: 500_000}}, + tax_unit_inputs, + ) + assert sim.calculate("medicare_irmaa_magi", 2025)[0] == pytest.approx(expected) + assert part_b_irmaa(sim, 2025)[0] == 0 + + +@settings( + max_examples=10, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + st.lists( + st.integers(min_value=-50_000, max_value=2_000_000), min_size=2, max_size=12 + ), + st.sampled_from([2024, 2025]), +) +def test_fallback_scales_every_tax_unit_by_one_positive_factor(incomes, year): + # Benefit years 2024 and 2025 look back to 2022 and 2023, before the 2024 + # inputs; every tax unit's lag MAGI is its 2024 MAGI times the same factor. + people, tax_units, households = {}, {}, {} + for i, income in enumerate(incomes): + people[f"p{i}"] = {"age": {2024: 70}, "employment_income": {2024: income}} + tax_units[f"t{i}"] = {"members": [f"p{i}"]} + households[f"h{i}"] = {"members": [f"p{i}"]} + sim = Simulation( + situation={"people": people, "tax_units": tax_units, "households": households} + ) + source = sim.calculate("medicare_irmaa_tax_year_magi", 2024) + lag = sim.calculate("medicare_irmaa_magi_two_years_prior", year) + factor = deflator(year - 2, 2024) + assert factor > 0 + np.testing.assert_allclose(lag, source * factor, rtol=1e-6, atol=0.05) + + +# Same-year switch + + +def test_switch_sets_irmaa_from_the_benefit_years_income(): + person = {"age": {2023: 66}, "employment_income": {2023: 50_000, 2025: 300_000}} + law = senior(person) + same_year = senior(person, reform=SAME_YEAR) + assert law.calculate("medicare_irmaa_magi", 2025)[0] == pytest.approx(50_000) + assert part_b_irmaa(law, 2025)[0] == 0 + assert same_year.calculate("medicare_irmaa_magi", 2025)[0] == pytest.approx(300_000) + # 2025 single, 200,001 up to 500,000: $406.90 a month. + assert part_b_irmaa(same_year, 2025)[0] == pytest.approx(12 * 406.9) + assert part_d_irmaa(same_year, 2025)[0] == pytest.approx(12 * 78.6) + + +def test_switch_ignores_a_provided_lag_magi(): + sim = senior( + {"age": {2025: 66}, "employment_income": {2025: 300_000}}, + {"medicare_irmaa_magi_two_years_prior": {2025: 0}}, + reform=SAME_YEAR, + ) + assert sim.calculate("medicare_irmaa_magi", 2025)[0] == pytest.approx(300_000) + + +def test_switch_does_not_deflate_benefit_year_income(): + # Inputs only for 2024: the same-year MAGI is 2025 income carried forward + # from 2024, not the fallback's deflated 2024 income. + sim = senior( + {"age": {2024: 66}, "employment_income": {2024: 200_000}}, reform=SAME_YEAR + ) + assert sim.calculate("medicare_irmaa_magi", 2025)[0] == pytest.approx( + sim.calculate("medicare_irmaa_tax_year_magi", 2025)[0] + ) + + +def bump_earnings(sim, year, delta): + """The earnings change marginal_tax_rate makes, in a branch of ``sim``.""" + branch = sim.get_branch("irmaa_bump") + inputs = user_set_variables(sim) + for variable in sim.tax_benefit_system.variables: + if variable not in inputs or variable == "employment_income": + branch.delete_arrays(variable) + branch.set_input( + "employment_income", year, sim.calculate("employment_income", year) + delta + ) + return branch + + +@pytest.mark.parametrize("same_year", [False, True]) +def test_counterfactual_earnings_move_irmaa_only_with_the_switch(same_year): + # The 2023 MAGI is known, so under the law the 2025 charge is fixed; 2025 + # earnings of 199,500 sit just under the 200,001 threshold. The lag MAGI + # is provided directly: a marginal tax rate branch keeps it, whereas it + # drops the earnings inputs of years other than the one it changes. + sim = senior( + {"age": {2025: 66}, "employment_income": {2025: 199_500}}, + {"medicare_irmaa_magi_two_years_prior": {2025: 150_000}}, + reform=SAME_YEAR if same_year else None, + ) + before = sim.calculate("gross_medicare_part_b_premium", 2025)[0] + branch = bump_earnings(sim, 2025, 1_000) + after = branch.calculate("gross_medicare_part_b_premium", 2025)[0] + if same_year: + # 167,001 up to 200,001 is $295.90 a month; above it, $406.90. + assert after - before == pytest.approx(12 * (406.9 - 295.9)) + else: + # 133,001 up to 167,001: $185 a month on the $185 standard premium. + assert before == pytest.approx(12 * (185 + 185)) + assert after == pytest.approx(before) + + +def test_one_year_household_irmaa_moves_with_that_years_income(): + # Intended: with inputs only for the benefit year, the income two years + # prior is imputed from that year's income, so a counterfactual earnings + # change moves the charges even with the switch off. 2026 earnings of + # 120,500 deflate to 108,653, under the 109,001 threshold; 121,500 deflate + # to 109,555, over it. + sim = senior( + {"age": {2026: 66}, "employment_income": {2026: 120_500}}, + ) + factor = deflator(2024, 2026) + assert sim.calculate("medicare_irmaa_magi", 2026)[0] == pytest.approx( + 120_500 * factor, rel=1e-6 + ) + before = sim.calculate("gross_medicare_part_b_premium", 2026)[0] + branch = bump_earnings(sim, 2026, 1_000) + after = branch.calculate("gross_medicare_part_b_premium", 2026)[0] + # 2026 single, 109,001 up to 137,001: $81.20 a month. + assert after - before == pytest.approx(12 * 81.2) + + +def test_earliest_input_year_skips_variables_the_system_no_longer_has(): + sim = senior( + { + "age": {2024: 66}, + "employment_income": {2024: 200_000}, + "tax_exempt_interest_income": {2023: 5}, + } + ) + assert earliest_input_year(sim) == 2023 + # A reform that removes a variable leaves its recorded input keys behind. + sim.tax_benefit_system.variables.pop("tax_exempt_interest_income") + assert earliest_input_year(sim) == 2024 + + +def test_earliest_input_year_skips_neutralized_variables(): + # A neutralized variable reads as its default, so its input is never used: + # its 2023 input must not turn the 2025 fallback off. + sim = senior( + { + "age": {2024: 66}, + "employment_income": {2024: 200_000}, + "tax_exempt_interest_income": {2023: 5}, + } + ) + sim.tax_benefit_system.neutralize_variable("tax_exempt_interest_income") + assert earliest_input_year(sim) == 2024 + assert sim.calculate("medicare_irmaa_magi_two_years_prior", 2025)[ + 0 + ] == pytest.approx(200_000 * deflator(2023, 2024), rel=1e-6) + + +# Bracket properties + + +def irmaa_by_magi(magis, filing_status, year): + people, tax_units, households = {}, {}, {} + for i, magi in enumerate(magis): + people[f"p{i}"] = { + "age": {year: 70}, + "is_medicare_eligible": {year: True}, + "medicare_enrolled": {year: True}, + } + tax_units[f"t{i}"] = { + "members": [f"p{i}"], + "filing_status": {year: filing_status}, + "medicare_irmaa_magi": {year: magi}, + } + households[f"h{i}"] = {"members": [f"p{i}"]} + sim = Simulation( + situation={"people": people, "tax_units": tax_units, "households": households} + ) + return part_b_irmaa(sim, year), part_d_irmaa(sim, year) + + +def first_threshold(part, filing_status, year): + scale = getattr( + getattr(SYSTEM.parameters(f"{year}-01-01").gov.hhs.medicare, part).irmaa, + filing_status.lower(), + ) + return scale.thresholds[1] + + +@settings( + max_examples=25, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + st.lists( + st.floats(min_value=-1e6, max_value=3e6, width=32), + min_size=2, + max_size=15, + ), + st.sampled_from(FILING_STATUSES), + st.sampled_from(BRACKET_YEARS), +) +def test_surcharges_never_fall_as_magi_rises(magis, filing_status, year): + magis = sorted(magis) + part_b, part_d = irmaa_by_magi(magis, filing_status, year) + assert np.all(np.diff(part_b) >= 0) + assert np.all(np.diff(part_d) >= 0) + + +@settings( + max_examples=25, + deadline=None, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + st.data(), + st.sampled_from(FILING_STATUSES), + st.sampled_from(BRACKET_YEARS), +) +def test_surcharges_are_zero_below_the_first_threshold(data, filing_status, year): + # MAGI is stored as float32, so draw values it can hold: 106,000.996 would + # be stored as the 106,001 threshold itself. + threshold = first_threshold("part_b", filing_status, year) + assert threshold == first_threshold("part_d", filing_status, year) + below = data.draw( + st.lists( + st.floats(min_value=-1e6, max_value=threshold, exclude_max=True, width=32), + min_size=1, + max_size=10, + ) + ) + above = data.draw( + st.lists( + st.floats(min_value=threshold, max_value=5e6, width=32), + min_size=1, + max_size=10, + ) + ) + part_b, part_d = irmaa_by_magi(below + above, filing_status, year) + n = len(below) + assert np.all(part_b[:n] == 0) and np.all(part_d[:n] == 0) + assert np.all(part_b[n:] > 0) and np.all(part_d[n:] > 0) diff --git a/policyengine_us/tools/general.py b/policyengine_us/tools/general.py index 5e9b7accd28..c5ac8779e6e 100644 --- a/policyengine_us/tools/general.py +++ b/policyengine_us/tools/general.py @@ -39,6 +39,46 @@ def has_input_for_period(simulation, variable, period): return False +def earliest_input_year(simulation): + """The earliest year for which the simulation holds a supplied input. + + Supplied inputs are the values ``set_input`` stored, from a dataset, a + situation, or a branch this simulation reads, and still stores. Core + records them apart from the values formulas calculate, so the answer does + not depend on what has been calculated. Inputs for an eternal variable + have no year and are skipped. Returns None when there are none. + + Before this year every input reads as its default: a variable is carried + over or uprated only from a period it is known in, never back from a + later one. + + Inputs of a variable the tax-benefit system no longer has, or has + neutralized, are skipped: they are never read. As with + ``has_input_for_period``, core versions before 3.32.27 can keep + an input key after the input is deleted, and a value recalculated for + that period then still counts. Formulas that read this cache their + result, so inputs set after they have run do not change it. + """ + visible_branches = set(simulation._get_visible_branch_names()) + variables = simulation.tax_benefit_system.variables + periods_by_variable = {} + for variable, branch_name, period in getattr(simulation, "_user_input_keys", ()): + if ( + branch_name in visible_branches + and period.unit != ETERNITY + and variable in variables + and not variables[variable].is_neutralized + ): + periods_by_variable.setdefault(variable, set()).add((branch_name, period)) + years = [ + period.start.year + for variable, input_keys in periods_by_variable.items() + for branch_name, period in input_keys + & set(simulation.get_holder(variable).get_known_branch_periods()) + ] + return min(years, default=None) + + def tax_unit_non_dep_sum(var, tax_unit, period): return tax_unit.sum( tax_unit.members(var, period) diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi.py new file mode 100644 index 00000000000..c0b5ed8560f --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class medicare_irmaa_magi(Variable): + value_type = float + entity = TaxUnit + label = "Medicare IRMAA MAGI applied in the benefit year" + unit = USD + definition_period = YEAR + reference = "https://www.law.cornell.edu/uscode/text/42/1395r#i_4_B" + documentation = ( + "Modified adjusted gross income that the Medicare Part B and Part D " + "income-related monthly adjustment amounts apply to in the benefit " + "year. The law uses the income from two years earlier " + "(medicare_irmaa_magi_two_years_prior). When " + "gov.simulation.medicare_irmaa_uses_current_year_income is true, the " + "benefit year's own income is used instead " + "(medicare_irmaa_tax_year_magi), so the charges move with that year's " + "earnings." + ) + + def formula(tax_unit, period, parameters): + if parameters(period).gov.simulation.medicare_irmaa_uses_current_year_income: + return tax_unit("medicare_irmaa_tax_year_magi", period) + return tax_unit("medicare_irmaa_magi_two_years_prior", period) diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.py index 0507bc3d442..16fc9d61c1d 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_magi_two_years_prior.py @@ -1,25 +1,27 @@ +from functools import reduce + from policyengine_us.model_api import * from policyengine_us.tools.parameters import FIRST_MODELED_YEAR +from policyengine_us.variables.gov.hhs.medicare.eligibility.medicare_irmaa_tax_year_magi import ( + PERSON_ADDITIONS, + TAX_UNIT_ADDITIONS, +) + +# Per-capita CBO AGI: the series that extends a single-year dataset's dollar +# inputs to later years when they have no series of their own +# (DEFAULT_MICRODATA_UPRATING, divided by population). Deflating by it runs +# that projection backward. +LOOKBACK_DEFLATOR = ( + "calibration.gov.cbo.income_by_source.adjusted_gross_income_per_capita" +) + -# 42 U.S.C. 1395r(i)(4)(A): adjusted gross income (i) determined without -# regard to sections 135, 911, 931 and 933 of the Internal Revenue Code and -# (ii) increased by tax-exempt interest. Income inputs are net of the section -# 911 amounts (Form 2555 lines 45 and 50); sections 135, 931 and 933 are -# above-the-line deductions in this model. Adding each amount back undoes it. -TAX_UNIT_ADDITIONS = [ - "section_911_excluded_income", - "specified_possession_income", - "puerto_rico_income", -] -# Person-level amounts on the filer's return, summed over the members whose -# amounts adjusted gross income counts: the head and spouse, and every member -# for amounts listed in gov.irs.ald.filer_amounts_recorded_on_dependents. A -# dependent's own tax-exempt interest is on the dependent's return. The -# section 135 exclusion is listed: only a bond issued to an owner aged 24 or -# older qualifies (26 U.S.C. 135(c)(1)(B); Form 8815), so an amount recorded -# on a dependent is the filer's exclusion for the dependent's tuition, which -# adjusted gross income deducts and this adds back. -PERSON_ADDITIONS = ["tax_exempt_interest_income", "us_bonds_for_higher_ed"] +def index_at(index, instant): + """The index at ``instant``, held at its first value before the series + begins, as policyengine-core holds an uprating index.""" + value = index(instant) + # ``values_list`` runs from the latest instant to the earliest. + return index.values_list[-1].value if value is None else value class medicare_irmaa_magi_two_years_prior(Variable): @@ -34,40 +36,65 @@ class medicare_irmaa_magi_two_years_prior(Variable): "https://www.irs.gov/pub/irs-prior/f8815--2025.pdf#page=3", ) documentation = ( - "Modified adjusted gross income used to determine Medicare IRMAA " - "charges. Callers may provide this value directly for the current " - "benefit year. When it is not provided, PolicyEngine computes it from " - "two years prior as adjusted gross income plus the head's and " - "spouse's tax-exempt interest and the amounts excluded or deducted " - "under IRC sections 135, 911, 931 and 933. " - "Single-year datasets without lagged income inputs therefore default " - "to the modeled prior-year values, which may be zero. When the year " - "two years prior precedes the first year PolicyEngine models (2015), " - "only income provided for that year counts, and the rest is zero." + "Modified adjusted gross income from two years before the benefit " + "year, which sets Medicare IRMAA charges " + "(medicare_irmaa_tax_year_magi for that year). Callers may provide " + "this value directly for the benefit year. " + "When the simulation holds no input for the year two years prior or " + "any earlier year, every input would read as its default there, so " + "PolicyEngine instead uses the IRMAA MAGI of the earliest year it " + "holds inputs for (no earlier than 2015, the first year it models), " + "deflated to the year two years prior by " + "calibration.gov.cbo.income_by_source.adjusted_gross_income_per_capita, " + "the index that extends single-year datasets to later years. A " + "single-year dataset thus supplies its own year's income to the two " + "benefit years whose income year precedes it. Any input supplied for " + "the year two years prior, or earlier, turns this off: the year's " + "inputs are then taken as given. " + "When the earliest input year is the benefit year itself, as for a " + "household entered for one year or a dataset's own year, the income " + "two years prior is imputed from the benefit year's income, so the " + "charges then move with that income in reforms and counterfactual " + "branches, as they do under " + "gov.simulation.medicare_irmaa_uses_current_year_income but " + "deflated. Provide this value, or inputs for the year two years " + "prior, to hold it fixed. " + "CBO AGI has no values for 2016 to 2018 and is held at its 2015 " + "value, so the index drifts down through 2018 and then rises 31% " + "from 2017 to 2019 and 39% from 2018 to 2020: a household entered " + "for 2016, 2017 or 2018 alone gets slightly more than that year's " + "income, and one entered for 2019 or 2020 alone gets that year's " + "income times 0.765 or 0.721. " + "When inputs reach back to 2013 or 2014 and the year two years prior " + "is one of those years, its income cannot be computed: only income " + "provided for it counts, and the rest is zero." ) def formula(tax_unit, period, parameters): prior_period = period.offset(-2, "year") - if prior_period.start.year >= FIRST_MODELED_YEAR: - filer_amounts = parameters( - prior_period - ).gov.irs.ald.filer_amounts_recorded_on_dependents - return add( - tax_unit, - prior_period, - ["adjusted_gross_income", *TAX_UNIT_ADDITIONS], - ) + tax_unit_non_dep_add( - tax_unit, - prior_period, - PERSON_ADDITIONS, - include_dependents=filer_amounts, + first_input_year = earliest_input_year(tax_unit.simulation) + if first_input_year is None or prior_period.start.year < first_input_year: + # Inputs start after the year two years prior. Use the earliest + # year that has them, which may be the benefit year itself. + source_year = max( + (period.start.year if first_input_year is None else first_input_year), + FIRST_MODELED_YEAR, + ) + source_period = period.offset(source_year - period.start.year, "year") + magi = tax_unit("medicare_irmaa_tax_year_magi", source_period) + index = reduce(getattr, LOOKBACK_DEFLATOR.split("."), parameters) + return ( + magi + * index_at(index, prior_period.start) + / index_at(index, source_period.start) ) - # Benefit years 2015 and 2016 look back to 2013 and 2014, before the - # parameters begin, so income for those years cannot be computed. Use - # income provided for them and treat the rest as zero, the value the - # formula yields in later years when no prior-year income is provided. - # Reading stored values computes and caches nothing for those years; - # tax unit roles are the benefit year's. + if prior_period.start.year >= FIRST_MODELED_YEAR: + return tax_unit("medicare_irmaa_tax_year_magi", prior_period) + # Inputs reach back to 2013 or 2014, before the parameters begin, so + # income for those years cannot be computed. Use income provided for + # them and treat the rest as zero. Reading stored values computes and + # caches nothing for those years; tax unit roles are the benefit + # year's. simulation = tax_unit.simulation filer_amounts = parameters( period diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_tax_year_magi.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_tax_year_magi.py new file mode 100644 index 00000000000..50320f28afa --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/medicare_irmaa_tax_year_magi.py @@ -0,0 +1,57 @@ +from policyengine_us.model_api import * + +# 42 U.S.C. 1395r(i)(4)(A): adjusted gross income (i) determined without +# regard to sections 135, 911, 931 and 933 of the Internal Revenue Code and +# (ii) increased by tax-exempt interest. Income inputs are net of the section +# 911 amounts (Form 2555 lines 45 and 50); sections 135, 931 and 933 are +# above-the-line deductions in this model. Adding each amount back undoes it. +TAX_UNIT_ADDITIONS = [ + "section_911_excluded_income", + "specified_possession_income", + "puerto_rico_income", +] +# Person-level amounts on the filer's return, summed over the members whose +# amounts adjusted gross income counts: the head and spouse, and every member +# for amounts listed in gov.irs.ald.filer_amounts_recorded_on_dependents. A +# dependent's own tax-exempt interest is on the dependent's return. The +# section 135 exclusion is listed: only a bond issued to an owner aged 24 or +# older qualifies (26 U.S.C. 135(c)(1)(B); Form 8815), so an amount recorded +# on a dependent is the filer's exclusion for the dependent's tuition, which +# adjusted gross income deducts and this adds back. +PERSON_ADDITIONS = ["tax_exempt_interest_income", "us_bonds_for_higher_ed"] + + +class medicare_irmaa_tax_year_magi(Variable): + value_type = float + entity = TaxUnit + label = "Medicare IRMAA MAGI on this tax year's return" + unit = USD + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/42/1395r#i_4", + "https://www.law.cornell.edu/uscode/text/26/135#c_1", + "https://www.irs.gov/pub/irs-prior/f8815--2025.pdf#page=3", + ) + documentation = ( + "Modified adjusted gross income as Medicare IRMAA defines it, from " + "this tax year's income: adjusted gross income plus the head's and " + "spouse's tax-exempt interest and the amounts excluded or deducted " + "under IRC sections 135, 911, 931 and 933. IRMAA for a benefit year " + "normally applies this amount from two years earlier " + "(medicare_irmaa_magi_two_years_prior)." + ) + + def formula(tax_unit, period, parameters): + filer_amounts = parameters( + period + ).gov.irs.ald.filer_amounts_recorded_on_dependents + return add( + tax_unit, + period, + ["adjusted_gross_income", *TAX_UNIT_ADDITIONS], + ) + tax_unit_non_dep_add( + tax_unit, + period, + PERSON_ADDITIONS, + include_dependents=filer_amounts, + ) diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/gross_medicare_part_b_premium.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/gross_medicare_part_b_premium.py index df2cd3bfe20..831bf5e8b9e 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/gross_medicare_part_b_premium.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/gross_medicare_part_b_premium.py @@ -9,7 +9,7 @@ class gross_medicare_part_b_premium(Variable): definition_period = YEAR defined_for = "is_medicare_eligible" reference = "https://www.medicare.gov/basics/costs/medicare-costs#partB" - documentation = "Annual Medicare Part B premium before Medicare Savings Program coverage, including any income-related monthly adjustment amount. Based on modified adjusted gross income from 2 years prior." + documentation = "Annual Medicare Part B premium before Medicare Savings Program coverage, including any income-related monthly adjustment amount. Based on modified adjusted gross income from 2 years prior, or from the benefit year when gov.simulation.medicare_irmaa_uses_current_year_income is true (medicare_irmaa_magi)." def formula(person, period, parameters): tax_unit = person.tax_unit @@ -21,7 +21,7 @@ def formula(person, period, parameters): is_head_of_household = filing_status == status.HEAD_OF_HOUSEHOLD is_surviving_spouse = filing_status == status.SURVIVING_SPOUSE is_separated = filing_status == status.SEPARATE - magi = tax_unit("medicare_irmaa_magi_two_years_prior", period) + magi = tax_unit("medicare_irmaa_magi", period) base = person("base_part_b_premium", period) p = parameters(period).gov.hhs.medicare.part_b.irmaa diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_d/income_adjusted_part_d_premium_surcharge.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_d/income_adjusted_part_d_premium_surcharge.py index b4813a80226..32a95a71946 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_d/income_adjusted_part_d_premium_surcharge.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_d/income_adjusted_part_d_premium_surcharge.py @@ -9,12 +9,12 @@ class income_adjusted_part_d_premium_surcharge(Variable): definition_period = YEAR defined_for = "medicare_enrolled" reference = "https://www.law.cornell.edu/cfr/text/42/423.286" - documentation = "Annualized Medicare Part D IRMAA surcharge under 42 U.S.C. 1395w-113(a)(7). This is the federally set income-related premium adjustment amount, paid in addition to the beneficiary's underlying Part D plan premium. Annual bracket values come from CMS fact sheets." + documentation = "Annualized Medicare Part D IRMAA surcharge under 42 U.S.C. 1395w-113(a)(7). This is the federally set income-related premium adjustment amount, paid in addition to the beneficiary's underlying Part D plan premium. Annual bracket values come from CMS fact sheets. The brackets apply to medicare_irmaa_magi: income from two years prior, or from the benefit year when gov.simulation.medicare_irmaa_uses_current_year_income is true." def formula(person, period, parameters): tax_unit = person.tax_unit filing_status = tax_unit("filing_status", period) - magi = tax_unit("medicare_irmaa_magi_two_years_prior", period) + magi = tax_unit("medicare_irmaa_magi", period) status = filing_status.possible_values statuses = [