diff --git a/changelog.d/8529.fixed.md b/changelog.d/8529.fixed.md new file mode 100644 index 00000000000..ffcf3346cbf --- /dev/null +++ b/changelog.d/8529.fixed.md @@ -0,0 +1 @@ +Include paid Medicare Part A premiums and Part D IRMAA in SPM medical out-of-pocket expenses. diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_a_premium.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_a_premium.yaml new file mode 100644 index 00000000000..ec1c29f2589 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_a_premium.yaml @@ -0,0 +1,26 @@ +- name: Medicare Part A premium equals base premium when no MSP coverage. + period: 2026 + input: + medicare_enrolled: true + base_part_a_premium: 6_780 + msp_part_a_premium_coverage: 0 + output: + medicare_part_a_premium: 6_780 + +- name: Medicare Part A premium is net of MSP coverage. + period: 2026 + input: + medicare_enrolled: true + base_part_a_premium: 6_780 + msp_part_a_premium_coverage: 6_780 + output: + medicare_part_a_premium: 0 + +- name: Medicare Part A premium floors at zero after MSP coverage. + period: 2026 + input: + medicare_enrolled: true + base_part_a_premium: 6_780 + msp_part_a_premium_coverage: 7_000 + output: + medicare_part_a_premium: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml new file mode 100644 index 00000000000..6a7ece615b0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml @@ -0,0 +1,26 @@ +- name: MSP Part A coverage pays the Part A premium for QMB enrollees. + period: 2026 + input: + medicare_enrolled: true + is_qmb_eligible: true + base_part_a_premium: 6_780 + output: + msp_part_a_premium_coverage: 6_780 + +- name: MSP Part A coverage is zero for non-QMB enrollees. + period: 2026 + input: + medicare_enrolled: true + is_qmb_eligible: false + base_part_a_premium: 6_780 + output: + msp_part_a_premium_coverage: 0 + +- name: MSP Part A coverage is zero when not enrolled. + period: 2026 + input: + medicare_enrolled: false + is_qmb_eligible: true + base_part_a_premium: 6_780 + output: + msp_part_a_premium_coverage: 0 diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses_medicare_part_b.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses_medicare_part_b.yaml index fcdc9838348..b73055cd7e7 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses_medicare_part_b.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses_medicare_part_b.yaml @@ -43,3 +43,55 @@ output: spm_unit_health_insurance_premiums: 5_528 spm_unit_medical_out_of_pocket_expenses: 5_528 + +- name: SPM unit MOOP includes paid Part A premium and Part D IRMAA. + period: 2026 + input: + people: + retiree: + age: 70 + medicare_enrolled: true + other_health_insurance_premiums: 500 + base_part_a_premium: 6_780 + msp_part_a_premium_coverage: 0 + medicare_part_b_premium: 4_654.80 + income_adjusted_part_d_premium_surcharge: 423.60 + tax_units: + tax_unit: + members: [retiree] + spm_units: + spm_unit: + members: [retiree] + households: + household: + members: [retiree] + state_code: CA + output: + medicare_part_a_premium: 6_780 + spm_unit_health_insurance_premiums: 12_358.40 + spm_unit_medical_out_of_pocket_expenses: 12_358.40 + +- name: SPM unit MOOP excludes QMB-covered Part A premium. + period: 2026 + input: + people: + retiree: + age: 70 + medicare_enrolled: true + base_part_a_premium: 6_780 + msp_part_a_premium_coverage: 6_780 + medicare_part_b_premium: 2_434.80 + tax_units: + tax_unit: + members: [retiree] + spm_units: + spm_unit: + members: [retiree] + households: + household: + members: [retiree] + state_code: CA + output: + medicare_part_a_premium: 0 + spm_unit_health_insurance_premiums: 2_434.80 + spm_unit_medical_out_of_pocket_expenses: 2_434.80 diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_part_a_premium.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_part_a_premium.py new file mode 100644 index 00000000000..d190322ee2c --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_part_a_premium.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class medicare_part_a_premium(Variable): + value_type = float + entity = Person + label = "Medicare Part A premium" + unit = USD + definition_period = YEAR + defined_for = "medicare_enrolled" + reference = "https://www.medicare.gov/basics/costs/medicare-costs" + documentation = ( + "Annual Medicare Part A premium paid out of pocket by the enrollee, " + "net of Medicare Savings Program coverage." + ) + + def formula(person, period, parameters): + base_premium = person("base_part_a_premium", period) + msp_coverage = person("msp_part_a_premium_coverage", period) + return max_(base_premium - msp_coverage, 0) diff --git a/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py new file mode 100644 index 00000000000..988e617f37f --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * + + +class msp_part_a_premium_coverage(Variable): + value_type = float + entity = Person + unit = USD + label = "Medicare Part A premium amount covered by MSP" + definition_period = YEAR + reference = ( + "https://www.medicare.gov/basics/costs/help/medicare-savings-programs", + ) + documentation = ( + "Annual Part A premium amount paid on the enrollee's behalf through " + "Qualified Medicare Beneficiary coverage." + ) + + def formula(person, period, parameters): + enrolled = person("medicare_enrolled", period) + monthly_part_a_premium = person("base_part_a_premium", period) / MONTHS_IN_YEAR + monthly_coverage = 0 + for month in period.get_subperiods(MONTH): + qmb_eligible = person("is_qmb_eligible", month) + monthly_coverage += where( + enrolled & qmb_eligible, + monthly_part_a_premium, + 0, + ) + return monthly_coverage diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py index 22949382701..6be89db1ae8 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py @@ -10,7 +10,8 @@ class spm_unit_health_insurance_premiums(Variable): documentation = ( "Health insurance premium expenses for an SPM unit, combining a " "data-imputed other premium component with modeled premium components " - "that can respond to policy reforms." + "that can respond to policy reforms, including Medicare Part A and " + "Part B premiums and the Part D IRMAA surcharge paid out of pocket." ) adds = [ @@ -18,5 +19,7 @@ class spm_unit_health_insurance_premiums(Variable): "chip_premium", "medicaid_premium", "marketplace_net_premium", + "medicare_part_a_premium", "medicare_part_b_premium", + "income_adjusted_part_d_premium_surcharge", ] diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.py index f54a75562af..f06025f553f 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.py @@ -11,10 +11,11 @@ class spm_unit_medical_out_of_pocket_expenses(Variable): "Total medical out-of-pocket expenses at the SPM unit level, " "combining health insurance premiums with non-premium medical " "expenses. Health insurance premiums include other health insurance " - "premiums plus modeled Marketplace, CHIP, Medicaid, and Medicare Part " - "B premiums net of Medicare Savings Program coverage. Non-premium " - "expenses include other medical expenses and over-the-counter health " - "expenses." + "premiums plus modeled Marketplace, CHIP, Medicaid, and Medicare " + "Part A and Part B premiums net of Medicare Savings Program coverage " + "where modeled, plus the Part D IRMAA surcharge. " + "Non-premium expenses include other medical expenses and " + "over-the-counter health expenses." ) adds = [