From fec071e55bba3f8e7049a833c44dcaec46ba200d Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:01:48 -0400 Subject: [PATCH 01/13] Add Marketplace premiums to SPM MOOP --- changelog.d/8095.fixed.md | 1 + ...m_unit_medical_out_of_pocket_expenses.yaml | 28 +++++++++++++++++++ ...spm_unit_medical_out_of_pocket_expenses.py | 15 ++++++---- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 changelog.d/8095.fixed.md diff --git a/changelog.d/8095.fixed.md b/changelog.d/8095.fixed.md new file mode 100644 index 00000000000..468d14b9c2a --- /dev/null +++ b/changelog.d/8095.fixed.md @@ -0,0 +1 @@ +Included Marketplace net premiums in SPM medical out-of-pocket expenses. diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml index 1b91a9cb447..a7240d4ca2b 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml @@ -11,6 +11,7 @@ tax_units: tax_unit: members: [parent, child] + marketplace_net_premium: 0 tax_unit_medicaid_income_level: 1.70 spm_units: spm_unit: @@ -33,6 +34,7 @@ tax_units: tax_unit: members: [parent, child] + marketplace_net_premium: 0 spm_units: spm_unit: members: [parent, child] @@ -42,3 +44,29 @@ state_code: VA output: spm_unit_medical_out_of_pocket_expenses: 600 + +- name: SPM unit medical OOP includes Marketplace net premium from selected plan. + period: 2024 + input: + people: + person1: + medical_out_of_pocket_expenses: 100 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person1] + slcsp: 10_000 + selected_marketplace_plan_benchmark_ratio: 0.8 + takes_up_aca_if_eligible: true + used_aca_ptc: 3_000 + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + selected_marketplace_plan_premium_proxy: 8_000 + marketplace_net_premium: 5_000 + spm_unit_medical_out_of_pocket_expenses: 5_100 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 a96b4032401..49bd9e5b97a 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 @@ -10,12 +10,13 @@ class spm_unit_medical_out_of_pocket_expenses(Variable): documentation = ( "Total medical out-of-pocket expenses at the SPM unit level, " "combining person-level imputed `medical_out_of_pocket_expenses` " - "with rules-based premium variables (CHIP premium, Medicare Part B " - "with IRMAA). The imputed Medicare Part B (`medicare_part_b_premiums`) " - "that ships with person-level MOOP is subtracted out and replaced " - "with the rules-based `income_adjusted_part_b_premium`, so reforms " - "to the Medicare Part B base premium or IRMAA thresholds propagate " - "through SPM resources. Consumers reading person-level " + "with rules-based premium variables (CHIP premium, Medicaid premium, " + "Marketplace net premium, and Medicare Part B with IRMAA). The " + "imputed Medicare Part B (`medicare_part_b_premiums`) that ships " + "with person-level MOOP is subtracted out and replaced with the " + "rules-based `income_adjusted_part_b_premium`, so reforms to the " + "Medicare Part B base premium or IRMAA thresholds propagate through " + "SPM resources. Consumers reading person-level " "`medical_out_of_pocket_expenses` directly (SNAP excess medical " "deduction, state itemized medical deductions) are unaffected." ) @@ -26,10 +27,12 @@ def formula(spm_unit, period, parameters): computed_part_b = add(spm_unit, period, ["income_adjusted_part_b_premium"]) chip_premium = add(spm_unit, period, ["chip_premium"]) medicaid_premium = add(spm_unit, period, ["medicaid_premium"]) + marketplace_net_premium = add(spm_unit, period, ["marketplace_net_premium"]) return ( imputed_moop - imputed_part_b + computed_part_b + chip_premium + medicaid_premium + + marketplace_net_premium ) From 2fefbee13eeb3560abd1d2ce1cfd468c6c214031 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:32:23 -0400 Subject: [PATCH 02/13] Isolate Marketplace premiums in SPM fixture tests --- policyengine_us/tests/policy/baseline/household/cliff_gap.yaml | 3 +++ .../income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/household/cliff_gap.yaml b/policyengine_us/tests/policy/baseline/household/cliff_gap.yaml index bf5a59f5ed5..da2039900ab 100644 --- a/policyengine_us/tests/policy/baseline/household/cliff_gap.yaml +++ b/policyengine_us/tests/policy/baseline/household/cliff_gap.yaml @@ -4,6 +4,7 @@ input: age: 40 employment_income: 100_000 + marketplace_net_premium: 0 taxable_interest_income: 100_000 state_fips: 48 # TX output: @@ -16,6 +17,7 @@ input: age: 40 employment_income: 100_000 + 2_000 + marketplace_net_premium: 0 taxable_interest_income: 100_000 state_fips: 48 # TX output: @@ -28,6 +30,7 @@ input: age: 40 employment_income: 100_000 + marketplace_net_premium: 0 taxable_interest_income: 100_000 state_fips: 48 # TX output: # as expected from above two test results diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml index 31441ccad2c..321ad85120e 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml @@ -14,6 +14,7 @@ tax_units: tax_unit: members: [parent, child] + marketplace_net_premium: 0 tax_unit_medicaid_income_level: 1.70 spm_units: spm_unit: @@ -38,6 +39,7 @@ tax_units: tax_unit: members: [child] + marketplace_net_premium: 0 tax_unit_medicaid_income_level: 1.70 spm_units: spm_unit: From 2381cdae0ca964929231f401f570016c77feff40 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 28 Apr 2026 19:48:32 -0400 Subject: [PATCH 03/13] Split SPM MOOP premium components --- changelog.d/8095.fixed.md | 2 +- ...m_unit_medical_out_of_pocket_expenses.yaml | 20 ++++++----- ...ut_of_pocket_expenses_medicare_part_b.yaml | 10 +++--- .../health_insurance_premium_residual.py | 16 +++++++++ .../spm_unit_health_insurance_premiums.py | 27 ++++++++++++++ ...spm_unit_medical_out_of_pocket_expenses.py | 35 +++++++------------ ..._premium_medical_out_of_pocket_expenses.py | 25 +++++++++++++ 7 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py create mode 100644 policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py create mode 100644 policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py diff --git a/changelog.d/8095.fixed.md b/changelog.d/8095.fixed.md index 468d14b9c2a..365e24401f6 100644 --- a/changelog.d/8095.fixed.md +++ b/changelog.d/8095.fixed.md @@ -1 +1 @@ -Included Marketplace net premiums in SPM medical out-of-pocket expenses. +Split SPM medical out-of-pocket expenses into residual premiums, modeled premiums, and non-premium medical expenses. diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml index a7240d4ca2b..21fdb365ff6 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_medical_out_of_pocket_expenses.yaml @@ -1,11 +1,11 @@ -- name: SPM unit medical OOP sums person MOOP and tax unit CHIP premium. +- name: SPM unit medical OOP sums non-premium expenses and tax unit CHIP premium. period: 2024 input: people: parent: - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 child: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 is_chip_eligible: true is_chip_eligible_child: true tax_units: @@ -21,16 +21,18 @@ members: [parent, child] state_code: TX output: + spm_unit_non_premium_medical_out_of_pocket_expenses: 600 spm_unit_medical_out_of_pocket_expenses: 635 -- name: Non-CHIP state SPM unit medical OOP is just the sum of person MOOP. +- name: Non-CHIP state SPM unit medical OOP includes OTC expenses. period: 2024 input: people: parent: - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 child: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 + over_the_counter_health_expenses: 50 tax_units: tax_unit: members: [parent, child] @@ -43,14 +45,15 @@ members: [parent, child] state_code: VA output: - spm_unit_medical_out_of_pocket_expenses: 600 + spm_unit_non_premium_medical_out_of_pocket_expenses: 650 + spm_unit_medical_out_of_pocket_expenses: 650 - name: SPM unit medical OOP includes Marketplace net premium from selected plan. period: 2024 input: people: person1: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 pays_aca_premium: true tax_units: tax_unit: @@ -69,4 +72,5 @@ output: selected_marketplace_plan_premium_proxy: 8_000 marketplace_net_premium: 5_000 + spm_unit_health_insurance_premiums: 5_000 spm_unit_medical_out_of_pocket_expenses: 5_100 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 b247131941a..d8f65df18c9 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 @@ -1,12 +1,11 @@ -- name: SPM unit MOOP swaps imputed Medicare Part B for the rules-based computation at the base premium. +- name: SPM unit MOOP includes residual premiums and rules-based Medicare Part B. period: 2024 input: people: retiree: age: 70 is_medicare_eligible: true - medical_out_of_pocket_expenses: 2_000 - medicare_part_b_premiums: 1_500 + health_insurance_premium_residual: 500 income_adjusted_part_b_premium: 2_220 tax_units: tax_unit: @@ -19,6 +18,7 @@ members: [retiree] state_code: CA output: + spm_unit_health_insurance_premiums: 2_720 spm_unit_medical_out_of_pocket_expenses: 2_720 - name: SPM unit MOOP reflects IRMAA surcharge for high-income Medicare household. @@ -28,8 +28,7 @@ retiree: age: 70 is_medicare_eligible: true - medical_out_of_pocket_expenses: 2_000 - medicare_part_b_premiums: 1_500 + health_insurance_premium_residual: 500 income_adjusted_part_b_premium: 5_028 tax_units: tax_unit: @@ -42,4 +41,5 @@ members: [retiree] state_code: CA output: + spm_unit_health_insurance_premiums: 5_528 spm_unit_medical_out_of_pocket_expenses: 5_528 diff --git a/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py b/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py new file mode 100644 index 00000000000..a0bce2a804b --- /dev/null +++ b/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class health_insurance_premium_residual(Variable): + value_type = float + entity = Person + label = "Residual health insurance premiums" + unit = USD + definition_period = YEAR + documentation = ( + "Residual person-level health insurance premiums after subtracting " + "baseline modeled premium components, such as Marketplace, CHIP, " + "Medicaid, and Medicare Part B premiums. This is an accounting " + "residual imputed by microdata pipelines, not a directly observed " + "coverage category." + ) 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 new file mode 100644 index 00000000000..fc9f209e9e7 --- /dev/null +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_health_insurance_premiums.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class spm_unit_health_insurance_premiums(Variable): + value_type = float + entity = SPMUnit + label = "SPM unit health insurance premiums" + unit = USD + definition_period = YEAR + documentation = ( + "Health insurance premium expenses for an SPM unit, combining a " + "data-imputed residual premium component with modeled premium " + "components that can respond to policy reforms." + ) + + def formula(spm_unit, period, parameters): + return add( + spm_unit, + period, + [ + "health_insurance_premium_residual", + "chip_premium", + "medicaid_premium", + "marketplace_net_premium", + "income_adjusted_part_b_premium", + ], + ) 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 49bd9e5b97a..46b73d72401 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 @@ -9,30 +9,19 @@ class spm_unit_medical_out_of_pocket_expenses(Variable): definition_period = YEAR documentation = ( "Total medical out-of-pocket expenses at the SPM unit level, " - "combining person-level imputed `medical_out_of_pocket_expenses` " - "with rules-based premium variables (CHIP premium, Medicaid premium, " - "Marketplace net premium, and Medicare Part B with IRMAA). The " - "imputed Medicare Part B (`medicare_part_b_premiums`) that ships " - "with person-level MOOP is subtracted out and replaced with the " - "rules-based `income_adjusted_part_b_premium`, so reforms to the " - "Medicare Part B base premium or IRMAA thresholds propagate through " - "SPM resources. Consumers reading person-level " - "`medical_out_of_pocket_expenses` directly (SNAP excess medical " - "deduction, state itemized medical deductions) are unaffected." + "combining health insurance premiums with non-premium medical " + "expenses. Health insurance premiums include data-imputed residual " + "premiums plus modeled Marketplace, CHIP, Medicaid, and Medicare " + "Part B premiums. Non-premium expenses include other medical expenses " + "and over-the-counter health expenses." ) def formula(spm_unit, period, parameters): - imputed_moop = add(spm_unit, period, ["medical_out_of_pocket_expenses"]) - imputed_part_b = add(spm_unit, period, ["medicare_part_b_premiums"]) - computed_part_b = add(spm_unit, period, ["income_adjusted_part_b_premium"]) - chip_premium = add(spm_unit, period, ["chip_premium"]) - medicaid_premium = add(spm_unit, period, ["medicaid_premium"]) - marketplace_net_premium = add(spm_unit, period, ["marketplace_net_premium"]) - return ( - imputed_moop - - imputed_part_b - + computed_part_b - + chip_premium - + medicaid_premium - + marketplace_net_premium + return add( + spm_unit, + period, + [ + "spm_unit_health_insurance_premiums", + "spm_unit_non_premium_medical_out_of_pocket_expenses", + ], ) diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py new file mode 100644 index 00000000000..141b8163a8b --- /dev/null +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class spm_unit_non_premium_medical_out_of_pocket_expenses(Variable): + value_type = float + entity = SPMUnit + label = "SPM unit non-premium medical out-of-pocket expenses" + unit = USD + definition_period = YEAR + documentation = ( + "Non-premium medical out-of-pocket expenses for an SPM unit. Unlike " + "the generic `medical_out_of_pocket_expenses` variable used by tax " + "and benefit programs, the SPM definition includes over-the-counter " + "health expenses." + ) + + def formula(spm_unit, period, parameters): + return add( + spm_unit, + period, + [ + "other_medical_expenses", + "over_the_counter_health_expenses", + ], + ) From 42ba2eacc8c76ddb355fe25ac94a10c5b8aa6321 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 28 Apr 2026 20:34:32 -0400 Subject: [PATCH 04/13] Add statutory medical expense variables --- changelog.d/8095.fixed.md | 2 +- .../itemizing/itemized_medical_expenses.yaml | 13 ++++++++++ .../itemizing/medical_expense_deduction.yaml | 8 ++++++ ...snap_excess_medical_expense_deduction.yaml | 1 + .../itemizing/itemized_medical_expenses.py | 24 +++++++++++++++++ .../itemizing/medical_expense_deduction.py | 4 +-- .../snap_allowable_medical_expenses.py | 26 +++++++++++++++++++ .../snap_excess_medical_expense_deduction.py | 7 +++-- .../spm_unit_health_insurance_premiums.py | 19 +++++--------- ...spm_unit_medical_out_of_pocket_expenses.py | 13 +++------- ..._premium_medical_out_of_pocket_expenses.py | 13 +++------- 11 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py create mode 100644 policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py diff --git a/changelog.d/8095.fixed.md b/changelog.d/8095.fixed.md index 365e24401f6..cf30b1b7af4 100644 --- a/changelog.d/8095.fixed.md +++ b/changelog.d/8095.fixed.md @@ -1 +1 @@ -Split SPM medical out-of-pocket expenses into residual premiums, modeled premiums, and non-premium medical expenses. +Split SPM medical out-of-pocket expenses into residual premiums, modeled premiums, and non-premium medical expenses, and add statutory SNAP and itemized medical expense variables. diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml new file mode 100644 index 00000000000..26086a0da86 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml @@ -0,0 +1,13 @@ +- name: Itemized medical expenses sum medical out-of-pocket expenses. + period: 2025 + input: + people: + person1: + medical_out_of_pocket_expenses: 1_200 + person2: + medical_out_of_pocket_expenses: 300 + tax_units: + tax_unit: + members: [person1, person2] + output: + itemized_medical_expenses: 1_500 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml new file mode 100644 index 00000000000..b84b39fab2e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml @@ -0,0 +1,8 @@ +- name: Medical expense deduction applies the AGI floor to itemized medical expenses. + period: 2025 + input: + medical_out_of_pocket_expenses: 10_000 + positive_agi: 100_000 + output: + itemized_medical_expenses: 10_000 + medical_expense_deduction: 2_500 diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml index 9b64a53e4d2..aa3153f2b38 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml @@ -16,6 +16,7 @@ is_usda_disabled: true medical_out_of_pocket_expenses: 35 * 12 output: + snap_allowable_medical_expenses: 35 * 12 snap_excess_medical_expense_deduction: 0 - name: Disabled person in California with $35.01 medical expenses gets the standard medical expense deduction. diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py new file mode 100644 index 00000000000..cba3cfaefcb --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class itemized_medical_expenses(Variable): + value_type = float + entity = TaxUnit + label = "Itemized medical expenses" + unit = USD + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/uscode/text/26/213#a", + "https://www.law.cornell.edu/uscode/text/26/213#d_1", + ] + documentation = ( + "Medical expenses counted before applying the itemized medical " + "expense deduction floor. IRC Section 213(d)(1) defines medical care " + "to include amounts paid for diagnosis, cure, mitigation, treatment, " + "or prevention of disease; transportation primarily for essential " + "medical care; qualified long-term care services; and insurance " + "premiums covering medical care. Current modeling uses the available " + "medical_out_of_pocket_expenses base." + ) + + adds = ["medical_out_of_pocket_expenses"] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py index 27ee994da01..52f16537188 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py @@ -6,12 +6,12 @@ class medical_expense_deduction(Variable): entity = TaxUnit definition_period = YEAR label = "Medical expense deduction" - reference = "https://www.law.cornell.edu/uscode/text/26/213" + reference = "https://www.law.cornell.edu/uscode/text/26/213#a" unit = USD documentation = "Medical expenses deducted from taxable income." def formula(tax_unit, period, parameters): - expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + expense = tax_unit("itemized_medical_expenses", period) p = parameters(period).gov.irs.deductions.itemized.medical medical_floor = p.floor * tax_unit("positive_agi", period) return max_(0, expense - medical_floor) diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py new file mode 100644 index 00000000000..a3db52e919c --- /dev/null +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class snap_allowable_medical_expenses(Variable): + value_type = float + entity = Person + label = "SNAP allowable medical expenses" + unit = USD + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/uscode/text/7/2014#e_5", + "https://www.law.cornell.edu/cfr/text/7/273.9#d_3", + ] + documentation = ( + "Medical expenses allowable for SNAP's excess medical expense " + "deduction. Federal statute allows actual allowable medical expenses " + "incurred by elderly or disabled household members, excluding special " + "diets, above the monthly disregard. The SNAP regulation identifies " + "allowable costs including medical and dental care, prescription " + "drugs, practitioner-approved over-the-counter medication, health " + "insurance premiums, Medicare premiums, cost sharing, medical " + "supplies, transportation, and related services. Current modeling " + "uses the available medical_out_of_pocket_expenses base." + ) + + adds = ["medical_out_of_pocket_expenses"] diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py index e9cc4d2f59f..9a5860c921f 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.py @@ -8,7 +8,10 @@ class snap_excess_medical_expense_deduction(Variable): unit = USD documentation = "Deduction from SNAP gross income for excess medical expenses" definition_period = MONTH - reference = "https://www.law.cornell.edu/uscode/text/7/2014#e_5" + reference = [ + "https://www.law.cornell.edu/uscode/text/7/2014#e_5", + "https://www.law.cornell.edu/cfr/text/7/273.9#d_3", + ] def formula(spm_unit, period, parameters): # Deduction applies to medical expenses incurred by elderly or disabled @@ -16,7 +19,7 @@ def formula(spm_unit, period, parameters): person = spm_unit.members elderly = person("is_usda_elderly", period) disabled = person("is_usda_disabled", period) - moop = person("medical_out_of_pocket_expenses", period) + moop = person("snap_allowable_medical_expenses", period) elderly_disabled_moop = spm_unit.sum(moop * (elderly | disabled)) p = parameters(period).gov.usda.snap.income.deductions.excess_medical_expense excess = max_(elderly_disabled_moop - p.disregard, 0) 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 fc9f209e9e7..e902661e1a2 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 @@ -13,15 +13,10 @@ class spm_unit_health_insurance_premiums(Variable): "components that can respond to policy reforms." ) - def formula(spm_unit, period, parameters): - return add( - spm_unit, - period, - [ - "health_insurance_premium_residual", - "chip_premium", - "medicaid_premium", - "marketplace_net_premium", - "income_adjusted_part_b_premium", - ], - ) + adds = [ + "health_insurance_premium_residual", + "chip_premium", + "medicaid_premium", + "marketplace_net_premium", + "income_adjusted_part_b_premium", + ] 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 46b73d72401..bfd113b03ba 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 @@ -16,12 +16,7 @@ class spm_unit_medical_out_of_pocket_expenses(Variable): "and over-the-counter health expenses." ) - def formula(spm_unit, period, parameters): - return add( - spm_unit, - period, - [ - "spm_unit_health_insurance_premiums", - "spm_unit_non_premium_medical_out_of_pocket_expenses", - ], - ) + adds = [ + "spm_unit_health_insurance_premiums", + "spm_unit_non_premium_medical_out_of_pocket_expenses", + ] diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py index 141b8163a8b..65336a96066 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py @@ -14,12 +14,7 @@ class spm_unit_non_premium_medical_out_of_pocket_expenses(Variable): "health expenses." ) - def formula(spm_unit, period, parameters): - return add( - spm_unit, - period, - [ - "other_medical_expenses", - "over_the_counter_health_expenses", - ], - ) + adds = [ + "other_medical_expenses", + "over_the_counter_health_expenses", + ] From b837fe1c2497dec455add51dede2b815b23ed5c8 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 28 Apr 2026 22:39:43 -0400 Subject: [PATCH 05/13] Remove generic medical expense aggregate --- changelog.d/8095.fixed.md | 2 +- .../is_medically_needy_for_medicaid.yaml | 2 +- .../baseline/gov/hud/hud_adjusted_income.yaml | 10 +-- .../itemizing/itemized_medical_expenses.yaml | 4 +- .../itemizing/medical_expense_deduction.yaml | 2 +- .../ca_la_infant_supplement.yaml | 4 +- .../ca/la/general_relief/integration.yaml | 2 +- .../itemized/al_medical_deduction.yaml | 4 +- .../ar_medical_expense_deduction_indiv.yaml | 8 +- .../ar_medical_expense_deduction_joint.yaml | 8 +- .../itemized/az_itemized_deductions.yaml | 12 +-- .../hi_medical_expense_deduction.yaml | 16 ++-- .../subtractions/mo_agi_subtractions.yaml | 16 ++-- ...o_qualified_health_insurance_premiums.yaml | 10 +-- .../mt_medical_expense_deduction_indiv.yaml | 6 +- .../mt_medical_expense_deduction_joint.yaml | 12 +-- .../nj_medical_expense_deduction.yaml | 6 +- .../credits/nm_medical_expense_credit.yaml | 12 +-- .../nm_medical_care_expense_deduction.yaml | 80 +++++++++---------- .../nm_medical_expense_exemption.yaml | 10 +-- .../itemized/ny_pre_tcja_deductions.yaml | 2 +- .../integration.yaml | 18 ++--- ...eimbursed_medical_care_expense_amount.yaml | 6 +- .../pa/dhs/ccw/pa_ccw_adjusted_income.yaml | 4 +- .../pr_medical_expense_deduction.yaml | 8 +- ...snap_excess_medical_expense_deduction.yaml | 16 ++-- .../spm_unit_spm_expenses_chip_premium.yaml | 6 +- policyengine_us/tools/default_uprating.py | 1 - .../is_medically_needy_for_medicaid.py | 2 +- ...dicaid_medically_needy_medical_expenses.py | 16 ++++ .../variables/gov/hud/hud_adjusted_income.py | 2 +- .../variables/gov/hud/hud_medical_expenses.py | 16 ++++ .../itemizing/itemized_medical_expenses.py | 10 ++- .../itemizing/medical_expense_deduction.py | 2 +- .../itemized/al_medical_expense_deduction.py | 2 +- .../ar_medical_expense_deduction_indiv.py | 2 +- .../ar_medical_expense_deduction_joint.py | 2 +- .../itemized/az_itemized_deductions.py | 2 +- .../itemized/hi_medical_expense_deduction.py | 2 +- .../mo_qualified_health_insurance_premiums.py | 7 +- .../mt_medical_expense_deduction_indiv.py | 4 +- .../mt_medical_expense_deduction_joint.py | 2 +- .../nj_medical_expense_deduction.py | 2 +- .../credits/nm_medical_expense_credit.py | 2 +- .../nm_medical_care_expense_deduction.py | 6 +- .../nm_medical_expense_exemption.py | 2 +- ...nreimbursed_medical_care_expense_amount.py | 6 +- .../dhs/ccw/income/pa_ccw_adjusted_income.py | 2 +- .../dhs/ccw/income/pa_ccw_medical_expenses.py | 17 ++++ .../pr_medical_expense_deduction.py | 2 +- .../snap_allowable_medical_expenses.py | 8 +- .../health/medical_out_of_pocket_expenses.py | 17 ---- ..._premium_medical_out_of_pocket_expenses.py | 7 +- 53 files changed, 229 insertions(+), 200 deletions(-) create mode 100644 policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py create mode 100644 policyengine_us/variables/gov/hud/hud_medical_expenses.py create mode 100644 policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py delete mode 100644 policyengine_us/variables/household/expense/health/medical_out_of_pocket_expenses.py diff --git a/changelog.d/8095.fixed.md b/changelog.d/8095.fixed.md index cf30b1b7af4..3d4d81b76c2 100644 --- a/changelog.d/8095.fixed.md +++ b/changelog.d/8095.fixed.md @@ -1 +1 @@ -Split SPM medical out-of-pocket expenses into residual premiums, modeled premiums, and non-premium medical expenses, and add statutory SNAP and itemized medical expense variables. +Split medical expenses into program-specific variables and remove the generic medical out-of-pocket expense aggregate. diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.yaml index 28042d41568..0894b1caa9a 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.yaml @@ -6,7 +6,7 @@ age: 77 employment_income: 200 ssi_countable_resources: 1_000 - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 person_2: age: 66 employment_income: 100 diff --git a/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml b/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml index c337ae5e497..899f122a9fc 100644 --- a/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml @@ -1,7 +1,7 @@ # TODOs: # - Tests involving is_hud_elderly_disabled_family # - Tests involving childcare_expenses -# - Tests involving medical_out_of_pocket_expenses (which only applies to elderly/disabled families) +# - Tests involving other_medical_expenses (which only applies to elderly/disabled families) - name: Default value is zero. period: 2022 @@ -95,7 +95,7 @@ input: hud_annual_income: 10_000 age: 70 - medical_out_of_pocket_expenses: 300 + other_medical_expenses: 300 output: hud_adjusted_income: 9_600 # $10,000 - $400 elderly/disabled - $300 medical expense deduction @@ -104,7 +104,7 @@ input: hud_annual_income: 10_000 is_disabled: true - medical_out_of_pocket_expenses: 301 + other_medical_expenses: 301 output: hud_adjusted_income: 9_599 @@ -113,7 +113,7 @@ input: hud_annual_income: 10_000 is_disabled: true - medical_out_of_pocket_expenses: 20_000 + other_medical_expenses: 20_000 output: hud_adjusted_income: 0 @@ -121,6 +121,6 @@ period: 2022 input: hud_annual_income: 10_000 - medical_out_of_pocket_expenses: 20_000 + other_medical_expenses: 20_000 output: hud_adjusted_income: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml index 26086a0da86..e9dfb1c6311 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.yaml @@ -3,9 +3,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 1_200 + other_medical_expenses: 1_200 person2: - medical_out_of_pocket_expenses: 300 + other_medical_expenses: 300 tax_units: tax_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml index b84b39fab2e..0ed831fd439 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.yaml @@ -1,7 +1,7 @@ - name: Medical expense deduction applies the AGI floor to itemized medical expenses. period: 2025 input: - medical_out_of_pocket_expenses: 10_000 + other_medical_expenses: 10_000 positive_agi: 100_000 output: itemized_medical_expenses: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/local/ca/la/dss/infant_supplement/ca_la_infant_supplement.yaml b/policyengine_us/tests/policy/baseline/gov/local/ca/la/dss/infant_supplement/ca_la_infant_supplement.yaml index a38ff8b8e79..203435fcc3a 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ca/la/dss/infant_supplement/ca_la_infant_supplement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ca/la/dss/infant_supplement/ca_la_infant_supplement.yaml @@ -71,7 +71,7 @@ age: 19 was_in_foster_care: true employment_income: 11_111 - medical_out_of_pocket_expenses: 1_332 + other_medical_expenses: 1_332 rent: 1_332 is_in_foster_care: true is_tax_unit_head_or_spouse: true @@ -92,7 +92,7 @@ age: 19 was_in_foster_care: true employment_income: 11_111 - medical_out_of_pocket_expenses: 1_332 + other_medical_expenses: 1_332 rent: 1_332 is_in_foster_care: true is_tax_unit_head_or_spouse: true diff --git a/policyengine_us/tests/policy/baseline/gov/local/ca/la/general_relief/integration.yaml b/policyengine_us/tests/policy/baseline/gov/local/ca/la/general_relief/integration.yaml index 2ada64b463d..b5a95fc405c 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ca/la/general_relief/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ca/la/general_relief/integration.yaml @@ -6,7 +6,7 @@ you: age: 44 immigration_status_str: CITIZEN - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 is_aca_eshi_eligible: false is_pregnant: false ca_calworks_child_care_time_category: MONTHLY diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_medical_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_medical_deduction.yaml index abb0888950d..5d0a3298c34 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_medical_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_medical_deduction.yaml @@ -2,7 +2,7 @@ period: 2021 input: state_code: AL - medical_out_of_pocket_expenses: 2_000 + other_medical_expenses: 2_000 al_agi: 80_000 output: al_medical_expense_deduction: 0 @@ -12,7 +12,7 @@ period: 2021 input: state_code: AL - medical_out_of_pocket_expenses: 7_000 + other_medical_expenses: 7_000 al_agi: 90_000 output: al_medical_expense_deduction: 3_400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.yaml index 1a2de6d5cba..eda22eb185e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.yaml @@ -3,10 +3,10 @@ input: people: person1: - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 5_000 ar_agi_indiv: 120_000 person2: - medical_out_of_pocket_expenses: 18_000 + other_medical_expenses: 18_000 ar_agi_indiv: 120_000 tax_units: tax_unit: @@ -23,10 +23,10 @@ input: people: person1: - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 5_000 ar_agi_indiv: 240_000 person2: - medical_out_of_pocket_expenses: 18_000 + other_medical_expenses: 18_000 ar_agi_indiv: 0 tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml index ee4d9a35dcf..53a0c7a8e2e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.yaml @@ -3,10 +3,10 @@ input: people: person1: - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 5_000 ar_agi_joint: 120_000 person2: - medical_out_of_pocket_expenses: 18_000 + other_medical_expenses: 18_000 ar_agi_joint: 120_000 tax_units: tax_unit: @@ -23,10 +23,10 @@ input: people: person1: - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 5_000 ar_agi_joint: 240_000 person2: - medical_out_of_pocket_expenses: 18_000 + other_medical_expenses: 18_000 ar_agi_joint: 0 tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml index d8880a4235f..8debc34464e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.yaml @@ -5,7 +5,7 @@ state_code: AZ interest_deduction: 100 casualty_loss_deduction: 200 - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 charitable_deduction: 500 az_charitable_contributions_credit_potential: 0 state_sales_tax: 0 @@ -19,7 +19,7 @@ state_code: AZ interest_deduction: 100 casualty_loss_deduction: 200 - medical_out_of_pocket_expenses: 700 + other_medical_expenses: 700 charitable_deduction: 500 az_charitable_contributions_credit_potential: 500 state_sales_tax: 0 @@ -33,7 +33,7 @@ state_code: AZ interest_deduction: 200 casualty_loss_deduction: 300 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 charitable_deduction: 500 az_charitable_contributions_credit_potential: 600 state_sales_tax: 0 @@ -48,7 +48,7 @@ state_code: AZ interest_deduction: 200 casualty_loss_deduction: 300 - medical_out_of_pocket_expenses: 700 + other_medical_expenses: 700 charitable_deduction: 700 az_charitable_contributions_credit_potential: 600 state_sales_tax: 0 @@ -63,7 +63,7 @@ state_code: AZ interest_deduction: 200 casualty_loss_deduction: 300 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 charitable_deduction: 700 az_charitable_contributions_credit_potential: 600 state_sales_tax: 0 @@ -77,7 +77,7 @@ state_code: AZ interest_deduction: 200 casualty_loss_deduction: 300 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 charitable_deduction: 700 az_charitable_contributions_credit_potential: 800 state_sales_tax: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.yaml index b6f39f08e65..abb0de0bc96 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.yaml @@ -3,9 +3,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 person2: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 tax_units: tax_unit: hi_agi: 1_000 @@ -22,9 +22,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 200 + other_medical_expenses: 200 person2: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 tax_units: tax_unit: hi_agi: 1_000 @@ -41,9 +41,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 person2: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 tax_units: tax_unit: hi_agi: 2_000 @@ -60,9 +60,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 person2: - medical_out_of_pocket_expenses: 100 + other_medical_expenses: 100 tax_units: tax_unit: hi_agi: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_agi_subtractions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_agi_subtractions.yaml index 7568da4d9c2..3eee4f44290 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_agi_subtractions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_agi_subtractions.yaml @@ -7,7 +7,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -31,7 +31,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -55,7 +55,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -79,7 +79,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -103,7 +103,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -127,7 +127,7 @@ long_term_capital_gains: 1_000 short_term_capital_gains: 500 health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 12_000 tax_units: tax_unit1: members: [person1] @@ -151,12 +151,12 @@ long_term_capital_gains: 6_000 short_term_capital_gains: 0 health_insurance_premiums: 6_000 - medical_out_of_pocket_expenses: 6_000 + other_medical_expenses: 6_000 person2: long_term_capital_gains: 4_000 short_term_capital_gains: 0 health_insurance_premiums: 6_000 - medical_out_of_pocket_expenses: 6_000 + other_medical_expenses: 6_000 tax_units: tax_unit1: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.yaml index 7e29934c2df..42dbfce011a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.yaml @@ -3,7 +3,7 @@ absolute_error_margin: 0 input: health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 0 medical_expense_deduction: 0 tax_unit_itemizes: False taxable_income: 10_000 @@ -16,7 +16,7 @@ absolute_error_margin: 0.01 input: health_insurance_premiums: 5_000 - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 0 medical_expense_deduction: 4_625 #medical expenses over 7.5% of taxable_income, equals to 92.5% of income and insurance_premiums tax_unit_itemizes: True taxable_income: 5_000 @@ -29,7 +29,7 @@ absolute_error_margin: 0.01 input: health_insurance_premiums: 12_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 0 medical_expense_deduction: 11_475 tax_unit_itemizes: True taxable_income: 7_000 @@ -37,12 +37,12 @@ output: mo_qualified_health_insurance_premiums: 525 -- name: Itemizes, equal amounts of medical out of pocket expenses and health insurance premiums +- name: Itemizes, equal amounts of other medical expenses and health insurance premiums period: 2021 absolute_error_margin: 0.01 input: health_insurance_premiums: 6_000 - medical_out_of_pocket_expenses: 12_000 + other_medical_expenses: 6_000 medical_expense_deduction: 11_475 tax_unit_itemizes: True taxable_income: 7_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_indiv.yaml index 1eb7724ca74..3804de55c08 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_indiv.yaml @@ -1,7 +1,7 @@ - name: Test case 1, medical_expense higher than 0.075 of agi. period: 2021 input: - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 10_000 state_code: MT is_tax_unit_head_or_spouse: true @@ -11,7 +11,7 @@ - name: Test case 21, medical_expense less than 0.075 of agi. period: 2021 input: - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 20_000 state_code: MT is_tax_unit_head_or_spouse: true @@ -21,7 +21,7 @@ - name: Not head or spouse ineligible period: 2021 input: - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 20_000 state_code: MT is_tax_unit_head_or_spouse: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_joint.yaml index c116a6b27eb..df9e02675bc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_medical_expense_deduction_joint.yaml @@ -5,11 +5,11 @@ people: person1: is_tax_unit_head: true - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 400 person2: is_tax_unit_head: false - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 600 households: household: @@ -25,11 +25,11 @@ people: person1: is_tax_unit_head: true - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 0 person2: is_tax_unit_head: false - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 mt_agi_indiv: 0 households: household: @@ -45,11 +45,11 @@ people: person1: is_tax_unit_head: true - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 mt_agi_indiv: 1_000 person2: is_tax_unit_head: false - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 mt_agi_indiv: 0 households: household: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.yaml index 4a5c529dfd4..1888f420be9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.yaml @@ -3,7 +3,7 @@ absolute_error_margin: 0 input: self_employed_health_insurance_ald: 1_000 - medical_out_of_pocket_expenses: 2_000 + other_medical_expenses: 2_000 nj_agi: 0 state_code: NJ output: @@ -14,7 +14,7 @@ absolute_error_margin: 0 input: self_employed_health_insurance_ald: 1_000 - medical_out_of_pocket_expenses: 2_000 + other_medical_expenses: 2_000 nj_agi: 100_000 state_code: NJ output: @@ -25,7 +25,7 @@ absolute_error_margin: 0 input: self_employed_health_insurance_ald: 1_000 - medical_out_of_pocket_expenses: 2_000 + other_medical_expenses: 2_000 nj_agi: 20_000 state_code: NJ output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml index 4da3dcac884..f558fbaa9ce 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml @@ -3,7 +3,7 @@ input: state_code: NM age: 64 - medical_out_of_pocket_expenses: 30_000 + other_medical_expenses: 30_000 head_is_dependent_elsewhere: false output: nm_medical_expense_credit: 0 @@ -13,7 +13,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 27_999 + other_medical_expenses: 27_999 filing_status: JOINT head_is_dependent_elsewhere: false output: @@ -24,7 +24,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 28_000 + other_medical_expenses: 28_000 filing_status: JOINT head_is_dependent_elsewhere: false output: @@ -35,7 +35,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 28_000 + other_medical_expenses: 28_000 filing_status: SEPARATE head_is_dependent_elsewhere: false output: @@ -46,7 +46,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 40_000 + other_medical_expenses: 40_000 filing_status: SINGLE head_is_dependent_elsewhere: false output: @@ -57,7 +57,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 40_000 + other_medical_expenses: 40_000 filing_status: SINGLE head_is_dependent_elsewhere: true output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.yaml index a0d1380d6a7..d5204ffe5e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.yaml @@ -3,7 +3,7 @@ input: state_code: NM adjusted_gross_income: 10_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 250 @@ -13,7 +13,7 @@ input: state_code: NM adjusted_gross_income: 15_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 250 @@ -23,7 +23,7 @@ input: state_code: NM adjusted_gross_income: 20_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 150 @@ -33,7 +33,7 @@ input: state_code: NM adjusted_gross_income: 35_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 150 @@ -43,7 +43,7 @@ input: state_code: NM adjusted_gross_income: 45_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 100 @@ -53,7 +53,7 @@ input: state_code: NM adjusted_gross_income: 10_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 250 @@ -63,7 +63,7 @@ input: state_code: NM adjusted_gross_income: 15_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 250 @@ -73,7 +73,7 @@ input: state_code: NM adjusted_gross_income: 20_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 150 @@ -83,7 +83,7 @@ input: state_code: NM adjusted_gross_income: 35_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 150 @@ -93,7 +93,7 @@ input: state_code: NM adjusted_gross_income: 45_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 100 @@ -103,7 +103,7 @@ input: state_code: NM adjusted_gross_income: 10_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 250 @@ -113,7 +113,7 @@ input: state_code: NM adjusted_gross_income: 30_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 250 @@ -123,7 +123,7 @@ input: state_code: NM adjusted_gross_income: 50_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 150 @@ -133,7 +133,7 @@ input: state_code: NM adjusted_gross_income: 70_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 150 @@ -143,7 +143,7 @@ input: state_code: NM adjusted_gross_income: 80_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 100 @@ -153,7 +153,7 @@ input: state_code: NM adjusted_gross_income: 10_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 250 @@ -163,7 +163,7 @@ input: state_code: NM adjusted_gross_income: 30_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 250 @@ -173,7 +173,7 @@ input: state_code: NM adjusted_gross_income: 50_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 150 @@ -183,7 +183,7 @@ input: state_code: NM adjusted_gross_income: 70_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 150 @@ -193,7 +193,7 @@ input: state_code: NM adjusted_gross_income: 80_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 100 @@ -203,7 +203,7 @@ input: state_code: NM adjusted_gross_income: 10_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 250 @@ -213,7 +213,7 @@ input: state_code: NM adjusted_gross_income: 20_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 250 @@ -223,7 +223,7 @@ input: state_code: NM adjusted_gross_income: 40_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 150 @@ -233,7 +233,7 @@ input: state_code: NM adjusted_gross_income: 50_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 150 @@ -243,7 +243,7 @@ input: state_code: NM adjusted_gross_income: 60_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 100 @@ -253,7 +253,7 @@ input: state_code: NM adjusted_gross_income: 15_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 0 @@ -263,7 +263,7 @@ input: state_code: NM adjusted_gross_income: 35_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 0 @@ -273,7 +273,7 @@ input: state_code: NM adjusted_gross_income: 45_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SINGLE output: nm_medical_care_expense_deduction: 0 @@ -283,7 +283,7 @@ input: state_code: NM adjusted_gross_income: 15_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 0 @@ -293,7 +293,7 @@ input: state_code: NM adjusted_gross_income: 35_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 0 @@ -303,7 +303,7 @@ input: state_code: NM adjusted_gross_income: 45_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SEPARATE output: nm_medical_care_expense_deduction: 0 @@ -313,7 +313,7 @@ input: state_code: NM adjusted_gross_income: 30_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 0 @@ -323,7 +323,7 @@ input: state_code: NM adjusted_gross_income: 70_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 0 @@ -333,7 +333,7 @@ input: state_code: NM adjusted_gross_income: 80_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: JOINT output: nm_medical_care_expense_deduction: 0 @@ -343,7 +343,7 @@ input: state_code: NM adjusted_gross_income: 30_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 0 @@ -353,7 +353,7 @@ input: state_code: NM adjusted_gross_income: 70_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 0 @@ -363,7 +363,7 @@ input: state_code: NM adjusted_gross_income: 80_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: SURVIVING_SPOUSE output: nm_medical_care_expense_deduction: 0 @@ -373,7 +373,7 @@ input: state_code: NM adjusted_gross_income: 20_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 0 @@ -383,7 +383,7 @@ input: state_code: NM adjusted_gross_income: 50_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 0 @@ -393,7 +393,7 @@ input: state_code: NM adjusted_gross_income: 60_000 - medical_out_of_pocket_expenses: 1_000 + other_medical_expenses: 1_000 filing_status: HEAD_OF_HOUSEHOLD output: nm_medical_care_expense_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml index da5adb06080..2a95d48a1ae 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml @@ -3,7 +3,7 @@ input: state_code: NM age: 64 - medical_out_of_pocket_expenses: 30_000 + other_medical_expenses: 30_000 filing_status: JOINT output: nm_medical_expense_exemption: 0 @@ -13,7 +13,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 27_999 + other_medical_expenses: 27_999 filing_status: JOINT output: nm_medical_expense_exemption: 0 @@ -23,7 +23,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 28_000 + other_medical_expenses: 28_000 filing_status: JOINT output: nm_medical_expense_exemption: 3_000 @@ -33,7 +33,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 28_000 + other_medical_expenses: 28_000 filing_status: SEPARATE output: nm_medical_expense_exemption: 1_500 @@ -43,7 +43,7 @@ input: state_code: NM age: 65 - medical_out_of_pocket_expenses: 40_000 + other_medical_expenses: 40_000 filing_status: SURVIVING_SPOUSE output: nm_medical_expense_exemption: 3_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_pre_tcja_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_pre_tcja_deductions.yaml index f6cbb8f91ab..8aaa948877a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_pre_tcja_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_pre_tcja_deductions.yaml @@ -71,7 +71,7 @@ real_estate_taxes: 12_000 state_sales_tax: 3_000 local_sales_tax: 0 - medical_out_of_pocket_expenses: 10_000 + other_medical_expenses: 10_000 unreimbursed_business_employee_expenses: 4_000 casualty_loss: 15_000 output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/integration.yaml index 74e04f73d1a..738c898e2d0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/integration.yaml @@ -5,7 +5,7 @@ is_medicare_eligible: true adjusted_gross_income: 1_000 health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 employer_contribution_to_health_insurance_premiums_category: "NONE" output: oh_insured_unreimbursed_medical_care_expenses: 1_025 @@ -17,7 +17,7 @@ is_medicare_eligible: true adjusted_gross_income: 10_000 health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 employer_contribution_to_health_insurance_premiums_category: "SOME" output: oh_insured_unreimbursed_medical_care_expenses: 0 @@ -29,7 +29,7 @@ is_medicare_eligible: true adjusted_gross_income: 10_000 health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 800 + other_medical_expenses: 800 employer_contribution_to_health_insurance_premiums_category: "NONE" output: oh_insured_unreimbursed_medical_care_expenses: 550 @@ -41,7 +41,7 @@ is_medicare_eligible: true adjusted_gross_income: 0 health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 800 + other_medical_expenses: 800 employer_contribution_to_health_insurance_premiums_category: "NONE" output: oh_insured_unreimbursed_medical_care_expenses: 1_300 @@ -53,7 +53,7 @@ adjusted_gross_income: 2_000 is_medicare_eligible: true health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 800 + other_medical_expenses: 800 employer_contribution_to_health_insurance_premiums_category: "NONE" output: oh_insured_unreimbursed_medical_care_expenses: 1_150 #800 - (2,000 * 7,5%) + 500 @@ -65,12 +65,12 @@ head: health_insurance_premiums: 300 is_medicare_eligible: true - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 employer_contribution_to_health_insurance_premiums_category: "NONE" spouse: health_insurance_premiums: 200 is_medicare_eligible: true - medical_out_of_pocket_expenses: 300 + other_medical_expenses: 300 employer_contribution_to_health_insurance_premiums_category: "NONE" tax_units: tax_unit: @@ -90,12 +90,12 @@ head: health_insurance_premiums: 300 is_medicare_eligible: true - medical_out_of_pocket_expenses: 500 + other_medical_expenses: 500 employer_contribution_to_health_insurance_premiums_category: "NONE" spouse: health_insurance_premiums: 200 is_medicare_eligible: true - medical_out_of_pocket_expenses: 300 + other_medical_expenses: 300 employer_contribution_to_health_insurance_premiums_category: "NA" tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.yaml index 82e3e0740ea..3ddeb38614e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/deductions/medical_expense/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.yaml @@ -4,7 +4,7 @@ state_code: OH is_medicare_eligible: true health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 employer_contribution_to_health_insurance_premiums_category: "NONE" output: oh_insured_unreimbursed_medical_care_expense_amount: 1_100 @@ -15,7 +15,7 @@ state_code: OH is_medicare_eligible: true health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 600 + other_medical_expenses: 600 employer_contribution_to_health_insurance_premiums_category: "SOME" output: oh_insured_unreimbursed_medical_care_expense_amount: 600 @@ -26,7 +26,7 @@ state_code: OH is_medicare_eligible: true health_insurance_premiums: 500 - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 employer_contribution_to_health_insurance_premiums_category: "SOME" output: oh_insured_unreimbursed_medical_care_expense_amount: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/pa/dhs/ccw/pa_ccw_adjusted_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/pa/dhs/ccw/pa_ccw_adjusted_income.yaml index 793cb3205a6..be942e60178 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/pa/dhs/ccw/pa_ccw_adjusted_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/pa/dhs/ccw/pa_ccw_adjusted_income.yaml @@ -5,7 +5,7 @@ # Deductions per Appendix A Part II: # A. alimony_expense # B. child_support_expense -# C. medical_out_of_pocket_expenses > 10% of gross monthly income +# C. other_medical_expenses > 10% of gross monthly income - name: Case 1, no deductions. period: 2025 @@ -92,7 +92,7 @@ person1: age: 35 employment_income: 36_000 - medical_out_of_pocket_expenses: 6_000 + other_medical_expenses: 6_000 person2: age: 4 spm_units: diff --git a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.yaml index 31d2661597f..d3256a9a554 100644 --- a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.yaml @@ -3,9 +3,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 15_000 + other_medical_expenses: 15_000 person2: - medical_out_of_pocket_expenses: 5_000 + other_medical_expenses: 5_000 tax_units: tax_unit: members: [person1, person2] @@ -23,9 +23,9 @@ input: people: person1: - medical_out_of_pocket_expenses: 2000 + other_medical_expenses: 2000 person2: - medical_out_of_pocket_expenses: 999 + other_medical_expenses: 999 tax_units: tax_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml index aa3153f2b38..8ee9e83b005 100644 --- a/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/usda/snap/income/deductions/snap_excess_medical_expense_deduction.yaml @@ -14,7 +14,7 @@ period: 2022 input: is_usda_disabled: true - medical_out_of_pocket_expenses: 35 * 12 + other_medical_expenses: 35 * 12 output: snap_allowable_medical_expenses: 35 * 12 snap_excess_medical_expense_deduction: 0 @@ -24,7 +24,7 @@ absolute_error_margin: 1 input: is_usda_disabled: true - medical_out_of_pocket_expenses: 35.01 * 12 + other_medical_expenses: 35.01 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: 120 * 12 @@ -35,7 +35,7 @@ input: is_usda_disabled: true is_usda_elderly: true - medical_out_of_pocket_expenses: 155 * 12 + other_medical_expenses: 155 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: (155 - 35) * 12 @@ -45,7 +45,7 @@ absolute_error_margin: 1 input: is_usda_elderly: true - medical_out_of_pocket_expenses: 156 * 12 + other_medical_expenses: 156 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: (156 - 35) * 12 @@ -55,7 +55,7 @@ absolute_error_margin: 1 input: is_usda_disabled: true - medical_out_of_pocket_expenses: 35.01 * 12 + other_medical_expenses: 35.01 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: 150 * 12 @@ -66,7 +66,7 @@ input: is_usda_disabled: true is_usda_elderly: true - medical_out_of_pocket_expenses: 185 * 12 + other_medical_expenses: 185 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: 150 * 12 @@ -76,7 +76,7 @@ absolute_error_margin: 1 input: is_usda_elderly: true - medical_out_of_pocket_expenses: 186 * 12 + other_medical_expenses: 186 * 12 state_code_str: CA output: snap_excess_medical_expense_deduction: 151 * 12 # $151 excess @@ -88,7 +88,7 @@ elderly: is_usda_elderly: true young: - medical_out_of_pocket_expenses: 100 * 12 + other_medical_expenses: 100 * 12 spm_units: spm_unit: members: [elderly, young] diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml index 321ad85120e..272ac3c69f9 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/spm_unit_spm_expenses_chip_premium.yaml @@ -5,12 +5,12 @@ parent: is_chip_eligible: false child_support_expense: 0 - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 child: is_chip_eligible: true is_chip_eligible_child: true child_support_expense: 0 - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 tax_units: tax_unit: members: [parent, child] @@ -35,7 +35,7 @@ is_chip_eligible: true is_chip_eligible_child: true child_support_expense: 0 - medical_out_of_pocket_expenses: 0 + other_medical_expenses: 0 tax_units: tax_unit: members: [child] diff --git a/policyengine_us/tools/default_uprating.py b/policyengine_us/tools/default_uprating.py index 99e160c08a2..cbdf49523c1 100644 --- a/policyengine_us/tools/default_uprating.py +++ b/policyengine_us/tools/default_uprating.py @@ -14,7 +14,6 @@ "social_security_disability", "health_savings_account_ald", "investment_income_elected_form_4952", - "medical_out_of_pocket_expenses", "unemployment_compensation", "self_employment_income", "interest_deduction", diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.py index d291f457c4a..aa3bcac08e8 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/is_medically_needy_for_medicaid.py @@ -11,7 +11,7 @@ class is_medically_needy_for_medicaid(Variable): def formula(person, period, parameters): in_category = person("is_in_medicaid_medically_needy_category", period) personal_income = person("ssi_countable_income", period) - medical_expenses = person("medical_out_of_pocket_expenses", period) + medical_expenses = person("medicaid_medically_needy_medical_expenses", period) personal_assets = person("ssi_countable_resources", period) tax_unit = person.tax_unit income = tax_unit.sum(personal_income - medical_expenses) diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py new file mode 100644 index 00000000000..9ff36db18d9 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class medicaid_medically_needy_medical_expenses(Variable): + value_type = float + entity = Person + label = "Medicaid medically needy medical expenses" + unit = USD + documentation = "Medical expenses used for Medicaid medically needy spenddown." + definition_period = YEAR + reference = "https://www.law.cornell.edu/cfr/text/42/part-435/subpart-D" + + adds = [ + "health_insurance_premiums", + "other_medical_expenses", + ] diff --git a/policyengine_us/variables/gov/hud/hud_adjusted_income.py b/policyengine_us/variables/gov/hud/hud_adjusted_income.py index f0568e6d193..ac17336eb56 100644 --- a/policyengine_us/variables/gov/hud/hud_adjusted_income.py +++ b/policyengine_us/variables/gov/hud/hud_adjusted_income.py @@ -24,7 +24,7 @@ def formula(spm_unit, period, parameters): # TODO: Attendant care (save for later) # Medical expenses for elderly/disabled families. ded = parameters(period).gov.hud.adjusted_income.deductions - moop = add(spm_unit, period, ["medical_out_of_pocket_expenses"]) + moop = spm_unit("hud_medical_expenses", period) # Only expenses beyond a percent of income are deductible. moop_threshold = ded.moop.threshold * income moop_deductible = max_(0, moop - moop_threshold) diff --git a/policyengine_us/variables/gov/hud/hud_medical_expenses.py b/policyengine_us/variables/gov/hud/hud_medical_expenses.py new file mode 100644 index 00000000000..8462d23e02c --- /dev/null +++ b/policyengine_us/variables/gov/hud/hud_medical_expenses.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class hud_medical_expenses(Variable): + value_type = float + entity = SPMUnit + label = "HUD medical expenses" + unit = USD + documentation = "Medical expenses considered in HUD adjusted income." + definition_period = YEAR + reference = "https://www.law.cornell.edu/cfr/text/24/5.611" + + adds = [ + "health_insurance_premiums", + "other_medical_expenses", + ] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py index cba3cfaefcb..3069b7787b8 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py @@ -17,8 +17,12 @@ class itemized_medical_expenses(Variable): "to include amounts paid for diagnosis, cure, mitigation, treatment, " "or prevention of disease; transportation primarily for essential " "medical care; qualified long-term care services; and insurance " - "premiums covering medical care. Current modeling uses the available " - "medical_out_of_pocket_expenses base." + "premiums covering medical care. Current modeling uses health " + "insurance premiums and other medical expenses, excluding general " + "over-the-counter health expenses." ) - adds = ["medical_out_of_pocket_expenses"] + adds = [ + "health_insurance_premiums", + "other_medical_expenses", + ] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py index 52f16537188..7dc775e2864 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/medical_expense_deduction.py @@ -8,7 +8,7 @@ class medical_expense_deduction(Variable): label = "Medical expense deduction" reference = "https://www.law.cornell.edu/uscode/text/26/213#a" unit = USD - documentation = "Medical expenses deducted from taxable income." + documentation = "Itemized medical expenses deducted from taxable income." def formula(tax_unit, period, parameters): expense = tax_unit("itemized_medical_expenses", period) diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_medical_expense_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_medical_expense_deduction.py index 517bf7d6292..a58fa7ede71 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_medical_expense_deduction.py @@ -15,7 +15,7 @@ class al_medical_expense_deduction(Variable): defined_for = StateCode.AL def formula(tax_unit, period, parameters): - expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + expense = tax_unit("itemized_medical_expenses", period) p = parameters( period ).gov.states.al.tax.income.deductions.itemized.medical_expense diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.py index bfd8799b709..509f2511b9b 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_indiv.py @@ -21,7 +21,7 @@ def formula(tax_unit, period, parameters): else: instant_str = f"2013-01-01" p = parameters(instant_str).gov.irs.deductions.itemized.medical - medical_expenses = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expenses = tax_unit("itemized_medical_expenses", period) return max_( 0, medical_expenses - p.floor * agi, diff --git a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py index d0decd8bc06..a2c29bb1f61 100644 --- a/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py +++ b/policyengine_us/variables/gov/states/ar/tax/income/deductions/itemized/ar_medical_expense_deduction_joint.py @@ -19,7 +19,7 @@ def formula(tax_unit, period, parameters): else: instant_str = f"2013-01-01" p = parameters(instant_str).gov.irs.deductions.itemized.medical - medical_expenses = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expenses = tax_unit("itemized_medical_expenses", period) return max_( 0, medical_expenses - p.floor * agi, diff --git a/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py b/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py index 947d7334f3a..4c64593721d 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/az/tax/income/deductions/itemized/az_itemized_deductions.py @@ -30,7 +30,7 @@ def formula(tax_unit, period, parameters): ] federal_deductions = add(tax_unit, period, deductions) # Arizona allows a complete deduction for medical and dental expenses - medical_expenses = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expenses = tax_unit("itemized_medical_expenses", period) # Adjustments to Charitable Contributions # Amount of charitable contributions for which you are claiming diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.py index eb2dfb7cac2..4e85acfad2f 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_medical_expense_deduction.py @@ -18,7 +18,7 @@ def formula(tax_unit, period, parameters): # 1. medical_expense_deduction: worksheet A-1 # use hi_agi instead of AGI - medical_expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expense = tax_unit("itemized_medical_expenses", period) hi_agi = tax_unit("hi_agi", period) medical_capped_amount = max_(0, p_deductions.itemized.medical.floor * hi_agi) return max_(0, medical_expense - medical_capped_amount) diff --git a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py index 9b138b7e257..ccd57a3c240 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py @@ -30,12 +30,7 @@ def formula(person, period, parameters): # this ratio is then used to scale the health_insurance_premium # amount that can be claimed # IRS Schedule A Line 1 - tax_unit_health_expenses = add( - tax_unit, - period, - # Out of pocket expenses include health insurance premiums - ["medical_out_of_pocket_expenses"], - ) + tax_unit_health_expenses = tax_unit("itemized_medical_expenses", period) med_expense_ratio = np.zeros_like(tax_unit_health_expenses) mask = tax_unit_health_expenses > 0 med_expense_ratio[mask] = ( diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py index 2998bd4713d..12b6febc94a 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py @@ -17,7 +17,9 @@ class mt_medical_expense_deduction_indiv(Variable): defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - expense = person("medical_out_of_pocket_expenses", period) + expense = person("health_insurance_premiums", period) + person( + "other_medical_expenses", period + ) p = parameters(period).gov.irs.deductions.itemized.medical # Law does not define Montana AGI as the cap. # Tax form points to page 1, line 14, which is Montana AGI. diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_joint.py index 3757d4ff276..fe4e9124013 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_joint.py @@ -15,7 +15,7 @@ class mt_medical_expense_deduction_joint(Variable): defined_for = StateCode.MT def formula(person, period, parameters): - expense = add(person.tax_unit, period, ["medical_out_of_pocket_expenses"]) + expense = person.tax_unit("itemized_medical_expenses", period) p = parameters(period).gov.irs.deductions.itemized.medical # Law does not define Montana AGI as the cap. # Tax form points to page 1, line 14, which is Montana AGI. diff --git a/policyengine_us/variables/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.py b/policyengine_us/variables/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.py index 09dbb29b111..cbd9e8c4fd8 100644 --- a/policyengine_us/variables/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/states/nj/tax/income/deductions/nj_medical_expense_deduction.py @@ -14,7 +14,7 @@ def formula(tax_unit, period, parameters): self_employed_medical_expense_deduction = tax_unit( "self_employed_health_insurance_ald", period ) - medical_expenses = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expenses = tax_unit("itemized_medical_expenses", period) agi = tax_unit("nj_agi", period) floor = p.rate * agi applicable_medical_expenses = max_(0, medical_expenses - floor) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py index f015950dc10..e516884f45e 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/credits/nm_medical_expense_credit.py @@ -14,7 +14,7 @@ def formula(tax_unit, period, parameters): pcredits = parameters(period).gov.states.nm.tax.income.credits p = pcredits.unreimbursed_medical_care_expense age = person("age", period) - medical_expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expense = tax_unit("itemized_medical_expenses", period) age_eligible = tax_unit.any(age >= p.age_eligibility) expense_eligible = medical_expense >= p.min_expenses dependent_elsewhere = tax_unit("head_is_dependent_elsewhere", period) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.py b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.py index 76cb831c4d7..244719e8ff2 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/deductions/nm_medical_care_expense_deduction.py @@ -23,11 +23,7 @@ def formula(tax_unit, period, parameters): filing_status = tax_unit("filing_status", period) statuses = filing_status.possible_values agi = tax_unit("adjusted_gross_income", period) - expenses = add( - tax_unit, - period, - ["medical_out_of_pocket_expenses"], - ) + expenses = tax_unit("itemized_medical_expenses", period) # Use `right=True` to reflect "over ... but not over ...". rate = select( [ diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py index 1c71ab5bc0e..3b5bed34f4a 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py @@ -15,7 +15,7 @@ def formula(tax_unit, period, parameters): period ).gov.states.nm.tax.income.exemptions.unreimbursed_medical_care_expense age = person("age", period) - medical_expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expense = tax_unit("itemized_medical_expenses", period) age_eligible = tax_unit.any(age >= p.age_eligibility) expense_eligible = medical_expense >= p.min_expenses eligible = age_eligible & expense_eligible diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py index 44a95f19180..5344d6098f3 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py @@ -27,7 +27,5 @@ def formula(person, period, parameters): ) * (employer_premium_contribution == status.NONE) # Premiums only count if the employer paid none. # Line 4 - medical_out_of_pocket_expenses = person( - "medical_out_of_pocket_expenses", period - ) - return eligible_premiums + medical_out_of_pocket_expenses + other_medical_expenses = person("other_medical_expenses", period) + return eligible_premiums + other_medical_expenses diff --git a/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_adjusted_income.py b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_adjusted_income.py index a9fc5f4d2c2..1870b9fbad1 100644 --- a/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_adjusted_income.py +++ b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_adjusted_income.py @@ -18,7 +18,7 @@ def formula(spm_unit, period, parameters): ) alimony_paid = add(spm_unit, period, ["alimony_expense"]) child_support_paid = add(spm_unit, period, ["child_support_expense"]) - medical_expenses = add(spm_unit, period, ["medical_out_of_pocket_expenses"]) + medical_expenses = spm_unit("pa_ccw_medical_expenses", period) medical_deduction = max_( medical_expenses - gross * p.medical_expense_threshold, 0 ) diff --git a/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py new file mode 100644 index 00000000000..1c644734666 --- /dev/null +++ b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class pa_ccw_medical_expenses(Variable): + value_type = float + entity = SPMUnit + label = "Pennsylvania CCW medical expenses" + unit = USD + documentation = "Medical expenses deducted from Pennsylvania CCW income." + definition_period = YEAR + defined_for = StateCode.PA + reference = "https://www.pacodeandbulletin.gov/secure/pacode/data/055/chapter3042/055_3042.pdf#page=18" + + adds = [ + "health_insurance_premiums", + "other_medical_expenses", + ] diff --git a/policyengine_us/variables/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.py b/policyengine_us/variables/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.py index 19963aa32f6..e7167077b43 100644 --- a/policyengine_us/variables/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.py +++ b/policyengine_us/variables/gov/territories/pr/tax/income/taxable_income/deductions/pr_medical_expense_deduction.py @@ -11,7 +11,7 @@ class pr_medical_expense_deduction(Variable): defined_for = StateCode.PR def formula(tax_unit, period, parameters): - expense = add(tax_unit, period, ["medical_out_of_pocket_expenses"]) + expense = tax_unit("itemized_medical_expenses", period) p = parameters( period ).gov.territories.pr.tax.income.taxable_income.deductions.medical diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py index a3db52e919c..b2c94ef24d8 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py @@ -20,7 +20,11 @@ class snap_allowable_medical_expenses(Variable): "drugs, practitioner-approved over-the-counter medication, health " "insurance premiums, Medicare premiums, cost sharing, medical " "supplies, transportation, and related services. Current modeling " - "uses the available medical_out_of_pocket_expenses base." + "uses health insurance premiums and other medical expenses, excluding " + "general over-the-counter health expenses." ) - adds = ["medical_out_of_pocket_expenses"] + adds = [ + "health_insurance_premiums", + "other_medical_expenses", + ] diff --git a/policyengine_us/variables/household/expense/health/medical_out_of_pocket_expenses.py b/policyengine_us/variables/household/expense/health/medical_out_of_pocket_expenses.py deleted file mode 100644 index b851fa9384c..00000000000 --- a/policyengine_us/variables/household/expense/health/medical_out_of_pocket_expenses.py +++ /dev/null @@ -1,17 +0,0 @@ -from policyengine_us.model_api import * - - -class medical_out_of_pocket_expenses(Variable): - value_type = float - entity = Person - label = "Medical out of pocket expenses" - unit = USD - definition_period = YEAR - adds = [ - "health_insurance_premiums", - "other_medical_expenses", - # Note: Excludes over_the_counter_health_expenses - # as IRS does not include them in the itemized deduction, and - # USDA only includes doctor-approved over-the-counter medications - # in their medical out-of-pocket expenses definition for SNAP. - ] diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py index 65336a96066..60cc0f1419d 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_non_premium_medical_out_of_pocket_expenses.py @@ -8,10 +8,9 @@ class spm_unit_non_premium_medical_out_of_pocket_expenses(Variable): unit = USD definition_period = YEAR documentation = ( - "Non-premium medical out-of-pocket expenses for an SPM unit. Unlike " - "the generic `medical_out_of_pocket_expenses` variable used by tax " - "and benefit programs, the SPM definition includes over-the-counter " - "health expenses." + "Non-premium medical out-of-pocket expenses for an SPM unit, " + "including other medical expenses and over-the-counter health " + "expenses." ) adds = [ From d980c3eb4c3788e26e421f4147befc21df07a83d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 06:01:54 -0400 Subject: [PATCH 06/13] Rename MOOP premium components --- ...premium.yaml => medicare_part_b_premium.yaml} | 12 ++++++------ ...l_out_of_pocket_expenses_medicare_part_b.yaml | 10 +++++----- policyengine_us/tools/default_uprating.py | 2 +- .../gov/hhs/medicare/costs/medicare_cost.py | 2 +- ...t_b_premium.py => medicare_part_b_premium.py} | 6 +++--- .../health/health_insurance_premium_residual.py | 16 ---------------- .../expense/health/health_insurance_premiums.py | 10 +++++----- .../expense/health/medicare_part_b_premiums.py | 10 ---------- .../health/other_health_insurance_premiums.py | 13 +++++++++++++ .../spm_unit_health_insurance_premiums.py | 8 ++++---- .../spm_unit_medical_out_of_pocket_expenses.py | 8 ++++---- 11 files changed, 42 insertions(+), 55 deletions(-) rename policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/{income_adjusted_part_b_premium.yaml => medicare_part_b_premium.yaml} (81%) rename policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/{income_adjusted_part_b_premium.py => medicare_part_b_premium.py} (87%) delete mode 100644 policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py delete mode 100644 policyengine_us/variables/household/expense/health/medicare_part_b_premiums.py create mode 100644 policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/income_adjusted_part_b_premium.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml similarity index 81% rename from policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/income_adjusted_part_b_premium.yaml rename to policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml index 22c5dedfdbc..aba17915046 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/income_adjusted_part_b_premium.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/eligibility/medicare_part_b_premium.yaml @@ -9,7 +9,7 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 2_220 # $185 * 12 months + medicare_part_b_premium: 2_220 # $185 * 12 months - name: unit test 2 - married, extra premium period: 2025 @@ -22,7 +22,7 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 4_440 # $370 * 12 months + medicare_part_b_premium: 4_440 # $370 * 12 months - name: unit test 3 - single, extra premium period: 2025 @@ -35,7 +35,7 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 7_546.8 # $628.90 * 12 months + medicare_part_b_premium: 7_546.8 # $628.90 * 12 months - name: unit test 4 - HOH, extra premium period: 2025 @@ -48,7 +48,7 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 3_108 # $259 * 12 months + medicare_part_b_premium: 3_108 # $259 * 12 months - name: unit test 5 - married, no extra premium period: 2025 @@ -61,7 +61,7 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 2_220 # $185 * 12 months + medicare_part_b_premium: 2_220 # $185 * 12 months - name: unit test 6 - married, extra premium, rich period: 2025 @@ -74,4 +74,4 @@ 2023: 0 is_medicare_eligible: true output: - income_adjusted_part_b_premium: 7_546.8 # $628.90 * 12 months + medicare_part_b_premium: 7_546.8 # $628.90 * 12 months 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 d8f65df18c9..fcdc9838348 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 @@ -1,12 +1,12 @@ -- name: SPM unit MOOP includes residual premiums and rules-based Medicare Part B. +- name: SPM unit MOOP includes other health insurance premiums and Medicare Part B. period: 2024 input: people: retiree: age: 70 is_medicare_eligible: true - health_insurance_premium_residual: 500 - income_adjusted_part_b_premium: 2_220 + other_health_insurance_premiums: 500 + medicare_part_b_premium: 2_220 tax_units: tax_unit: members: [retiree] @@ -28,8 +28,8 @@ retiree: age: 70 is_medicare_eligible: true - health_insurance_premium_residual: 500 - income_adjusted_part_b_premium: 5_028 + other_health_insurance_premiums: 500 + medicare_part_b_premium: 5_028 tax_units: tax_unit: members: [retiree] diff --git a/policyengine_us/tools/default_uprating.py b/policyengine_us/tools/default_uprating.py index cbdf49523c1..cfad0d1e5b1 100644 --- a/policyengine_us/tools/default_uprating.py +++ b/policyengine_us/tools/default_uprating.py @@ -102,8 +102,8 @@ "strike_benefits", "other_medical_expenses", "over_the_counter_health_expenses", - "medicare_part_b_premiums", "health_insurance_premiums_without_medicare_part_b", + "other_health_insurance_premiums", ] diff --git a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py index 96f78ece9a3..53cfb392666 100644 --- a/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py +++ b/policyengine_us/variables/gov/hhs/medicare/costs/medicare_cost.py @@ -23,7 +23,7 @@ def formula(person, period, parameters): # Premiums paid by beneficiary part_a_premium = person("base_part_a_premium", period) - part_b_premium = person("income_adjusted_part_b_premium", period) + part_b_premium = person("medicare_part_b_premium", period) total_premiums = part_a_premium + part_b_premium # Net benefit = spending - premiums diff --git a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/income_adjusted_part_b_premium.py b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/medicare_part_b_premium.py similarity index 87% rename from policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/income_adjusted_part_b_premium.py rename to policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/medicare_part_b_premium.py index 0a857e02654..836af3a5048 100644 --- a/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/income_adjusted_part_b_premium.py +++ b/policyengine_us/variables/gov/hhs/medicare/eligibility/part_b/medicare_part_b_premium.py @@ -1,15 +1,15 @@ from policyengine_us.model_api import * -class income_adjusted_part_b_premium(Variable): +class medicare_part_b_premium(Variable): value_type = float entity = Person - label = "Medicare Part B premium (income-adjusted)" + label = "Medicare Part B premium" unit = USD definition_period = YEAR defined_for = "is_medicare_eligible" reference = "https://www.medicare.gov/your-medicare-costs/part-b-costs" - documentation = "Medicare Part B premium adjusted for income (IRMAA). Based on modified adjusted gross income from 2 years prior." + documentation = "Annual Medicare Part B premium, including any income-related monthly adjustment amount. Based on modified adjusted gross income from 2 years prior." def formula(person, period, parameters): tax_unit = person.tax_unit diff --git a/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py b/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py deleted file mode 100644 index a0bce2a804b..00000000000 --- a/policyengine_us/variables/household/expense/health/health_insurance_premium_residual.py +++ /dev/null @@ -1,16 +0,0 @@ -from policyengine_us.model_api import * - - -class health_insurance_premium_residual(Variable): - value_type = float - entity = Person - label = "Residual health insurance premiums" - unit = USD - definition_period = YEAR - documentation = ( - "Residual person-level health insurance premiums after subtracting " - "baseline modeled premium components, such as Marketplace, CHIP, " - "Medicaid, and Medicare Part B premiums. This is an accounting " - "residual imputed by microdata pipelines, not a directly observed " - "coverage category." - ) diff --git a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py index a187f0b8d0e..dc648ee8540 100644 --- a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py +++ b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py @@ -7,8 +7,8 @@ class health_insurance_premiums(Variable): label = "Health insurance premiums" unit = USD definition_period = YEAR - - adds = [ - "health_insurance_premiums_without_medicare_part_b", - "medicare_part_b_premiums", - ] + documentation = ( + "Person-level health insurance premiums supplied directly as an " + "input. Program-specific medical expense variables should use explicit " + "premium components where available." + ) diff --git a/policyengine_us/variables/household/expense/health/medicare_part_b_premiums.py b/policyengine_us/variables/household/expense/health/medicare_part_b_premiums.py deleted file mode 100644 index 90bfde15ad8..00000000000 --- a/policyengine_us/variables/household/expense/health/medicare_part_b_premiums.py +++ /dev/null @@ -1,10 +0,0 @@ -from policyengine_us.model_api import * - - -class medicare_part_b_premiums(Variable): - value_type = float - entity = Person - label = "Medicare Part B premiums" - definition_period = YEAR - unit = USD - uprating = "calibration.gov.hhs.cms.moop_per_capita" diff --git a/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py new file mode 100644 index 00000000000..aa4b945737f --- /dev/null +++ b/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class other_health_insurance_premiums(Variable): + value_type = float + entity = Person + label = "Other health insurance premiums" + unit = USD + definition_period = YEAR + documentation = ( + "Person-level health insurance premiums not otherwise represented by " + "modeled Marketplace, CHIP, Medicaid, or Medicare Part B premiums." + ) 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 e902661e1a2..22949382701 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 @@ -9,14 +9,14 @@ class spm_unit_health_insurance_premiums(Variable): definition_period = YEAR documentation = ( "Health insurance premium expenses for an SPM unit, combining a " - "data-imputed residual premium component with modeled premium " - "components that can respond to policy reforms." + "data-imputed other premium component with modeled premium components " + "that can respond to policy reforms." ) adds = [ - "health_insurance_premium_residual", + "other_health_insurance_premiums", "chip_premium", "medicaid_premium", "marketplace_net_premium", - "income_adjusted_part_b_premium", + "medicare_part_b_premium", ] 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 bfd113b03ba..3f2b5242a7a 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 @@ -10,10 +10,10 @@ class spm_unit_medical_out_of_pocket_expenses(Variable): documentation = ( "Total medical out-of-pocket expenses at the SPM unit level, " "combining health insurance premiums with non-premium medical " - "expenses. Health insurance premiums include data-imputed residual " - "premiums plus modeled Marketplace, CHIP, Medicaid, and Medicare " - "Part B premiums. Non-premium expenses include other medical expenses " - "and over-the-counter health expenses." + "expenses. Health insurance premiums include other health insurance " + "premiums plus modeled Marketplace, CHIP, Medicaid, and Medicare Part " + "B premiums. Non-premium expenses include other medical expenses and " + "over-the-counter health expenses." ) adds = [ From b194650b1b9600edffe60138edf929bda93e16d2 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 06:37:09 -0400 Subject: [PATCH 07/13] Cover Medicare cost Part B premium input --- .../gov/hhs/medicare/costs/medicare_cost.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml index 8e7e655bd86..66f300d8f3c 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicare/costs/medicare_cost.yaml @@ -63,3 +63,14 @@ 2019: 0 output: medicare_cost: 9_298 # $11,080 - $1,782 Part B (2021: $148.50*12) + +- name: unit test 6 - direct Part B premium input + period: 2025 + input: + age: 65 + is_medicare_eligible: true + medicare_enrolled: true + medicare_quarters_of_coverage: 40 + medicare_part_b_premium: 3_000 + output: + medicare_cost: 11_500 # $14,500 spending - $3,000 Part B premium From a4a87818c2e223b5add2af778f255982500cc53e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 07:00:25 -0400 Subject: [PATCH 08/13] Address MOOP subagent review --- .../methodology/moop-decomposition.qmd | 58 +++++++++---------- .../health/health_insurance_premiums.yaml | 9 +++ .../health/health_insurance_premiums.py | 10 +++- .../health/other_health_insurance_premiums.py | 1 + 4 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml diff --git a/docs-quarto/methodology/moop-decomposition.qmd b/docs-quarto/methodology/moop-decomposition.qmd index 27bc0d02d0b..6386cb8b662 100644 --- a/docs-quarto/methodology/moop-decomposition.qmd +++ b/docs-quarto/methodology/moop-decomposition.qmd @@ -7,77 +7,73 @@ subtitle: "Layering rules-based premium computations on CPS-imputed medical cost The Supplemental Poverty Measure subtracts medical out-of-pocket expenses (MOOP) from household resources. PolicyEngine US imports MOOP from the Current Population Survey's reported private health insurance premiums and other medical expenses. Imputed MOOP is accurate on average for the year the CPS was collected, but it does not respond to policy reform. If a user eliminates the ACA premium tax credit, simulated PTC goes to zero while imputed MOOP stays at the pre-reform value, understating the true cost to households. -The fix is to decompose MOOP into a **rules-based** part (variables computable from statute) and an **imputed residual** (everything not modeled). Reforms move the rules-based part; the residual covers copays, over-the-counter costs, non-modeled employer premiums, etc. +The fix is to decompose MOOP into a **rules-based** part (variables computable from statute) and imputed other components for costs the model cannot yet calculate directly. Reforms move the rules-based part; the imputed other components cover copays, over-the-counter costs, non-modeled employer premiums, etc. ## What's shipped today -The SPM-unit-level wrapper combines imputed and computed pieces: +The SPM-unit-level wrapper now combines explicit premium and non-premium +pieces: ```python -def formula(spm_unit, period, parameters): - imputed_moop = add(spm_unit, period, ["medical_out_of_pocket_expenses"]) - imputed_part_b = add(spm_unit, period, ["medicare_part_b_premiums"]) - computed_part_b = add( - spm_unit, period, ["income_adjusted_part_b_premium"] - ) - chip_premium = add(spm_unit, period, ["chip_premium"]) - medicaid_premium = add(spm_unit, period, ["medicaid_premium"]) - return ( - imputed_moop - - imputed_part_b - + computed_part_b - + chip_premium - + medicaid_premium - ) +spm_unit_medical_out_of_pocket_expenses = ( + spm_unit_health_insurance_premiums + + spm_unit_non_premium_medical_out_of_pocket_expenses +) ``` | Component | How it flows | Double-count risk | |---|---|---| -| Imputed person-level MOOP (private premiums + other medical) | In via `medical_out_of_pocket_expenses` | — | -| Medicare Part B | Imputed subtracted, rules-based added (base + IRMAA) | No — clean swap | -| CHIP premium (`chip_premium`) | Added on top of imputed MOOP | Yes — small overstatement for CHIP-paying families until CPS imputation strips the CHIP share at baseline | -| Medicaid expansion premium (`medicaid_premium`) | Added on top of imputed MOOP | Yes — same pattern; IN HIP is the only state with a live (if currently suspended) schedule | -| Marketplace net premium (`marketplace_net_premium`) | **Not wired in yet** | — | +| Other health insurance premiums | In via `other_health_insurance_premiums` from data construction | No once data are built with baseline modeled premiums removed | +| Medicare Part B | Computed by `medicare_part_b_premium` (base + IRMAA) | No | +| CHIP premium (`chip_premium`) | Computed by state schedules | No once data are built with baseline modeled premiums removed | +| Medicaid premium (`medicaid_premium`) | Computed by state schedules | No once data are built with baseline modeled premiums removed | +| Marketplace net premium (`marketplace_net_premium`) | Computed from selected plan premium minus used PTC | No once data are built with baseline modeled premiums removed | +| Other medical expenses and OTC health expenses | In via `other_medical_expenses` and `over_the_counter_health_expenses` | No | -Person-level `medical_out_of_pocket_expenses` is deliberately left untouched so downstream consumers that use it directly — SNAP excess medical deduction, state itemized medical deductions — continue to read the imputed figure. Only SPM resources see the decomposed value. +The old generic `medical_out_of_pocket_expenses` input is removed. Program +rules outside SPM use their own statutory medical-expense wrappers rather than +the SPM MOOP definition. ## The target architecture -Once the CPS-imputation side strips each premium's baseline share from the reported aggregate, the wrapper becomes cleanly layered: +Once the CPS-imputation side strips each premium's baseline share from the reported aggregate, the wrapper is cleanly layered: ``` -spm_unit_MOOP = imputed_residual +spm_unit_MOOP = other_health_insurance_premiums + computed_chip_premium + computed_medicare_part_b_premium + computed_marketplace_net_premium + computed_medicaid_premium + + other_medical_expenses + + over_the_counter_health_expenses ``` with ``` -imputed_residual = imputed_MOOP_total − baseline_computed_premium_sum +other_health_insurance_premiums = + reported_non_medicare_premiums − baseline_computed_premium_sum ``` -where `baseline_computed_premium_sum` is evaluated under the CPS reference year's rules during data construction. Baseline totals reconcile to CPS-reported MOOP exactly; reforms propagate through the computed pieces. Medicare Part B already works this way. CHIP and Medicaid will follow once per-household baseline premiums are pre-subtracted in `policyengine-us-data`. +where `baseline_computed_premium_sum` is evaluated under the CPS reference year's rules during data construction. Baseline totals reconcile to CPS-reported premiums; reforms propagate through the computed pieces. ## Components computable today | Component | Variable | Entity | Status | |---|---|---|---| | CHIP premium | `chip_premium` | TaxUnit | 17 states encoded (see [CHIP](../programs/chip.qmd)) | -| Medicare Part B | `income_adjusted_part_b_premium` | Person | Rules-based (base + IRMAA tiers); **fully swapped** in SPM wrapper | -| Marketplace net premium | `marketplace_net_premium` | TaxUnit | `SLCSP × benchmark_ratio − used_PTC`; benchmark ratio imputed per-household via CPS back-out; not yet wired into the SPM wrapper | +| Medicare Part B | `medicare_part_b_premium` | Person | Rules-based (base + IRMAA tiers) | +| Marketplace net premium | `marketplace_net_premium` | TaxUnit | `SLCSP × benchmark_ratio − used_PTC`; benchmark ratio imputed per-household via CPS back-out | | Medicaid expansion premium | `medicaid_premium` | TaxUnit | IN HIP POWER Account (suspended since 2020, schedule reform-ready) | ## Why Marketplace net premium is layered differently -CPS private-premium reports conflate Marketplace, employer, and direct-purchase coverage in a single aggregate. `selected_marketplace_plan_benchmark_ratio` is now populated per tax unit via back-out from the reported premium, but the baseline subtraction of "imputed Marketplace premium" requires isolating the Marketplace share from the CPS aggregate — in-progress as a data-side follow-up. Until then, `marketplace_net_premium` is available as a variable but not wired into the SPM wrapper. +CPS private-premium reports conflate Marketplace, employer, and direct-purchase coverage in a single aggregate. `selected_marketplace_plan_benchmark_ratio` is populated per tax unit via back-out from the reported premium. Data construction then subtracts baseline `marketplace_net_premium` from reported non-Medicare premiums so SPM can add the modeled Marketplace premium without double counting. ## Gaps - **Copays** — the federal 5 % of income CHIP cost-sharing cap (42 CFR 457.560) binds on premiums + copays combined. Without copay modeling, the cap never bites on premium alone in realistic cases. -- **Employer plan employee share** — stays in the imputed residual; employer plans aren't statutory and can't be computed from rules without a separate employer-plan model. +- **Employer plan employee share** — stays in `other_health_insurance_premiums`; employer plans aren't statutory and can't be computed from rules without a separate employer-plan model. - **Michigan and Montana historical Medicaid expansion premiums** — eliminated 2024-01-01 and 2023-01-01 respectively; historical schedules not yet encoded for reform analysis. ## See also diff --git a/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml new file mode 100644 index 00000000000..724de962c30 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml @@ -0,0 +1,9 @@ +- name: Health insurance premiums include non-Medicare and Medicare Part B premiums. + period: 2025 + input: + age: 65 + is_medicare_eligible: true + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premium: 2_400 + output: + health_insurance_premiums: 3_600 diff --git a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py index dc648ee8540..07b6331084d 100644 --- a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py +++ b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py @@ -8,7 +8,11 @@ class health_insurance_premiums(Variable): unit = USD definition_period = YEAR documentation = ( - "Person-level health insurance premiums supplied directly as an " - "input. Program-specific medical expense variables should use explicit " - "premium components where available." + "Person-level health insurance premiums used by statutory medical " + "expense definitions. SPM MOOP uses its own decomposed premium stack." ) + + adds = [ + "health_insurance_premiums_without_medicare_part_b", + "medicare_part_b_premium", + ] diff --git a/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py index aa4b945737f..8a06f6f3844 100644 --- a/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py +++ b/policyengine_us/variables/household/expense/health/other_health_insurance_premiums.py @@ -7,6 +7,7 @@ class other_health_insurance_premiums(Variable): label = "Other health insurance premiums" unit = USD definition_period = YEAR + uprating = "calibration.gov.hhs.cms.moop_per_capita" documentation = ( "Person-level health insurance premiums not otherwise represented by " "modeled Marketplace, CHIP, Medicaid, or Medicare Part B premiums." From 74dd3924469ba799f2ddc179648d50f5e5ddfbf7 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 07:12:26 -0400 Subject: [PATCH 09/13] Split medical expense premium aggregate --- .../baseline/gov/hud/hud_adjusted_income.yaml | 2 +- .../credits/nm_medical_expense_credit.yaml | 1 + .../nm_medical_expense_exemption.yaml | 1 + .../health/health_insurance_premiums.yaml | 9 ------- ...cal_expense_health_insurance_premiums.yaml | 19 ++++++++++++++ ...dicaid_medically_needy_medical_expenses.py | 2 +- .../variables/gov/hud/hud_medical_expenses.py | 2 +- .../itemizing/itemized_medical_expenses.py | 2 +- .../mi/tax/income/mi_household_resources.py | 4 ++- .../mo_qualified_health_insurance_premiums.py | 4 ++- .../mt_medical_expense_deduction_indiv.py | 6 ++--- ...nreimbursed_medical_care_expense_amount.py | 3 ++- ...ured_unreimbursed_medical_care_expenses.py | 4 ++- .../dhs/ccw/income/pa_ccw_medical_expenses.py | 2 +- .../snap_allowable_medical_expenses.py | 2 +- .../health/health_insurance_premiums.py | 10 +++----- ...dical_expense_health_insurance_premiums.py | 25 +++++++++++++++++++ 17 files changed, 69 insertions(+), 29 deletions(-) delete mode 100644 policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/expense/health/medical_expense_health_insurance_premiums.yaml create mode 100644 policyengine_us/variables/household/expense/health/medical_expense_health_insurance_premiums.py diff --git a/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml b/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml index 899f122a9fc..8e486258812 100644 --- a/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hud/hud_adjusted_income.yaml @@ -94,7 +94,7 @@ period: 2022 input: hud_annual_income: 10_000 - age: 70 + age: 62 other_medical_expenses: 300 output: hud_adjusted_income: 9_600 # $10,000 - $400 elderly/disabled - $300 medical expense deduction diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml index f558fbaa9ce..df55277d73a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/credits/nm_medical_expense_credit.yaml @@ -13,6 +13,7 @@ input: state_code: NM age: 65 + medicare_enrolled: false other_medical_expenses: 27_999 filing_status: JOINT head_is_dependent_elsewhere: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml index 2a95d48a1ae..65264be9353 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml @@ -13,6 +13,7 @@ input: state_code: NM age: 65 + medicare_enrolled: false other_medical_expenses: 27_999 filing_status: JOINT output: diff --git a/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml deleted file mode 100644 index 724de962c30..00000000000 --- a/policyengine_us/tests/policy/baseline/household/expense/health/health_insurance_premiums.yaml +++ /dev/null @@ -1,9 +0,0 @@ -- name: Health insurance premiums include non-Medicare and Medicare Part B premiums. - period: 2025 - input: - age: 65 - is_medicare_eligible: true - health_insurance_premiums_without_medicare_part_b: 1_200 - medicare_part_b_premium: 2_400 - output: - health_insurance_premiums: 3_600 diff --git a/policyengine_us/tests/policy/baseline/household/expense/health/medical_expense_health_insurance_premiums.yaml b/policyengine_us/tests/policy/baseline/household/expense/health/medical_expense_health_insurance_premiums.yaml new file mode 100644 index 00000000000..51b5e2b1372 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/health/medical_expense_health_insurance_premiums.yaml @@ -0,0 +1,19 @@ +- name: Medical-expense premiums use direct input when supplied. + period: 2025 + input: + health_insurance_premiums: 1_000 + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premium: 2_400 + output: + medical_expense_health_insurance_premiums: 1_000 + +- name: Medical-expense premiums fall back to non-Medicare and Medicare Part B premiums. + period: 2025 + input: + age: 65 + is_medicare_eligible: true + medicare_enrolled: true + health_insurance_premiums_without_medicare_part_b: 1_200 + medicare_part_b_premium: 2_400 + output: + medical_expense_health_insurance_premiums: 3_600 diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py index 9ff36db18d9..cd95bdbce86 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/medically_needy/medicaid_medically_needy_medical_expenses.py @@ -11,6 +11,6 @@ class medicaid_medically_needy_medical_expenses(Variable): reference = "https://www.law.cornell.edu/cfr/text/42/part-435/subpart-D" adds = [ - "health_insurance_premiums", + "medical_expense_health_insurance_premiums", "other_medical_expenses", ] diff --git a/policyengine_us/variables/gov/hud/hud_medical_expenses.py b/policyengine_us/variables/gov/hud/hud_medical_expenses.py index 8462d23e02c..bea476968e1 100644 --- a/policyengine_us/variables/gov/hud/hud_medical_expenses.py +++ b/policyengine_us/variables/gov/hud/hud_medical_expenses.py @@ -11,6 +11,6 @@ class hud_medical_expenses(Variable): reference = "https://www.law.cornell.edu/cfr/text/24/5.611" adds = [ - "health_insurance_premiums", + "medical_expense_health_insurance_premiums", "other_medical_expenses", ] diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py index 3069b7787b8..6b2d9b21d76 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/itemized_medical_expenses.py @@ -23,6 +23,6 @@ class itemized_medical_expenses(Variable): ) adds = [ - "health_insurance_premiums", + "medical_expense_health_insurance_premiums", "other_medical_expenses", ] diff --git a/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py b/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py index 5ea1d3c7c5f..7886ff8f9ca 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py @@ -44,7 +44,9 @@ def formula(tax_unit, period, parameters): # Other sources are added as-is without flooring total += add(tax_unit, period, [source]) - health_insurance_premiums = add(tax_unit, period, ["health_insurance_premiums"]) + health_insurance_premiums = add( + tax_unit, period, ["medical_expense_health_insurance_premiums"] + ) above_the_line_deductions = tax_unit("above_the_line_deductions", period) return max_( 0, diff --git a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py index ccd57a3c240..6e07d0e3cb1 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py @@ -37,7 +37,9 @@ def formula(person, period, parameters): med_expense_deduction[mask] / tax_unit_health_expenses[mask] ) - person_premiums = person("health_insurance_premiums", period) + person_premiums = person( + "medical_expense_health_insurance_premiums", period + ) tax_unit_premiums = tax_unit.sum(person_premiums) # Line 13 of MO Form 5695, represents the portion of diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py index 12b6febc94a..3be9cfa1335 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py @@ -17,9 +17,9 @@ class mt_medical_expense_deduction_indiv(Variable): defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - expense = person("health_insurance_premiums", period) + person( - "other_medical_expenses", period - ) + expense = person( + "medical_expense_health_insurance_premiums", period + ) + person("other_medical_expenses", period) p = parameters(period).gov.irs.deductions.itemized.medical # Law does not define Montana AGI as the cap. # Tax form points to page 1, line 14, which is Montana AGI. diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py index 5344d6098f3..b7adead891b 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/insured_unreimbursed_expenses/oh_insured_unreimbursed_medical_care_expense_amount.py @@ -23,7 +23,8 @@ def formula(person, period, parameters): medicare_eligible = person("is_medicare_eligible", period) # Line 3 eligible_premiums = ( - person("health_insurance_premiums", period) * medicare_eligible + person("medical_expense_health_insurance_premiums", period) + * medicare_eligible ) * (employer_premium_contribution == status.NONE) # Premiums only count if the employer paid none. # Line 4 diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py index bd9befac63b..1acb7876f8f 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py @@ -21,7 +21,9 @@ def formula(person, period, parameters): period, ) status = employer_premium_contribution.possible_values - health_insurance_premiums = person("health_insurance_premiums", period) + health_insurance_premiums = person( + "medical_expense_health_insurance_premiums", period + ) # Premiums only count if the employer paid none. eligible_health_insurance_premiums = health_insurance_premiums * ( employer_premium_contribution == status.NONE diff --git a/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py index 1c644734666..d8e0b7ad524 100644 --- a/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py +++ b/policyengine_us/variables/gov/states/pa/dhs/ccw/income/pa_ccw_medical_expenses.py @@ -12,6 +12,6 @@ class pa_ccw_medical_expenses(Variable): reference = "https://www.pacodeandbulletin.gov/secure/pacode/data/055/chapter3042/055_3042.pdf#page=18" adds = [ - "health_insurance_premiums", + "medical_expense_health_insurance_premiums", "other_medical_expenses", ] diff --git a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py index b2c94ef24d8..fddfa2f03a5 100644 --- a/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py +++ b/policyengine_us/variables/gov/usda/snap/income/deductions/snap_allowable_medical_expenses.py @@ -25,6 +25,6 @@ class snap_allowable_medical_expenses(Variable): ) adds = [ - "health_insurance_premiums", + "medical_expense_health_insurance_premiums", "other_medical_expenses", ] diff --git a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py index 07b6331084d..79341afc631 100644 --- a/policyengine_us/variables/household/expense/health/health_insurance_premiums.py +++ b/policyengine_us/variables/household/expense/health/health_insurance_premiums.py @@ -8,11 +8,7 @@ class health_insurance_premiums(Variable): unit = USD definition_period = YEAR documentation = ( - "Person-level health insurance premiums used by statutory medical " - "expense definitions. SPM MOOP uses its own decomposed premium stack." + "Person-level health insurance premiums supplied directly as an input. " + "SPM MOOP and statutory medical expense definitions use decomposed " + "premium aggregates." ) - - adds = [ - "health_insurance_premiums_without_medicare_part_b", - "medicare_part_b_premium", - ] diff --git a/policyengine_us/variables/household/expense/health/medical_expense_health_insurance_premiums.py b/policyengine_us/variables/household/expense/health/medical_expense_health_insurance_premiums.py new file mode 100644 index 00000000000..dfb010bfed6 --- /dev/null +++ b/policyengine_us/variables/household/expense/health/medical_expense_health_insurance_premiums.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class medical_expense_health_insurance_premiums(Variable): + value_type = float + entity = Person + label = "Health insurance premiums for medical expense definitions" + unit = USD + definition_period = YEAR + documentation = ( + "Person-level health insurance premiums counted by statutory medical " + "expense definitions. Uses a direct premium input when supplied; " + "otherwise combines reported non-Medicare premiums with modeled " + "Medicare Part B premiums." + ) + + def formula(person, period, parameters): + direct = person("health_insurance_premiums", period) + non_medicare = person( + "health_insurance_premiums_without_medicare_part_b", period + ) + medicare_enrolled = person("medicare_enrolled", period) + medicare_part_b = person("medicare_part_b_premium", period) * medicare_enrolled + decomposed = non_medicare + medicare_part_b + return where(direct != 0, direct, decomposed) From 1b1aa6c2e1e3817d45806042daa5c86706d30772 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 07:17:18 -0400 Subject: [PATCH 10/13] Limit medical premium aggregate scope --- .../gov/states/mi/tax/income/mi_household_resources.py | 4 +--- .../oh_uninsured_unreimbursed_medical_care_expenses.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py b/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py index 7886ff8f9ca..5ea1d3c7c5f 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/mi_household_resources.py @@ -44,9 +44,7 @@ def formula(tax_unit, period, parameters): # Other sources are added as-is without flooring total += add(tax_unit, period, [source]) - health_insurance_premiums = add( - tax_unit, period, ["medical_expense_health_insurance_premiums"] - ) + health_insurance_premiums = add(tax_unit, period, ["health_insurance_premiums"]) above_the_line_deductions = tax_unit("above_the_line_deductions", period) return max_( 0, diff --git a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py index 1acb7876f8f..bd9befac63b 100644 --- a/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py +++ b/policyengine_us/variables/gov/states/oh/tax/income/deductions/medical_exepenses/oh_uninsured_unreimbursed_medical_care_expenses.py @@ -21,9 +21,7 @@ def formula(person, period, parameters): period, ) status = employer_premium_contribution.possible_values - health_insurance_premiums = person( - "medical_expense_health_insurance_premiums", period - ) + health_insurance_premiums = person("health_insurance_premiums", period) # Premiums only count if the employer paid none. eligible_health_insurance_premiums = health_insurance_premiums * ( employer_premium_contribution == status.NONE From 6f537e032060418353ef56b6f22100b14549d437 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 07:26:31 -0400 Subject: [PATCH 11/13] Format medical premium review changes --- .../subtractions/mo_qualified_health_insurance_premiums.py | 4 +--- .../itemized/medical/mt_medical_expense_deduction_indiv.py | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py index 6e07d0e3cb1..f0af27f3da0 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py @@ -37,9 +37,7 @@ def formula(person, period, parameters): med_expense_deduction[mask] / tax_unit_health_expenses[mask] ) - person_premiums = person( - "medical_expense_health_insurance_premiums", period - ) + person_premiums = person("medical_expense_health_insurance_premiums", period) tax_unit_premiums = tax_unit.sum(person_premiums) # Line 13 of MO Form 5695, represents the portion of diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py index 3be9cfa1335..62c2a25b411 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/medical/mt_medical_expense_deduction_indiv.py @@ -17,9 +17,9 @@ class mt_medical_expense_deduction_indiv(Variable): defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - expense = person( - "medical_expense_health_insurance_premiums", period - ) + person("other_medical_expenses", period) + expense = person("medical_expense_health_insurance_premiums", period) + person( + "other_medical_expenses", period + ) p = parameters(period).gov.irs.deductions.itemized.medical # Law does not define Montana AGI as the cap. # Tax form points to page 1, line 14, which is Montana AGI. From d2ca0f8bc1452c51b6cc389559b3f486f9df5193 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 07:53:49 -0400 Subject: [PATCH 12/13] Keep Missouri premium subtraction on reported premiums --- .../subtractions/mo_qualified_health_insurance_premiums.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py index f0af27f3da0..ccd57a3c240 100644 --- a/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py +++ b/policyengine_us/variables/gov/states/mo/tax/income/subtractions/mo_qualified_health_insurance_premiums.py @@ -37,7 +37,7 @@ def formula(person, period, parameters): med_expense_deduction[mask] / tax_unit_health_expenses[mask] ) - person_premiums = person("medical_expense_health_insurance_premiums", period) + person_premiums = person("health_insurance_premiums", period) tax_unit_premiums = tax_unit.sum(person_premiums) # Line 13 of MO Form 5695, represents the portion of From fdd3313375754874598a164b9c5c6b19c81d5011 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 29 Apr 2026 09:15:06 -0400 Subject: [PATCH 13/13] Bump policyengine-us for MOOP decomposition --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5689344e25d..9c6320c8cfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "policyengine-us" -version = "1.670.2" +version = "1.673.0" description = "Add your description here." readme = "README.md" authors = [ @@ -87,4 +87,3 @@ showcontent = true [tool.ruff.lint] select = ["E", "F"] ignore = ["E402", "E501", "E712", "E713", "E721", "E722", "E741", "F401", "F402", "F403", "F405", "F541", "F811", "F821", "F841"] -