From d812f194ff95df339aa437ce2db315f5e15655e2 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Mon, 15 Jun 2026 23:05:03 -0400 Subject: [PATCH] Zero imputed Medicare Part B premiums to restore TAXSIM parity TAXSIM has no medical-expense input, but PolicyEngine imputes Medicare Part B premiums for Medicare-enrolled people via `medical_expense_health_insurance_premiums`. When a record itemizes federally (e.g. a large mortgage), that imputed amount flows through the federal itemized medical deduction into state medical exemptions and deductions, understating state tax versus TAXSIM/TaxAct. Zero `medical_expense_health_insurance_premiums` per person in the Microsimulation runner (the CLI path) and in the single-household `input_mapper`/`yaml_generator` paths, mirroring the existing zeroing of SSI/SNAP/TANF/Head Start. It is the only itemized-medical component with an imputation formula; the others are pure inputs that already default to zero. Verified against the NBER taxsimtest binary: - taxsim #981 (OH): $0.00 -> $321.41 (binary $321.41) - taxsim #982 (OH): $0.00 -> $330.83 (binary $330.83) - taxsim #978 (OK): $44.63 -> $83.20 (TaxAct $83) - taxsim #985 (MA): $246.08 -> $385.89 (TaxAct ~$386) Updates test_mappers expected situations for the new zeroed field. Co-Authored-By: Claude Opus 4.8 (1M context) --- changelog.d/zero-imputed-medical-premiums.fixed.md | 1 + policyengine_taxsim/core/input_mapper.py | 10 ++++++++++ policyengine_taxsim/core/yaml_generator.py | 6 ++++++ policyengine_taxsim/runners/policyengine_runner.py | 4 ++++ tests/test_mappers.py | 14 ++++++++++++++ 5 files changed, 35 insertions(+) create mode 100644 changelog.d/zero-imputed-medical-premiums.fixed.md diff --git a/changelog.d/zero-imputed-medical-premiums.fixed.md b/changelog.d/zero-imputed-medical-premiums.fixed.md new file mode 100644 index 0000000..02cc830 --- /dev/null +++ b/changelog.d/zero-imputed-medical-premiums.fixed.md @@ -0,0 +1 @@ +Description: Zero PE's imputed Medicare Part B premiums (medical_expense_health_insurance_premiums) so they don't inflate state medical exemptions/deductions, restoring TAXSIM/TaxAct parity for elderly itemizers (MA, OK, OH). diff --git a/policyengine_taxsim/core/input_mapper.py b/policyengine_taxsim/core/input_mapper.py index 4d521c8..2be72a6 100644 --- a/policyengine_taxsim/core/input_mapper.py +++ b/policyengine_taxsim/core/input_mapper.py @@ -225,6 +225,16 @@ def form_household_situation(year, state, taxsim_vars): "commodity_supplemental_food_program" ] = {str(year): 0} + # Explicitly set imputed medical expenses to 0 for all people. TAXSIM has no + # medical-expense input, so PE's imputed Medicare Part B premiums would + # otherwise flow through the federal itemized medical deduction into state + # medical exemptions/deductions (e.g. MA Schedule Y, OK Schedule 511-D, OH), + # understating state tax relative to TAXSIM/TaxAct. + for person_name in household_situation["people"]: + household_situation["people"][person_name][ + "medical_expense_health_insurance_premiums" + ] = {str(year): 0} + # Explicitly set SNAP to 0 for all SPM units to prevent PolicyEngine from imputing SNAP benefits # TAXSIM does not model SNAP, so we need to ensure it's not automatically calculated for spm_unit_name in household_situation["spm_units"]: diff --git a/policyengine_taxsim/core/yaml_generator.py b/policyengine_taxsim/core/yaml_generator.py index ff02ab3..fe41b3e 100644 --- a/policyengine_taxsim/core/yaml_generator.py +++ b/policyengine_taxsim/core/yaml_generator.py @@ -170,6 +170,12 @@ def generate_yaml( "commodity_supplemental_food_program": person_data.get( "commodity_supplemental_food_program", {} ).get(year_str, 0), + # TAXSIM has no medical-expense input; zero PE's imputed Medicare + # Part B premiums so they don't flow into state medical + # exemptions/deductions via the federal itemized medical deduction. + "medical_expense_health_insurance_premiums": person_data.get( + "medical_expense_health_insurance_premiums", {} + ).get(year_str, 0), } # Add optional fields only if they have non-zero values diff --git a/policyengine_taxsim/runners/policyengine_runner.py b/policyengine_taxsim/runners/policyengine_runner.py index 0247ec2..c7976e7 100644 --- a/policyengine_taxsim/runners/policyengine_runner.py +++ b/policyengine_taxsim/runners/policyengine_runner.py @@ -167,6 +167,7 @@ def _initialize_dataset_structure(self) -> dict: "head_start", # Head Start should be 0 to match TAXSIM (which doesn't model Head Start) "early_head_start", # Early Head Start should be 0 to match TAXSIM (which doesn't model Early Head Start) "commodity_supplemental_food_program", # Commodity supplemental food program should be 0 to match TAXSIM (which doesn't model this program) + "medical_expense_health_insurance_premiums", # TAXSIM has no medical-expense input; zero PE's imputed Medicare Part B premiums so they don't flow into state medical exemptions/deductions via the federal itemized medical deduction } # Combine all variables @@ -837,6 +838,9 @@ def generate(self) -> None: data["commodity_supplemental_food_program"][year_int] = np.zeros( total_people_for_year ) # Set commodity supplemental food program to 0 to match TAXSIM (which doesn't model this program) + data["medical_expense_health_insurance_premiums"][year_int] = np.zeros( + total_people_for_year + ) # TAXSIM has no medical-expense input; zero PE's imputed Medicare Part B premiums so they don't flow into state medical exemptions/deductions via the federal itemized medical deduction # Household data data["household_id"][year_int] = year_household_ids diff --git a/tests/test_mappers.py b/tests/test_mappers.py index a24792a..2f87a0d 100644 --- a/tests/test_mappers.py +++ b/tests/test_mappers.py @@ -94,6 +94,7 @@ def test_import_single_household(sample_taxsim_input): "head_start": {"2021": 0}, "early_head_start": {"2021": 0}, "commodity_supplemental_food_program": {"2021": 0}, + "medical_expense_health_insurance_premiums": {"2021": 0}, } }, "spm_units": { @@ -128,6 +129,7 @@ def test_import_single_household_without_state(sample_taxsim_input_without_state "head_start": {"2021": 0}, "early_head_start": {"2021": 0}, "commodity_supplemental_food_program": {"2021": 0}, + "medical_expense_health_insurance_premiums": {"2021": 0}, } }, "spm_units": { @@ -162,6 +164,7 @@ def test_import_single_household_with_state_eq_0(sample_taxsim_input_with_state_ "head_start": {"2021": 0}, "early_head_start": {"2021": 0}, "commodity_supplemental_food_program": {"2021": 0}, + "medical_expense_health_insurance_premiums": {"2021": 0}, } }, "spm_units": { @@ -250,6 +253,7 @@ def test_joint_household(sample_taxsim_input_for_joint): "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your partner": { "age": {"2023": 40}, @@ -259,6 +263,7 @@ def test_joint_household(sample_taxsim_input_for_joint): "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your first dependent": { "age": {"2023": 10}, @@ -270,6 +275,7 @@ def test_joint_household(sample_taxsim_input_for_joint): "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your second dependent": { "age": {"2023": 10}, @@ -281,6 +287,7 @@ def test_joint_household(sample_taxsim_input_for_joint): "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, }, "spm_units": { @@ -356,6 +363,7 @@ def test_household_with_dependent(sample_taxsim_input_for_household_with_depende "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your partner": { "age": {"2023": 40}, @@ -365,6 +373,7 @@ def test_household_with_dependent(sample_taxsim_input_for_household_with_depende "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your first dependent": { "age": {"2023": 4}, @@ -376,6 +385,7 @@ def test_household_with_dependent(sample_taxsim_input_for_household_with_depende "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your second dependent": { "age": {"2023": 10}, @@ -387,6 +397,7 @@ def test_household_with_dependent(sample_taxsim_input_for_household_with_depende "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, }, "spm_units": { @@ -455,6 +466,7 @@ def test_household_with_dependent_single_parent( "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your first dependent": { "age": {"2023": 4}, @@ -466,6 +478,7 @@ def test_household_with_dependent_single_parent( "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, "your second dependent": { "age": {"2023": 10}, @@ -477,6 +490,7 @@ def test_household_with_dependent_single_parent( "head_start": {"2023": 0}, "early_head_start": {"2023": 0}, "commodity_supplemental_food_program": {"2023": 0}, + "medical_expense_health_insurance_premiums": {"2023": 0}, }, }, "spm_units": {