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/medicare-part-d-enrollment.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Charge the Medicare Part D income-related monthly adjustment amount only to Part D enrollees, through a new takes_up_medicare_part_d_if_eligible input that defaults to true.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,44 @@
output:
income_adjusted_part_d_premium_surcharge: 0

- name: unit test 4a - 2026 Medicare enrollee above the IRMAA threshold without Part D.
period: 2026
input:
age: 66
filing_status: SINGLE
adjusted_gross_income:
2024: 109_001
tax_exempt_interest_income:
2024: 0
takes_up_medicare_part_d_if_eligible: false
output:
income_adjusted_part_d_premium_surcharge: 0

- name: unit test 4b - 2026 joint couple in the third tier where only one spouse has Part D.
period: 2026
input:
people:
head:
age: 67
spouse:
age: 67
takes_up_medicare_part_d_if_eligible: false
tax_units:
tax_unit:
members: [head, spouse]
filing_status: JOINT
adjusted_gross_income:
2024: 342_001
households:
household:
members: [head, spouse]
output:
medicare_part_d_enrolled: [true, false]
# $60.40 a month for the enrolled spouse; none for the other.
income_adjusted_part_d_premium_surcharge: [724.8, 0]
# Part B premium and IRMAA ($202.90 + $324.60 a month) are unaffected.
gross_medicare_part_b_premium: [6_330, 6_330]

- name: Case 5, 2026 single filer at the IRMAA threshold.
period: 2026
input:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
- name: Case 1, a Medicare enrollee takes up Part D by default.
period: 2026
input:
age: 70
output:
medicare_part_d_enrolled: true

- name: Case 2, a Medicare enrollee without Part D.
period: 2026
input:
age: 70
takes_up_medicare_part_d_if_eligible: false
output:
medicare_part_d_enrolled: false

- name: Case 3, no Part D without Part A or Part B.
period: 2026
input:
age: 70
takes_up_medicare_if_eligible: false
output:
medicare_part_d_enrolled: false

- name: Case 4, no Part D below Medicare age without disability entitlement.
period: 2026
input:
age: 50
output:
medicare_part_d_enrolled: false

- name: Case 5, disability entitlement under 65 opens Part D.
period: 2026
input:
age: 50
social_security_disability: 20_000
months_receiving_social_security_disability: 24
output:
medicare_part_d_enrolled: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""The Part D income-related premium applies only to Part D enrollees.

42 U.S.C. 1395w-113(a)(7) raises the Part D beneficiary premium, which only
Part D enrollees pay. Invariants checked for every drawn set of people:
- the surcharge is never negative and is zero for anyone not in Part D;
- for Part D enrollees it equals the surcharge when every Medicare enrollee
takes up Part D, so the enrollment gate removes charges and changes none;
- Part D enrollment implies Medicare enrollment, which implies eligibility;
- Part D take-up leaves the Part B premium unchanged.

Each example builds two small simulations on the shared model system; no
extra tax-benefit system stays resident.
"""

import numpy as np
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st

from policyengine_us import Simulation

FILING_STATUSES = [
"SINGLE",
"JOINT",
"HEAD_OF_HOUSEHOLD",
"SURVIVING_SPOUSE",
"SEPARATE",
]
# Years with brackets of their own.
YEARS = [2024, 2025, 2026]

PERSON = st.fixed_dictionaries(
{
"age": st.integers(min_value=55, max_value=95),
"takes_up_medicare": st.booleans(),
"takes_up_part_d": st.booleans(),
# MAGI is stored as float32; draw values it can hold.
"magi": st.floats(min_value=0, max_value=1_500_000, width=32),
"filing_status": st.sampled_from(FILING_STATUSES),
}
)


def simulate(people, year, everyone_takes_up_part_d):
situation = {"people": {}, "tax_units": {}, "households": {}}
for i, person in enumerate(people):
name = f"person_{i}"
situation["people"][name] = {
"age": {year: person["age"]},
"takes_up_medicare_if_eligible": {year: person["takes_up_medicare"]},
"takes_up_medicare_part_d_if_eligible": {
year: everyone_takes_up_part_d or person["takes_up_part_d"]
},
}
situation["tax_units"][f"tax_unit_{i}"] = {
"members": [name],
"filing_status": {year: person["filing_status"]},
"medicare_irmaa_magi_two_years_prior": {year: person["magi"]},
}
situation["households"][f"household_{i}"] = {"members": [name]}
return Simulation(situation=situation)


@settings(
max_examples=25,
deadline=None,
suppress_health_check=[HealthCheck.too_slow],
)
@given(
people=st.lists(PERSON, min_size=1, max_size=8),
year=st.sampled_from(YEARS),
)
def test_part_d_surcharge_applies_only_to_part_d_enrollees(people, year):
sim = simulate(people, year, everyone_takes_up_part_d=False)
everyone = simulate(people, year, everyone_takes_up_part_d=True)

surcharge = sim.calculate("income_adjusted_part_d_premium_surcharge", year)
surcharge_if_all_enroll = everyone.calculate(
"income_adjusted_part_d_premium_surcharge", year
)
part_d = sim.calculate("medicare_part_d_enrolled", year)
enrolled = sim.calculate("medicare_enrolled", year)
eligible = sim.calculate("is_medicare_eligible", year)
takes_up_part_d = np.array([person["takes_up_part_d"] for person in people])

assert (surcharge >= 0).all()
assert (surcharge[~part_d] == 0).all()
np.testing.assert_array_equal(surcharge[part_d], surcharge_if_all_enroll[part_d])
np.testing.assert_array_equal(part_d, enrolled & takes_up_part_d)
assert (~part_d | enrolled).all()
assert (~enrolled | eligible).all()
np.testing.assert_array_equal(
sim.calculate("gross_medicare_part_b_premium", year),
everyone.calculate("gross_medicare_part_b_premium", year),
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class income_adjusted_part_d_premium_surcharge(Variable):
label = "Medicare Part D premium surcharge (income-adjusted)"
unit = USD
definition_period = YEAR
defined_for = "medicare_enrolled"
defined_for = "medicare_part_d_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."

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from policyengine_us.model_api import *


class medicare_part_d_enrolled(Variable):
value_type = bool
entity = Person
label = "Enrolled in Medicare Part D"
documentation = (
"Whether the person is enrolled in Medicare Part D. Only a Part D "
"eligible individual, one entitled to Part A or enrolled in Part B "
"(medicare_enrolled), can enroll."
)
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/42/1395w-101#a_3_A"
defined_for = "medicare_enrolled"
adds = ["takes_up_medicare_part_d_if_eligible"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from policyengine_us.model_api import *


class takes_up_medicare_part_d_if_eligible(Variable):
value_type = bool
entity = Person
label = "Whether a Medicare enrollee takes up Medicare Part D"
documentation = (
"Whether a person enrolled in Medicare also enrolls in Part D drug "
"coverage: a stand-alone prescription drug plan, a Medicare "
"Advantage plan with drug coverage, or an employer group waiver plan. "
"People whose drug coverage comes instead from a retiree drug subsidy "
"plan, the Federal Employees Health Benefits Program, TRICARE for "
"Life or the VA are not enrolled in Part D. Microdata assign this "
"input; it defaults to true."
)
definition_period = YEAR
default_value = True
Loading