diff --git a/changelog.d/8529.fixed.md b/changelog.d/8529.fixed.md new file mode 100644 index 00000000000..386be8aa63c --- /dev/null +++ b/changelog.d/8529.fixed.md @@ -0,0 +1 @@ +- Included paid Medicare Part A premiums and Medicare Part D IRMAA surcharges in SPM-unit health insurance premiums and medical out-of-pocket expenses. 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..b72c2f4aa43 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.yaml @@ -0,0 +1,19 @@ +- name: MSP Part A coverage pays the base premium for QMB enrollees. + period: 2025 + input: + medicare_enrolled: true + is_qmb_eligible: true + base_part_a_premium: 6_216 + output: + msp_part_a_premium_coverage: 6_216 + medicare_part_a_premium: 0 + +- name: MSP Part A coverage is zero for non-QMB enrollees. + period: 2025 + input: + medicare_enrolled: true + is_qmb_eligible: false + base_part_a_premium: 6_216 + output: + msp_part_a_premium_coverage: 0 + medicare_part_a_premium: 6_216 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..b2a348b97c9 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 @@ -6,6 +6,7 @@ age: 70 is_medicare_eligible: true other_health_insurance_premiums: 500 + base_part_a_premium: 0 medicare_part_b_premium: 2_220 tax_units: tax_unit: @@ -29,6 +30,7 @@ age: 70 is_medicare_eligible: true other_health_insurance_premiums: 500 + base_part_a_premium: 0 medicare_part_b_premium: 5_028 tax_units: tax_unit: @@ -43,3 +45,58 @@ 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 and Part D IRMAA premiums. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + retiree: + age: 70 + is_medicare_eligible: true + is_qmb_eligible: false + other_health_insurance_premiums: 500 + base_part_a_premium: 6_216 + medicare_part_b_premium: 2_220 + income_adjusted_part_d_premium_surcharge: 423.6 + tax_units: + tax_unit: + members: [retiree] + spm_units: + spm_unit: + members: [retiree] + households: + household: + members: [retiree] + state_code: CA + output: + spm_unit_health_insurance_premiums: 9_359.6 + spm_unit_medical_out_of_pocket_expenses: 9_359.6 + +- name: SPM unit MOOP excludes Part A premiums covered by MSP. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + retiree: + age: 70 + is_medicare_eligible: true + other_health_insurance_premiums: 500 + base_part_a_premium: 6_216 + msp_part_a_premium_coverage: 6_216 + medicare_part_b_premium: 2_220 + income_adjusted_part_d_premium_surcharge: 423.6 + 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: 3_143.6 + spm_unit_medical_out_of_pocket_expenses: 3_143.6 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..ef4dadb42b4 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_a/medicare_part_a_premium.py @@ -0,0 +1,22 @@ +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/help/medicare-savings-programs", + ) + documentation = ( + "Annual Medicare Part A premium paid out of pocket by the enrollee, " + "net of Medicare Savings Program coverage." + ) + + def formula(person, period, parameters): + gross_premium = person("base_part_a_premium", period) + msp_coverage = person("msp_part_a_premium_coverage", period) + return max_(gross_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..4fba23f2b0d --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicare/savings_programs/msp_part_a_premium_coverage.py @@ -0,0 +1,30 @@ +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", + "https://www.law.cornell.edu/uscode/text/42/1396d#p_3", + ) + documentation = ( + "Annual Part A premium amount paid on the enrollee's behalf through " + "the Qualified Medicare Beneficiary pathway." + ) + + 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..1ace570cf4c 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 @@ -18,5 +18,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..9b749046757 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,10 @@ 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, Medicare Part A " + "and Part B net of Medicare Savings Program coverage, and Medicare " + "Part D IRMAA premiums. Non-premium expenses include other medical " + "expenses and over-the-counter health expenses." ) adds = [