From 8054c934d7f82eefeababb46b0061c6b2c4e3596 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 11:44:37 -0400 Subject: [PATCH 1/3] Count the pre-2018 miscellaneous deduction once in Mississippi and Missouri itemized_deductions_less_salt and itemized_taxable_income_deductions already include misc_deduction (federal Schedule A line 27, above the 2% floor). Mississippi added misc_deduction again on top of the first, and Missouri listed it beside the second. The 2017 Mississippi Form 80-108 (lines 7a-7d) and 2017 Form MO-A Part 2 (line 1, Form 1040 line 40) take it once. From 2018 misc_deduction is zero, so only years before 2018 change. Co-Authored-By: Claude Opus 5.5 --- ...ms-mo-misc-deduction-double-count.fixed.md | 1 + .../mo/tax/income/deductions/itemized.yaml | 7 +++-- .../deductions/mo_itemized_deductions.yaml | 26 +++++++++++++++---- .../itemized/ms_itemized_deductions_unit.yaml | 20 ++++++++++++++ .../itemized/ms_itemized_deductions_unit.py | 5 +++- 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 changelog.d/ms-mo-misc-deduction-double-count.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml diff --git a/changelog.d/ms-mo-misc-deduction-double-count.fixed.md b/changelog.d/ms-mo-misc-deduction-double-count.fixed.md new file mode 100644 index 00000000000..b3afeabe014 --- /dev/null +++ b/changelog.d/ms-mo-misc-deduction-double-count.fixed.md @@ -0,0 +1 @@ +Count the pre-2018 federal miscellaneous deduction once in Mississippi and Missouri itemized deductions. diff --git a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml index 29872f6ad0b..14c58b0561d 100644 --- a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml +++ b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml @@ -1,8 +1,9 @@ description: Missouri lets filers itemize the following deductions. values: 1993-01-01: - - itemized_taxable_income_deductions # includes casualty_loss_deduction, charitable_deduction, interest_deduction, salt_deduction ,and medical_expense_deduction - - misc_deduction + # Form MO-A Part 2 line 1: total federal itemized deductions, which include + # the miscellaneous deduction above the 2% floor before 2018. + - itemized_taxable_income_deductions - employee_social_security_tax - employee_medicare_tax - additional_medicare_tax @@ -22,3 +23,5 @@ metadata: href: https://dor.mo.gov/forms/MO-1040%20Instructions_2021.pdf#page=16 - title: 2024 Form MO-A - Individual Income Tax Adjustments, Part 2 - Missouri Itemized Deductions href: https://dor.mo.gov/forms/MO-A_2024.pdf#page=2 + - title: 2017 Form MO-1040 instructions, Form MO-A Part 2 line 1 + href: https://dor.mo.gov/forms/MO-1040%20Instructions_2017.pdf#page=22 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml index d2510d5c88d..f453eb1dd78 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml @@ -5,7 +5,6 @@ itemized_taxable_income_deductions: 0 employee_social_security_tax: 0 employee_medicare_tax: 0 - misc_deduction: 0 mo_net_state_income_taxes: 0 state_code: MO output: @@ -15,10 +14,11 @@ period: 2021 absolute_error_margin: 0.01 input: - itemized_taxable_income_deductions: 10 + # Form MO-A Part 2 line 1 is the federal total, which already includes + # any miscellaneous deduction. + itemized_taxable_income_deductions: 20 employee_social_security_tax: 0 employee_medicare_tax: 0 - misc_deduction: 10 mo_net_state_income_taxes: 10 state_code: MO output: @@ -28,10 +28,9 @@ period: 2021 absolute_error_margin: 0.01 input: - itemized_taxable_income_deductions: 1000 + itemized_taxable_income_deductions: 2000 employee_social_security_tax: 0 employee_medicare_tax: 0 - misc_deduction: 1000 mo_net_state_income_taxes: 1000 state_code: MO output: @@ -51,3 +50,20 @@ households: {household: {members: [head, spouse], state_code: MO}} output: mo_itemized_deductions: 50_000 + +# 2017 Form MO-A Part 2 line 1 is total federal itemized deductions from Form +# 1040 line 40, which already includes the miscellaneous deduction above the +# 2% floor; Part 2 has no separate line for it. +- name: 2017 miscellaneous deduction counted once + period: 2017 + absolute_error_margin: 0.01 + input: + charitable_deduction: 9_000 + misc_deduction: 1_000 + employee_social_security_tax: 0 + employee_medicare_tax: 0 + mo_net_state_income_taxes: 0 + state_code: MO + output: + itemized_taxable_income_deductions: 10_000 + mo_itemized_deductions: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml new file mode 100644 index 00000000000..38ffd8ebeb0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml @@ -0,0 +1,20 @@ +# 2017 Form 80-108 lines 7a to 7d take miscellaneous expenses above 2% of +# federal AGI once; itemized_deductions_less_salt already includes them. +- name: 2017 miscellaneous deduction counted once + absolute_error_margin: 0.01 + period: 2017 + input: + charitable_deduction: 2_000 + misc_deduction: 1_000 + state_code: MS + output: + ms_itemized_deductions_unit: 3_000 + +- name: 2025 no miscellaneous deduction subject to the 2% floor + absolute_error_margin: 0.01 + period: 2025 + input: + charitable_deduction: 2_000 + state_code: MS + output: + ms_itemized_deductions_unit: 2_000 diff --git a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py index 0cbb0cdbd17..163abcd9ae8 100644 --- a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py @@ -12,12 +12,15 @@ class ms_itemized_deductions_unit(Variable): "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80100221.pdf#page=15", "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80108228.pdf", # Line 7 "https://law.justia.com/codes/mississippi/title-27/chapter-7/article-1/section-27-7-17/", + # 2017 instructions, Form 80-108 lines 7a to 7d. + "https://taxsim.nber.org/historical_state_tax_forms/MS/2017/80100178.pdf#page=15", ) defined_for = StateCode.MS + # itemized_deductions_less_salt already includes the federal miscellaneous + # deduction above the 2% floor (Form 80-108 lines 7a to 7d before 2018). adds = [ "itemized_deductions_less_salt", - "misc_deduction", "ms_real_estate_tax_deduction", ] From 7bd38cb0894ecf77e19b0b44ef7b708e84b8757a Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 16:27:53 +0000 Subject: [PATCH 2/3] Add Missouri and Mississippi witnesses and a counted-once property test Adds the spec's 2017 witnesses (federal total with and without the state income tax adjustment for Missouri, with and without real estate taxes for Mississippi) and a 2016 Missouri case, since parameter backdating makes the federal list live from 2015. Cites Miss. Code Ann. 27-7-17(4) editions read for the spec and both 2017 MO-1040 instruction pages. Renames two Missouri tests whose names still described the old inputs. A Hypothesis test checks that, with the federal totals fixed, the miscellaneous deduction never changes either state's total in 2016, 2017 or 2025. Co-Authored-By: Claude Opus 5.5 --- .../mo/tax/income/deductions/itemized.yaml | 2 +- .../deductions/mo_itemized_deductions.yaml | 50 +++++- .../itemized/ms_itemized_deductions_unit.yaml | 23 +++ ...st_ms_mo_misc_deduction_once_properties.py | 150 ++++++++++++++++++ .../itemized/ms_itemized_deductions_unit.py | 7 +- 5 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py diff --git a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml index 14c58b0561d..3697722ac65 100644 --- a/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml +++ b/policyengine_us/parameters/gov/states/mo/tax/income/deductions/itemized.yaml @@ -23,5 +23,5 @@ metadata: href: https://dor.mo.gov/forms/MO-1040%20Instructions_2021.pdf#page=16 - title: 2024 Form MO-A - Individual Income Tax Adjustments, Part 2 - Missouri Itemized Deductions href: https://dor.mo.gov/forms/MO-A_2024.pdf#page=2 - - title: 2017 Form MO-1040 instructions, Form MO-A Part 2 line 1 + - title: "2017 Form MO-1040 instructions, Form MO-A Part 2 line 1 #page 22,42" href: https://dor.mo.gov/forms/MO-1040%20Instructions_2017.pdf#page=22 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml index f453eb1dd78..b6d54f54e1c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mo/tax/income/deductions/mo_itemized_deductions.yaml @@ -10,7 +10,7 @@ output: mo_itemized_deductions: 0.0 -- name: 60 in Federal itemizations, 10 in net_state_income_taxes +- name: 20 in Federal itemizations, 10 in net_state_income_taxes period: 2021 absolute_error_margin: 0.01 input: @@ -24,7 +24,7 @@ output: mo_itemized_deductions: 10.00 -- name: 6000 in Federal itemizations, 1000 in net_state_income_taxes +- name: 2000 in Federal itemizations, 1000 in net_state_income_taxes period: 2021 absolute_error_margin: 0.01 input: @@ -67,3 +67,49 @@ output: itemized_taxable_income_deductions: 10_000 mo_itemized_deductions: 10_000 + +# RSMo 143.141 starts from the allowable federal itemized deductions, and the +# 2017 instructions (MO-A Part 2 line 1) take the federal total once. +- name: 2017 federal total that includes miscellaneous deductions counted once + period: 2017 + input: + itemized_taxable_income_deductions: 10_000 + misc_deduction: 1_000 + employee_social_security_tax: 0 + employee_medicare_tax: 0 + additional_medicare_tax: 0 + self_employment_tax: 0 + mo_net_state_income_taxes: 0 + state_code: MO + output: + # The 10,000 already includes the 1,000; before the fix 11,000. + mo_itemized_deductions: 10_000 + +- name: 2017 state income tax adjustment without duplicating miscellaneous deductions + period: 2017 + input: + itemized_taxable_income_deductions: 10_000 + misc_deduction: 1_000 + employee_social_security_tax: 0 + employee_medicare_tax: 0 + additional_medicare_tax: 0 + self_employment_tax: 0 + mo_net_state_income_taxes: 2_000 + state_code: MO + output: + # 10,000 - 2,000; before the fix 9,000. + mo_itemized_deductions: 8_000 + +- name: 2016 miscellaneous deduction counted once + period: 2016 + input: + charitable_deduction: 9_000 + misc_deduction: 1_000 + employee_social_security_tax: 0 + employee_medicare_tax: 0 + additional_medicare_tax: 0 + self_employment_tax: 0 + mo_net_state_income_taxes: 0 + state_code: MO + output: + mo_itemized_deductions: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml index 38ffd8ebeb0..fec806c77f9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.yaml @@ -18,3 +18,26 @@ state_code: MS output: ms_itemized_deductions_unit: 2_000 + +# Miss. Code Ann. 27-7-17(4) bars deducting the same item more than once. +- name: 2017 non-SALT total that includes miscellaneous deductions counted once + period: 2017 + input: + itemized_deductions_less_salt: 10_000 + misc_deduction: 1_000 + ms_real_estate_tax_deduction: 0 + state_code: MS + output: + # The 10,000 already includes the 1,000; before the fix 11,000. + ms_itemized_deductions_unit: 10_000 + +- name: 2017 real estate taxes added without duplicating miscellaneous deductions + period: 2017 + input: + itemized_deductions_less_salt: 10_000 + misc_deduction: 1_000 + ms_real_estate_tax_deduction: 2_000 + state_code: MS + output: + # 10,000 + 2,000; before the fix 13,000. + ms_itemized_deductions_unit: 12_000 diff --git a/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py b/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py new file mode 100644 index 00000000000..a40f898cc69 --- /dev/null +++ b/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py @@ -0,0 +1,150 @@ +"""Mississippi and Missouri count the federal miscellaneous deduction once. + +Before 2018 the federal miscellaneous deduction above the 2% floor is part of +itemized_taxable_income_deductions and of itemized_deductions_less_salt. +RSMo 143.141 starts Missouri's itemized deduction from the allowable federal +itemized deductions, and Miss. Code Ann. 27-7-17(4) bars deducting the same +item more than once. So for every generated tax unit and year: + +1. Holding the federal totals fixed, the miscellaneous deduction does not + change mo_itemized_deductions or ms_itemized_deductions_unit. +2. Mississippi's total is its non-SALT federal total plus its real estate + tax deduction. +3. Missouri's total is never negative and never exceeds the federal total + plus payroll taxes. The worked cases also show it falls with the state + income tax adjustment. + +Property 1 fails whenever the miscellaneous deduction is positive if either +state adds misc_deduction on top of the federal total; the strategy draws it +from $1 to $20,000, never zero, and the years include 2016 and 2017, when +the federal deduction applies. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEARS = (2016, 2017, 2025) +TOLERANCE = 0.01 + + +def build_situation(cases, state): + people, tax_units, households = {}, {}, {} + for i, case in enumerate(cases): + for with_misc in (True, False): + key = f"{i}_{int(with_misc)}" + person = f"person_{key}" + people[person] = { + "age": {year: 40 for year in YEARS}, + "employee_social_security_tax": { + year: case["payroll_tax"] for year in YEARS + }, + "employee_medicare_tax": {year: 0 for year in YEARS}, + "self_employment_tax": {year: 0 for year in YEARS}, + } + misc = case["misc"] if with_misc else 0 + tax_unit = { + "members": [person], + "misc_deduction": {year: misc for year in YEARS}, + "additional_medicare_tax": {year: 0 for year in YEARS}, + } + if state == "MO": + tax_unit["itemized_taxable_income_deductions"] = { + year: case["federal_total"] for year in YEARS + } + tax_unit["mo_net_state_income_taxes"] = { + year: case["state_tax"] for year in YEARS + } + else: + tax_unit["itemized_deductions_less_salt"] = { + year: case["federal_total"] for year in YEARS + } + tax_unit["ms_real_estate_tax_deduction"] = { + year: case["real_estate"] for year in YEARS + } + tax_units[f"tax_unit_{key}"] = tax_unit + households[f"household_{key}"] = { + "members": [person], + "state_code": {year: state for year in YEARS}, + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def calculate(cases, state, variable): + simulation = Simulation(situation=build_situation(cases, state)) + return { + year: np.asarray(simulation.calculate(variable, year)).reshape(len(cases), 2) + for year in YEARS + } + + +def assert_properties(cases): + mo = calculate(cases, "MO", "mo_itemized_deductions") + ms = calculate(cases, "MS", "ms_itemized_deductions_unit") + for i, case in enumerate(cases): + for year in YEARS: + # Column 0 has the miscellaneous deduction, column 1 has none. + assert mo[year][i, 0] == pytest.approx(mo[year][i, 1], abs=TOLERANCE), ( + case, + year, + ) + assert ms[year][i, 0] == pytest.approx(ms[year][i, 1], abs=TOLERANCE), ( + case, + year, + ) + assert ms[year][i, 0] == pytest.approx( + case["federal_total"] + case["real_estate"], abs=TOLERANCE + ), (case, year) + assert mo[year][i, 0] >= 0, (case, year) + assert ( + mo[year][i, 0] + <= case["federal_total"] + case["payroll_tax"] + TOLERANCE + ), (case, year) + + +def test_state_tax_adjustment_does_not_raise_missouri_deduction(): + base = { + "federal_total": 10_000, + "misc": 1_000, + "payroll_tax": 0, + "real_estate": 2_000, + "state_tax": 0, + } + cases = [base, {**base, "state_tax": 2_000}, {**base, "state_tax": 50_000}] + mo = calculate(cases, "MO", "mo_itemized_deductions") + # 10,000 once, less the state income tax adjustment, floored at zero. + assert mo[2017][:, 0] == pytest.approx([10_000, 8_000, 0], abs=TOLERANCE) + ms = calculate(cases[:1], "MS", "ms_itemized_deductions_unit") + # 10,000 once, plus 2,000 real estate taxes. + assert ms[2017][0, 0] == pytest.approx(12_000, abs=TOLERANCE) + assert_properties(cases) + + +AMOUNTS = st.integers(min_value=0, max_value=50_000) + + +@hypothesis.settings( + max_examples=10, + deadline=None, + suppress_health_check=[hypothesis.HealthCheck.too_slow], +) +@hypothesis.given( + st.lists( + st.fixed_dictionaries( + { + "federal_total": AMOUNTS, + "misc": st.integers(min_value=1, max_value=20_000), + "payroll_tax": st.integers(min_value=0, max_value=10_000), + "real_estate": AMOUNTS, + "state_tax": AMOUNTS, + } + ), + min_size=1, + max_size=8, + ) +) +def test_ms_mo_misc_deduction_counted_once(cases): + assert_properties(cases) diff --git a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py index 163abcd9ae8..8724624db9a 100644 --- a/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ms/tax/income/deductions/itemized/ms_itemized_deductions_unit.py @@ -12,13 +12,18 @@ class ms_itemized_deductions_unit(Variable): "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80100221.pdf#page=15", "https://www.dor.ms.gov/sites/default/files/tax-forms/individual/80108228.pdf", # Line 7 "https://law.justia.com/codes/mississippi/title-27/chapter-7/article-1/section-27-7-17/", + # Miss. Code Ann. 27-7-17(4), 2016 and 2020 editions. + "https://law.justia.com/codes/mississippi/2016/title-27/chapter-7/article-1/section-27-7-17/", + "https://law.justia.com/codes/mississippi/2020/title-27/chapter-7/article-1/section-27-7-17/", # 2017 instructions, Form 80-108 lines 7a to 7d. "https://taxsim.nber.org/historical_state_tax_forms/MS/2017/80100178.pdf#page=15", ) defined_for = StateCode.MS # itemized_deductions_less_salt already includes the federal miscellaneous - # deduction above the 2% floor (Form 80-108 lines 7a to 7d before 2018). + # deduction above the 2% floor (Form 80-108 lines 7a to 7d before 2018), so + # misc_deduction is not added again: Miss. Code Ann. 27-7-17(4) bars + # deducting the same item more than once. adds = [ "itemized_deductions_less_salt", "ms_real_estate_tax_deduction", From 3df0a25873204e31d80f09272b2b2739b75a3791 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 14:35:20 -0500 Subject: [PATCH 3/3] Derandomize the MS and MO misc-deduction property test Follows the repo convention (derandomize=True), so a failing draw reproduces on the next run. Review r1 finding P3-1. Co-Authored-By: Claude Opus 5.5 --- .../tests/unit/test_ms_mo_misc_deduction_once_properties.py | 1 + 1 file changed, 1 insertion(+) diff --git a/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py b/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py index a40f898cc69..5ca013599cc 100644 --- a/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py +++ b/policyengine_us/tests/unit/test_ms_mo_misc_deduction_once_properties.py @@ -129,6 +129,7 @@ def test_state_tax_adjustment_does_not_raise_missouri_deduction(): @hypothesis.settings( max_examples=10, deadline=None, + derandomize=True, suppress_health_check=[hypothesis.HealthCheck.too_slow], ) @hypothesis.given(