From 6d6d6331505cfce9ee79695f223b0976e39aa080 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 09:13:44 -0400 Subject: [PATCH 1/3] Allow non-disaster casualty losses in California and Minnesota itemized deductions California and Minnesota read the federal casualty_loss_deduction, which is zero from 2018 under 26 U.S.C. 165(h)(5). Neither state follows that limit: Cal. Rev. & Tax. Code 17204(b) and Schedule CA (540) line 15; Minn. Stat. 290.0122, subd. 8, and Schedule M1CAT. Add ca_casualty_loss_deduction and mn_casualty_loss_deduction: the head's and spouse's loss, less $100, less 10% of federal adjusted gross income. Use them in place of the federal deduction in ca_itemized_deductions_pre_limitation, California's limitation exclusion list, mn_itemized_deductions, mn_amt_taxable_income (Schedule M1MT line 12) and the Walz HF1938 reform. The $100 comes from gov.irs.deductions.itemized.casualty.per_casualty_reduction, the same file PolicyEngine/policyengine-us#9888 adds, copied unchanged so the two merge cleanly in either order. Co-Authored-By: Claude Opus 5.5 --- changelog.d/fix-ca-mn-casualty-loss.fixed.md | 1 + .../casualty/per_casualty_reduction.yaml | 20 ++ .../itemized/limit/excluded_deductions.yaml | 11 +- .../reforms/states/mn/walz/mn_walz_hf1938.py | 13 +- .../itemized/ca_casualty_loss_deduction.yaml | 198 +++++++++++++++++ .../itemized/ca_itemized_deductions.yaml | 23 ++ ...ca_itemized_deductions_pre_limitation.yaml | 28 +++ .../mn/tax/income/mn_amt_taxable_income.yaml | 26 +++ .../income/mn_casualty_loss_deduction.yaml | 175 +++++++++++++++ .../mn/tax/income/mn_itemized_deductions.yaml | 61 ++++- .../states/mn/walz/mn_walz_hf1938.yaml | 19 ++ ...a_mn_casualty_loss_deduction_properties.py | 208 ++++++++++++++++++ .../itemized/ca_casualty_loss_deduction.py | 56 +++++ .../ca_itemized_deductions_pre_limitation.py | 9 +- .../tax/income/amt/mn_amt_taxable_income.py | 6 +- .../deductions/mn_casualty_loss_deduction.py | 53 +++++ .../deductions/mn_itemized_deductions.py | 22 +- 17 files changed, 921 insertions(+), 8 deletions(-) create mode 100644 changelog.d/fix-ca-mn-casualty-loss.fixed.md create mode 100644 policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml create mode 100644 policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.py create mode 100644 policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py diff --git a/changelog.d/fix-ca-mn-casualty-loss.fixed.md b/changelog.d/fix-ca-mn-casualty-loss.fixed.md new file mode 100644 index 00000000000..50af78741a3 --- /dev/null +++ b/changelog.d/fix-ca-mn-casualty-loss.fixed.md @@ -0,0 +1 @@ +Allow non-disaster personal casualty and theft losses in California and Minnesota itemized deductions (and Minnesota alternative minimum taxable income), which do not follow the federal limit to declared disasters (Cal. Rev. & Tax. Code 17204(b); Minn. Stat. 290.0122, subd. 8): add `ca_casualty_loss_deduction` and `mn_casualty_loss_deduction`, each the loss less $100 and 10% of federal adjusted gross income, in place of the federal deduction. 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..b56d5b69462 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/deductions/itemized/casualty/per_casualty_reduction.yaml @@ -0,0 +1,20 @@ +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: + 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: 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 + href: https://www.irs.gov/pub/irs-prior/p547--2022.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml b/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml index d3d5fd934b8..f0fc3bc9320 100644 --- a/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml +++ b/policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml @@ -2,7 +2,9 @@ description: Itemized deductions excluded from CA high-AGI limitation. values: 2021-01-01: - medical_expense_deduction - - casualty_loss_deduction + # California's own casualty and theft loss (Schedule CA line 15), which + # replaces the federal one in the pre-limitation total. + - ca_casualty_loss_deduction - non_mortgage_interest metadata: @@ -18,5 +20,12 @@ metadata: href: https://www.ftb.ca.gov/forms/2024/2024-540-ca-instructions.html - title: 2025 Form 540 Schedule CA instructions href: https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html + - title: 2024 Schedule CA (540) instructions, line 29 itemized deductions worksheet, line 2 + href: https://www.ftb.ca.gov/forms/2024/2024-540-booklet.pdf#page=68 - title: Cal. Rev. & Tax. Code § 17077 href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17077 + # R&TC 17077 adopts 26 U.S.C. 68, whose subsection (c)(3) (before P.L. 119-21) + # leaves casualty and theft losses described in 165(c)(2) or (3) out of the + # deductions the limitation reduces. + - title: 26 U.S.C. § 68 (amendment notes for subsection (c)(3)) + href: https://www.law.cornell.edu/uscode/text/26/68 diff --git a/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py b/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py index 78aed52cd19..0a2f4903d04 100644 --- a/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py +++ b/policyengine_us/reforms/states/mn/walz/mn_walz_hf1938.py @@ -112,13 +112,22 @@ def formula(tax_unit, period, parameters): # ... calculate pre-limitation itemized deductions itm_deds_less_salt = tax_unit("itemized_deductions_less_salt", period) capped_property_taxes = tax_unit("capped_property_taxes", period) - mn_itm_deds = itm_deds_less_salt + capped_property_taxes + # Schedule M1SA line 19 takes Minnesota's own casualty and theft + # loss (Schedule M1CAT) in place of the federal one. + federal_casualty_loss = tax_unit("casualty_loss_deduction", period) + mn_casualty_loss = tax_unit("mn_casualty_loss_deduction", period) + mn_itm_deds = ( + itm_deds_less_salt + - federal_casualty_loss + + mn_casualty_loss + + capped_property_taxes + ) # ... calculate itemized deductions offset p = parameters(period).gov.states.mn.tax.income.deductions.itemized exempt_deds = add( tax_unit, period, - ["medical_expense_deduction", "casualty_loss_deduction"], + ["medical_expense_deduction", "mn_casualty_loss_deduction"], ) net_deds = max_(0, mn_itm_deds - exempt_deds) net_deds_offset = p.reduction.alternate.rate * net_deds diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.yaml new file mode 100644 index 00000000000..ff9f57920e5 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.yaml @@ -0,0 +1,198 @@ +# 2024 Schedule CA (540) instructions, line 15 (Form 540 booklet, PDF p. 66): +# federal law allows the personal casualty and theft loss deduction only for a +# federally declared disaster; "California law does not conform." Filers +# "complete another federal Form 4684 ... using California amounts". +# Cal. Rev. & Tax. Code 17204(b) (as amended by SB 711, 2025): 26 U.S.C. +# 165(h)(5) "shall not apply". +# FTB Publication 1034 (PDF p. 4): "subtract $100 and then 10% of your federal +# adjusted gross income." +# 2024 Form 4684: line 11 enters $100; line 17 enters 10% of adjusted gross +# income from Form 1040 line 11; line 18 is line 16 less line 17, not below 0. +# 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 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: 2024 + input: + state_code: CA + employment_income: 100_000 + casualty_loss: 15_000 + output: + # Form 4684: line 12 = 15,000 - 100 = 14,900; line 17 = 10% x 100,000 + # = 10,000; line 18 = 14,900 - 10,000. + ca_casualty_loss_deduction: 4_900 + # California 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: 2024 + input: + state_code: CA + employment_income: 100_000 + casualty_loss: 10_100 + output: + # (10,100 - 100) - 10% x 100,000 = 10,000 - 10,000 + ca_casualty_loss_deduction: 0 + +- name: Case 3, a loss $101 above the floor leaves $1. + period: 2024 + input: + state_code: CA + employment_income: 100_000 + casualty_loss: 10_101 + output: + # (10,101 - 100) - 10% x 100,000 = 10,001 - 10,000 + ca_casualty_loss_deduction: 1 + +- name: Case 4, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2024 + input: + state_code: CA + casualty_loss: 100 + output: + ca_casualty_loss_deduction: 0 + +- name: Case 5, with no income a loss is deductible above the $100. + period: 2024 + input: + state_code: CA + casualty_loss: 101 + output: + # (101 - 100) - 10% x 0 + ca_casualty_loss_deduction: 1 + +- name: Case 6, Publication 547 Example 1 with $80,000 of AGI. + period: 2024 + input: + state_code: CA + 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. + ca_casualty_loss_deduction: 6_700 + +- name: Case 7, the floor uses federal AGI, not California AGI. + period: 2024 + input: + state_code: CA + adjusted_gross_income: 100_000 + ca_agi: 60_000 + casualty_loss: 15_000 + output: + # (15,000 - 100) - 10% x 100,000 federal AGI = 4,900. On California AGI it + # would be 14,900 - 6,000 = 8,900. + ca_casualty_loss_deduction: 4_900 + +- name: Case 8, a negative federal AGI gives no floor, not a larger loss. + period: 2024 + input: + state_code: CA + adjusted_gross_income: -20_000 + casualty_loss: 15_000 + output: + # The 10% floor is not allowed below zero: 15,000 - 100. + ca_casualty_loss_deduction: 14_900 + +- name: Case 9, a joint return reduces the spouses' pooled losses by $100 once. + period: 2024 + 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: CA + output: + filing_status: JOINT + # (10,000 + 5,000 - 100) - 10% x 100,000 = 14,900 - 10,000 + ca_casualty_loss_deduction: 4_900 + +- name: Case 10, a dependent's loss is left to the dependent's own return. + period: 2024 + 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: CA + 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 + ca_casualty_loss_deduction: 4_900 + +- name: Case 11, a loss only the dependent had gives the parent nothing. + period: 2024 + 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: CA + output: + is_tax_unit_dependent: [false, true] + ca_casualty_loss_deduction: 0 + +- name: Case 12, California keeps allowing the loss under its 2025 conformity date. + period: 2026 + input: + state_code: CA + employment_income: 100_000 + casualty_loss: 15_000 + output: + # R&TC 17204(b) turns 165(h)(5) off for taxable years from 2025. + ca_casualty_loss_deduction: 4_900 + casualty_loss_deduction: 0 + +- name: Case 13, the deduction is zero outside California. + period: 2024 + input: + state_code: NY + employment_income: 100_000 + casualty_loss: 15_000 + output: + ca_casualty_loss_deduction: 0 + +- name: Case 14, lifting the federal suspension does not change California's deduction. + period: 2024 + input: + gov.irs.deductions.itemized.casualty.active: true + state_code: CA + employment_income: 100_000 + casualty_loss: 15_000 + output: + # California's deduction does not read the federal switch. + ca_casualty_loss_deduction: 4_900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml index 13ce42b7d1c..a6847243c65 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions.yaml @@ -51,3 +51,26 @@ ca_itemized_deductions_pre_limitation: 10_000 output: ca_itemized_deductions: 7_000 # charity subject to limitation + +# 2024 Schedule CA (540) instructions, line 29 worksheet (Form 540 booklet, +# PDF p. 68): line 2 takes medical expenses, investment interest, casualty and +# theft losses and gambling losses out before the 80%/6% limitation. Cal. Rev. +# & Tax. Code 17077 adopts 26 U.S.C. 68, which leaves 165(c)(3) losses out. +- name: California casualty loss is not subject to the high-AGI limitation + absolute_error_margin: 0.01 + period: 2024 + input: + state_code: CA + filing_status: SINGLE + adjusted_gross_income: 244_857 + 50_000 + ca_charitable_deduction: 1_000 + casualty_loss: 40_000 + output: + # Form 4684: (40,000 - 100) - 10% x 294,857 = 39,900 - 29,485.70 + ca_casualty_loss_deduction: 10_414.30 + ca_itemized_deductions_pre_limitation: 11_414.30 + # Worksheet: line 3 = 11,414.30 - 10,414.30 = 1,000; line 4 = 80% x 1,000 + # = 800; line 8 = 6% x 50,000 = 3,000; line 9 = 800. + # 11,414.30 - 800. Were the loss limited too, line 4 would be 9,131.44, + # line 9 would be 3,000 and the result 8,414.30. + ca_itemized_deductions: 10_614.30 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml index 524500a4c91..eb522ef4498 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yaml @@ -385,3 +385,31 @@ # Also includes #10032's IRA 219(g) phase-out, since this is a two-cause # CA + IRA cell. state_income_tax_before_refundable_credits: 4441.45 + +# 2024 Schedule CA (540) instructions, line 15 (Form 540 booklet, PDF p. 66): +# "California law does not conform" to the federal limit of personal casualty +# losses to declared disasters; filers complete a second Form 4684 with +# California amounts. +- name: California allows a non-disaster casualty loss the federal return does not. + period: 2024 + input: + state_code: CA + employment_income: 100_000 + casualty_loss: 15_000 + output: + casualty_loss_deduction: 0 + # Form 4684: (15,000 - 100) - 10% x 100,000 = 4,900 + ca_itemized_deductions_pre_limitation: 4_900 + +- name: California counts the casualty loss once when the federal suspension is lifted. + period: 2024 + input: + gov.irs.deductions.itemized.casualty.active: true + state_code: CA + employment_income: 100_000 + casualty_loss: 15_000 + output: + ca_casualty_loss_deduction: 4_900 + # The federal deduction, now positive, is replaced by California's 4,900, + # not added to it. + ca_itemized_deductions_pre_limitation: 4_900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml index 3a65303cfad..4310e3a2c90 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_amt_taxable_income.yaml @@ -19,3 +19,29 @@ state_code: MN output: mn_amt_taxable_income: 200_000 - 250 - 3_000 + +# 2024 Schedule M1MT, line 12 (PDF p. 1): "Casualty and theft losses (from line +# 19 of Schedule M1SA)", Minnesota's own amount from Schedule M1CAT. +- name: MN AMT taxable income subtracts the Minnesota casualty loss + period: 2024 + input: + adjusted_gross_income: 200_000 + mn_itemizing: true + casualty_loss: 40_000 + state_code: MN + output: + # The federal deduction is suspended. + casualty_loss_deduction: 0 + # M1CAT: (40,000 - 100) - 10% x 200,000 = 19,900 + mn_casualty_loss_deduction: 19_900 + mn_amt_taxable_income: 200_000 - 19_900 + +- name: MN AMT taxable income leaves the casualty loss out for a non-itemizer + period: 2024 + input: + adjusted_gross_income: 200_000 + mn_itemizing: false + casualty_loss: 40_000 + state_code: MN + output: + mn_amt_taxable_income: 200_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml new file mode 100644 index 00000000000..8218e8addc0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml @@ -0,0 +1,175 @@ +# Minn. Stat. 290.0122, subd. 8: losses under 26 U.S.C. 165(c)(3), "including +# the provisions of section 165(h) ... but disregarding paragraph (h)(5)". +# 2024 Schedule M1SA instructions, line 19 (PDF p. 7): a loss is deductible +# only to the extent each loss "is more than $100" and the year's losses +# "reduced by $100" are "more than 10% of the amount on line 1 of Form M1". +# 2024 Schedule M1CAT (PDF p. 1): line 11 "Enter $100"; line 18 "Enter the +# amount from line 1 of Form M1" (federal adjusted gross income); line 19 +# "Multiply line 18 by 10%"; line 20 subtracts line 19 from line 17, not below +# 0, and goes to M1SA line 19. The 2018 M1CAT has the same lines. +# 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 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: 2024 + input: + state_code: MN + employment_income: 100_000 + casualty_loss: 15_000 + output: + # M1CAT: line 12 = 15,000 - 100 = 14,900; line 19 = 10% x 100,000 + # = 10,000; line 20 = 14,900 - 10,000. + mn_casualty_loss_deduction: 4_900 + # Minnesota 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: 2024 + input: + state_code: MN + employment_income: 100_000 + casualty_loss: 10_100 + output: + # (10,100 - 100) - 10% x 100,000 = 10,000 - 10,000 + mn_casualty_loss_deduction: 0 + +- name: Case 3, a loss $101 above the floor leaves $1. + period: 2024 + input: + state_code: MN + employment_income: 100_000 + casualty_loss: 10_101 + output: + # (10,101 - 100) - 10% x 100,000 = 10,001 - 10,000 + mn_casualty_loss_deduction: 1 + +- name: Case 4, a loss of exactly $100 is fully absorbed by the $100 reduction. + period: 2024 + input: + state_code: MN + casualty_loss: 100 + output: + mn_casualty_loss_deduction: 0 + +- name: Case 5, Publication 547 Example 1 with $80,000 of AGI. + period: 2024 + input: + state_code: MN + employment_income: 80_000 + casualty_loss: 14_800 + output: + # IRS Publication 547 (2017, PDF p. 10), Example 1: 14,800 - 100 = 14,700; + # less 10% of 80,000 = 6,700. + mn_casualty_loss_deduction: 6_700 + +- name: Case 6, a negative federal AGI gives no floor, not a larger loss. + period: 2024 + input: + state_code: MN + adjusted_gross_income: -20_000 + casualty_loss: 15_000 + output: + # M1CAT line 18 has no "enter 0" rule; the floor is held at zero so the + # deduction never exceeds the reduced loss: 15,000 - 100. + mn_casualty_loss_deduction: 14_900 + +- name: Case 7, a joint return reduces the spouses' pooled losses by $100 once. + period: 2024 + 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: MN + output: + filing_status: JOINT + # (10,000 + 5,000 - 100) - 10% x 100,000 = 14,900 - 10,000 + mn_casualty_loss_deduction: 4_900 + +- name: Case 8, a dependent's loss is left to the dependent's own return. + period: 2024 + 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: MN + output: + is_tax_unit_dependent: [false, true] + # (15,000 - 100) - 10% x 100,000 = 14,900 - 10,000 + mn_casualty_loss_deduction: 4_900 + +- name: Case 9, a loss only the dependent had gives the parent nothing. + period: 2024 + 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: MN + output: + is_tax_unit_dependent: [false, true] + mn_casualty_loss_deduction: 0 + +- name: Case 10, the 2018 Schedule M1CAT already allows a non-disaster loss. + period: 2018 + input: + state_code: MN + employment_income: 50_000 + casualty_loss: 8_000 + output: + # (8,000 - 100) - 10% x 50,000 = 7,900 - 5,000 + mn_casualty_loss_deduction: 2_900 + casualty_loss_deduction: 0 + +- name: Case 11, the deduction is zero outside Minnesota. + period: 2024 + input: + state_code: WI + employment_income: 100_000 + casualty_loss: 15_000 + output: + mn_casualty_loss_deduction: 0 + +- name: Case 12, lifting the federal suspension does not change Minnesota's deduction. + period: 2024 + input: + gov.irs.deductions.itemized.casualty.active: true + state_code: MN + employment_income: 100_000 + casualty_loss: 15_000 + output: + # Minnesota's deduction does not read the federal switch. + mn_casualty_loss_deduction: 4_900 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml index 632dba542f1..acb1fa03b73 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml @@ -39,7 +39,9 @@ adjusted_gross_income: 1_206_050 interest_deduction: 10_000 medical_expense_deduction: 400 - casualty_loss_deduction: 200 + # Minnesota's own casualty loss (Schedule M1SA line 19) is exempt from the + # reduction, as medical expenses are. + mn_casualty_loss_deduction: 200 state_code: MN output: mn_itemized_deductions: (10_000 + 400 + 200) - 0.80 * 10_000 @@ -90,3 +92,60 @@ state_code: MN output: mn_itemized_deductions: 10_000 + +# Schedule M1SA line 19 takes the casualty and theft loss from Schedule M1CAT: +# each casualty less $100, then less 10% of Form M1 line 1 (federal AGI). Minn. +# Stat. 290.0122, subd. 8, disregards the federal limit to declared disasters. +- name: 2024 MN itemized deductions include a non-disaster casualty loss + absolute_error_margin: 0.01 + period: 2024 + input: + filing_status: SINGLE + adjusted_gross_income: 100_000 + interest_deduction: 10_000 + casualty_loss: 15_000 + state_code: MN + output: + # The federal deduction is suspended. + casualty_loss_deduction: 0 + # M1CAT: (15,000 - 100) - 10% x 100,000 = 4,900 + mn_casualty_loss_deduction: 4_900 + # M1SA line 25: 10,000 interest + 4,900 casualty; AGI is under the + # $232,500 reduction threshold. + mn_itemized_deductions: 14_900 + +# M1SA line 26 Worksheet B, step 2, takes lines 4, 13 and 19 out before the +# 80% reduction (Minn. Stat. 290.0122, subd. 2(d)(3)). +- name: 2024 MN casualty loss is exempt from the high-income reduction + absolute_error_margin: 0.01 + period: 2024 + input: + filing_status: SINGLE + adjusted_gross_income: 2_000_000 + interest_deduction: 10_000 + casualty_loss: 250_000 + state_code: MN + output: + # M1CAT: (250,000 - 100) - 10% x 2,000,000 = 49,900 + mn_casualty_loss_deduction: 49_900 + # AGI is over $1,053,750, so the reduction is 80% of the deductions + # other than the casualty loss: 0.8 x 10,000 = 8,000. + # 10,000 + 49,900 - 8,000. Were the loss reduced too, the result would be + # 59,900 - 0.8 x 59,900 = 11,980. + mn_itemized_deductions: 51_900 + +- name: 2024 MN counts the casualty loss once when the federal suspension is lifted + absolute_error_margin: 0.01 + period: 2024 + input: + gov.irs.deductions.itemized.casualty.active: true + filing_status: SINGLE + adjusted_gross_income: 100_000 + interest_deduction: 10_000 + casualty_loss: 15_000 + state_code: MN + output: + mn_casualty_loss_deduction: 4_900 + # The federal deduction, now positive inside itemized_deductions_less_salt, + # is replaced by Minnesota's 4,900, not added to it. + mn_itemized_deductions: 14_900 diff --git a/policyengine_us/tests/policy/contrib/states/mn/walz/mn_walz_hf1938.yaml b/policyengine_us/tests/policy/contrib/states/mn/walz/mn_walz_hf1938.yaml index ba403f7eda6..aacb657d111 100644 --- a/policyengine_us/tests/policy/contrib/states/mn/walz/mn_walz_hf1938.yaml +++ b/policyengine_us/tests/policy/contrib/states/mn/walz/mn_walz_hf1938.yaml @@ -109,3 +109,22 @@ state_code: MN output: mn_itemized_deductions: 10_000 + +- name: Itemized deductions with reform take the Minnesota casualty loss and exempt it from the offset + absolute_error_margin: 0.5 + period: 2023 + reforms: policyengine_us.reforms.states.mn.walz.mn_walz_hf1938.mn_walz_hf1938 + input: + gov.contrib.states.mn.walz.hf1938.repeal: true + filing_status: SINGLE + adjusted_gross_income: 2_000_000 + interest_deduction: 10_000 + casualty_loss: 250_000 + state_code: MN + output: + # Schedule M1CAT: (250,000 - 100) - 10% x 2,000,000 = 49,900 + mn_casualty_loss_deduction: 49_900 + # Offset: the lesser of 80% of the deductions other than the casualty loss + # (0.8 x 10,000 = 8,000) and 3% of AGI over $220,650 (53,380.50). + # 10,000 + 49,900 - 8,000 + mn_itemized_deductions: 51_900 diff --git a/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py b/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py new file mode 100644 index 00000000000..592f35336ea --- /dev/null +++ b/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py @@ -0,0 +1,208 @@ +"""Properties of California's and Minnesota's casualty loss deductions. + +Both states allow a personal casualty or theft loss under 26 U.S.C. 165(h) +without the federal limit to declared disasters (Cal. Rev. & Tax. Code +17204(b), Schedule CA (540) line 15; Minn. Stat. 290.0122, subd. 8, Schedule +M1CAT). Each reduces the loss by $100 (Form 4684 line 11; M1CAT line 11) and +allows the rest above 10% of federal adjusted gross income (Form 4684 line 17 +and FTB Publication 1034; M1CAT lines 18-19). A joint return is one individual +for the $100 rule, 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. + +`ca_casualty_loss_deduction` and `mn_casualty_loss_deduction` are two copies +of that computation. Hypothesis draws batches of tax units (single or joint, +with up to two dependents who have their own losses), places each in +California and in Minnesota, and runs each batch 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: + max(max(loss - 100, 0) - 10% x max(AGI, 0), 0). Dependents' losses never + enter it, and neither does California AGI. +2. Bounds: 0 <= deduction <= max(loss - 100, 0). +3. Each copy is zero outside its state. +4. Differential: the two copies agree on the same drawn units. +5. The federal `casualty_loss_deduction` is zero in these years, so neither + state follows the federal suspension. + +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. +""" + +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; Schedule M1CAT line 11 +FLOOR = 0.1 # Form 4684 line 17; Schedule M1CAT line 19 +TOLERANCE = 0.01 +CA_AGI_OFFSET = 7_000 # makes California AGI differ from federal AGI +# 2018 is the first year of the federal suspension; 2026 follows California's +# move to a January 1, 2025 conformity date. +YEARS = [2018, 2026] +STATE_OF = { + "ca_casualty_loss_deduction": "CA", + "mn_casualty_loss_deduction": "MN", +} +STATES = list(STATE_OF.values()) + + +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 federal AGI.""" + 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), + # California's floor is on federal AGI (FTB Publication 1034), + # so a different California AGI must not move its copy. + "ca_agi": every_year(agi + CA_AGI_OFFSET), + } + 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): + for variable, own_state in STATE_OF.items(): + for year in YEARS: + value = sim.calculate(variable, year) + expected = np.where(state == own_state, reference(loss, agi), 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): + for year in YEARS: + california = sim.calculate("ca_casualty_loss_deduction", year) + minnesota = sim.calculate("mn_casualty_loss_deduction", year) + # The same drawn units, in the same order, sit in each state. + np.testing.assert_allclose( + california[state == "CA"], minnesota[state == "MN"], atol=TOLERANCE + ) + # Neither state follows the federal suspension. + assert np.all(sim.calculate("casualty_loss_deduction", year) == 0) + + +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) + check_copies_agree(sim, state) + + +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", list(itertools.product(STATE_OF, YEARS))) +def test_grid_monotone_and_boundaries(grid, variable, year): + sim, loss, agi, state = grid + rows = state == STATE_OF[variable] + 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/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.py new file mode 100644 index 00000000000..1e4ef8389cb --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_casualty_loss_deduction.py @@ -0,0 +1,56 @@ +from policyengine_us.model_api import * + + +class ca_casualty_loss_deduction(Variable): + value_type = float + entity = TaxUnit + label = "California casualty loss deduction" + unit = USD + definition_period = YEAR + reference = ( + # Cal. Rev. & Tax. Code § 17201: the IRC itemized deductions, + # including 26 U.S.C. 165, apply + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17201", + # Cal. Rev. & Tax. Code § 17204(b): 26 U.S.C. 165(h)(5) shall not apply + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17204", + # 2024 Schedule CA (540) instructions, line 15 + "https://www.ftb.ca.gov/forms/2024/2024-540-booklet.pdf#page=66", + # 2025 Schedule CA (540) instructions, line 15 + "https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html", + # FTB Publication 1034, How to Calculate a Disaster Loss - Individuals + # (2025 edition; the 2024 edition has the same text on the same page) + "https://www.ftb.ca.gov/forms/misc/1034.pdf#page=4", + "https://web.archive.org/web/20250529011540/https://www.ftb.ca.gov/forms/misc/1034.pdf#page=4", + # 2024 Form 4684, lines 10-18 + "https://www.irs.gov/pub/irs-prior/f4684--2024.pdf#page=1", + ) + defined_for = StateCode.CA + documentation = """ + California adopts 26 U.S.C. 165 (R&TC 17201) but not the federal limit of + personal casualty losses to declared disasters (R&TC 17204(b)). Schedule CA + (540) line 15 has filers complete a second federal Form 4684 with California + amounts, so a non-disaster personal casualty or theft loss stays + deductible: each casualty is reduced by $100, and the rest is deductible + above 10% of federal adjusted gross income. + """ + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.deductions.itemized.casualty + # California never applies the federal suspension: its IRC conformity + # date was January 1, 2015 through 2024, before 26 U.S.C. 165(h)(5) + # was enacted, and R&TC 17204(b) turns (h)(5) off from 2025. + # 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"]) + # Form 4684 line 11 reduces each casualty by $100. California states no + # amount of its own; it takes 165(h)(1) through R&TC 17201. 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) + # Form 4684 line 17 and FTB Publication 1034: 10% of federal adjusted + # gross income (Form 540 line 13), not California AGI. + positive_agi = tax_unit("positive_agi", period) + return max_(reduced_loss - positive_agi * p.floor, 0) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py index 80c39a22960..091b5150824 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.py @@ -12,12 +12,17 @@ class ca_itemized_deductions_pre_limitation(Variable): "https://www.ftb.ca.gov/forms/2022/2022-540-ca-instructions.html", "https://www.ftb.ca.gov/forms/2025/2025-540-ca-instructions.html", "https://www.ftb.ca.gov/about-ftb/data-reports-plans/summary-of-federal-income-tax-changes/index.html", + # 2024 Schedule CA (540) instructions, line 15 + "https://www.ftb.ca.gov/forms/2024/2024-540-booklet.pdf#page=66", ) defined_for = StateCode.CA def formula(tax_unit, period, parameters): # Exclude the replaced federal amounts before summation, so changes - # to federal charity/misc rules cannot cause cancellation errors. + # to federal charity/misc/casualty rules cannot cause cancellation + # errors. Schedule CA line 15 replaces the federal casualty loss, + # which is limited to declared disasters from 2018, with California's + # own. federal_components = parameters(period).gov.irs.deductions.itemized_deductions deductions = [ variable @@ -28,6 +33,7 @@ def formula(tax_unit, period, parameters): "charitable_deduction", "charitable_deduction_for_non_itemizers", "misc_deduction", + "casualty_loss_deduction", ) ] deductions += [ @@ -35,6 +41,7 @@ def formula(tax_unit, period, parameters): "real_estate_taxes", "ca_charitable_deduction", "ca_misc_deduction", + "ca_casualty_loss_deduction", ] # Federal investment interest is nested in interest_deduction. return add(tax_unit, period, deductions) - add( diff --git a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py index 50dc8cc7602..83736e79da5 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/amt/mn_amt_taxable_income.py @@ -10,6 +10,8 @@ class mn_amt_taxable_income(Variable): reference = ( "https://www.revenue.state.mn.us/sites/default/files/2023-02/m1mt_21.pdf", "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1mt_22.pdf", + # 2024 Schedule M1MT, line 12 + "https://www.revenue.state.mn.us/sites/default/files/2025-07/m1mt-24.pdf#page=1", ) defined_for = StateCode.MN @@ -19,7 +21,9 @@ def formula(tax_unit, period, parameters): SOME_DEDUCTIONS = [ "charitable_deduction", "medical_expense_deduction", - "casualty_loss_deduction", + # M1MT line 12 takes the casualty and theft loss from Schedule + # M1SA line 19, Minnesota's own amount. + "mn_casualty_loss_deduction", ] some_itm_deds = itemizing * add(tax_unit, period, SOME_DEDUCTIONS) AMT_SUBTRACTIONS = [ diff --git a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py new file mode 100644 index 00000000000..e7ac2512841 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py @@ -0,0 +1,53 @@ +from policyengine_us.model_api import * + + +class mn_casualty_loss_deduction(Variable): + value_type = float + entity = TaxUnit + label = "Minnesota casualty loss deduction" + unit = USD + definition_period = YEAR + reference = ( + # Minn. Stat. § 290.0122, subd. 8: losses under 26 U.S.C. 165(c)(3), + # including 165(h) but disregarding (h)(5) + "https://www.revisor.mn.gov/statutes/cite/290.0122#stat.290.0122.8", + # 2024 Schedule M1SA, line 19, and its instructions + # PDF pages 1, 7 + "https://www.revenue.state.mn.us/sites/default/files/2024-12/m1sa-24.pdf#page=1", + # 2024 Schedule M1CAT, lines 10-20 + "https://www.revenue.state.mn.us/sites/default/files/2024-12/m1cat-24.pdf#page=1", + # 2018 Schedule M1CAT, lines 10-20 + "https://www.revenue.state.mn.us/sites/default/files/2018-11/m1cat_18.pdf#page=1", + ) + defined_for = StateCode.MN + documentation = """ + Minnesota allows personal casualty and theft losses under 26 U.S.C. 165(h) + but disregards the federal limit to declared disasters (Minn. Stat. + 290.0122, subd. 8). Schedule M1CAT reduces each casualty by $100 and allows + the rest above 10% of Form M1 line 1 (federal adjusted gross income); the + result goes on Schedule M1SA line 19. + """ + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.irs.deductions.itemized.casualty + # For 2018 Minnesota computed tax under the Internal Revenue Code as + # amended through December 16, 2016 (Minn. Stat. 290.993), before + # 26 U.S.C. 165(h)(5) existed, and Schedule M1CAT has applied the same + # $100 and 10% computation in every year since, so the federal + # suspension never applies. + # 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"]) + # M1CAT line 11: "Enter $100". 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) + # M1CAT lines 18-20: subtract 10% of Form M1 line 1 (federal adjusted + # gross income). The form does not say to enter 0 for a negative + # AGI; like Form 4684 and the federal deduction, the floor is not + # allowed to go below zero, so the deduction never exceeds the + # reduced loss. + positive_agi = tax_unit("positive_agi", period) + return max_(reduced_loss - positive_agi * p.floor, 0) diff --git a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py index bbd8191185d..7fc89edb407 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py @@ -12,6 +12,11 @@ class mn_itemized_deductions(Variable): "https://www.revenue.state.mn.us/sites/default/files/2023-12/m1_inst_21.pdf", "https://www.revenue.state.mn.us/sites/default/files/2023-12/m1_22.pdf", "https://www.revenue.state.mn.us/sites/default/files/2024-02/m1-inst-22.pdf", + # 2024 Schedule M1SA, line 19, and the line 26 limitation worksheets + # PDF pages 1, 8 + "https://www.revenue.state.mn.us/sites/default/files/2024-12/m1sa-24.pdf#page=1", + # Minn. Stat. § 290.0122, subd. 2(d) and subd. 8 + "https://www.revisor.mn.gov/statutes/cite/290.0122", ) defined_for = StateCode.MN @@ -24,13 +29,26 @@ def formula(tax_unit, period, parameters): # ... calculate pre-limitation itemized deductions itm_deds_less_salt = tax_unit("itemized_deductions_less_salt", period) capped_property_taxes = tax_unit("capped_property_taxes", period) - mn_itm_deds = itm_deds_less_salt + capped_property_taxes + # Schedule M1SA line 19 takes Minnesota's own casualty and theft loss + # (Schedule M1CAT) in place of the federal one, which is limited to + # declared disasters from 2018. + federal_casualty_loss = tax_unit("casualty_loss_deduction", period) + mn_casualty_loss = tax_unit("mn_casualty_loss_deduction", period) + mn_itm_deds = ( + itm_deds_less_salt + - federal_casualty_loss + + mn_casualty_loss + + capped_property_taxes + ) # ... calculate itemized deductions offset p = parameters(period).gov.states.mn.tax.income.deductions.itemized + # Minn. Stat. 290.0122, subd. 2(d), and the M1SA line 26 worksheets + # (step 2: lines 4, 13 and 19) leave medical expenses and casualty + # and theft losses out of the reduction. exempt_deds = add( tax_unit, period, - ["medical_expense_deduction", "casualty_loss_deduction"], + ["medical_expense_deduction", "mn_casualty_loss_deduction"], ) net_deds = max_(0, mn_itm_deds - exempt_deds) filing_status = tax_unit("filing_status", period) From 36412e0aa8b46bd495a0e4a835dbc0a172a900ef Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 15:33:58 -0400 Subject: [PATCH 2/3] Qualify the Minnesota casualty loss documentation for 2018 and 2020 Review r1: the 2018 Schedule M1CAT line 18 takes Schedule M1NC line 38 from filers of that schedule, and the 2020 Schedule M1SA takes a net qualified disaster loss from federal Form 4684 line 15 ($500, no 10% floor), deductible without itemizing. The model has neither input, so say so in the variable, the YAML header and the property test. No formula or expected value changes. Co-Authored-By: Claude Opus 5.5 --- .../income/mn_casualty_loss_deduction.yaml | 9 +++++-- ...a_mn_casualty_loss_deduction_properties.py | 5 +++- .../deductions/mn_casualty_loss_deduction.py | 25 +++++++++++++------ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml index 8218e8addc0..029347f591f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml @@ -6,7 +6,11 @@ # 2024 Schedule M1CAT (PDF p. 1): line 11 "Enter $100"; line 18 "Enter the # amount from line 1 of Form M1" (federal adjusted gross income); line 19 # "Multiply line 18 by 10%"; line 20 subtracts line 19 from line 17, not below -# 0, and goes to M1SA line 19. The 2018 M1CAT has the same lines. +# 0, and goes to M1SA line 19. The 2018 M1CAT has the same lines, except that +# line 18 takes Schedule M1NC line 38 (federal adjustments) from filers of that +# schedule, which the model does not have. These cases are ordinary losses: +# the 2020 Schedule M1SA's net qualified disaster loss (federal Form 4684 line +# 15, $500 and no 10% floor) is not modeled. # 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 a # return's losses are treated as one casualty. A dependent's loss belongs on @@ -150,7 +154,8 @@ employment_income: 50_000 casualty_loss: 8_000 output: - # (8,000 - 100) - 10% x 50,000 = 7,900 - 5,000 + # For a filer with no Schedule M1NC: (8,000 - 100) - 10% x 50,000 + # = 7,900 - 5,000 mn_casualty_loss_deduction: 2_900 casualty_loss_deduction: 0 diff --git a/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py b/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py index 592f35336ea..96193c6eb9b 100644 --- a/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py +++ b/policyengine_us/tests/unit/test_ca_mn_casualty_loss_deduction_properties.py @@ -11,7 +11,10 @@ return's losses are treated as one casualty. `ca_casualty_loss_deduction` and `mn_casualty_loss_deduction` are two copies -of that computation. Hypothesis draws batches of tax units (single or joint, +of that computation for an ordinary loss. The model has no disaster input and +no Minnesota Schedule M1NC, so the properties below do not cover the 2020 +Schedule M1SA's net qualified disaster loss or the 2018 M1CAT's Schedule M1NC +line 38 base. Hypothesis draws batches of tax units (single or joint, with up to two dependents who have their own losses), places each in California and in Minnesota, and runs each batch as one vectorized baseline simulation. For every tax unit and year: diff --git a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py index e7ac2512841..e8d3b9bfdf1 100644 --- a/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py +++ b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_casualty_loss_deduction.py @@ -18,6 +18,9 @@ class mn_casualty_loss_deduction(Variable): "https://www.revenue.state.mn.us/sites/default/files/2024-12/m1cat-24.pdf#page=1", # 2018 Schedule M1CAT, lines 10-20 "https://www.revenue.state.mn.us/sites/default/files/2018-11/m1cat_18.pdf#page=1", + # 2020 Schedule M1SA instructions, line 19, qualified disaster loss + # PDF pages 6-7 + "https://www.revenue.state.mn.us/sites/default/files/2023-01/m1sa_20.pdf#page=6", ) defined_for = StateCode.MN documentation = """ @@ -25,16 +28,20 @@ class mn_casualty_loss_deduction(Variable): but disregards the federal limit to declared disasters (Minn. Stat. 290.0122, subd. 8). Schedule M1CAT reduces each casualty by $100 and allows the rest above 10% of Form M1 line 1 (federal adjusted gross income); the - result goes on Schedule M1SA line 19. + result goes on Schedule M1SA line 19. Two cases on the forms are not + modeled: for 2018, filers of Schedule M1NC (federal adjustments) use its + line 38 in place of Form M1 line 1; and the 2020 Schedule M1SA takes a + net qualified disaster loss from federal Form 4684 line 15 ($500 per + casualty, no 10% floor), which can be deducted without itemizing. """ def formula(tax_unit, period, parameters): p = parameters(period).gov.irs.deductions.itemized.casualty # For 2018 Minnesota computed tax under the Internal Revenue Code as # amended through December 16, 2016 (Minn. Stat. 290.993), before - # 26 U.S.C. 165(h)(5) existed, and Schedule M1CAT has applied the same - # $100 and 10% computation in every year since, so the federal - # suspension never applies. + # 26 U.S.C. 165(h)(5) existed, and Schedule M1CAT has applied the $100 + # and 10% to an ordinary (not qualified disaster) loss in every year + # since, so the federal suspension never applies. # 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. @@ -45,9 +52,11 @@ def formula(tax_unit, period, parameters): # treated as one casualty and the reduction applies once. reduced_loss = max_(loss - p.per_casualty_reduction, 0) # M1CAT lines 18-20: subtract 10% of Form M1 line 1 (federal adjusted - # gross income). The form does not say to enter 0 for a negative - # AGI; like Form 4684 and the federal deduction, the floor is not - # allowed to go below zero, so the deduction never exceeds the - # reduced loss. + # gross income). The 2018 form takes Schedule M1NC line 38 instead + # from filers of that schedule; the model has no Schedule M1NC, so + # 2018 also uses federal adjusted gross income. The form does not say + # to enter 0 for a negative AGI; like Form 4684 and the federal + # deduction, the floor is not allowed to go below zero, so the + # deduction never exceeds the reduced loss. positive_agi = tax_unit("positive_agi", period) return max_(reduced_loss - positive_agi * p.floor, 0) From ac929133fcfec1851ba8b2599a85bd1500d37b19 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 16:05:50 -0400 Subject: [PATCH 3/3] Say that the 2018 M1CAT sends its result to M1SA line 18 Review r2 (P3): the 2018 form has the same $100 reduction and 10% floor but a different destination line. Comment only. Co-Authored-By: Claude Opus 5.5 --- .../states/mn/tax/income/mn_casualty_loss_deduction.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml index 029347f591f..a051c1798fa 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_casualty_loss_deduction.yaml @@ -6,9 +6,10 @@ # 2024 Schedule M1CAT (PDF p. 1): line 11 "Enter $100"; line 18 "Enter the # amount from line 1 of Form M1" (federal adjusted gross income); line 19 # "Multiply line 18 by 10%"; line 20 subtracts line 19 from line 17, not below -# 0, and goes to M1SA line 19. The 2018 M1CAT has the same lines, except that -# line 18 takes Schedule M1NC line 38 (federal adjustments) from filers of that -# schedule, which the model does not have. These cases are ordinary losses: +# 0, and goes to M1SA line 19. The 2018 M1CAT has the same $100 reduction and +# 10% floor; its line 20 goes to M1SA line 18, and its line 18 takes Schedule +# M1NC line 38 (federal adjustments) from filers of that schedule, which the +# model does not have. These cases are ordinary losses: # the 2020 Schedule M1SA's net qualified disaster loss (federal Form 4684 line # 15, $500 and no 10% floor) is not modeled. # A joint return is one individual for the $100 rule (165(h)(4)(B)). The model