Skip to content
Draft
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/8677-medicare-part-b-enrollment.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add enrollment-gated gross Medicare Part B premiums and an explicit lagged IRMAA MAGI input path.
5 changes: 4 additions & 1 deletion policyengine_us/programs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,10 @@ programs:
variable: is_medicare_eligible
parameter_prefix: gov.hhs.medicare
verified_start_year: 2024
notes: Includes Part A (hospital) and Part B (medical) premiums with IRMAA adjustments
notes: >-
Includes Part A (hospital) and Part B (medical) premiums with IRMAA
adjustments; gross Part B premium calibration should use the
enrollment-gated gross_medicare_part_b_premium_if_enrolled variable.

# --- HUD programs ---
- id: section_8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,16 @@
takes_up_medicare_if_eligible: false
output:
income_adjusted_part_d_premium_surcharge: 0

- name: unit test 5 - direct Medicare IRMAA MAGI input
period: 2025
input:
age: 65
filing_status: SINGLE
adjusted_gross_income:
2023: 50_000
tax_exempt_interest_income:
2023: 0
medicare_irmaa_magi_two_years_prior: 500_000
output:
income_adjusted_part_d_premium_surcharge: 1_029.6
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ def test_medicare_part_b_premium_is_zero_when_not_enrolled():

assert sim.calculate("msp_part_b_premium_coverage", PERIOD)[0] == pytest.approx(0)
assert sim.calculate("medicare_part_b_premium", PERIOD)[0] == pytest.approx(0)
assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx(
2_220
)
assert sim.calculate("gross_medicare_part_b_premium_if_enrolled", PERIOD)[
0
] == pytest.approx(0)


def test_msp_part_b_premium_coverage_scales_with_eligible_months():
Expand Down Expand Up @@ -292,3 +298,87 @@ def test_gross_medicare_part_b_premium_handles_direct_filing_status_inputs():
assert result[0] == pytest.approx(7_546.8)
assert result[1] == pytest.approx(7_546.8)
assert result[2] == pytest.approx(2_220)


def test_gross_medicare_part_b_premium_if_enrolled_preserves_irmaa():
sim = make_simulation(
medicare_enrolled=True,
gross_part_b_premium=4_440,
base_part_b_premium=2_220,
msp_income_eligible=False,
msp_asset_eligible=False,
)

assert sim.calculate("gross_medicare_part_b_premium_if_enrolled", PERIOD)[
0
] == pytest.approx(4_440)
assert sim.calculate("medicare_cost", PERIOD)[0] == pytest.approx(10_060)


def test_medicare_irmaa_magi_two_years_prior_falls_back_to_lagged_income():
sim = Simulation(
tax_benefit_system=SYSTEM,
situation={
"people": {
"person": {
"age": {PERIOD: 65},
"base_part_b_premium": {PERIOD: 2_220},
"is_medicare_eligible": {PERIOD: True},
"tax_exempt_interest_income": {"2023": 6_002},
}
},
"households": {"household": {"members": ["person"]}},
"tax_units": {
"tax_unit": {
"members": ["person"],
"filing_status": {PERIOD: "SINGLE"},
"adjusted_gross_income": {"2023": 100_000},
}
},
"spm_units": {"spm_unit": {"members": ["person"]}},
"families": {"family": {"members": ["person"]}},
"marital_units": {"marital_unit": {"members": ["person"]}},
},
)

assert sim.calculate("medicare_irmaa_magi_two_years_prior", PERIOD)[
0
] == pytest.approx(106_002)
assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx(
3_108
)


def test_medicare_irmaa_magi_two_years_prior_input_drives_irmaa():
sim = Simulation(
tax_benefit_system=SYSTEM,
situation={
"people": {
"person": {
"age": {PERIOD: 65},
"base_part_b_premium": {PERIOD: 2_220},
"is_medicare_eligible": {PERIOD: True},
"tax_exempt_interest_income": {"2023": 0},
}
},
"households": {"household": {"members": ["person"]}},
"tax_units": {
"tax_unit": {
"members": ["person"],
"filing_status": {PERIOD: "SINGLE"},
"adjusted_gross_income": {"2023": 50_000},
"medicare_irmaa_magi_two_years_prior": {PERIOD: 500_000},
}
},
"spm_units": {"spm_unit": {"members": ["person"]}},
"families": {"family": {"members": ["person"]}},
"marital_units": {"marital_unit": {"members": ["person"]}},
},
)

assert sim.calculate("medicare_irmaa_magi_two_years_prior", PERIOD)[
0
] == pytest.approx(500_000)
assert sim.calculate("gross_medicare_part_b_premium", PERIOD)[0] == pytest.approx(
7_546.8
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ def formula(person, period, parameters):
period
).calibration.gov.hhs.medicare.per_capita_cost

# Premium offsets to Medicare program cost. Use gross Part B premiums
# before MSP offsets so MSP support does not inflate Medicare's value.
# Premium offsets to Medicare program cost. Use the enrollment-gated
# gross Part B premium before MSP offsets so MSP support does not
# inflate Medicare's value.
part_a_premium = person("base_part_a_premium", period)
part_b_premium = person("gross_medicare_part_b_premium", period)
part_b_premium = person("gross_medicare_part_b_premium_if_enrolled", period)
total_premiums = part_a_premium + part_b_premium

# Net benefit = spending - premiums
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from policyengine_us.model_api import *


class medicare_irmaa_magi_two_years_prior(Variable):
value_type = float
entity = TaxUnit
label = "Medicare IRMAA MAGI from two years prior"
unit = USD
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/42/1395r"
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 as "
"adjusted gross income plus tax-exempt interest from two years prior. "
"Single-year datasets without lagged income inputs therefore default "
"to the modeled prior-year values, which may be zero."
)

def formula(tax_unit, period, parameters):
prior_period = period.offset(-2, "year")
return add(
tax_unit,
prior_period,
["adjusted_gross_income", "tax_exempt_interest_income"],
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +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
# Medicare Part B IRMAA is based on MAGI from 2 years prior.
# MAGI = AGI + tax-exempt interest.
prior_period = period.offset(-2, "year")
agi = tax_unit("adjusted_gross_income", prior_period)
tax_exempt_interest = tax_unit("tax_exempt_interest_income", prior_period)
magi = agi + tax_exempt_interest
magi = tax_unit("medicare_irmaa_magi_two_years_prior", period)
base = person("base_part_b_premium", period)

p = parameters(period).gov.hhs.medicare.part_b.irmaa
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from policyengine_us.model_api import *


class gross_medicare_part_b_premium_if_enrolled(Variable):
value_type = float
entity = Person
label = "Gross Medicare Part B premium if enrolled"
unit = USD
definition_period = YEAR
defined_for = "medicare_enrolled"
reference = "https://www.medicare.gov/your-medicare-costs/part-b-costs"
documentation = (
"Annual Medicare Part B premium for enrolled beneficiaries before "
"Medicare Savings Program coverage, including any income-related "
"monthly adjustment amount. Use this enrollment-gated gross premium "
"for CMS premiums-from-enrollees calibration targets."
)

def formula(person, period, parameters):
return person("gross_medicare_part_b_premium", period)
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ class income_adjusted_part_d_premium_surcharge(Variable):
def formula(person, period, parameters):
tax_unit = person.tax_unit
filing_status = tax_unit("filing_status", period)
prior_period = period.offset(-2, "year")
agi = tax_unit("adjusted_gross_income", prior_period)
tax_exempt_interest = tax_unit("tax_exempt_interest_income", prior_period)
magi = agi + tax_exempt_interest
magi = tax_unit("medicare_irmaa_magi_two_years_prior", period)

status = filing_status.possible_values
statuses = [
Expand Down
Loading