From 52ffe10226cd26b3ff24e48ce7f9bc555924dd07 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 07:08:30 -0400 Subject: [PATCH 1/8] Align Montana separate-filing allocations and the elderly credit with the forms - Federal income tax deduction (through 2023): attribute federal tax to the spouse who earned the income and cap each spouse at $5,000, instead of prorating the jointly capped amount by Montana AGI (2022 Form 2 instructions, Itemized Deductions Schedule, line 4). - Child and dependent care expense deduction (through 2023): phase out on the couple's combined Montana AGI and split the result equally only for spouses filing separately on the same form; a single filer and a joint return take the whole deduction (Form 2441-M lines 3-7). - Elderly homeowner/renter credit: count federal AGI without regard to losses (MCA 15-30-2337(9)(a); Form 2EC line 9). Co-Authored-By: Claude Opus 5.5 --- ...ix-mt-separate-filing-allocations.fixed.md | 1 + .../deductions/itemized/misc_deductions.yaml | 7 +- ...mt_elderly_homeowner_or_renter_credit.yaml | 31 ++++ ..._renter_credit_gross_household_income.yaml | 47 ++++++ ...hild_dependent_care_expense_deduction.yaml | 134 ++++++++++++++---- ...duction_for_federal_itemization_indiv.yaml | 27 ++-- ...uctions_for_federal_itemization_joint.yaml | 6 +- ...mt_federal_income_tax_deduction_indiv.yaml | 103 ++++++++++++-- .../mt_itemized_deductions_joint.yaml | 6 +- ...or_renter_credit_gross_household_income.py | 16 ++- ..._child_dependent_care_expense_deduction.py | 44 +++--- ...are_expense_deduction_eligible_expenses.py | 34 +++++ ..._dependent_care_expense_deduction_joint.py | 30 ++++ ...deduction_for_federal_itemization_indiv.py | 26 ++-- ...eductions_for_federal_itemization_indiv.py | 1 + ...eductions_for_federal_itemization_joint.py | 1 + .../mt_federal_income_tax_deduction_indiv.py | 31 ++-- .../mt_federal_income_tax_deduction_share.py | 33 +++++ .../general/mt_itemized_deductions_indiv.py | 1 + .../general/mt_itemized_deductions_joint.py | 1 + 20 files changed, 472 insertions(+), 108 deletions(-) create mode 100644 changelog.d/fix-mt-separate-filing-allocations.fixed.md create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py diff --git a/changelog.d/fix-mt-separate-filing-allocations.fixed.md b/changelog.d/fix-mt-separate-filing-allocations.fixed.md new file mode 100644 index 00000000000..c7d3be16f20 --- /dev/null +++ b/changelog.d/fix-mt-separate-filing-allocations.fixed.md @@ -0,0 +1 @@ +Montana's federal income tax deduction for spouses filing separately on the same form now attributes federal tax to the spouse who earned the income and caps each spouse at 5,000; the child and dependent care expense deduction phases out on the couple's combined Montana AGI and is halved only for spouses filing separately on the same form; and the elderly homeowner/renter credit counts income without regard to losses. diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml index 1eeb0351b1a..c4744269768 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml @@ -1,8 +1,11 @@ description: Montana counts these sources as miscellaneous deductions. +# The child and dependent care expense deduction is also a miscellaneous +# deduction (Form 2 Itemized Deductions Schedule, line 14). It is added +# separately because its income limit uses the return's own Montana AGI, which +# differs between a joint return and separate columns (Form 2441-M line 3). values: 2021-01-01: - - mt_child_dependent_care_expense_deduction - - casualty_loss_deduction + - casualty_loss_deduction 2024-01-01: [] metadata: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml index 9e4f2540b71..154bf57d561 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml @@ -223,3 +223,34 @@ # Same household, larger exclusion ($12,600) leaves a smaller expected # contribution, so the credit is higher: $539.67. mt_elderly_homeowner_or_renter_credit: 539.67 + +- name: 2023 Form 2EC worked case, a capital loss does not reduce gross household income + period: 2023 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + long_term_capital_gains: -3_000 + social_security_retirement: 10_000 + real_estate_taxes: 2_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # Line 1 federal AGI 17_000 (Social Security untaxed: 17_000 + 5_000 is + # under the 25_000 base); line 5 untaxed Social Security 10_000; line 9 + # losses 3_000; line 10 gross household income 30_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 30_000 + # Line 12 = 30_000 - 12_600 = 17_400; line 13 multiplier 0.05; + # line 14 = 870. + mt_elderly_homeowner_or_renter_credit_net_household_income: 870 + # Line 18 = 2_000; line 19 = 1_130; line 20 = min(1_130, 1_150); + # line 21 = 100% (under 35_000); line 22 = 1_130. + mt_elderly_homeowner_or_renter_credit: 1_130 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml index cee870a82af..f7d12a544dc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml @@ -37,3 +37,50 @@ # § 15-30-2337(9)(a)(viii) requires all SS to be counted, even the # portion excluded from federal AGI. mt_elderly_homeowner_or_renter_credit_gross_household_income: 30_000 + +- name: A capital loss deducted in federal AGI is added back + period: 2023 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + long_term_capital_gains: -3_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_name: MT + output: + # § 15-30-2337(9)(a): federal AGI "without regard to loss". 2023 Form 2EC: + # line 1 federal AGI 20_000 - 3_000 = 17_000; line 9 adds back the 3_000 + # capital loss; line 10 = 20_000. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_000 + +- name: A business loss deducted in federal AGI is added back once for the return + period: 2024 + input: + people: + person1: + age: 70 + taxable_pension_income: 20_000 + mt_refundable_credits_before_renter_credit: 0 + person2: + age: 68 + self_employment_income: -5_000 + mt_refundable_credits_before_renter_credit: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_name: MT + output: + # Federal AGI 20_000 - 5_000 = 15_000; the 5_000 loss is added back, so + # gross household income is 20_000 (Form 2EC line 18). Net household + # income: (20_000 - 12_600) * 0.035 = 259, allocated to the head. + mt_elderly_homeowner_or_renter_credit_net_household_income: [259, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml index df49679c68b..b7ce4721911 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml @@ -19,8 +19,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400. - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Care expense greater than maximum amount with moderate AGI and 1 child period: 2021 @@ -43,8 +43,8 @@ state_code: MT output: # 2_400 - 0.5 * (20_000 - 18_000) = 1_400. - # Head receives half: 1_400 * 0.5 = 700. - mt_child_dependent_care_expense_deduction: [700, 0] + # A single filer enters the full line 7 amount: 1_400. + mt_child_dependent_care_expense_deduction: [1_400, 0] - name: Care expense greater than maximum amount with high AGI and 1 child period: 2021 @@ -92,8 +92,8 @@ state_code: MT output: # Two qualifying individuals cap expenses at 3_600. - # Head receives half: 3_600 * 0.5 = 1_800. - mt_child_dependent_care_expense_deduction: [1_800, 0, 0] + # A single filer enters the full line 7 amount: 3_600. + mt_child_dependent_care_expense_deduction: [3_600, 0, 0] - name: Care expense greater than maximum amount with moderate AGI and 2 children period: 2021 @@ -119,8 +119,8 @@ state_code: MT output: # 3_600 - 0.5 * (22_000 - 18_000) = 1_600. - # Head receives half: 1_600 * 0.5 = 800. - mt_child_dependent_care_expense_deduction: [800, 0, 0] + # A single filer enters the full line 7 amount: 1_600. + mt_child_dependent_care_expense_deduction: [1_600, 0, 0] - name: Care expense greater than maximum amount with high AGI and 2 children period: 2021 @@ -174,8 +174,8 @@ state_code: MT output: # Three qualifying individuals cap expenses at 4_800. - # Head receives half: 4_800 * 0.5 = 2_400. - mt_child_dependent_care_expense_deduction: [2_400, 0, 0, 0] + # A single filer enters the full line 7 amount: 4_800. + mt_child_dependent_care_expense_deduction: [4_800, 0, 0, 0] - name: Care expense greater than maximum amount with moderate AGI and 3 children period: 2021 @@ -204,8 +204,8 @@ state_code: MT output: # 4_800 - 0.5 * (24_000 - 18_000) = 1_800. - # Head receives half: 1_800 * 0.5 = 900. - mt_child_dependent_care_expense_deduction: [900, 0, 0, 0] + # A single filer enters the full line 7 amount: 1_800. + mt_child_dependent_care_expense_deduction: [1_800, 0, 0, 0] - name: Care expense greater than maximum amount with high AGI and 3 children period: 2021 @@ -255,8 +255,8 @@ members: [person1, person2] state_code: MT output: - # Head receives half: 2_000 * 0.5 = 1_000. - mt_child_dependent_care_expense_deduction: [1_000, 0] + # A single filer enters the full line 7 amount: 2_000. + mt_child_dependent_care_expense_deduction: [2_000, 0] - name: Care expense less than maximum amount with high AGI and 1 child period: 2021 @@ -303,8 +303,8 @@ members: [person1, person2, person3] state_code: MT output: - # Head receives half: 3_000 * 0.5 = 1_500. - mt_child_dependent_care_expense_deduction: [1_500, 0, 0] + # A single filer enters the full line 7 amount: 3_000. + mt_child_dependent_care_expense_deduction: [3_000, 0, 0] - name: Care expense less than maximum amount with high AGI and 2 children period: 2021 @@ -357,8 +357,8 @@ members: [person1, person2, person3, person4] state_code: MT output: - # Head receives half: 4_000 * 0.5 = 2_000. - mt_child_dependent_care_expense_deduction: [2_000, 0, 0, 0] + # A single filer enters the full line 7 amount: 4_000. + mt_child_dependent_care_expense_deduction: [4_000, 0, 0, 0] - name: Care expense less than maximum amount with high AGI and 3 children period: 2021 @@ -441,8 +441,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 18, capable disabled adult care expenses are excluded. period: 2021 @@ -470,8 +470,8 @@ state_code: MT output: # Only the child qualifies, so 2_000 of adult care expenses are excluded. - # Head receives half of the 1_000 childcare expense deduction. - mt_child_dependent_care_expense_deduction: [500, 0, 0] + # A single filer enters the full 1_000 childcare expense deduction. + mt_child_dependent_care_expense_deduction: [1_000, 0, 0] - name: Case 19, a child and an incapacitated adult dependent both count toward the tier. period: 2021 @@ -500,8 +500,8 @@ output: # Two qualifying individuals cap expenses at 3_600 (Form 2441-M line 2). # Childcare 3_000 + adult care 1_000 = 4_000, capped at 3_600. - # Head receives half: 3_600 * 0.5 = 1_800. - mt_child_dependent_care_expense_deduction: [1_800, 0, 0] + # A single filer enters the full line 7 amount: 3_600. + mt_child_dependent_care_expense_deduction: [3_600, 0, 0] - name: Case 20, adult care expenses cannot exceed the one-qualifying-individual tier cap. period: 2021 @@ -525,8 +525,8 @@ state_code: MT output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 21, incapacitated spouse care expenses qualify. period: 2021 @@ -606,8 +606,8 @@ output: # One qualifying individual caps expenses at 2_400 (Form 2441-M line 2). # Childcare 1_500 + care expenses 1_000 = 2_500, capped at 2_400. - # Head receives half: 2_400 * 0.5 = 1_200. - mt_child_dependent_care_expense_deduction: [1_200, 0] + # A single filer enters the full line 7 amount: 2_400. + mt_child_dependent_care_expense_deduction: [2_400, 0] - name: Case 24, combined childcare and adult care expenses are capped by the three-qualifying-individual tier. period: 2021 @@ -643,3 +643,81 @@ # Childcare 4_000 + adult care 2_000 = 6_000, capped at 4_800. # The deduction is divided equally between spouses: 4_800 * 0.5 = 2_400. mt_child_dependent_care_expense_deduction: [2_400, 2_400, 0, 0] + +- name: Case 25, the phase-out uses the couple's combined Montana AGI, then each column gets half + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + is_tax_unit_head: true + mt_agi_indiv: 20_000 + person2: + is_tax_unit_spouse: true + mt_agi_indiv: 0 + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_childcare_expenses: 3_000 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Form 2441-M (2022): line 2 = min(3_000, 2_400) = 2_400; line 3 adds + # columns A and B of Form 2 line 14 = 20_000 + 0 = 20_000; line 5 = 2_000; + # line 6 = 1_000; line 7 = 1_400. Filing separately on the same form, each + # spouse enters one-half of line 7 (700). Applying the phase-out to each + # spouse's own AGI would give 700 and 1_200. + mt_child_dependent_care_expense_deduction_eligible_expenses: 2_400 + mt_child_dependent_care_expense_deduction: [700, 700, 0] + +- name: Case 26, a joint return uses the return's Montana AGI and claims the whole deduction + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + is_tax_unit_head: true + person2: + is_tax_unit_spouse: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_childcare_expenses: 3_000 + mt_agi_joint: 20_000 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Form 2441-M lines 2-7 with line 3 = 20_000: 2_400 - 0.5 * 2_000 = 1_400, + # entered in full on the joint return. + mt_child_dependent_care_expense_deduction_joint: 1_400 + +- name: Case 27, the joint deduction is repealed beginning in 2024 + period: 2024 + input: + people: + person1: + is_tax_unit_head: true + person2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + tax_unit_childcare_expenses: 3_000 + mt_agi_joint: 10_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_child_dependent_care_expense_deduction_joint: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml index e0fed58cbeb..941a60e6c1b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.yaml @@ -1,39 +1,46 @@ -- name: Deductions split evenly +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 (PDF p. 33): +# withholding is reported by the spouse who earned the income, and each +# spouse filing separately is limited to 5,000. + +- name: Each spouse deducts their own withholding estimate period: 2021 input: people: person1: - mt_agi_indiv: 500 + mt_withheld_income_tax: 3_000 + adjusted_gross_income_person: 50_000 person2: - mt_agi_indiv: 500 + mt_withheld_income_tax: 1_000 + adjusted_gross_income_person: 50_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_for_federal_itemization: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_for_federal_itemization_indiv: [1_500, 1_500] + # Prorating the couple's 4_000 by Montana AGI would give 2_000 / 2_000. + mt_federal_income_tax_deduction_for_federal_itemization_indiv: [3_000, 1_000] -- name: Deductions capped at max amount individually +- name: Each spouse is capped at 5,000 on their own amount period: 2021 input: people: person1: - mt_agi_indiv: 100 + mt_withheld_income_tax: 7_000 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 900 + mt_withheld_income_tax: 1_000 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_for_federal_itemization: 10_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_for_federal_itemization_indiv: [1_000, 5_000] + mt_federal_income_tax_deduction_for_federal_itemization_indiv: [5_000, 1_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index 8a1114f4ef5..41a8d808975 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -7,7 +7,7 @@ mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 casualty_loss_deduction: 400 - mt_child_dependent_care_expense_deduction: 123 + mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: @@ -22,7 +22,7 @@ mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 0 - mt_child_dependent_care_expense_deduction: 0 + mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: @@ -37,7 +37,7 @@ mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 745 - mt_child_dependent_care_expense_deduction: 1_004 + mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml index 4b0d588ce5e..c7604752c4d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_federal_income_tax_deduction_indiv.yaml @@ -1,39 +1,120 @@ -- name: Deductions split evenly +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 (PDF p. 33): +# "If you are married filing separately with your spouse, the federal income +# tax withheld should be reported by the spouse who earned the income." and +# "If your filing status is Married Filing Separately, you are each limited to +# the $5,000 federal tax deduction and you cannot arbitrarily allocate this +# deduction between spouses." + +- name: Federal income tax is attributed by each spouse's share of federal AGI period: 2021 + absolute_error_margin: 0.01 input: people: person1: - mt_agi_indiv: 500 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 500 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_unit: 3_000 + income_tax_before_refundable_credits: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_indiv: [1_500, 1_500] + mt_federal_income_tax_deduction_share: [0.6, 0.4] + mt_federal_income_tax_deduction_indiv: [1_800, 1_200] -- name: Deductions capped at max amount individually - period: 2021 +- name: Each spouse's own federal tax is capped at 5,000, not a share of the joint cap + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 80_000 + person2: + adjusted_gross_income_person: 20_000 + tax_units: + tax_unit: + members: [person1, person2] + income_tax_before_refundable_credits: 30_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2] + state_code: MT + output: + # Federal tax attributed 24_000 / 6_000; each spouse is limited to 5_000. + # Capping the couple at 10_000 first and then splitting 80% / 20% would + # give 5_000 / 2_000. + mt_federal_income_tax_deduction_indiv: [5_000, 5_000] + +- name: Montana-only subtractions do not shift federal tax between spouses + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 50_000 + person2: + adjusted_gross_income_person: 50_000 + mt_subtractions: 20_000 + tax_units: + tax_unit: + members: [person1, person2] + income_tax_before_refundable_credits: 6_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2] + state_code: MT + output: + # The federal tax was paid on 50_000 of federal income each. Prorating by + # Montana AGI (50_000 / 30_000) would give 3_750 / 2_250. + mt_federal_income_tax_deduction_indiv: [3_000, 3_000] + +- name: A dependent's income does not draw a share of the parents' federal tax + period: 2022 + absolute_error_margin: 0.01 + input: + people: + person1: + adjusted_gross_income_person: 40_000 + person2: + adjusted_gross_income_person: 0 + person3: + is_tax_unit_dependent: true + adjusted_gross_income_person: 0 + tax_units: + tax_unit: + members: [person1, person2, person3] + income_tax_before_refundable_credits: 2_000 + state_filing_status_if_married_filing_separately_on_same_return: SEPARATE + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + mt_federal_income_tax_deduction_indiv: [2_000, 0, 0] + +- name: The deduction is repealed from 2024 + period: 2024 input: people: person1: - mt_agi_indiv: 100 + adjusted_gross_income_person: 60_000 person2: - mt_agi_indiv: 900 + adjusted_gross_income_person: 40_000 tax_units: tax_unit: members: [person1, person2] - mt_federal_income_tax_deduction_unit: 10_000 + income_tax_before_refundable_credits: 3_000 state_filing_status_if_married_filing_separately_on_same_return: SEPARATE households: household: members: [person1, person2] state_code: MT output: - mt_federal_income_tax_deduction_indiv: [1_000, 5_000] + mt_federal_income_tax_deduction_indiv: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index 6b25795e07a..329641eafa3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -7,7 +7,7 @@ mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 casualty_loss_deduction: 400 - mt_child_dependent_care_expense_deduction: 123 + mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: @@ -22,7 +22,7 @@ mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 0 - mt_child_dependent_care_expense_deduction: 0 + mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: @@ -37,7 +37,7 @@ mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 casualty_loss_deduction: 745 - mt_child_dependent_care_expense_deduction: 1_004 + mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT output: diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py index e4a6976f246..4f18254eed0 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py @@ -8,7 +8,12 @@ class mt_elderly_homeowner_or_renter_credit_gross_household_income(Variable): unit = USD definition_period = YEAR defined_for = StateCode.MT - reference = "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/" + reference = ( + "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/", + "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", + # 2023 Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 9 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=52", + ) def formula(person, period, parameters): p = parameters( @@ -20,4 +25,11 @@ def formula(person, period, parameters): social_security = person("social_security", period) taxable_social_security = person("taxable_social_security", period) untaxed_social_security = max_(social_security - taxable_social_security, 0) - return sources + untaxed_social_security + # Income is federal AGI "without regard to loss" (§ 15-30-2337(9)(a)), + # so the business and capital losses deducted in computing federal + # AGI are added back (2023 Form 2EC line 9: "The gross household + # income cannot be reduced by any losses."). The return's losses are + # added once, to the head. + head = person("is_tax_unit_head", period) + losses = head * person.tax_unit("loss_ald", period) + return sources + untaxed_social_security + losses diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py index 2efb5ad2407..a7ee18fe4f8 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py @@ -7,9 +7,16 @@ class mt_child_dependent_care_expense_deduction(Variable): label = "Montana child dependent care expense deduction" unit = USD definition_period = YEAR + documentation = ( + "The deduction in each filer's column: an unmarried filer's return, or " + "each spouse's column when spouses file separately on the same form. " + "A joint return uses mt_child_dependent_care_expense_deduction_joint." + ) reference = ( "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 14 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", ) defined_for = StateCode.MT @@ -19,26 +26,21 @@ def formula(person, period, parameters): ).gov.states.mt.tax.income.deductions.child_dependent_care_expense if not p.in_effect: return 0 - tax_unit = person.tax_unit - members = tax_unit.members - qualifying_individual = members( - "mt_child_dependent_care_expense_deduction_qualifying_individual", - period, - ) - # Form 2441-M line 1 - qualifying_individuals = tax_unit.sum(qualifying_individual) - qualifying_care_expenses = tax_unit.sum( - members("care_expenses", period) * qualifying_individual + # Form 2441-M line 2 + eligible_expenses = person.tax_unit( + "mt_child_dependent_care_expense_deduction_eligible_expenses", period ) - childcare_expenses = tax_unit("tax_unit_childcare_expenses", period) - total_expenses = childcare_expenses + qualifying_care_expenses - # Form 2441-M line 2: the lesser of actual dependent care expenses - # or the cap based on the number of qualifying individuals. - capped_expenses = min_(total_expenses, p.cap.calc(qualifying_individuals)) - # Line 3 - agi = person("mt_agi_indiv", period) - # Line 6 - reduction = p.phase_out.calc(agi) - # The deduction has to be allocated equally between spouses + # Line 3: add the Montana adjusted gross income in columns A and B, + # that is, the couple's combined income, not each spouse's own. head_or_spouse = person("is_tax_unit_head_or_spouse", period) - return head_or_spouse * (max_(0, capped_expenses - reduction) * 0.5) + combined_agi = person.tax_unit.sum( + head_or_spouse * person("mt_agi_indiv", period) + ) + # Lines 4-7 + deduction = max_(eligible_expenses - p.phase_out.calc(combined_agi), 0) + # Spouses filing separately on the same form each enter one-half of + # line 7 in their column; an unmarried filer enters all of it. + married = person.tax_unit("tax_unit_married", period) + head = person("is_tax_unit_head", period) + share = where(married, head_or_spouse * 0.5, head * 1.0) + return deduction * share diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py new file mode 100644 index 00000000000..642214c3e55 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_eligible_expenses.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class mt_child_dependent_care_expense_deduction_eligible_expenses(Variable): + value_type = float + entity = TaxUnit + label = "Montana child and dependent care expenses eligible for the deduction" + unit = USD + definition_period = YEAR + reference = ( + "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", + "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + ) + defined_for = StateCode.MT + + def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.states.mt.tax.income.deductions.child_dependent_care_expense + person = tax_unit.members + qualifying_individual = person( + "mt_child_dependent_care_expense_deduction_qualifying_individual", + period, + ) + # Form 2441-M line 1 + qualifying_individuals = tax_unit.sum(qualifying_individual) + qualifying_care_expenses = tax_unit.sum( + person("care_expenses", period) * qualifying_individual + ) + childcare_expenses = tax_unit("tax_unit_childcare_expenses", period) + total_expenses = childcare_expenses + qualifying_care_expenses + # Form 2441-M line 2: the lesser of actual dependent care expenses + # or the cap based on the number of qualifying individuals. + return min_(total_expenses, p.cap.calc(qualifying_individuals)) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py new file mode 100644 index 00000000000..ae9a5f6eb02 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class mt_child_dependent_care_expense_deduction_joint(Variable): + value_type = float + entity = TaxUnit + label = "Montana child dependent care expense deduction on a joint return" + unit = USD + definition_period = YEAR + reference = ( + "https://mca.legmt.gov/bills/2019/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", + "https://web.archive.org/web/20230914140156/https://mtrevenue.gov/wp-content/uploads/dlm_uploads/2022/12/2441-M_2022.pdf#page=1", + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 14 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", + ) + defined_for = StateCode.MT + + def formula(tax_unit, period, parameters): + p = parameters( + period + ).gov.states.mt.tax.income.deductions.child_dependent_care_expense + if not p.in_effect: + return 0 + # Form 2441-M line 2 + eligible_expenses = tax_unit( + "mt_child_dependent_care_expense_deduction_eligible_expenses", period + ) + # Line 3: the return's Montana adjusted gross income; lines 4-7. + agi = tax_unit("mt_agi_joint", period) + return max_(eligible_expenses - p.phase_out.calc(agi), 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py index 94951d1ad9c..967ce5bdfb5 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_federal_income_tax_deduction_for_federal_itemization_indiv.py @@ -7,28 +7,24 @@ class mt_federal_income_tax_deduction_for_federal_itemization_indiv(Variable): definition_period = YEAR label = "Montana federal income tax deduction when married couples are filing separately" reference = ( - "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/" + "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2021) (1)(b) + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", ) unit = USD defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - unit_deds = person.tax_unit( - "mt_federal_income_tax_deduction_for_federal_itemization", period - ) - person_agi = person("mt_agi_indiv", period) - total_agi = person.tax_unit.sum(person_agi) - - prorate = np.zeros_like(total_agi) - mask = total_agi > 0 - prorate[mask] = person_agi[mask] / total_agi[mask] + p = parameters( + period + ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax + # The withholding estimate is computed on each person's own income, so + # each spouse deducts their own amount, up to their own cap. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + withheld_income_tax = head_or_spouse * person("mt_withheld_income_tax", period) filing_status = person.tax_unit( "state_filing_status_if_married_filing_separately_on_same_return", period, ) - p = parameters( - period - ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax - cap = p.cap[filing_status] - return min_(unit_deds * prorate, cap) + return min_(withheld_income_tax, p.cap[filing_status]) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py index 5d1cb0c5153..0d5e7d0e577 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py @@ -29,6 +29,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", "mt_federal_income_tax_deduction_for_federal_itemization_indiv", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py index 3469bb0b4b3..ed3371fd0ab 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py @@ -24,6 +24,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", "mt_federal_income_tax_deduction_for_federal_itemization", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py index 792174d9142..f84c008f403 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_indiv.py @@ -7,26 +7,31 @@ class mt_federal_income_tax_deduction_indiv(Variable): definition_period = YEAR label = "Montana federal income tax deduction when married couples are filing separately" reference = ( - "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/" + "https://law.justia.com/codes/montana/2021/title-15/chapter-30/part-21/section-15-30-2131/", # MT Code § 15-30-2131 (2021) (1)(b) + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", ) unit = USD defined_for = "mt_married_filing_separately_on_same_return_eligible" def formula(person, period, parameters): - unit_deds = person.tax_unit("mt_federal_income_tax_deduction_unit", period) - person_agi = person("mt_agi_indiv", period) - total_agi = person.tax_unit.sum(person_agi) - - prorate = np.zeros_like(total_agi) - mask = total_agi > 0 - prorate[mask] = person_agi[mask] / total_agi[mask] + p = parameters( + period + ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax + if not p.applies: + return 0 + # Federal income tax is reported by the spouse who earned the income + # it was paid on, and each spouse is held to their own cap; the + # deduction cannot be arbitrarily allocated. Without each spouse's + # federal withholding, attribute the couple's federal income tax by + # each spouse's share of federal adjusted gross income, then cap it. + federal_income_tax = max_( + person.tax_unit("income_tax_before_refundable_credits", period), 0 + ) + share = person("mt_federal_income_tax_deduction_share", period) filing_status = person.tax_unit( "state_filing_status_if_married_filing_separately_on_same_return", period, ) - p = parameters( - period - ).gov.states.mt.tax.income.deductions.itemized.federal_income_tax - cap = p.cap[filing_status] - return min_(unit_deds * prorate, cap) + return min_(federal_income_tax * share, p.cap[filing_status]) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py new file mode 100644 index 00000000000..2c6dfd8556b --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_tax/mt_federal_income_tax_deduction_share.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * + + +class mt_federal_income_tax_deduction_share(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "Share of the couple's federal income tax attributed to each spouse for the Montana deduction" + reference = ( + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 4a: + # "If you are married filing separately with your spouse, the federal + # income tax withheld should be reported by the spouse who earned the + # income." + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=33", + ) + unit = "/1" + defined_for = "mt_married_filing_separately_on_same_return_eligible" + + def formula(person, period, parameters): + # Each filer's share of the federal income the tax was paid on, + # counting only positive federal adjusted gross income. A dependent's + # income is on the dependent's own return. With no positive income, + # the tax is attributed to the head. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + income = head_or_spouse * max_( + person("adjusted_gross_income_person", period), 0 + ) + total_income = person.tax_unit.sum(income) + share = np.zeros_like(total_income) + mask = total_income > 0 + share[mask] = income[mask] / total_income[mask] + head = person("is_tax_unit_head", period) + return where(total_income > 0, share, head) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py index 7b31319e265..ff75bc2277f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py @@ -31,6 +31,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", "mt_federal_income_tax_deduction_indiv", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py index a3328b0290d..1a13c3a97de 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py @@ -24,6 +24,7 @@ def formula(person, period, parameters): period, [ "mt_misc_deductions", + "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", "mt_federal_income_tax_deduction_unit", From 304d880bb6e205730d6dd25b00db4cb89f8551ca Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 07:34:58 -0400 Subject: [PATCH 2/8] Update the 2021 Montana rebate case for per-spouse federal tax deductions Each spouse filing separately on the same form deducts their own federal income tax up to $5,000, so both spouses itemize and Montana tax is 6,295.56. Co-Authored-By: Claude Opus 5.5 --- .../baseline/gov/states/mt/tax/income/integration.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml index 1427235ceaa..687a3ecabbf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml @@ -564,9 +564,12 @@ output: # $2,500 per-return cap split $1,250 per spouse (was double-counted to $5,000) mt_income_tax_rebate: [1_250, 1_250] - # Files separately: 5,099.51 - 1,250 + 3,719.51 - 1,250 = 6,319.02 + # Each spouse deducts their own federal income tax up to $5,000 (2021 + # federal tax 21,175 attributed 90/160 and 70/160), so both itemize 5,000 + # over the 4,830 standard deduction. Taxable income 82,420 and 62,420. + # Files separately: 5,087.78 - 1,250 + 3,707.78 - 1,250 = 6,295.56 # (old double-count subtracted $2,500 per column, understating tax by $2,500) - mt_income_tax: 6_319.02 + mt_income_tax: 6_295.56 - name: MT property tax rebate is not a Form 2 credit and is not tripled (issue 9347) absolute_error_margin: 0.01 From 6eefe271dc9ea5e5bd119221eb70b6e176801e8e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 08:30:36 -0400 Subject: [PATCH 3/8] Address review: no care deduction on a separate form; name the 2023 schedule - A married person filing on a separate form (federal filing status SEPARATE) cannot take the child and dependent care expense deduction: "married couples shall file a joint return or file separately on the same form" (MCA 15-30-2131(1)(c)(vi)(A)); 2022 instructions, line 14. - The 2023 credit schedule was the Elderly Homeowner/Renter Credit Schedule; it became Schedule 2EC in 2024. Co-Authored-By: Claude Opus 5.5 --- ...mt_elderly_homeowner_or_renter_credit.yaml | 2 +- ..._renter_credit_gross_household_income.yaml | 5 ++-- ...hild_dependent_care_expense_deduction.yaml | 28 +++++++++++++++++++ ...or_renter_credit_gross_household_income.py | 7 +++-- ..._child_dependent_care_expense_deduction.py | 7 ++++- ..._dependent_care_expense_deduction_joint.py | 7 ++++- 6 files changed, 48 insertions(+), 8 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml index 154bf57d561..8cfb8bc8538 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit.yaml @@ -224,7 +224,7 @@ # contribution, so the credit is higher: $539.67. mt_elderly_homeowner_or_renter_credit: 539.67 -- name: 2023 Form 2EC worked case, a capital loss does not reduce gross household income +- name: 2023 Elderly Homeowner/Renter Credit Schedule worked case, a capital loss does not reduce gross household income period: 2023 absolute_error_margin: 0.01 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml index f7d12a544dc..38e02773559 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.yaml @@ -55,7 +55,8 @@ members: [person1] state_name: MT output: - # § 15-30-2337(9)(a): federal AGI "without regard to loss". 2023 Form 2EC: + # § 15-30-2337(9)(a): federal AGI "without regard to loss". 2023 Elderly + # Homeowner/Renter Credit Schedule: # line 1 federal AGI 20_000 - 3_000 = 17_000; line 9 adds back the 3_000 # capital loss; line 10 = 20_000. mt_elderly_homeowner_or_renter_credit_gross_household_income: 20_000 @@ -81,6 +82,6 @@ state_name: MT output: # Federal AGI 20_000 - 5_000 = 15_000; the 5_000 loss is added back, so - # gross household income is 20_000 (Form 2EC line 18). Net household + # gross household income is 20_000 (2024 Schedule 2EC line 18). Net household # income: (20_000 - 12_600) * 0.035 = 259, allocated to the head. mt_elderly_homeowner_or_renter_credit_net_household_income: [259, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml index b7ce4721911..4cdfcd2395d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.yaml @@ -721,3 +721,31 @@ state_code: MT output: mt_child_dependent_care_expense_deduction_joint: 0 + +- name: Case 28, a married person filing on a separate form cannot take the deduction + period: 2022 + input: + people: + person1: + is_tax_unit_head: true + mt_agi_indiv: 15_000 + person2: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + filing_status: SEPARATE + tax_unit_childcare_expenses: 2_000 + mt_agi_joint: 15_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + # MCA 15-30-2131(1)(c)(vi)(A): "married couples shall file a joint return + # or file separately on the same form". 2022 instructions, line 14: "If + # you are married and filing separately on separate forms, or your spouse + # is not filing, you cannot qualify for this deduction." + mt_child_dependent_care_expense_deduction: [0, 0] + mt_child_dependent_care_expense_deduction_joint: 0 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py index 4f18254eed0..21c77cdc399 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/credits/mt_elderly_homeowner_or_renter/mt_elderly_homeowner_or_renter_credit_gross_household_income.py @@ -12,6 +12,7 @@ class mt_elderly_homeowner_or_renter_credit_gross_household_income(Variable): "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-23/section-15-30-2337/", "https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0230/section_0370/0150-0300-0230-0370.html", # 2023 Form 2 instructions, Elderly Homeowner/Renter Credit Schedule, line 9 + # (renamed Schedule 2EC from 2024) "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=52", ) @@ -27,9 +28,9 @@ def formula(person, period, parameters): untaxed_social_security = max_(social_security - taxable_social_security, 0) # Income is federal AGI "without regard to loss" (§ 15-30-2337(9)(a)), # so the business and capital losses deducted in computing federal - # AGI are added back (2023 Form 2EC line 9: "The gross household - # income cannot be reduced by any losses."). The return's losses are - # added once, to the head. + # AGI are added back (2023 Elderly Homeowner/Renter Credit Schedule, + # line 9: "The gross household income cannot be reduced by any + # losses."). The return's losses are added once, to the head. head = person("is_tax_unit_head", period) losses = head * person.tax_unit("loss_ald", period) return sources + untaxed_social_security + losses diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py index a7ee18fe4f8..229fe2db8e6 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction.py @@ -43,4 +43,9 @@ def formula(person, period, parameters): married = person.tax_unit("tax_unit_married", period) head = person("is_tax_unit_head", period) share = where(married, head_or_spouse * 0.5, head * 1.0) - return deduction * share + # Married couples must file a joint return or file separately on the + # same form (MCA 15-30-2131(1)(c)(vi)(A)); a spouse filing on a + # separate form cannot take the deduction. + filing_status = person.tax_unit("filing_status", period) + separate_form = filing_status == filing_status.possible_values.SEPARATE + return deduction * share * ~separate_form diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py index ae9a5f6eb02..967ce181cb0 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/cdcc/mt_child_dependent_care_expense_deduction_joint.py @@ -27,4 +27,9 @@ def formula(tax_unit, period, parameters): ) # Line 3: the return's Montana adjusted gross income; lines 4-7. agi = tax_unit("mt_agi_joint", period) - return max_(eligible_expenses - p.phase_out.calc(agi), 0) + deduction = max_(eligible_expenses - p.phase_out.calc(agi), 0) + # A married person filing on a separate form cannot take the deduction + # (MCA 15-30-2131(1)(c)(vi)(A)). + filing_status = tax_unit("filing_status", period) + separate_form = filing_status == filing_status.possible_values.SEPARATE + return deduction * ~separate_form From 95d94db81e660b0fdb7fd8d11a63883435e2b218 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 08:31:26 -0400 Subject: [PATCH 4/8] Count a Montana casualty loss once per joint return and per owning spouse mt_misc_deductions is a Person variable whose `adds` list included the TaxUnit casualty_loss_deduction. policyengine-core evaluates `adds` with calculate(..., map_to=person), which projects a tax-unit value onto every member, so the joint path (summing over the tax unit) counted a couple's casualty loss once per member, and each spouse filing separately got the full amount. Replace it with two Montana variables following the 2021-2023 Form 2 instructions, Itemized Deductions Schedule line 15: complete federal Form 4684 using Montana AGI in place of federal AGI; spouses filing separately each complete their own Form 4684 (and a deduction attributable to one spouse is claimed by that spouse). - mt_casualty_loss_deduction_joint (TaxUnit): the unit's loss above 10% of mt_agi_joint, counted once. - mt_casualty_loss_deduction_indiv (Person): each spouse's own loss above 10% of their own mt_agi_indiv; dependents' losses go to the head, as mt_agi_indiv does with dependents' income. - casualty_loss/applies ends the line in 2024, when Montana discontinued its Itemized Deductions Schedule (2024 instructions, p. 8). With the child care deduction moved out of the misc list (stacked on fix-mt-separate-filing-allocations), the list held only the casualty loss, so the list parameter and mt_misc_deductions are removed. Co-Authored-By: Claude Opus 5.5 --- ...x-mt-casualty-loss-misc-deduction.fixed.md | 1 + .../itemized/casualty_loss/applies.yaml | 20 +++ .../deductions/itemized/misc_deductions.yaml | 25 ---- .../mt_casualty_loss_deduction_indiv.yaml | 107 ++++++++++++++ .../mt_casualty_loss_deduction_joint.yaml | 133 ++++++++++++++++++ ...uctions_for_federal_itemization_indiv.yaml | 44 +++++- ...uctions_for_federal_itemization_joint.yaml | 72 +++++++++- .../mt_itemized_deductions_indiv.yaml | 44 +++++- .../mt_itemized_deductions_joint.yaml | 72 +++++++++- .../mt_casualty_loss_deduction_indiv.py | 38 +++++ .../mt_casualty_loss_deduction_joint.py | 26 ++++ ...eductions_for_federal_itemization_indiv.py | 2 +- ...eductions_for_federal_itemization_joint.py | 2 +- .../general/mt_itemized_deductions_indiv.py | 2 +- .../general/mt_itemized_deductions_joint.py | 2 +- .../itemized/general/mt_misc_deductions.py | 17 --- 16 files changed, 535 insertions(+), 72 deletions(-) create mode 100644 changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md create mode 100644 policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml delete mode 100644 policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py delete mode 100644 policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_misc_deductions.py diff --git a/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md b/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md new file mode 100644 index 00000000000..63e5c33d9ac --- /dev/null +++ b/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md @@ -0,0 +1 @@ +Count a Montana casualty and theft loss once on a joint return, and only in the column of the spouse who had it when spouses file separately, with the 10% floor on Montana AGI (2021-2023 Form 2 Itemized Deductions Schedule, line 15). diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml new file mode 100644 index 00000000000..cd9a5c74f67 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml @@ -0,0 +1,20 @@ +description: Montana allows a casualty and theft loss deduction on its Itemized Deductions Schedule, if this is true. +metadata: + label: Montana casualty and theft loss deduction applies + period: year + unit: bool + reference: + - title: 2021 Montana Form 2 Instructions, Itemized Deductions Schedule, line 15 + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=34 + - title: 2022 Montana Form 2 Instructions, Itemized Deductions Schedule, line 15 + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35 + - title: 2023 Montana Form 2 Instructions, Itemized Deductions Schedule, line 15 + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2023_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=39 + # From 2024, Montana taxable income starts from federal taxable income and + # the Itemized Deductions Schedule is discontinued (SB 399). + - title: 2024 Montana Form 2 Instructions, Discontinued Worksheets and Schedules + href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=8 + +values: + 2021-01-01: true + 2024-01-01: false diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml deleted file mode 100644 index c4744269768..00000000000 --- a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/misc_deductions.yaml +++ /dev/null @@ -1,25 +0,0 @@ -description: Montana counts these sources as miscellaneous deductions. -# The child and dependent care expense deduction is also a miscellaneous -# deduction (Form 2 Itemized Deductions Schedule, line 14). It is added -# separately because its income limit uses the return's own Montana AGI, which -# differs between a joint return and separate columns (Form 2441-M line 3). -values: - 2021-01-01: - - casualty_loss_deduction - 2024-01-01: [] - -metadata: - unit: list - label: Montana miscellaneous deductions - period: year - reference: - - title: Montana Individual Income Tax Return (Form 2) 2024 - href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2024_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=8 - - title: Montana Individual Income Tax Return (Form 2) 2022 - href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7 - - title: Montana Individual Income Tax Return (Form 2) 2021 - href: https://revenue.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2021_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7 - - title: Montana Tax From Instructions 2021 - href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35 - - title: MT Code § 15-30-2131 (2022) (1)(a)&(c) - href: https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/ diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml new file mode 100644 index 00000000000..45789157040 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml @@ -0,0 +1,107 @@ +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# "To determine your casualty and/or theft loss, use your Montana Adjusted +# Gross Income in place of the Federal Adjusted Gross Income. If you and your +# spouse are filing separately, you each should complete a separate federal +# Form 4684 to determine the amount of your separate casualty and/or theft +# loss." +# 2022 Form 2 instructions, Filing Separately When Married (PDF p. 11): +# "Deductions that are attributable to only one spouse must be claimed by that +# spouse." +# Form 4684 allows the loss above 10% of adjusted gross income. The federal +# suspension of losses outside federally declared disasters is modeled by +# gov.irs.deductions.itemized.casualty.active, which these tests switch on. + +- name: Only the spouse who had the loss deducts it, above 10% of their own Montana AGI + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + mt_agi_indiv: 60_000 + person2: + age: 40 + mt_agi_indiv: 40_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 60,000; the spouse has no loss. + mt_casualty_loss_deduction_indiv: [24_000, 0] + +- name: Each spouse's loss is floored at 10% of that spouse's Montana AGI + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + mt_agi_indiv: 60_000 + person2: + age: 40 + casualty_loss: 5_000 + mt_agi_indiv: 40_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 6,000 and 5,000 - 4,000. + mt_casualty_loss_deduction_indiv: [24_000, 1_000] + +- name: A dependent's loss goes to the head, with the dependent's income + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + mt_agi_indiv: 60_000 + person2: + age: 40 + mt_agi_indiv: 40_000 + person3: + age: 10 + is_tax_unit_dependent: true + casualty_loss: 10_000 + mt_agi_indiv: 0 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # 10,000 - 10% x 60,000, in the head's column. + mt_casualty_loss_deduction_indiv: [4_000, 0, 0] + +- name: Losses outside federally declared disasters are suspended + period: 2022 + input: + people: + person1: + age: 40 + casualty_loss: 30_000 + mt_agi_indiv: 60_000 + person2: + age: 40 + mt_agi_indiv: 40_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_casualty_loss_deduction_indiv: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml new file mode 100644 index 00000000000..2a70d9efad0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml @@ -0,0 +1,133 @@ +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# "You may deduct a casualty and theft loss from a federally declared disaster +# (other than net qualified disaster losses). If you have a casualty and/or +# theft loss, complete federal Form 4684 to figure the amount of the Montana +# loss that you can enter on this line. To determine your casualty and/or theft +# loss, use your Montana Adjusted Gross Income in place of the Federal Adjusted +# Gross Income." and "Deductions for all other casualty and theft losses are +# suspended until tax years beginning after December 31, 2025." +# Form 4684 allows the loss above 10% of adjusted gross income. The federal +# suspension is modeled by gov.irs.deductions.itemized.casualty.active, which +# these tests switch on to exercise a deductible loss. + +- name: A joint return deducts the couple's loss once, above 10% of Montana AGI + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + person2: + age: 40 + tax_units: + tax_unit: + members: [person1, person2] + mt_agi_joint: 100_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 100,000 + mt_casualty_loss_deduction_joint: 20_000 + +- name: Both spouses' losses go on the joint return's one Form 4684 + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + person2: + age: 40 + casualty_loss: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + mt_agi_joint: 100_000 + households: + household: + members: [person1, person2] + state_code: MT + output: + # 35,000 - 10% x 100,000 + mt_casualty_loss_deduction_joint: 25_000 + +- name: The 10% floor uses Montana AGI, not federal AGI + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 100_000 + casualty_loss: 30_000 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 80_000 + households: + household: + members: [person1] + state_code: MT + output: + # 30,000 - 10% x 80,000. The federal floor would give 30,000 - 10,000. + mt_casualty_loss_deduction_joint: 22_000 + +- name: A loss at or below 10% of Montana AGI is not deductible + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 8_000 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 100_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_casualty_loss_deduction_joint: 0 + +- name: Losses outside federally declared disasters are suspended + period: 2022 + input: + people: + person1: + age: 40 + casualty_loss: 30_000 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 100_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_casualty_loss_deduction_joint: 0 + +- name: Montana discontinued its Itemized Deductions Schedule in 2024 + period: 2024 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 100_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_casualty_loss_deduction_joint: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml index 4435e64a801..0c0eebb22af 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml @@ -5,7 +5,7 @@ investment_interest_expense: 265 mt_salt_deduction: 5_000 mt_medical_expense_deduction_indiv: 1_000 - casualty_loss_deduction: 400 + mt_casualty_loss_deduction_indiv: 400 mt_child_dependent_care_expense_deduction: 123 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 2_000 state_code: MT @@ -19,7 +19,7 @@ investment_interest_expense: 1_234 mt_salt_deduction: 10_000 mt_medical_expense_deduction_indiv: 3_000 - casualty_loss_deduction: 0 + mt_casualty_loss_deduction_indiv: 0 mt_child_dependent_care_expense_deduction: 0 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 2_000 state_code: MT @@ -33,7 +33,7 @@ mortgage_interest: 34 mt_salt_deduction: 1_035 mt_medical_expense_deduction_indiv: 3_000 - casualty_loss_deduction: 745 + mt_casualty_loss_deduction_indiv: 745 mt_child_dependent_care_expense_deduction: 1_004 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 2_000 state_code: MT @@ -47,20 +47,20 @@ people: person1: is_tax_unit_head_or_spouse: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 200 mt_salt_deduction: 300 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 400 person2: is_tax_unit_head_or_spouse: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 200 mt_salt_deduction: 300 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 100 person3: is_tax_unit_head_or_spouse: false is_tax_unit_dependent: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 12 mt_salt_deduction: 200 mt_federal_income_tax_deduction_for_federal_itemization_indiv: 100 @@ -74,3 +74,35 @@ state_code: MT output: mt_itemized_deductions_for_federal_itemization_indiv: [1_000, 700, 0] + +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# complete federal Form 4684 using Montana AGI in place of federal AGI; spouses +# filing separately each complete their own Form 4684. The casualty loss is a +# tax unit amount, so it must not be repeated for each member. + +- name: Only the spouse who had the casualty loss deducts it when filing separately + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_for_federal_itemization_indiv: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 60,000 Montana AGI in the head's column; none for the spouse. + mt_itemized_deductions_for_federal_itemization_indiv: [24_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index 41a8d808975..616477e630d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -6,7 +6,7 @@ investment_interest_expense: 265 mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 - casualty_loss_deduction: 400 + mt_casualty_loss_deduction_joint: 400 mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT @@ -21,7 +21,7 @@ mortgage_interest: 1_234 mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 - casualty_loss_deduction: 0 + mt_casualty_loss_deduction_joint: 0 mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT @@ -36,7 +36,7 @@ mortgage_interest: 34 mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 - casualty_loss_deduction: 745 + mt_casualty_loss_deduction_joint: 745 mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_for_federal_itemization: 2_000 state_code: MT @@ -50,18 +50,15 @@ people: person1: is_tax_unit_head: true - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 200 mt_salt_deduction: 300 investment_interest_expense: 100 person2: is_tax_unit_head: false - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 200 mt_salt_deduction: 300 person3: is_tax_unit_head: false - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 12 mt_salt_deduction: 200 households: @@ -72,5 +69,66 @@ tax_unit: members: [person1, person2, person3] mt_federal_income_tax_deduction_for_federal_itemization: 600 + mt_casualty_loss_deduction_joint: 100 output: - mt_itemized_deductions_for_federal_itemization_joint: [2_212, 0, 0] + mt_itemized_deductions_for_federal_itemization_joint: [2_012, 0, 0] + +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# complete federal Form 4684 using Montana AGI in place of federal AGI; spouses +# filing separately each complete their own Form 4684. The casualty loss is a +# tax unit amount, so it must not be repeated for each member. + +- name: A couple's casualty loss counts once on a joint return + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + tax_units: + tax_unit: + members: [person1, person2] + mt_federal_income_tax_deduction_for_federal_itemization: 0 + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 100,000 Montana AGI, counted once. + mt_itemized_deductions_for_federal_itemization_joint: [20_000, 0] + +- name: A dependent in the tax unit does not repeat the joint casualty loss + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + person3: + age: 10 + is_tax_unit_dependent: true + mt_salt_deduction: 0 + tax_units: + tax_unit: + members: [person1, person2, person3] + mt_federal_income_tax_deduction_for_federal_itemization: 0 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + mt_itemized_deductions_for_federal_itemization_joint: [20_000, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml index 2bc390f8ad3..8f5d35063f0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml @@ -5,7 +5,7 @@ investment_interest_expense: 265 mt_salt_deduction: 5_000 mt_medical_expense_deduction_indiv: 1_000 - casualty_loss_deduction: 400 + mt_casualty_loss_deduction_indiv: 400 mt_child_dependent_care_expense_deduction: 123 mt_federal_income_tax_deduction_indiv: 2_000 state_code: MT @@ -19,7 +19,7 @@ investment_interest_expense: 1_234 mt_salt_deduction: 10_000 mt_medical_expense_deduction_indiv: 3_000 - casualty_loss_deduction: 0 + mt_casualty_loss_deduction_indiv: 0 mt_child_dependent_care_expense_deduction: 0 mt_federal_income_tax_deduction_indiv: 2_000 state_code: MT @@ -33,7 +33,7 @@ mortgage_interest: 34 mt_salt_deduction: 1_035 mt_medical_expense_deduction_indiv: 3_000 - casualty_loss_deduction: 745 + mt_casualty_loss_deduction_indiv: 745 mt_child_dependent_care_expense_deduction: 1_004 mt_federal_income_tax_deduction_indiv: 2_000 state_code: MT @@ -47,20 +47,20 @@ people: person1: is_tax_unit_head_or_spouse: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 200 mt_salt_deduction: 300 mt_federal_income_tax_deduction_indiv: 400 person2: is_tax_unit_head_or_spouse: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 200 mt_salt_deduction: 300 mt_federal_income_tax_deduction_indiv: 100 person3: is_tax_unit_head_or_spouse: false is_tax_unit_dependent: true - mt_misc_deductions: 100 + mt_casualty_loss_deduction_indiv: 100 mt_medical_expense_deduction_indiv: 12 mt_salt_deduction: 200 mt_federal_income_tax_deduction_indiv: 100 @@ -74,3 +74,35 @@ state_code: MT output: mt_itemized_deductions_indiv: [1_000, 700, 0] + +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# complete federal Form 4684 using Montana AGI in place of federal AGI; spouses +# filing separately each complete their own Form 4684. The casualty loss is a +# tax unit amount, so it must not be repeated for each member. + +- name: Only the spouse who had the casualty loss deducts it when filing separately + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_indiv: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + mt_federal_income_tax_deduction_indiv: 0 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 60,000 Montana AGI in the head's column; none for the spouse. + mt_itemized_deductions_indiv: [24_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index 329641eafa3..a9daeb667a3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -6,7 +6,7 @@ investment_interest_expense: 265 mt_salt_deduction: 5_000 mt_medical_expense_deduction_joint: 1_000 - casualty_loss_deduction: 400 + mt_casualty_loss_deduction_joint: 400 mt_child_dependent_care_expense_deduction_joint: 123 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT @@ -21,7 +21,7 @@ mortgage_interest: 1_234 mt_salt_deduction: 10_000 mt_medical_expense_deduction_joint: 3_000 - casualty_loss_deduction: 0 + mt_casualty_loss_deduction_joint: 0 mt_child_dependent_care_expense_deduction_joint: 0 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT @@ -36,7 +36,7 @@ mortgage_interest: 34 mt_salt_deduction: 1_035 mt_medical_expense_deduction_joint: 3_000 - casualty_loss_deduction: 745 + mt_casualty_loss_deduction_joint: 745 mt_child_dependent_care_expense_deduction_joint: 1_004 mt_federal_income_tax_deduction_unit: 2_000 state_code: MT @@ -50,18 +50,15 @@ people: person1: is_tax_unit_head: true - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 200 mt_salt_deduction: 300 investment_interest_expense: 100 person2: is_tax_unit_head: false - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 200 mt_salt_deduction: 300 person3: is_tax_unit_head: false - mt_misc_deductions: 100 mt_medical_expense_deduction_joint: 12 mt_salt_deduction: 200 households: @@ -72,5 +69,66 @@ tax_unit: members: [person1, person2, person3] mt_federal_income_tax_deduction_unit: 600 + mt_casualty_loss_deduction_joint: 100 output: - mt_itemized_deductions_joint: [2_212, 0, 0] + mt_itemized_deductions_joint: [2_012, 0, 0] + +# 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): +# complete federal Form 4684 using Montana AGI in place of federal AGI; spouses +# filing separately each complete their own Form 4684. The casualty loss is a +# tax unit amount, so it must not be repeated for each member. + +- name: A couple's casualty loss counts once on a joint return + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + tax_units: + tax_unit: + members: [person1, person2] + mt_federal_income_tax_deduction_unit: 0 + households: + household: + members: [person1, person2] + state_code: MT + output: + # 30,000 - 10% x 100,000 Montana AGI, counted once. + mt_itemized_deductions_joint: [20_000, 0] + +- name: A dependent in the tax unit does not repeat the joint casualty loss + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + mt_salt_deduction: 0 + person2: + age: 40 + employment_income: 40_000 + mt_salt_deduction: 0 + person3: + age: 10 + is_tax_unit_dependent: true + mt_salt_deduction: 0 + tax_units: + tax_unit: + members: [person1, person2, person3] + mt_federal_income_tax_deduction_unit: 0 + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + mt_itemized_deductions_joint: [20_000, 0, 0] diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py new file mode 100644 index 00000000000..4ee3a76c5a3 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class mt_casualty_loss_deduction_indiv(Variable): + value_type = float + entity = Person + label = ( + "Montana casualty and theft loss deduction when married couples file separately" + ) + unit = USD + definition_period = YEAR + reference = ( + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", + # 2022 Form 2 instructions, allocation of deductions when filing separately + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=11", + ) + defined_for = "mt_married_filing_separately_on_same_return_eligible" + + def formula(person, period, parameters): + p = parameters(period).gov.states.mt.tax.income.deductions.itemized + if not p.casualty_loss.applies: + return 0 + # Spouses filing separately each complete a separate federal Form 4684 + # for their own loss, using their own Montana adjusted gross income in + # place of federal adjusted gross income. A deduction attributable to + # only one spouse must be claimed by that spouse. + p_casualty = parameters(period).gov.irs.deductions.itemized.casualty + own_loss = person("casualty_loss", period) + # Dependents' losses go to the head, as mt_agi_indiv does with + # dependents' income. + is_dependent = person("is_tax_unit_dependent", period) + is_head = person("is_tax_unit_head", period) + dependents_loss = person.tax_unit.sum(is_dependent * own_loss) + loss = ~is_dependent * own_loss + is_head * dependents_loss + agi = person("mt_agi_indiv", period) + return p_casualty.active * max_(loss - agi * p_casualty.floor, 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py new file mode 100644 index 00000000000..972f03bcede --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class mt_casualty_loss_deduction_joint(Variable): + value_type = float + entity = TaxUnit + label = "Montana casualty and theft loss deduction on a joint return" + unit = USD + definition_period = YEAR + reference = ( + # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", + "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", + ) + defined_for = StateCode.MT + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.mt.tax.income.deductions.itemized + if not p.casualty_loss.applies: + return 0 + # The return completes one federal Form 4684, using its Montana + # adjusted gross income in place of federal adjusted gross income. + p_casualty = parameters(period).gov.irs.deductions.itemized.casualty + loss = add(tax_unit, period, ["casualty_loss"]) + agi = tax_unit("mt_agi_joint", period) + return p_casualty.active * max_(loss - agi * p_casualty.floor, 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py index 0d5e7d0e577..a47e94c3ea8 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.py @@ -28,7 +28,7 @@ def formula(person, period, parameters): person, period, [ - "mt_misc_deductions", + "mt_casualty_loss_deduction_indiv", "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py index ed3371fd0ab..5e159b7d606 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.py @@ -23,7 +23,7 @@ def formula(person, period, parameters): person.tax_unit, period, [ - "mt_misc_deductions", + "mt_casualty_loss_deduction_joint", "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py index ff75bc2277f..808615ac88f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_indiv.py @@ -30,7 +30,7 @@ def formula(person, period, parameters): person, period, [ - "mt_misc_deductions", + "mt_casualty_loss_deduction_indiv", "mt_child_dependent_care_expense_deduction", "mt_medical_expense_deduction_indiv", "mt_salt_deduction", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py index 1a13c3a97de..5648c843633 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_itemized_deductions_joint.py @@ -23,7 +23,7 @@ def formula(person, period, parameters): person.tax_unit, period, [ - "mt_misc_deductions", + "mt_casualty_loss_deduction_joint", "mt_child_dependent_care_expense_deduction_joint", "mt_medical_expense_deduction_joint", "mt_salt_deduction", diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_misc_deductions.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_misc_deductions.py deleted file mode 100644 index 354dc6ca059..00000000000 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/general/mt_misc_deductions.py +++ /dev/null @@ -1,17 +0,0 @@ -from policyengine_us.model_api import * - - -class mt_misc_deductions(Variable): - value_type = float - entity = Person - label = "Montana miscellaneous deductions" - unit = USD - definition_period = YEAR - reference = ( - "https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2/2022_Montana_Individual_Income_Tax_Return_Form_2.pdf#page=7", - "https://law.justia.com/codes/montana/2022/title-15/chapter-30/part-21/section-15-30-2131/", - # Montana Code Annotated MT Code § 15-30-2131 (2022) (1)(a)&(c) - ) - defined_for = StateCode.MT - - adds = "gov.states.mt.tax.income.deductions.itemized.misc_deductions" From 9220847cfe2a0c939f749ebea75c2a2d44ff3af7 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 08:48:03 -0400 Subject: [PATCH 5/8] Apply the Form 4684 $100 reduction and add Montana casualty loss properties Montana's line 15 has filers complete federal Form 4684, whose line 11 reduces each casualty by $100 before the 10%-of-AGI floor (line 17). IRS Publication 547: a joint return is one individual for the $100 rule; spouses filing separately each reduce their own loss by $100. Add the federal amount as gov.irs.deductions.itemized.casualty.per_casualty_reduction (26 U.S.C. 165(h)(1)) and apply it once per joint return and once per spouse's column. The model has one loss amount per person, so each return's (or column's) losses are treated as one casualty, as the Arkansas casualty deduction already does. Add grid property tests: both variants match a closed-form Form 4684 reference; the joint return counts the unit's loss once whatever the number of members; separate filers deduct only their own loss; the deduction is non-negative, bounded, monotone; single-filer variants agree; and it ends in 2024. Co-Authored-By: Claude Opus 5.5 --- ...x-mt-casualty-loss-misc-deduction.fixed.md | 2 +- .../casualty/per_casualty_reduction.yaml | 15 ++ .../mt_casualty_loss_deduction_indiv.yaml | 24 ++- .../mt_casualty_loss_deduction_joint.yaml | 63 +++++- ...uctions_for_federal_itemization_indiv.yaml | 5 +- ...uctions_for_federal_itemization_joint.yaml | 6 +- .../mt_itemized_deductions_indiv.yaml | 5 +- .../mt_itemized_deductions_joint.yaml | 6 +- ...t_mt_casualty_loss_deduction_properties.py | 191 ++++++++++++++++++ .../mt_casualty_loss_deduction_indiv.py | 8 +- .../mt_casualty_loss_deduction_joint.py | 10 +- 11 files changed, 302 insertions(+), 33 deletions(-) create mode 100644 policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml create mode 100644 policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py diff --git a/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md b/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md index 63e5c33d9ac..c88a9dbce78 100644 --- a/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md +++ b/changelog.d/fix-mt-casualty-loss-misc-deduction.fixed.md @@ -1 +1 @@ -Count a Montana casualty and theft loss once on a joint return, and only in the column of the spouse who had it when spouses file separately, with the 10% floor on Montana AGI (2021-2023 Form 2 Itemized Deductions Schedule, line 15). +Count a Montana casualty and theft loss once on a joint return, and only in the column of the spouse who had it when spouses file separately, figured as on federal Form 4684 ($100 reduction, then the 10% floor on Montana AGI) per the 2021-2023 Form 2 Itemized Deductions Schedule, line 15. diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml new file mode 100644 index 00000000000..8ba4c1b3038 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml @@ -0,0 +1,15 @@ +description: The IRS reduces each casualty or theft loss of personal-use property by this amount before applying the income floor. +values: + 2009-01-01: 500 + 2010-01-01: 100 +metadata: + unit: currency-USD + period: year + label: Casualty loss reduction per casualty + reference: + - title: 26 U.S.C. § 165(h)(1) + href: https://www.law.cornell.edu/uscode/text/26/165#h_1 + - title: 2022 Form 4684, line 11 + href: https://www.irs.gov/pub/irs-prior/f4684--2022.pdf#page=1 + - title: IRS Publication 547 (2022), $100 Rule + href: https://www.irs.gov/pub/irs-prior/p547--2022.pdf#page=10 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml index 45789157040..fc4a5df8698 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml @@ -7,9 +7,14 @@ # 2022 Form 2 instructions, Filing Separately When Married (PDF p. 11): # "Deductions that are attributable to only one spouse must be claimed by that # spouse." -# Form 4684 allows the loss above 10% of adjusted gross income. The federal -# suspension of losses outside federally declared disasters is modeled by -# gov.irs.deductions.itemized.casualty.active, which these tests switch on. +# Form 4684 reduces each casualty by $100 (2022 Form 4684 line 11) and allows the +# rest above 10% of adjusted gross income (line 17). IRS Publication 547 (2022, +# PDF p. 10): "If you and your spouse have a casualty or theft loss and you file +# separate returns, each of you must reduce your loss by $100." and "If one +# spouse owns the property, only that spouse can claim a loss deduction on a +# separate return." The federal suspension of losses outside federally declared +# disasters is modeled by gov.irs.deductions.itemized.casualty.active, which +# these tests switch on. - name: Only the spouse who had the loss deducts it, above 10% of their own Montana AGI period: 2022 @@ -31,8 +36,8 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 60,000; the spouse has no loss. - mt_casualty_loss_deduction_indiv: [24_000, 0] + # (30,000 - 100) - 10% x 60,000; the spouse has no loss. + mt_casualty_loss_deduction_indiv: [23_900, 0] - name: Each spouse's loss is floored at 10% of that spouse's Montana AGI period: 2022 @@ -55,8 +60,9 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 6,000 and 5,000 - 4,000. - mt_casualty_loss_deduction_indiv: [24_000, 1_000] + # (30,000 - 100) - 6,000 and (5,000 - 100) - 4,000: each spouse reduces + # their own loss by $100. + mt_casualty_loss_deduction_indiv: [23_900, 900] - name: A dependent's loss goes to the head, with the dependent's income period: 2022 @@ -82,8 +88,8 @@ members: [person1, person2, person3] state_code: MT output: - # 10,000 - 10% x 60,000, in the head's column. - mt_casualty_loss_deduction_indiv: [4_000, 0, 0] + # (10,000 - 100) - 10% x 60,000, in the head's column. + mt_casualty_loss_deduction_indiv: [3_900, 0, 0] - name: Losses outside federally declared disasters are suspended period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml index 2a70d9efad0..4b04030c638 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml @@ -6,9 +6,12 @@ # loss, use your Montana Adjusted Gross Income in place of the Federal Adjusted # Gross Income." and "Deductions for all other casualty and theft losses are # suspended until tax years beginning after December 31, 2025." -# Form 4684 allows the loss above 10% of adjusted gross income. The federal -# suspension is modeled by gov.irs.deductions.itemized.casualty.active, which -# these tests switch on to exercise a deductible loss. +# Form 4684 reduces each casualty by $100 (2022 Form 4684 line 11) and allows the +# rest above 10% of adjusted gross income (line 17). IRS Publication 547 (2022, +# PDF p. 10): "If you and your spouse file a joint return, you are treated as one +# individual in applying the $100 rule." The federal suspension is modeled by +# gov.irs.deductions.itemized.casualty.active, which these tests switch on to +# exercise a deductible loss. - name: A joint return deducts the couple's loss once, above 10% of Montana AGI period: 2022 @@ -29,8 +32,8 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 100,000 - mt_casualty_loss_deduction_joint: 20_000 + # (30,000 - 100) - 10% x 100,000 + mt_casualty_loss_deduction_joint: 19_900 - name: Both spouses' losses go on the joint return's one Form 4684 period: 2022 @@ -52,8 +55,8 @@ members: [person1, person2] state_code: MT output: - # 35,000 - 10% x 100,000 - mt_casualty_loss_deduction_joint: 25_000 + # (35,000 - 100) - 10% x 100,000; the joint return reduces by $100 once. + mt_casualty_loss_deduction_joint: 24_900 - name: The 10% floor uses Montana AGI, not federal AGI period: 2022 @@ -73,17 +76,17 @@ members: [person1] state_code: MT output: - # 30,000 - 10% x 80,000. The federal floor would give 30,000 - 10,000. - mt_casualty_loss_deduction_joint: 22_000 + # (30,000 - 100) - 10% x 80,000. A federal AGI floor would give 19,900. + mt_casualty_loss_deduction_joint: 21_900 -- name: A loss at or below 10% of Montana AGI is not deductible +- name: A loss whose excess over $100 equals 10% of Montana AGI is not deductible period: 2022 input: gov.irs.deductions.itemized.casualty.active: true people: person1: age: 40 - casualty_loss: 8_000 + casualty_loss: 10_100 tax_units: tax_unit: members: [person1] @@ -95,6 +98,44 @@ output: mt_casualty_loss_deduction_joint: 0 +- name: One dollar above the floor is deductible + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 10_101 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 100_000 + households: + household: + members: [person1] + state_code: MT + output: + mt_casualty_loss_deduction_joint: 1 + +- name: A loss under $100 is not deductible even with no Montana AGI + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 80 + tax_units: + tax_unit: + members: [person1] + mt_agi_joint: 0 + households: + household: + members: [person1] + state_code: MT + output: + mt_casualty_loss_deduction_joint: 0 + - name: Losses outside federally declared disasters are suspended period: 2022 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml index 0c0eebb22af..89ffbe37823 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml @@ -104,5 +104,6 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 60,000 Montana AGI in the head's column; none for the spouse. - mt_itemized_deductions_for_federal_itemization_indiv: [24_000, 0] + # (30,000 - 100) - 10% x 60,000 Montana AGI in the head's column; none for + # the spouse. + mt_itemized_deductions_for_federal_itemization_indiv: [23_900, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index 616477e630d..ef35f6d8213 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -101,8 +101,8 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 100,000 Montana AGI, counted once. - mt_itemized_deductions_for_federal_itemization_joint: [20_000, 0] + # (30,000 - 100) - 10% x 100,000 Montana AGI, counted once. + mt_itemized_deductions_for_federal_itemization_joint: [19_900, 0] - name: A dependent in the tax unit does not repeat the joint casualty loss period: 2022 @@ -131,4 +131,4 @@ members: [person1, person2, person3] state_code: MT output: - mt_itemized_deductions_for_federal_itemization_joint: [20_000, 0, 0] + mt_itemized_deductions_for_federal_itemization_joint: [19_900, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml index 8f5d35063f0..e4dc6286dad 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml @@ -104,5 +104,6 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 60,000 Montana AGI in the head's column; none for the spouse. - mt_itemized_deductions_indiv: [24_000, 0] + # (30,000 - 100) - 10% x 60,000 Montana AGI in the head's column; none for + # the spouse. + mt_itemized_deductions_indiv: [23_900, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index a9daeb667a3..0630694465f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -101,8 +101,8 @@ members: [person1, person2] state_code: MT output: - # 30,000 - 10% x 100,000 Montana AGI, counted once. - mt_itemized_deductions_joint: [20_000, 0] + # (30,000 - 100) - 10% x 100,000 Montana AGI, counted once. + mt_itemized_deductions_joint: [19_900, 0] - name: A dependent in the tax unit does not repeat the joint casualty loss period: 2022 @@ -131,4 +131,4 @@ members: [person1, person2, person3] state_code: MT output: - mt_itemized_deductions_joint: [20_000, 0, 0] + mt_itemized_deductions_joint: [19_900, 0, 0] diff --git a/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py b/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py new file mode 100644 index 00000000000..1c676c9c780 --- /dev/null +++ b/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py @@ -0,0 +1,191 @@ +"""Properties of the Montana casualty and theft loss deduction (2021-2023). + +The 2021-2023 Form 2 instructions (Itemized Deductions Schedule, line 15) say +to complete federal Form 4684 with Montana AGI in place of federal AGI, and +that spouses filing separately each complete their own Form 4684. Each test +evaluates every point of an input grid in one vectorized simulation, so the +properties hold for all grid inputs rather than for a few examples: + +- both variants match a closed-form reference of Form 4684 lines 11-12 and + 17-18: the loss less $100, above 10% of Montana AGI; +- a joint return counts the unit's loss once, however many members the tax + unit has, and Montana's joint itemized deductions add it once; +- filing separately, each spouse deducts only their own loss (dependents' + losses go to the head), floored at their own Montana AGI; +- the deductions are non-negative, never exceed the loss less $100, rise with + the loss and fall with Montana AGI; +- for a single filer the joint and separate variants agree; +- the deduction ends in 2024, when Montana discontinued its Itemized + Deductions Schedule. + +The federal suspension of losses outside federally declared disasters is +modeled by gov.irs.deductions.itemized.casualty.active; a reform switches it +on so the properties are not vacuous. +""" + +import itertools + +import numpy as np +import pytest +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + +REDUCTION = 100 # Form 4684 line 11 +FLOOR = 0.1 # Form 4684 line 17 + +HEAD_LOSSES = [0, 80, 100, 101, 5_000, 30_000] +SPOUSE_LOSSES = [0, 100, 5_000] +HEAD_AGIS = [0, 40_000, 300_000] +SPOUSE_AGIS = [0, 60_000] +# (number of dependents, the first dependent's loss) +DEPENDENTS = [(0, 0), (1, 0), (1, 2_000), (2, 0)] +COUPLE_GRID = list( + itertools.product(HEAD_LOSSES, SPOUSE_LOSSES, HEAD_AGIS, SPOUSE_AGIS, DEPENDENTS) +) +SINGLE_GRID = list(itertools.product(HEAD_LOSSES, HEAD_AGIS)) +YEARS = [2021, 2022, 2023, 2024] + +ACTIVE = Reform.from_dict( + {"gov.irs.deductions.itemized.casualty.active": {"2010-01-01.2100-12-31": True}}, + country_id="us", +) + + +def reference(loss, agi): + return np.maximum(np.maximum(loss - REDUCTION, 0) - FLOOR * agi, 0) + + +def every_year(value): + return {year: value for year in YEARS} + + +@pytest.fixture(scope="module") +def grid(): + """One simulation holding every couple and single-filer grid point.""" + people, tax_units, households = {}, {}, {} + roles = [] # (unit index, role) for each person, in order + for i, (l1, l2, a1, a2, (n_deps, dep_loss)) in enumerate(COUPLE_GRID): + members = [f"c{i}_head", f"c{i}_spouse"] + people[members[0]] = { + "age": every_year(40), + "casualty_loss": every_year(l1), + "mt_agi_indiv": every_year(a1), + } + people[members[1]] = { + "age": every_year(40), + "casualty_loss": every_year(l2), + "mt_agi_indiv": every_year(a2), + } + roles += [(i, "head"), (i, "spouse")] + for d in range(n_deps): + name = f"c{i}_dep{d}" + people[name] = { + "age": every_year(10), + "is_tax_unit_dependent": every_year(True), + "casualty_loss": every_year(dep_loss if d == 0 else 0), + "mt_agi_indiv": every_year(0), + } + members.append(name) + roles.append((i, "dependent")) + tax_units[f"c{i}"] = {"members": members, "mt_agi_joint": every_year(a1 + a2)} + households[f"c{i}"] = {"members": members, "state_code": every_year("MT")} + for j, (loss, agi) in enumerate(SINGLE_GRID): + name = f"s{j}" + people[name] = { + "age": every_year(40), + "casualty_loss": every_year(loss), + "mt_agi_indiv": every_year(agi), + } + roles.append((len(COUPLE_GRID) + j, "single")) + tax_units[name] = {"members": [name], "mt_agi_joint": every_year(agi)} + households[name] = {"members": [name], "state_code": every_year("MT")} + # Zero the other itemized deductions so the aggregators show the casualty + # loss alone; the grid has no medical, care, charitable or interest items. + for person in people.values(): + person["mt_salt_deduction"] = every_year(0) + person["mt_federal_income_tax_deduction_indiv"] = every_year(0) + for unit in tax_units.values(): + unit["mt_federal_income_tax_deduction_unit"] = every_year(0) + sim = Simulation( + situation={"people": people, "tax_units": tax_units, "households": households}, + reform=ACTIVE, + ) + unit = np.array([r[0] for r in roles]) + role = np.array([r[1] for r in roles]) + return sim, unit, role + + +def couple_arrays(): + columns = np.array( + [(l1, l2, a1, a2, n, d) for l1, l2, a1, a2, (n, d) in COUPLE_GRID], + dtype=float, + ).T + return columns # head loss, spouse loss, head AGI, spouse AGI, deps, dep loss + + +@pytest.mark.parametrize("year", [2021, 2022, 2023]) +def test_joint_return_counts_the_unit_loss_once(grid, year): + sim, unit, role = grid + l1, l2, a1, a2, _, dep_loss = couple_arrays() + n = len(COUPLE_GRID) + joint = sim.calculate("mt_casualty_loss_deduction_joint", year)[:n] + np.testing.assert_allclose(joint, reference(l1 + l2 + dep_loss, a1 + a2)) + # Montana's joint itemized deductions add it once, at the head, however + # many members the tax unit has. + itemized = sim.calculate("mt_itemized_deductions_joint", year) + per_unit = np.bincount(unit, weights=itemized)[:n] + np.testing.assert_allclose(per_unit, joint) + assert np.all(itemized[(role != "head") & (role != "single")] == 0) + assert np.all(joint >= 0) + assert np.all(joint <= np.maximum(l1 + l2 + dep_loss - REDUCTION, 0)) + # Guard against a vacuous pass. + assert joint.max() > 0 + + +@pytest.mark.parametrize("year", [2021, 2022, 2023]) +def test_separate_filers_deduct_only_their_own_loss(grid, year): + sim, unit, role = grid + l1, l2, a1, a2, _, dep_loss = couple_arrays() + indiv = sim.calculate("mt_casualty_loss_deduction_indiv", year) + itemized = sim.calculate("mt_itemized_deductions_indiv", year) + head = indiv[role == "head"] + spouse = indiv[role == "spouse"] + # Each spouse's own Form 4684, on their own Montana AGI; a dependent's + # loss is in the head's column. + np.testing.assert_allclose(head, reference(l1 + dep_loss, a1)) + np.testing.assert_allclose(spouse, reference(l2, a2)) + assert np.all(indiv[role == "dependent"] == 0) + # A spouse without a loss deducts nothing, whatever the other spouse lost. + assert np.all(spouse[l2 == 0] == 0) + assert np.all(head[l1 + dep_loss == 0] == 0) + # The separate itemized deductions carry each spouse's own amount. + np.testing.assert_allclose(itemized[role == "head"], head) + np.testing.assert_allclose(itemized[role == "spouse"], spouse) + assert np.all(itemized[role == "dependent"] == 0) + assert np.all(indiv >= 0) + assert head.max() > 0 and spouse.max() > 0 + + +@pytest.mark.parametrize("year", [2021, 2022, 2023]) +def test_single_filer_variants_agree_and_are_monotone(grid, year): + sim, unit, role = grid + single = role == "single" + single_units = unit[single] + loss = np.array([g[0] for g in SINGLE_GRID], dtype=float) + agi = np.array([g[1] for g in SINGLE_GRID], dtype=float) + indiv = sim.calculate("mt_casualty_loss_deduction_indiv", year)[single] + joint = sim.calculate("mt_casualty_loss_deduction_joint", year)[single_units] + np.testing.assert_allclose(joint, reference(loss, agi)) + np.testing.assert_allclose(indiv, joint) + table = joint.reshape(len(HEAD_LOSSES), len(HEAD_AGIS)) + assert np.all(np.diff(table, axis=0) >= 0), "falls as the loss rises" + assert np.all(np.diff(table, axis=1) <= 0), "rises as Montana AGI rises" + + +def test_deduction_ends_with_the_2024_schedule(grid): + sim, _, _ = grid + assert np.all(sim.calculate("mt_casualty_loss_deduction_joint", 2024) == 0) + assert np.all(sim.calculate("mt_casualty_loss_deduction_indiv", 2024) == 0) + # The same inputs are deductible in 2023. + assert sim.calculate("mt_casualty_loss_deduction_joint", 2023).max() > 0 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py index 4ee3a76c5a3..f35a75e6a8f 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py @@ -15,6 +15,9 @@ class mt_casualty_loss_deduction_indiv(Variable): "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", # 2022 Form 2 instructions, allocation of deductions when filing separately "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=11", + # 2022 Form 4684, lines 10-12 and 17; IRS Publication 547, $100 Rule + "https://www.irs.gov/pub/irs-prior/f4684--2022.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/p547--2022.pdf#page=10", ) defined_for = "mt_married_filing_separately_on_same_return_eligible" @@ -34,5 +37,8 @@ def formula(person, period, parameters): is_head = person("is_tax_unit_head", period) dependents_loss = person.tax_unit.sum(is_dependent * own_loss) loss = ~is_dependent * own_loss + is_head * dependents_loss + # Spouses filing separately each reduce their own loss by $100 + # (Form 4684 line 11), treating each column's losses as one casualty. + reduced_loss = max_(loss - p_casualty.per_casualty_reduction, 0) agi = person("mt_agi_indiv", period) - return p_casualty.active * max_(loss - agi * p_casualty.floor, 0) + return p_casualty.active * max_(reduced_loss - agi * p_casualty.floor, 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py index 972f03bcede..63a9693ba18 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py @@ -11,6 +11,9 @@ class mt_casualty_loss_deduction_joint(Variable): # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", + # 2022 Form 4684, lines 10-12 and 17; IRS Publication 547, $100 Rule + "https://www.irs.gov/pub/irs-prior/f4684--2022.pdf#page=1", + "https://www.irs.gov/pub/irs-prior/p547--2022.pdf#page=10", ) defined_for = StateCode.MT @@ -21,6 +24,11 @@ def formula(tax_unit, period, parameters): # The return completes one federal Form 4684, using its Montana # adjusted gross income in place of federal adjusted gross income. p_casualty = parameters(period).gov.irs.deductions.itemized.casualty + # A joint return is one individual for the $100 rule. The model records + # one loss amount per person, not separate casualty events, so the + # return's losses are treated as one casualty and the $100 reduction + # (Form 4684 line 11) applies once. loss = add(tax_unit, period, ["casualty_loss"]) + reduced_loss = max_(loss - p_casualty.per_casualty_reduction, 0) agi = tax_unit("mt_agi_joint", period) - return p_casualty.active * max_(loss - agi * p_casualty.floor, 0) + return p_casualty.active * max_(reduced_loss - agi * p_casualty.floor, 0) From e352ed05bba085eb2502ef60bb96505e3322acde Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 09:24:29 -0400 Subject: [PATCH 6/8] Leave dependents' casualty losses to their own returns; address review From the adversarial review of #9888 (law, code and test lenses, all approving): - A dependent's casualty loss belongs on the dependent's own return, as the dependent's income does (adjusted_gross_income_person, and so Montana AGI, leaves it out; Pub. 547: only the owner claims the loss). The joint path now sums non-dependents' losses (tax_unit_non_dep_add) and the separate path counts only the head's or spouse's own loss, instead of moving dependents' losses into the head's column while their income stayed out of it. - Say what the suspension flag models: PE has no input distinguishing federally declared disaster losses, so, like the federal deduction, no personal casualty loss is deductible from 2018 unless gov.irs.deductions.itemized.casualty.active is on. - Cite former MCA 15-30-2131(1)(a) (MCA 2021), which allowed the items of IRC 161 (including 165 losses), on the parameter and variables. - Describe the Form 4684 computation per lines 10-18 rather than as one form per return, and fix the separate-path test headers. - Add the 2008 $100 value to per_casualty_reduction (2008 and 2009 Form 4684 line 11), so it is defined before 2009. - Tests: natural-input dependent cases on both paths, and a 2024 case with filing status 2a allowed that exercises the separate path's applies gate (it fails if the gate is removed). Co-Authored-By: Claude Opus 5.5 --- .../casualty/per_casualty_reduction.yaml | 5 ++ .../itemized/casualty_loss/applies.yaml | 2 + .../mt_casualty_loss_deduction_indiv.yaml | 51 +++++++++++++++---- .../mt_casualty_loss_deduction_joint.yaml | 41 +++++++++++++-- ...uctions_for_federal_itemization_indiv.yaml | 5 +- ...uctions_for_federal_itemization_joint.yaml | 4 +- .../mt_itemized_deductions_indiv.yaml | 5 +- .../mt_itemized_deductions_joint.yaml | 4 +- ...t_mt_casualty_loss_deduction_properties.py | 29 ++++++----- .../mt_casualty_loss_deduction_indiv.py | 18 ++++--- .../mt_casualty_loss_deduction_joint.py | 16 ++++-- 11 files changed, 133 insertions(+), 47 deletions(-) diff --git a/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml b/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml index 8ba4c1b3038..b56d5b69462 100644 --- a/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml @@ -1,5 +1,6 @@ description: The IRS reduces each casualty or theft loss of personal-use property by this amount before applying the income floor. values: + 2008-01-01: 100 2009-01-01: 500 2010-01-01: 100 metadata: @@ -9,6 +10,10 @@ metadata: reference: - title: 26 U.S.C. § 165(h)(1) href: https://www.law.cornell.edu/uscode/text/26/165#h_1 + - title: 2008 Form 4684, line 11 + href: https://www.irs.gov/pub/irs-prior/f4684--2008.pdf#page=1 + - title: 2009 Form 4684, line 11 + href: https://www.irs.gov/pub/irs-prior/f4684--2009.pdf#page=1 - title: 2022 Form 4684, line 11 href: https://www.irs.gov/pub/irs-prior/f4684--2022.pdf#page=1 - title: IRS Publication 547 (2022), $100 Rule diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml index cd9a5c74f67..6c6d2c05de1 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/deductions/itemized/casualty_loss/applies.yaml @@ -4,6 +4,8 @@ metadata: period: year unit: bool reference: + - title: MCA § 15-30-2131(1)(a) (2021), itemized deductions by reference to 26 U.S.C. 161 and 211 (repealed effective 2024) + href: https://mca.legmt.gov/bills/2022/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html - title: 2021 Montana Form 2 Instructions, Itemized Deductions Schedule, line 15 href: https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2021_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=34 - title: 2022 Montana Form 2 Instructions, Itemized Deductions Schedule, line 15 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml index fc4a5df8698..06f6e05ad30 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.yaml @@ -12,9 +12,10 @@ # PDF p. 10): "If you and your spouse have a casualty or theft loss and you file # separate returns, each of you must reduce your loss by $100." and "If one # spouse owns the property, only that spouse can claim a loss deduction on a -# separate return." The federal suspension of losses outside federally declared -# disasters is modeled by gov.irs.deductions.itemized.casualty.active, which -# these tests switch on. +# separate return." The model has no input that distinguishes federally declared +# disaster losses; like the federal deduction, it allows no personal casualty +# loss from 2018 unless gov.irs.deductions.itemized.casualty.active is switched +# on, as these tests do. - name: Only the spouse who had the loss deducts it, above 10% of their own Montana AGI period: 2022 @@ -64,22 +65,23 @@ # their own loss by $100. mt_casualty_loss_deduction_indiv: [23_900, 900] -- name: A dependent's loss goes to the head, with the dependent's income +- name: A dependent's loss stays off the parents' columns, like the dependent's income period: 2022 input: gov.irs.deductions.itemized.casualty.active: true people: person1: age: 40 - mt_agi_indiv: 60_000 + employment_income: 60_000 + casualty_loss: 30_000 person2: age: 40 - mt_agi_indiv: 40_000 + employment_income: 40_000 person3: - age: 10 + age: 17 is_tax_unit_dependent: true + employment_income: 5_000 casualty_loss: 10_000 - mt_agi_indiv: 0 tax_units: tax_unit: members: [person1, person2, person3] @@ -88,10 +90,37 @@ members: [person1, person2, person3] state_code: MT output: - # (10,000 - 100) - 10% x 60,000, in the head's column. - mt_casualty_loss_deduction_indiv: [3_900, 0, 0] + # The dependent's wages are not in the head's Montana AGI (60,000), and the + # dependent's loss belongs on the dependent's own return: + # (30,000 - 100) - 10% x 60,000 for the head. + mt_casualty_loss_deduction_indiv: [23_900, 0, 0] + +- name: Spouses filing separately on one form in 2024 have no Montana casualty line + period: 2024 + input: + # Allowing filing status 2a in 2024 (as a reform would) reaches the + # separate path; the discontinued schedule still gives no deduction. + gov.states.mt.tax.income.married_filing_separately_on_same_return_allowed: true + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + casualty_loss: 30_000 + mt_agi_indiv: 60_000 + person2: + age: 40 + mt_agi_indiv: 40_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + mt_casualty_loss_deduction_indiv: [0, 0] -- name: Losses outside federally declared disasters are suspended +- name: No loss is deductible while the federal casualty deduction is suspended period: 2022 input: people: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml index 4b04030c638..261add5e5c5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.yaml @@ -9,9 +9,10 @@ # Form 4684 reduces each casualty by $100 (2022 Form 4684 line 11) and allows the # rest above 10% of adjusted gross income (line 17). IRS Publication 547 (2022, # PDF p. 10): "If you and your spouse file a joint return, you are treated as one -# individual in applying the $100 rule." The federal suspension is modeled by -# gov.irs.deductions.itemized.casualty.active, which these tests switch on to -# exercise a deductible loss. +# individual in applying the $100 rule." The model has no input that +# distinguishes federally declared disaster losses; like the federal deduction, +# it allows no personal casualty loss from 2018 unless +# gov.irs.deductions.itemized.casualty.active is switched on, as these tests do. - name: A joint return deducts the couple's loss once, above 10% of Montana AGI period: 2022 @@ -35,7 +36,7 @@ # (30,000 - 100) - 10% x 100,000 mt_casualty_loss_deduction_joint: 19_900 -- name: Both spouses' losses go on the joint return's one Form 4684 +- name: Both spouses' losses are pooled on a joint return period: 2022 input: gov.irs.deductions.itemized.casualty.active: true @@ -58,6 +59,36 @@ # (35,000 - 100) - 10% x 100,000; the joint return reduces by $100 once. mt_casualty_loss_deduction_joint: 24_900 +- name: A dependent's loss is left to the dependent's own return + period: 2022 + input: + gov.irs.deductions.itemized.casualty.active: true + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 30_000 + person2: + age: 40 + employment_income: 40_000 + person3: + age: 17 + is_tax_unit_dependent: true + employment_income: 5_000 + casualty_loss: 10_000 + tax_units: + tax_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Montana AGI is the spouses' 100,000 (the dependent's wages are on the + # dependent's own return), and so is the dependent's loss: + # (30,000 - 100) - 10% x 100,000. + mt_casualty_loss_deduction_joint: 19_900 + - name: The 10% floor uses Montana AGI, not federal AGI period: 2022 input: @@ -136,7 +167,7 @@ output: mt_casualty_loss_deduction_joint: 0 -- name: Losses outside federally declared disasters are suspended +- name: No loss is deductible while the federal casualty deduction is suspended period: 2022 input: people: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml index 89ffbe37823..c7a4ffc9f8b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_indiv.yaml @@ -77,8 +77,9 @@ # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): # complete federal Form 4684 using Montana AGI in place of federal AGI; spouses -# filing separately each complete their own Form 4684. The casualty loss is a -# tax unit amount, so it must not be repeated for each member. +# filing separately each complete their own Form 4684. Each spouse deducts only +# their own loss, on their own Montana AGI; the tax unit's loss must not be +# given to every member. - name: Only the spouse who had the casualty loss deducts it when filing separately period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml index ef35f6d8213..af31671a593 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/federal_itemization/mt_itemized_deductions_for_federal_itemization_joint.yaml @@ -75,8 +75,8 @@ # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): # complete federal Form 4684 using Montana AGI in place of federal AGI; spouses -# filing separately each complete their own Form 4684. The casualty loss is a -# tax unit amount, so it must not be repeated for each member. +# filing separately each complete their own Form 4684. A joint return pools the +# tax unit's losses, so they must be counted once, not once per member. - name: A couple's casualty loss counts once on a joint return period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml index e4dc6286dad..d41523a13f9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_indiv.yaml @@ -77,8 +77,9 @@ # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): # complete federal Form 4684 using Montana AGI in place of federal AGI; spouses -# filing separately each complete their own Form 4684. The casualty loss is a -# tax unit amount, so it must not be repeated for each member. +# filing separately each complete their own Form 4684. Each spouse deducts only +# their own loss, on their own Montana AGI; the tax unit's loss must not be +# given to every member. - name: Only the spouse who had the casualty loss deducts it when filing separately period: 2022 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml index 0630694465f..dbab8e1a2a5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/deductions/itemized/mt_itemized_deductions_joint.yaml @@ -75,8 +75,8 @@ # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 (PDF pp. 35-36): # complete federal Form 4684 using Montana AGI in place of federal AGI; spouses -# filing separately each complete their own Form 4684. The casualty loss is a -# tax unit amount, so it must not be repeated for each member. +# filing separately each complete their own Form 4684. A joint return pools the +# tax unit's losses, so they must be counted once, not once per member. - name: A couple's casualty loss counts once on a joint return period: 2022 diff --git a/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py b/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py index 1c676c9c780..bb78c499d1f 100644 --- a/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py +++ b/policyengine_us/tests/unit/test_mt_casualty_loss_deduction_properties.py @@ -8,18 +8,20 @@ - both variants match a closed-form reference of Form 4684 lines 11-12 and 17-18: the loss less $100, above 10% of Montana AGI; -- a joint return counts the unit's loss once, however many members the tax - unit has, and Montana's joint itemized deductions add it once; -- filing separately, each spouse deducts only their own loss (dependents' - losses go to the head), floored at their own Montana AGI; +- a joint return counts the spouses' losses once, however many members the + tax unit has, and Montana's joint itemized deductions add it once; +- filing separately, each spouse deducts only their own loss, floored at their + own Montana AGI; +- a dependent's loss is left to the dependent's own return on both paths; - the deductions are non-negative, never exceed the loss less $100, rise with the loss and fall with Montana AGI; - for a single filer the joint and separate variants agree; - the deduction ends in 2024, when Montana discontinued its Itemized Deductions Schedule. -The federal suspension of losses outside federally declared disasters is -modeled by gov.irs.deductions.itemized.casualty.active; a reform switches it +The model has no input that distinguishes federally declared disaster losses, +so, like the federal deduction, it allows no personal casualty loss from 2018 +unless gov.irs.deductions.itemized.casualty.active is on; a reform switches it on so the properties are not vacuous. """ @@ -127,10 +129,11 @@ def couple_arrays(): @pytest.mark.parametrize("year", [2021, 2022, 2023]) def test_joint_return_counts_the_unit_loss_once(grid, year): sim, unit, role = grid - l1, l2, a1, a2, _, dep_loss = couple_arrays() + l1, l2, a1, a2, _, _ = couple_arrays() n = len(COUPLE_GRID) joint = sim.calculate("mt_casualty_loss_deduction_joint", year)[:n] - np.testing.assert_allclose(joint, reference(l1 + l2 + dep_loss, a1 + a2)) + # A dependent's loss is left to the dependent's own return. + np.testing.assert_allclose(joint, reference(l1 + l2, a1 + a2)) # Montana's joint itemized deductions add it once, at the head, however # many members the tax unit has. itemized = sim.calculate("mt_itemized_deductions_joint", year) @@ -138,7 +141,7 @@ def test_joint_return_counts_the_unit_loss_once(grid, year): np.testing.assert_allclose(per_unit, joint) assert np.all(itemized[(role != "head") & (role != "single")] == 0) assert np.all(joint >= 0) - assert np.all(joint <= np.maximum(l1 + l2 + dep_loss - REDUCTION, 0)) + assert np.all(joint <= np.maximum(l1 + l2 - REDUCTION, 0)) # Guard against a vacuous pass. assert joint.max() > 0 @@ -146,19 +149,19 @@ def test_joint_return_counts_the_unit_loss_once(grid, year): @pytest.mark.parametrize("year", [2021, 2022, 2023]) def test_separate_filers_deduct_only_their_own_loss(grid, year): sim, unit, role = grid - l1, l2, a1, a2, _, dep_loss = couple_arrays() + l1, l2, a1, a2, _, _ = couple_arrays() indiv = sim.calculate("mt_casualty_loss_deduction_indiv", year) itemized = sim.calculate("mt_itemized_deductions_indiv", year) head = indiv[role == "head"] spouse = indiv[role == "spouse"] # Each spouse's own Form 4684, on their own Montana AGI; a dependent's - # loss is in the head's column. - np.testing.assert_allclose(head, reference(l1 + dep_loss, a1)) + # loss is in neither column. + np.testing.assert_allclose(head, reference(l1, a1)) np.testing.assert_allclose(spouse, reference(l2, a2)) assert np.all(indiv[role == "dependent"] == 0) # A spouse without a loss deducts nothing, whatever the other spouse lost. assert np.all(spouse[l2 == 0] == 0) - assert np.all(head[l1 + dep_loss == 0] == 0) + assert np.all(head[l1 == 0] == 0) # The separate itemized deductions carry each spouse's own amount. np.testing.assert_allclose(itemized[role == "head"], head) np.testing.assert_allclose(itemized[role == "spouse"], spouse) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py index f35a75e6a8f..fa9b40ac705 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_indiv.py @@ -10,6 +10,8 @@ class mt_casualty_loss_deduction_indiv(Variable): unit = USD definition_period = YEAR reference = ( + # MCA § 15-30-2131(1)(a) (2021) + "https://mca.legmt.gov/bills/2022/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", @@ -29,14 +31,16 @@ def formula(person, period, parameters): # for their own loss, using their own Montana adjusted gross income in # place of federal adjusted gross income. A deduction attributable to # only one spouse must be claimed by that spouse. + # Montana allows only federally declared disaster losses (line 15). The + # model has no input that distinguishes them, so, like the federal + # deduction, no personal casualty loss is deductible while + # gov.irs.deductions.itemized.casualty.active is false (from 2018). p_casualty = parameters(period).gov.irs.deductions.itemized.casualty - own_loss = person("casualty_loss", period) - # Dependents' losses go to the head, as mt_agi_indiv does with - # dependents' income. - is_dependent = person("is_tax_unit_dependent", period) - is_head = person("is_tax_unit_head", period) - dependents_loss = person.tax_unit.sum(is_dependent * own_loss) - loss = ~is_dependent * own_loss + is_head * dependents_loss + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return, as the dependent's income does: + # adjusted_gross_income_person, and so Montana AGI, leaves it out. + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + loss = head_or_spouse * person("casualty_loss", period) # Spouses filing separately each reduce their own loss by $100 # (Form 4684 line 11), treating each column's losses as one casualty. reduced_loss = max_(loss - p_casualty.per_casualty_reduction, 0) diff --git a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py index 63a9693ba18..ef6dcd421e1 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/deductions/itemized/casualty_loss/mt_casualty_loss_deduction_joint.py @@ -8,6 +8,8 @@ class mt_casualty_loss_deduction_joint(Variable): unit = USD definition_period = YEAR reference = ( + # MCA § 15-30-2131(1)(a) (2021) + "https://mca.legmt.gov/bills/2022/mca/title_0150/chapter_0300/part_0210/section_0310/0150-0300-0210-0310.html", # 2022 Form 2 instructions, Itemized Deductions Schedule, line 15 "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=35", "https://revenue.mt.gov/files/forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2022_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=36", @@ -21,14 +23,22 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.mt.tax.income.deductions.itemized if not p.casualty_loss.applies: return 0 - # The return completes one federal Form 4684, using its Montana - # adjusted gross income in place of federal adjusted gross income. + # The return figures its loss as on federal Form 4684 (lines 10-18), + # using its Montana adjusted gross income in place of federal adjusted + # gross income. + # Montana allows only federally declared disaster losses (line 15). The + # model has no input that distinguishes them, so, like the federal + # deduction, no personal casualty loss is deductible while + # gov.irs.deductions.itemized.casualty.active is false (from 2018). p_casualty = parameters(period).gov.irs.deductions.itemized.casualty # A joint return is one individual for the $100 rule. The model records # one loss amount per person, not separate casualty events, so the # return's losses are treated as one casualty and the $100 reduction # (Form 4684 line 11) applies once. - loss = add(tax_unit, period, ["casualty_loss"]) + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return, as the dependent's income does: + # adjusted_gross_income_person, and so Montana AGI, leaves it out. + loss = tax_unit_non_dep_add(tax_unit, period, ["casualty_loss"]) reduced_loss = max_(loss - p_casualty.per_casualty_reduction, 0) agi = tax_unit("mt_agi_joint", period) return p_casualty.active * max_(reduced_loss - agi * p_casualty.floor, 0) From 266f3e245f55ff925d596cce96cc3657b0e8189f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 12:30:01 -0400 Subject: [PATCH 7/8] Apply the Form 4684 $100 per-casualty reduction and leave dependents' losses off the filer's return 26 U.S.C. 165(h)(1) reduces each personal casualty loss by $100 (Form 4684 line 11) before the 10%-of-AGI floor (line 17); a joint return is one individual for the $100 rule (165(h)(4)(B)). Only the owner of the property claims the loss, so a tax unit dependent's loss belongs on the dependent's own return, as their income already does. - casualty_loss_deduction (federal, TY2015-2017 and reforms): non-dependent losses less gov.irs.deductions.itemized.casualty.per_casualty_reduction (from #9888), then the floor. - ny_casualty_loss_deduction: IT-196 line 20 uses the 2017 federal rules, so the same computation on federal AGI, ungated by the federal suspension. - al_casualty_loss_deduction: Ala. Code 40-18-15(a)(6) adopts 165(h) as in effect from time to time (40-18-1.1), and Schedule A line 19a takes Form 4684 after its disaster limit, so the deduction follows the federal suspension flag; the floor uses Alabama AGI, not below zero. - hi_casualty_loss_deduction: Hawaii's own $100 (HRS 235-2.4(l)(1)) on Hawaii AGI, and from 2026 Act 35, SLH 2026, makes 165(h)(5) operative. Adds YAML cases that fail on main and a Hypothesis/grid property test of the four copies against a closed-form reference. Co-Authored-By: Claude Opus 5.5 --- .../fix-casualty-loss-165h-reduction.fixed.md | 1 + .../non_disaster_losses_allowed.yaml | 19 ++ .../casualty_loss/per_casualty_reduction.yaml | 18 ++ .../itemizing/casualty_loss_deduction.yaml | 171 +++++++++++++ .../itemized/al_casualty_loss_deduction.yaml | 140 ++++++++++- .../itemized/hi_casualty_loss_deduction.yaml | 150 ++++++++++- .../itemized/ny_casualty_loss_deduction.yaml | 145 +++++++++++ .../itemized/ny_pre_tcja_deductions.yaml | 12 +- ...test_casualty_loss_deduction_properties.py | 235 ++++++++++++++++++ .../itemizing/casualty_loss_deduction.py | 22 +- .../itemized/al_casualty_loss_deduction.py | 47 +++- .../itemized/hi_casualty_loss_deduction.py | 56 +++-- .../pre_tcja/ny_casualty_loss_deduction.py | 37 ++- 13 files changed, 1012 insertions(+), 41 deletions(-) create mode 100644 changelog.d/fix-casualty-loss-165h-reduction.fixed.md create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/non_disaster_losses_allowed.yaml create mode 100644 policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/per_casualty_reduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_casualty_loss_deduction.yaml create mode 100644 policyengine_us/tests/unit/test_casualty_loss_deduction_properties.py diff --git a/changelog.d/fix-casualty-loss-165h-reduction.fixed.md b/changelog.d/fix-casualty-loss-165h-reduction.fixed.md new file mode 100644 index 00000000000..10d9637e1b5 --- /dev/null +++ b/changelog.d/fix-casualty-loss-165h-reduction.fixed.md @@ -0,0 +1 @@ +Apply the Form 4684 $100 per-casualty reduction (26 U.S.C. 165(h)(1)) before the 10% floor, and leave tax unit dependents' losses off the filer's return, in the federal, New York, Alabama and Hawaii casualty loss deductions. Limit Alabama's deduction to declared-disaster losses from 2018, as federal law does, and Hawaii's from 2026 (Act 35, SLH 2026). diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/non_disaster_losses_allowed.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/non_disaster_losses_allowed.yaml new file mode 100644 index 00000000000..30ba00f91da --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/non_disaster_losses_allowed.yaml @@ -0,0 +1,19 @@ +description: Hawaii allows personal casualty and theft losses that are not attributable to a declared disaster, if this is true. +values: + 2018-01-01: true + 2026-01-01: false +metadata: + unit: bool + period: year + label: Hawaii non-disaster casualty losses allowed + reference: + - title: HRS § 235-2.4(l)(3) (unofficial compilation as of December 31, 2025) + href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=12 + - title: Act 35, SLH 2026 (HB 2329 CD1), striking HRS § 235-2.4(l)(3) + href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=23 + - title: Act 35, SLH 2026 (HB 2329 CD1), effective date + href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=34 + - title: Hawaii Department of Taxation Announcement No. 2026-06 + href: https://files.hawaii.gov/tax/news/announce/ann26-06.pdf#page=2 + - title: 2025 Hawaii Form N-11 Instructions, Casualty and Theft Losses + href: https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=18 diff --git a/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/per_casualty_reduction.yaml b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/per_casualty_reduction.yaml new file mode 100644 index 00000000000..f7a5c2dc1f9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/hi/tax/income/deductions/itemized/casualty_loss/per_casualty_reduction.yaml @@ -0,0 +1,18 @@ +description: Hawaii reduces each casualty or theft loss of personal-use property by this amount before applying the income floor. +values: + 2018-01-01: 100 +metadata: + unit: currency-USD + period: year + label: Hawaii casualty loss reduction per casualty + reference: + - title: HRS § 235-2.4(l)(1) (unofficial compilation as of December 31, 2025) + href: https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=12 + - title: Act 35, SLH 2026 (HB 2329 CD1), HRS § 235-2.4(k)(1) + href: https://data.capitol.hawaii.gov/sessions/session2026/bills/HB2329_CD1_.pdf#page=22 + - title: 2022 Hawaii Form N-11 Instructions, Casualty and Theft Losses + href: https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=18 + - title: 2025 Hawaii Form N-11 Instructions, Casualty and Theft Losses + href: https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=19 + - title: 2017 Form 4684, line 11 + href: https://www.irs.gov/pub/irs-prior/f4684--2017.pdf#page=1 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.yaml new file mode 100644 index 00000000000..472fc8349f5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.yaml @@ -0,0 +1,171 @@ +# 26 U.S.C. 165(h)(1), as in effect for these years: a personal casualty loss is +# allowed "only to the extent that the amount of the loss to such individual +# arising from each casualty, or from each theft, exceeds $500 ($100 for taxable +# years beginning after December 31, 2009)". P.L. 119-108 (2026) keeps $100 for +# losses other than qualified disaster-related losses, which the model has no +# input for. 26 U.S.C. 165(h)(2)(A): the net loss is allowed only to the extent +# it exceeds 10 percent of adjusted gross income. 165(h)(4)(B): "a husband and +# wife making a joint return for the taxable year shall be treated as 1 +# individual." +# 2017 Form 4684: line 11 "Enter $100 ($500 if qualified disaster loss rules +# apply; see instructions)", line 12 "Subtract line 11 from line 10. If zero or +# less; enter -0-", line 17 "Enter 10% of your adjusted gross income", line 18 +# "Subtract line 17 from line 16. If zero or less, enter -0-". +# IRS Publication 547 (2017, PDF p. 10), $100 Rule: "If you and your spouse file +# a joint return, you are treated as one individual in applying the $100 rule." +# "If you and your spouse have a casualty or theft loss and you file separate +# returns, each of you must reduce your loss by $100." +# The model records one loss amount per person, not separate casualty events, +# so a return's losses are treated as one casualty. A dependent's loss belongs +# on the dependent's own return, as the dependent's income does. + +- name: Case 1, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2017 + input: + casualty_loss: 100 + output: + # max(100 - 100, 0) - 10% x 0 + casualty_loss_deduction: 0 + +- name: Case 2, a loss of $101 leaves $1 after the $100 reduction. + period: 2017 + input: + casualty_loss: 101 + output: + # max(101 - 100, 0) - 10% x 0 + casualty_loss_deduction: 1 + +- name: Case 3, a loss under $100 is not deductible. + period: 2017 + input: + casualty_loss: 80 + output: + # max(80 - 100, 0) = 0 + casualty_loss_deduction: 0 + +- name: Case 4, the 10 percent floor applies after the $100 reduction. + period: 2017 + input: + employment_income: 50_000 + casualty_loss: 10_000 + output: + # (10,000 - 100) - 10% x 50,000 = 9,900 - 5,000 + casualty_loss_deduction: 4_900 + +- name: Case 5, a loss $100 above the floor nets to zero. + period: 2017 + input: + employment_income: 50_000 + casualty_loss: 5_100 + output: + # (5,100 - 100) - 10% x 50,000 = 5,000 - 5,000 + casualty_loss_deduction: 0 + +- name: Case 6, a loss $101 above the floor leaves $1. + period: 2017 + input: + employment_income: 50_000 + casualty_loss: 5_101 + output: + # (5,101 - 100) - 10% x 50,000 = 5,001 - 5,000 + casualty_loss_deduction: 1 + +- name: Case 7, a joint return reduces the spouses' pooled losses by $100 once. + period: 2017 + input: + people: + person1: + age: 40 + employment_income: 30_000 + casualty_loss: 6_000 + person2: + age: 40 + employment_income: 20_000 + casualty_loss: 4_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + output: + filing_status: JOINT + # (6,000 + 4,000 - 100) - 10% x 50,000 = 9,900 - 5,000 + casualty_loss_deduction: 4_900 + +- name: Case 8, a dependent's loss is left to the dependent's own return. + period: 2017 + input: + people: + person1: + age: 40 + employment_income: 50_000 + casualty_loss: 10_000 + person2: + age: 10 + casualty_loss: 3_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + output: + is_tax_unit_dependent: [false, true] + # The child's 3,000 is not the parent's loss. + # (10,000 - 100) - 10% x 50,000 = 9,900 - 5,000 + casualty_loss_deduction: 4_900 + +- name: Case 9, a working dependent's loss stays off the parent's return. + period: 2017 + input: + people: + person1: + age: 45 + employment_income: 60_000 + casualty_loss: 8_000 + person2: + age: 17 + employment_income: 4_000 + casualty_loss: 9_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + output: + is_tax_unit_dependent: [false, true] + # The parent's AGI leaves out the dependent's wages, and the parent's loss + # leaves out the dependent's loss. + # (8,000 - 100) - 10% x 60,000 = 7,900 - 6,000 + adjusted_gross_income: 60_000 + casualty_loss_deduction: 1_900 + +- name: Case 10, the deduction is suspended from 2018. + period: 2018 + input: + employment_income: 50_000 + casualty_loss: 10_000 + output: + casualty_loss_deduction: 0 + +- name: Case 11, a reform that restores the deduction applies the $100 reduction. + period: 2025 + input: + gov.irs.deductions.itemized.casualty.active: true + employment_income: 50_000 + casualty_loss: 10_000 + output: + # (10,000 - 100) - 10% x 50,000 = 9,900 - 5,000 + casualty_loss_deduction: 4_900 + +- name: Case 12, a separate return reduces its own loss by $100. + period: 2017 + input: + employment_income: 20_000 + casualty_loss: 5_000 + filing_status: SEPARATE + output: + # (5,000 - 100) - 10% x 20,000 = 4,900 - 2,000 + casualty_loss_deduction: 2_900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml index da91a20db54..b02c2bfbe4e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml @@ -1,17 +1,151 @@ +# Ala. Code 40-18-15(a)(6) allows casualty and theft losses "as determined in +# accordance with subsections (c)(3) and (h) of 26 U.S.C. § 165", and 40-18-1.1 +# defines "26 U.S.C." as the Internal Revenue Code "as in effect from time to +# time". The 2025 Form 40 instructions (PDF p. 20): "A casualty or theft loss +# is determined in the same manner as on your federal return" except that "The +# loss on personal property must be reduced by 10% of the Alabama adjusted +# gross income"; Schedule A line 19a takes the loss from federal Form 4684, after +# its $100 reduction (line 11) and, from 2018, its limit to losses attributable +# to declared disasters (26 U.S.C. 165(h)(5)). The Department of Revenue's +# OBBBA Executive Summary (PDF p. 8) lists 165(h)(5) as "Tied to Federal: Yes" +# through 40-18-15(a)(6). The model has no input that +# distinguishes disaster losses; like the federal deduction, it allows no +# personal casualty loss from 2018 unless +# gov.irs.deductions.itemized.casualty.active is switched on. +# A joint return is one individual for the $100 rule (26 U.S.C. 165(h)(4)(B)). +# The model records one loss amount per person, so a return's losses are +# treated as one casualty. + - name: Reduction of casualty loss - period: 2021 + period: 2017 input: state_code: AL casualty_loss: 2_000 al_agi: 1_000 output: - al_casualty_loss_deduction: 1_900 + # (2,000 - 100) - 10% x 1,000 = 1,900 - 100 + al_casualty_loss_deduction: 1_800 - name: Capped at 0 - period: 2021 + period: 2017 input: state_code: AL casualty_loss: 1_000 al_agi: 11_000 + output: + # (1,000 - 100) - 10% x 11,000 = 900 - 1,100, below zero + al_casualty_loss_deduction: 0 + +- name: Case 3, a loss not attributable to a declared disaster is not deductible from 2018. + period: 2021 + input: + state_code: AL + casualty_loss: 2_000 + al_agi: 1_000 + output: + al_casualty_loss_deduction: 0 + +- name: Case 4, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2017 + input: + state_code: AL + casualty_loss: 100 + al_agi: 0 + output: + # max(100 - 100, 0) - 10% x 0 + al_casualty_loss_deduction: 0 + +- name: Case 5, a loss of $101 leaves $1 after the $100 reduction. + period: 2017 + input: + state_code: AL + casualty_loss: 101 + al_agi: 0 + output: + al_casualty_loss_deduction: 1 + +- name: Case 6, a loss under $100 is not deductible. + period: 2017 + input: + state_code: AL + casualty_loss: 60 + al_agi: 0 output: al_casualty_loss_deduction: 0 + +- name: Case 7, the 10 percent floor on Alabama AGI applies after the $100 reduction. + period: 2017 + input: + state_code: AL + casualty_loss: 5_000 + al_agi: 20_000 + output: + # (5,000 - 100) - 10% x 20,000 = 4,900 - 2,000 + al_casualty_loss_deduction: 2_900 + +- name: Case 8, a joint return reduces the spouses' pooled losses by $100 once. + period: 2017 + input: + people: + person1: + age: 40 + casualty_loss: 1_500 + person2: + age: 40 + casualty_loss: 1_500 + tax_units: + tax_unit: + members: [person1, person2] + al_agi: 10_000 + households: + household: + members: [person1, person2] + state_code: AL + output: + # (1,500 + 1,500 - 100) - 10% x 10,000 = 2,900 - 1,000 + al_casualty_loss_deduction: 1_900 + +- name: Case 9, a dependent's loss is left to the dependent's own return. + period: 2017 + input: + people: + person1: + age: 40 + casualty_loss: 3_000 + person2: + age: 10 + casualty_loss: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + al_agi: 10_000 + households: + household: + members: [person1, person2] + state_code: AL + output: + is_tax_unit_dependent: [false, true] + # The child's 2,000 is not the parent's loss. + # (3,000 - 100) - 10% x 10,000 = 2,900 - 1,000 + al_casualty_loss_deduction: 1_900 + +- name: Case 10, a negative Alabama AGI gives no floor. + period: 2017 + input: + state_code: AL + casualty_loss: 300 + al_agi: -1_000 + output: + # The loss after the $100 reduction is the most that can be deducted. + al_casualty_loss_deduction: 200 + +- name: Case 11, a reform that restores the federal deduction flows to Alabama. + period: 2025 + input: + gov.irs.deductions.itemized.casualty.active: true + state_code: AL + casualty_loss: 5_000 + al_agi: 20_000 + output: + # (5,000 - 100) - 10% x 20,000 = 4,900 - 2,000 + al_casualty_loss_deduction: 2_900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.yaml index 0d28ce06199..5d1c126b001 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.yaml @@ -1,3 +1,16 @@ +# 2022 Form N-11 instructions, Casualty and Theft Losses (PDF p. 18): a loss is +# deductible only to the extent "The amount of EACH separate casualty or theft +# loss is more than $100, and ... The total amount of ALL losses during the +# year (reduced by the $100 limit) is more than 10% of your adjusted gross +# income." Worksheet A-5 (PDF p. 32): line 19 is the 2017 federal Form 4684, +# line 16 (after the $100 on line 11); line 21 is 10% of Hawaii AGI, "If zero +# or less, enter zero"; line 22 is line 19 minus line 21, not below zero. +# HRS 235-2.4(l)(1): "a $100 limitation per casualty". A joint return is one +# individual for the $100 rule (26 U.S.C. 165(h)(4)(B)). The model records one +# loss amount per person, so a return's losses are treated as one casualty. +# Act 35, SLH 2026, limits personal casualty losses to declared disasters from +# 2026; the model has no input that distinguishes them. + - name: base test period: 2022 input: @@ -15,7 +28,9 @@ members: [person1, person2] state_code: HI output: - hi_casualty_loss_deduction: 100 + filing_status: JOINT + # (100 + 100 - 100) - 10% x 1,000 = 100 - 100 + hi_casualty_loss_deduction: 0 - name: larger AGI period: 2022 @@ -34,8 +49,10 @@ members: [person1, person2] state_code: HI output: + filing_status: JOINT + # (100 + 100 - 100) - 10% x 2,000 = 100 - 200, below zero hi_casualty_loss_deduction: 0 - + - name: larger loss period: 2022 input: @@ -53,7 +70,9 @@ members: [person1, person2] state_code: HI output: - hi_casualty_loss_deduction: 200 + filing_status: JOINT + # (200 + 100 - 100) - 10% x 1,000 = 200 - 100 + hi_casualty_loss_deduction: 100 - name: AGI exceed loss period: 2022 @@ -72,4 +91,129 @@ members: [person1, person2] state_code: HI output: + filing_status: JOINT + # (100 + 100 - 100) - 10% x 3,000 = 100 - 300, below zero + hi_casualty_loss_deduction: 0 + +- name: Case 5, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2025 + input: + casualty_loss: 100 + hi_agi: 0 + state_code: HI + output: + # max(100 - 100, 0) - 10% x 0 + hi_casualty_loss_deduction: 0 + +- name: Case 6, a loss of $101 leaves $1 after the $100 reduction. + period: 2025 + input: + casualty_loss: 101 + hi_agi: 0 + state_code: HI + output: + # max(101 - 100, 0) - 10% x 0 + hi_casualty_loss_deduction: 1 + +- name: Case 7, a loss under $100 is not deductible. + period: 2025 + input: + casualty_loss: 60 + hi_agi: 0 + state_code: HI + output: + hi_casualty_loss_deduction: 0 + +- name: Case 8, the 10 percent floor applies after the $100 reduction. + period: 2025 + input: + casualty_loss: 5_000 + hi_agi: 20_000 + state_code: HI + output: + # Worksheet A-5: line 19 = 5,000 - 100 = 4,900; line 21 = 10% x 20,000 + # = 2,000; line 22 = 4,900 - 2,000. + hi_casualty_loss_deduction: 2_900 + # Hawaii allows the loss though the federal deduction is suspended. + casualty_loss_deduction: 0 + +- name: Case 9, a joint return reduces the spouses' pooled losses by $100 once. + period: 2025 + input: + people: + person1: + age: 40 + casualty_loss: 600 + person2: + age: 40 + casualty_loss: 400 + tax_units: + tax_unit: + members: [person1, person2] + hi_agi: 3_000 + households: + household: + members: [person1, person2] + state_code: HI + output: + # (600 + 400 - 100) - 10% x 3,000 = 900 - 300 + hi_casualty_loss_deduction: 600 + +- name: Case 10, a dependent's loss is left to the dependent's own return. + period: 2025 + input: + people: + person1: + age: 40 + casualty_loss: 1_000 + person2: + age: 10 + casualty_loss: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + hi_agi: 5_000 + households: + household: + members: [person1, person2] + state_code: HI + output: + is_tax_unit_dependent: [false, true] + # The child's 2,000 is not the parent's loss. + # (1,000 - 100) - 10% x 5,000 = 900 - 500 + hi_casualty_loss_deduction: 400 + +- name: Case 11, a negative Hawaii AGI gives no floor. + period: 2025 + input: + casualty_loss: 300 + hi_agi: -1_000 + state_code: HI + output: + # Line 21: 10% x -1,000 is less than zero, so zero. 300 - 100 - 0 + hi_casualty_loss_deduction: 200 + +- name: Case 12, from 2026 Hawaii allows only declared-disaster losses. + period: 2026 + input: + casualty_loss: 5_000 + hi_agi: 20_000 + state_code: HI + output: + # Act 35, SLH 2026, makes 26 U.S.C. 165(h)(5) operative for Hawaii. + hi_casualty_loss_deduction: 0 + +- name: Case 13, a reform that restores the federal deduction does not reach Hawaii. + period: 2026 + input: + gov.irs.deductions.itemized.casualty.active: true + casualty_loss: 5_000 + hi_agi: 20_000 + adjusted_gross_income: 20_000 + state_code: HI + output: + # Hawaii conforms to the Code as of December 31, 2025 and keeps the + # 165(h)(5) limit from 2026; the federal deduction follows the reform. + # (5,000 - 100) - 10% x 20,000 = 4,900 - 2,000 + casualty_loss_deduction: 2_900 hi_casualty_loss_deduction: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_casualty_loss_deduction.yaml new file mode 100644 index 00000000000..d0e452a5448 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/taxable_income/deductions/itemized/ny_casualty_loss_deduction.yaml @@ -0,0 +1,145 @@ +# 2025 Form IT-196 instructions, line 20 (PDF p. 3): "Your New York itemized +# deduction for casualty and theft losses is calculated using the federal rules +# that applied to tax year 2017." A loss is deductible only to the extent "the +# amount of each separate casualty or theft loss is more than $100, and ... the +# total amount of all losses during the year (reduced by the $100 limit in +# number 1 above) is more than 10% of the amount on Form IT-201, line 19". +# Casualty and theft worksheet, Section A (PDF p. 4): line 11 "Limitation per +# casualty" 100; line 17 "Enter 10% of your federal AGI from Form IT-201, line +# 19"; line 18 "Subtract line 17 from line 16 ... If the amount is zero or +# less, enter 0." +# IRS Publication 547 (2017, PDF p. 10): "If you and your spouse file a joint +# return, you are treated as one individual in applying the $100 rule." +# The model records one loss amount per person, not separate casualty events, +# so a return's losses are treated as one casualty. A dependent's loss belongs +# on the dependent's own return, as the dependent's income does. + +- name: Case 1, the $100 reduction applies before the 10 percent floor. + period: 2025 + input: + state_code: NY + employment_income: 100_000 + casualty_loss: 15_000 + output: + # Worksheet: line 12 = 15,000 - 100 = 14,900; line 17 = 10% x 100,000 + # = 10,000; line 18 = 14,900 - 10,000. + ny_casualty_loss_deduction: 4_900 + # New York allows the loss though the federal deduction is suspended. + casualty_loss_deduction: 0 + +- name: Case 2, a loss $100 above the floor nets to zero. + period: 2025 + input: + state_code: NY + employment_income: 100_000 + casualty_loss: 10_100 + output: + # (10,100 - 100) - 10% x 100,000 = 10,000 - 10,000 + ny_casualty_loss_deduction: 0 + +- name: Case 3, a loss $101 above the floor leaves $1. + period: 2025 + input: + state_code: NY + employment_income: 100_000 + casualty_loss: 10_101 + output: + # (10,101 - 100) - 10% x 100,000 = 10,001 - 10,000 + ny_casualty_loss_deduction: 1 + +- name: Case 4, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2025 + input: + state_code: NY + casualty_loss: 100 + output: + ny_casualty_loss_deduction: 0 + +- name: Case 5, a loss under $100 is not deductible. + period: 2025 + input: + state_code: NY + casualty_loss: 90 + output: + ny_casualty_loss_deduction: 0 + +- name: Case 6, Publication 547 Example 1 with $80,000 of AGI. + period: 2025 + input: + state_code: NY + employment_income: 80_000 + casualty_loss: 14_800 + output: + # IRS Publication 547 (2017, PDF p. 10), Example 1: loss after + # reimbursement 14,800; subtract $100 = 14,700; subtract 10% of 80,000 AGI + # = 6,700. + ny_casualty_loss_deduction: 6_700 + +- name: Case 7, a joint return reduces the spouses' pooled losses by $100 once. + period: 2025 + input: + people: + person1: + age: 40 + employment_income: 60_000 + casualty_loss: 10_000 + person2: + age: 40 + employment_income: 40_000 + casualty_loss: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + output: + filing_status: JOINT + # (10,000 + 5,000 - 100) - 10% x 100,000 = 14,900 - 10,000 + ny_casualty_loss_deduction: 4_900 + +- name: Case 8, a dependent's loss is left to the dependent's own return. + period: 2025 + input: + people: + person1: + age: 40 + employment_income: 100_000 + casualty_loss: 15_000 + person2: + age: 10 + casualty_loss: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + output: + is_tax_unit_dependent: [false, true] + # The child's 5,000 is not the parent's loss. + # (15,000 - 100) - 10% x 100,000 = 14,900 - 10,000 + ny_casualty_loss_deduction: 4_900 + +- name: Case 9, a loss only the dependent had gives the parent nothing. + period: 2025 + input: + people: + person1: + age: 40 + employment_income: 100_000 + person2: + age: 10 + casualty_loss: 15_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: NY + output: + is_tax_unit_dependent: [false, true] + ny_casualty_loss_deduction: 0 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 502aab68f83..38c4e4f88cc 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 @@ -59,10 +59,10 @@ employment_income: 100_000 casualty_loss: 15_000 output: - # Casualty loss: 15,000 + # Casualty loss: 15,000, less $100 per casualty = 14,900 # AGI floor (10%): 100,000 * 0.10 = 10,000 - # Deduction: 15,000 - 10,000 = 5,000 - ny_casualty_loss_deduction: 5_000 + # Deduction: 14,900 - 10,000 = 4,900 + ny_casualty_loss_deduction: 4_900 - name: NY itemized deductions max includes all pre-TCJA components period: 2024 @@ -81,6 +81,6 @@ # Medical: 10,000 - (100,000 * 0.075) = 2,500 # NY SALT: 12,000 real estate only (sales tax 3,000 added back per s.615(c)(1)) # NY Misc: 4,000 - (100,000 * 0.02) = 2,000 - # NY Casualty: 15,000 - (100,000 * 0.10) = 5,000 - # Total: 5,000 + 2,500 + 12,000 + 2,000 + 5,000 = 26,500 - ny_itemized_deductions_max: 26_500 + # NY Casualty: (15,000 - 100) - (100,000 * 0.10) = 4,900 + # Total: 5,000 + 2,500 + 12,000 + 2,000 + 4,900 = 26,400 + ny_itemized_deductions_max: 26_400 diff --git a/policyengine_us/tests/unit/test_casualty_loss_deduction_properties.py b/policyengine_us/tests/unit/test_casualty_loss_deduction_properties.py new file mode 100644 index 00000000000..a60f2fc5814 --- /dev/null +++ b/policyengine_us/tests/unit/test_casualty_loss_deduction_properties.py @@ -0,0 +1,235 @@ +"""Properties of PolicyEngine's copies of the Form 4684 casualty loss computation. + +26 U.S.C. 165(h)(1) reduces each personal casualty loss by $100 (Form 4684 +line 11), and 165(h)(2)(A) allows the rest only above 10% of adjusted gross +income (line 17). A joint return is one individual for the $100 rule +(165(h)(4)(B)), and a dependent's loss belongs on the dependent's own return. +The model records one loss amount per person and no event count, so a return's +losses are treated as one casualty. Four variables copy the computation: + +- the federal `casualty_loss_deduction`, on federal AGI, suspended from 2018 + (gov.irs.deductions.itemized.casualty.active); +- New York's `ny_casualty_loss_deduction`, on federal AGI, under the 2017 + federal rules in every year (Form IT-196 line 20); +- Alabama's `al_casualty_loss_deduction`, on Alabama AGI, following 165(h) as in + effect from time to time, so suspended with the federal deduction; +- Hawaii's `hi_casualty_loss_deduction`, on Hawaii AGI (floored at zero), with + Hawaii's own $100, allowed for non-disaster losses through 2025 (Act 35, + SLH 2026, adopts 165(h)(5) from 2026). + +Hypothesis draws batches of tax units (single or joint, with up to two +dependents who have their own losses), each placed in New York, Alabama and +Hawaii, and gives every copy the same AGI. Each batch runs as one vectorized +baseline simulation. For every tax unit and year: + +1. Each copy equals a closed-form reference on the head's and spouse's losses: + gate x max(max(loss - 100, 0) - 10% x max(AGI, 0), 0). Dependents' losses + never enter it. +2. Bounds: 0 <= deduction <= max(loss - 100, 0). +3. A state copy is zero outside its state. +4. Differential: where their gates are on, the four copies agree with each + other, since they share the computation and the AGI. + +A deterministic grid adds the boundary inputs ($0, $99, $100, $101, the floor +plus $100) and checks monotonicity: each copy rises with the loss and falls +with AGI, and is zero for a loss of $100 or less. The YAML tests cover a reform +that lifts the federal suspension, which Alabama follows and Hawaii does not. +""" + +import itertools + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +REDUCTION = 100 # Form 4684 line 11; HRS 235-2.4(l)(1) +FLOOR = 0.1 # Form 4684 line 17 +TOLERANCE = 0.01 +STATES = ["NY", "AL", "HI"] +YEARS = [2017, 2025, 2026] +# Whether each copy allows a non-disaster loss, by year, in baseline. Hawaii's +# variables start in 2018, when its itemized deductions do. +GATES = { + "casualty_loss_deduction": {2017: 1, 2025: 0, 2026: 0}, + "ny_casualty_loss_deduction": {2017: 1, 2025: 1, 2026: 1}, + "al_casualty_loss_deduction": {2017: 1, 2025: 0, 2026: 0}, + "hi_casualty_loss_deduction": {2025: 1, 2026: 0}, +} +STATE_OF = { + "ny_casualty_loss_deduction": "NY", + "al_casualty_loss_deduction": "AL", + "hi_casualty_loss_deduction": "HI", +} + + +def reference(loss, agi): + reduced = np.maximum(loss - REDUCTION, 0) + return np.maximum(reduced - FLOOR * np.maximum(agi, 0), 0) + + +def every_year(value): + return {year: value for year in YEARS} + + +def build_situation(units): + """Place each unit in each state, with the same AGI for every copy.""" + people, tax_units, households = {}, {}, {} + rows = [] # (filer loss, AGI, state) for each tax unit, in order + for state in STATES: + for i, unit in enumerate(units): + name = f"{state}{i}" + members = [f"{name}_head"] + people[members[0]] = { + "age": every_year(40), + "casualty_loss": every_year(unit["head_loss"]), + } + filer_loss = unit["head_loss"] + if unit["joint"]: + members.append(f"{name}_spouse") + people[members[-1]] = { + "age": every_year(40), + "casualty_loss": every_year(unit["spouse_loss"]), + } + filer_loss += unit["spouse_loss"] + for d, dependent_loss in enumerate(unit["dependent_losses"]): + members.append(f"{name}_dep{d}") + people[members[-1]] = { + "age": every_year(10), + "is_tax_unit_dependent": every_year(True), + "casualty_loss": every_year(dependent_loss), + } + agi = unit["agi"] + tax_units[name] = { + "members": members, + "adjusted_gross_income": every_year(agi), + "al_agi": every_year(agi), + "hi_agi": every_year(agi), + } + households[name] = {"members": members, "state_code": every_year(state)} + rows.append((filer_loss, agi, state)) + situation = {"people": people, "tax_units": tax_units, "households": households} + loss, agi, state = (np.array(column) for column in zip(*rows)) + return situation, loss.astype(float), agi.astype(float), state + + +def check_reference_and_bounds(sim, loss, agi, state, gates): + for variable, by_year in gates.items(): + for year, gate in by_year.items(): + value = sim.calculate(variable, year) + expected = gate * reference(loss, agi) + in_state = state == STATE_OF.get(variable, state) + expected = np.where(in_state, expected, 0) + np.testing.assert_allclose( + value, expected, atol=TOLERANCE, err_msg=f"{variable} {year}" + ) + assert np.all(value >= 0) + assert np.all(value <= np.maximum(loss - REDUCTION, 0) + TOLERANCE) + + +def check_copies_agree(sim, state, gates): + for year in YEARS: + on = [v for v, by_year in gates.items() if by_year.get(year) == 1] + federal = sim.calculate("casualty_loss_deduction", year) + for variable in on: + value = sim.calculate(variable, year) + if variable in STATE_OF: + in_state = state == STATE_OF[variable] + if "casualty_loss_deduction" in on: + np.testing.assert_allclose( + value[in_state], federal[in_state], atol=TOLERANCE + ) + for other in on: + if other in STATE_OF and other != variable: + # Compare the same drawn units across states. + other_state = state == STATE_OF[other] + np.testing.assert_allclose( + value[in_state], + sim.calculate(other, year)[other_state], + atol=TOLERANCE, + ) + + +money = st.one_of( + st.sampled_from([0, 50, 99, 100, 101, 150]), + st.integers(0, 60_000), +).map(float) +agis = st.one_of( + st.sampled_from([0, 1_000, 50_000]), + st.integers(-20_000, 300_000), +).map(float) +tax_unit_strategy = st.fixed_dictionaries( + { + "joint": st.booleans(), + "head_loss": money, + "spouse_loss": money, + "dependent_losses": st.lists(money, max_size=2), + "agi": agis, + } +) +SETTINGS = dict( + max_examples=20, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +@settings(**SETTINGS) +@given(st.lists(tax_unit_strategy, min_size=4, max_size=15)) +def test_copies_match_the_form_4684_reference(units): + situation, loss, agi, state = build_situation(units) + sim = Simulation(situation=situation) + check_reference_and_bounds(sim, loss, agi, state, GATES) + check_copies_agree(sim, state, GATES) + + +GRID_LOSSES = [0, 50, 99, 100, 101, 150, 5_000, 10_100, 10_101, 30_000] +GRID_AGIS = [-10_000, 0, 1_000, 50_000, 100_000, 300_000] +GRID_DEPENDENT_LOSSES = [0, 20_000] + + +def grid_situation(): + units = [ + { + "joint": False, + "head_loss": float(loss), + "spouse_loss": 0.0, + "dependent_losses": [float(dependent_loss)], + "agi": float(agi), + } + for loss, agi, dependent_loss in itertools.product( + GRID_LOSSES, GRID_AGIS, GRID_DEPENDENT_LOSSES + ) + ] + return build_situation(units) + + +@pytest.fixture(scope="module") +def grid(): + situation, loss, agi, state = grid_situation() + return Simulation(situation=situation), loss, agi, state + + +@pytest.mark.parametrize( + "variable, year", + [(v, y) for v, by_year in GATES.items() for y, gate in by_year.items() if gate], +) +def test_grid_monotone_and_boundaries(grid, variable, year): + sim, loss, agi, state = grid + rows = state == STATE_OF.get(variable, "NY") + value = sim.calculate(variable, year)[rows] + np.testing.assert_allclose(value, reference(loss[rows], agi[rows]), atol=TOLERANCE) + shape = (len(GRID_LOSSES), len(GRID_AGIS), len(GRID_DEPENDENT_LOSSES)) + table = value.reshape(shape) + # A dependent's loss never changes the filer's deduction. + np.testing.assert_allclose(table[..., 0], table[..., 1], atol=TOLERANCE) + assert np.all(np.diff(table, axis=0) >= -TOLERANCE), "falls as the loss rises" + assert np.all(np.diff(table, axis=1) <= TOLERANCE), "rises as AGI rises" + # A loss of $100 or less is absorbed by the $100 reduction. + small = np.array(GRID_LOSSES) <= REDUCTION + assert np.all(table[small] == 0) + # Guard against a vacuous pass. + assert table.max() > 0 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.py index 095a3feceb7..329bd3b95fb 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/itemizing/casualty_loss_deduction.py @@ -7,10 +7,28 @@ class casualty_loss_deduction(Variable): label = "Casualty loss deduction" unit = USD definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/uscode/text/26/165#h", + # 2017 Form 4684, lines 10-18 + "https://www.irs.gov/pub/irs-prior/f4684--2017.pdf#page=1", + # IRS Publication 547 (2017), $100 Rule + "https://www.irs.gov/pub/irs-prior/p547--2017.pdf#page=10", + ) def formula(tax_unit, period, parameters): - loss = add(tax_unit, period, ["casualty_loss"]) p = parameters(period).gov.irs.deductions.itemized.casualty + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return, as the dependent's income + # does: adjusted gross income leaves it out. + loss = tax_unit_non_dep_add(tax_unit, period, ["casualty_loss"]) + # 26 U.S.C. 165(h)(1) reduces each casualty by $100 (Form 4684 + # line 11). A joint return is one individual for the $100 rule + # (165(h)(4)(B)). The model records one loss amount per person, not + # separate casualty events, so the return's losses are treated as one + # casualty and the reduction applies once. + reduced_loss = max_(loss - p.per_casualty_reduction, 0) + # 165(h)(2)(A): the rest is allowed above 10% of adjusted gross income + # (Form 4684 line 17). positive_agi = tax_unit("positive_agi", period) - amount_over_floor = max_(0, loss - positive_agi * p.floor) + amount_over_floor = max_(reduced_loss - positive_agi * p.floor, 0) return p.active * amount_over_floor diff --git a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.py index 595e55871f9..313414d75d7 100644 --- a/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.py @@ -7,10 +7,49 @@ class al_casualty_loss_deduction(Variable): label = "Alabama casualty loss deduction" unit = USD definition_period = YEAR + reference = ( + # Ala. Code § 40-18-15(a)(6) + "https://alison.legislature.state.al.us/code-of-alabama?section=40-18-15", + # Ala. Code § 40-18-1.1(b): "26 U.S.C." means the Internal Revenue + # Code as in effect from time to time + "https://alison.legislature.state.al.us/code-of-alabama?section=40-18-1.1", + # 2025 Form 40 instructions, Schedule A lines 19a-c, and Schedule A + "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40bk.pdf#page=20", + "https://www.revenue.alabama.gov/wp-content/uploads/2026/01/25f40schabdc_blk.pdf#page=1", + # 2021 Form 40 instructions, Schedule A lines 19a-c + "https://www.revenue.alabama.gov/wp-content/uploads/2022/06/21f40bk.pdf#page=20", + # 2025 Form 4684, lines 10-16 + "https://www.irs.gov/pub/irs-prior/f4684--2025.pdf#page=1", + # Alabama Department of Revenue, OBBBA Executive Summary: 26 U.S.C. + # 165(h)(5) is "Tied to Federal: Yes" through 40-18-15(a)(6) + "https://www.revenue.alabama.gov/wp-content/uploads/2025/11/OBBBA-Executive-Summary_FinalwAppendixA_10.31.25.pdf#page=8", + ) defined_for = StateCode.AL def formula(tax_unit, period, parameters): - loss = add(tax_unit, period, ["casualty_loss"]) - deduction = parameters(period).gov.irs.deductions.itemized.casualty - al_agi = tax_unit("al_agi", period) - return max_(0, loss - al_agi * deduction.floor) + p = parameters(period).gov.irs.deductions.itemized.casualty + # Alabama allows the loss as determined under 26 U.S.C. 165(c)(3) and + # (h), as in effect from time to time. Schedule A line 19a takes the + # loss from federal Form 4684, after each casualty is reduced by $100 + # (line 11) and, from 2018, after the limit to losses attributable to + # declared disasters (165(h)(5), line 14). The Department of Revenue + # ties Alabama to 165(h)(5) explicitly for tax years after 2025; for + # 2018-2025 the limit follows from the rolling conformity and the form + # flow. The model has no input that distinguishes disaster losses, so, + # like the federal deduction, no personal casualty loss is deductible + # while gov.irs.deductions.itemized.casualty.active is false (from + # 2018). + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return. + loss = tax_unit_non_dep_add(tax_unit, period, ["casualty_loss"]) + # A joint return is one individual for the $100 rule. The model records + # one loss amount per person, not separate casualty events, so the + # return's losses are treated as one casualty and the reduction + # applies once. + reduced_loss = max_(loss - p.per_casualty_reduction, 0) + # Line 19b: 10% of Alabama adjusted gross income (Form 40, line 10). + # The form is silent on a negative AGI; like the federal deduction + # (positive_agi) and Hawaii's Worksheet A-5, the floor is not allowed + # to go below zero, so the deduction never exceeds the reduced loss. + al_agi = max_(tax_unit("al_agi", period), 0) + return p.active * max_(reduced_loss - al_agi * p.floor, 0) diff --git a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.py index 29ec2090d92..eb9a110cde9 100644 --- a/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/states/hi/tax/income/deductions/itemized/hi_casualty_loss_deduction.py @@ -6,22 +6,50 @@ class hi_casualty_loss_deduction(Variable): entity = TaxUnit label = "Hawaii casualty loss deduction" unit = USD - documentation = ( - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=18\n" - "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=32" # total itemized deduction worksheet - ) definition_period = YEAR + reference = ( + # HRS § 235-2.4(l) + "https://files.hawaii.gov/tax/legal/hrs/hrs_235.pdf#page=12", + # 2022 Form N-11 instructions, Casualty and Theft Losses and + # Worksheet A-5 + "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=18", + "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=32", + # 2025 Form N-11 instructions, Casualty and Theft Losses and + # Worksheet A-5 + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=19", + "https://files.hawaii.gov/tax/forms/2025/n11ins.pdf#page=34", + # 2017 Form 4684, lines 10-16 + "https://www.irs.gov/pub/irs-prior/f4684--2017.pdf#page=1", + # Department of Taxation Announcement 2026-06: Act 35, SLH 2026, + # conforms to 26 U.S.C. 165(h)(5) from 2026 + "https://files.hawaii.gov/tax/news/announce/ann26-06.pdf#page=2", + ) defined_for = StateCode.HI def formula(tax_unit, period, parameters): - p_deductions = parameters(period).gov.irs.deductions - # 5. casualty_loss_deduction: worksheet A-5 - # Hawaii did not - # (1) limit the personal casualty loss deduction for property losses (not used in connection with a trade or business - # or transaction entered into for profit) - # (2) waive the requirement that casualty losses from qualified disasters exceed 10% of adjusted gross income - # to be deductible, and that such losses must exceed $500. - casualty_loss = add(tax_unit, period, ["casualty_loss"]) + p = parameters(period).gov.states.hi.tax.income.deductions.itemized + p_irs = parameters(period).gov.irs.deductions.itemized.casualty + # Worksheet A-5 line 19 takes the loss from the 2017 federal Form 4684, + # line 16: each casualty less Hawaii's $100 (HRS 235-2.4(l)(1)). + # Hawaii did not adopt the federal qualified-disaster rules, which + # replace the $100 with $500 and waive the 10% floor. + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return. + loss = tax_unit_non_dep_add(tax_unit, period, ["casualty_loss"]) + # A joint return is one individual for the $100 rule. The model records + # one loss amount per person, not separate casualty events, so the + # return's losses are treated as one casualty and the reduction + # applies once. + reduced_loss = max_(loss - p.casualty_loss.per_casualty_reduction, 0) + # Lines 20-22: less 10% of Hawaii adjusted gross income, floored at + # zero. hi_agi = tax_unit("hi_agi", period) - casualty_agi_amount = max_(0, p_deductions.itemized.casualty.floor * hi_agi) - return max_(0, casualty_loss - casualty_agi_amount) + floor = max_(0, p_irs.floor * hi_agi) + deduction = max_(reduced_loss - floor, 0) + # Through 2025 Hawaii did not limit personal casualty losses to + # declared disasters (HRS 235-2.4(l)(3) made 26 U.S.C. 165(h)(5) + # inoperative). Act 35, SLH 2026, adopts that limit from 2026. The + # model has no input that distinguishes disaster losses, so, like the + # federal deduction, no personal casualty loss is deductible once the + # limit applies. + return p.casualty_loss.non_disaster_losses_allowed * deduction diff --git a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py index b179c6f0fab..815ff506ac5 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/taxable_income/deductions/itemized/pre_tcja/ny_casualty_loss_deduction.py @@ -9,21 +9,40 @@ class ny_casualty_loss_deduction(Variable): definition_period = YEAR reference = ( "https://www.nysenate.gov/legislation/laws/TAX/615", - "https://www.tax.ny.gov/pit/file/itemized-deductions.htm", + # 2025 Form IT-196 instructions, line 20 and the Casualty and theft + # worksheet, Section A + "https://www.tax.ny.gov/pdf/2025/inc/it196i_2025.pdf#page=3", + "https://www.tax.ny.gov/pdf/2025/inc/it196i_2025.pdf#page=4", + "https://www.tax.ny.gov/pdf/2024/inc/it196i_2024.pdf#page=3", + "https://www.tax.ny.gov/pdf/2024/inc/it196i_2024.pdf#page=4", + # IRS Publication 547 (2017), $100 Rule + "https://www.irs.gov/pub/irs-prior/p547--2017.pdf#page=10", ) defined_for = StateCode.NY documentation = """ NY Tax Law § 615 requires itemized deductions to be computed using - pre-TCJA federal rules. Unlike federal rules post-2017, NY still - allows casualty and theft loss deductions not limited to federally - declared disasters. The 10% AGI floor still applies. + pre-TCJA federal rules. Form IT-196 line 20 figures the casualty and theft + loss "using the federal rules that applied to tax year 2017", so, unlike the + federal deduction after 2017, it is not limited to federally declared + disasters. Each casualty is reduced by $100, and the rest is deductible + above 10% of federal adjusted gross income. """ def formula(tax_unit, period, parameters): - # NY uses pre-TCJA rules: casualty losses are still deductible - # (not limited to federally declared disasters like federal post-2017) - loss = add(tax_unit, period, ["casualty_loss"]) p = parameters(period).gov.irs.deductions.itemized.casualty + # NY uses pre-TCJA rules: casualty losses are still deductible (not + # limited to federally declared disasters like federal post-2017), so + # the federal suspension flag does not apply. + # Only the owner of the property claims the loss. A dependent's loss + # belongs on the dependent's own return, as the dependent's income + # does: federal adjusted gross income leaves it out. + loss = tax_unit_non_dep_add(tax_unit, period, ["casualty_loss"]) + # Worksheet line 11 reduces each casualty by $100. A joint return is + # one individual for the $100 rule. The model records one loss amount + # per person, not separate casualty events, so the return's losses are + # treated as one casualty and the reduction applies once. + reduced_loss = max_(loss - p.per_casualty_reduction, 0) + # Worksheet line 17: 10% of federal adjusted gross income (Form IT-201, + # line 19). positive_agi = tax_unit("positive_agi", period) - # Apply the 10% AGI floor (pre-TCJA rule) - return max_(0, loss - positive_agi * p.floor) + return max_(reduced_loss - positive_agi * p.floor, 0) From 43eed2a0f82e6fba624ca3fbbebbf41180e79e46 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 6 Oct 2026 13:30:54 -0400 Subject: [PATCH 8/8] Test that a negative Alabama AGI with no loss gives no casualty deduction Main's max_(0, loss - al_agi * floor) gave every Alabama filer with negative AGI a deduction of 10% of |AGI| even with no loss; the zero clamp on the floor removes it. Co-Authored-By: Claude Opus 5.5 --- .../itemized/al_casualty_loss_deduction.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml index b02c2bfbe4e..515a980aed1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/al/tax/income/deductions/itemized/al_casualty_loss_deduction.yaml @@ -149,3 +149,13 @@ output: # (5,000 - 100) - 10% x 20,000 = 4,900 - 2,000 al_casualty_loss_deduction: 2_900 + +- name: Case 12, a negative Alabama AGI with no loss gives no deduction. + period: 2017 + input: + state_code: AL + casualty_loss: 0 + al_agi: -10_000 + output: + # Nothing to deduct: max(0 - 100, 0) = 0, whatever the AGI. + al_casualty_loss_deduction: 0