diff --git a/changelog.d/ca-mn-investment-interest.fixed.md b/changelog.d/ca-mn-investment-interest.fixed.md new file mode 100644 index 00000000000..133ac10349e --- /dev/null +++ b/changelog.d/ca-mn-investment-interest.fixed.md @@ -0,0 +1 @@ +California's high-income itemized limitation now leaves out the FTB 3526 investment interest deduction instead of the interest paid; Minnesota leaves investment interest out of its itemized deduction reduction, and its AMT subtracts investment interest and miscellaneous deductions. 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 f0fc3bc9320..1b4ee81991e 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 @@ -5,7 +5,11 @@ values: # 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 + # California's FTB 3526 investment interest, the amount that + # ca_itemized_deductions_pre_limitation holds in place of the federal one. + # 26 U.S.C. 68(c)(2), adopted by R&TC 17077, leaves out the deduction + # allowed for investment interest. + - ca_investment_interest_expense_deduction metadata: unit: list diff --git a/policyengine_us/tests/core/test_ca_mn_itemized_limitation_properties.py b/policyengine_us/tests/core/test_ca_mn_itemized_limitation_properties.py new file mode 100644 index 00000000000..370e3c1073e --- /dev/null +++ b/policyengine_us/tests/core/test_ca_mn_itemized_limitation_properties.py @@ -0,0 +1,94 @@ +"""Invariants of the California and Minnesota high-income itemized limitations. + +California (R&TC 17077, adopting 26 U.S.C. 68) and Minnesota (Minn. Stat. +290.0122, subd. 2(d)) reduce itemized deductions of high-income filers, but +leave medical expenses, investment interest and casualty and theft losses out +of the deductions the reduction is computed on. + +Every tax unit in a grid of states, filing statuses, incomes and deductions +shares one simulation per year. Each unit has twins that differ only in their +investment interest, and one that differs only in having no income. With the +deduction inputs set directly, income changes only the reduction, so the +no-income twin carries the pre-limitation total. For every unit: + +1. The deduction lies between 0 and its pre-limitation total. +2. The reduction (pre-limitation total less the deduction) does not change + with investment interest, since investment interest is never reduced. +""" + +from itertools import product + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEARS = (2024, 2026) +STATES = ("CA", "MN") +FILING_STATUSES = ("SINGLE", "JOINT") +AGIS = (0, 150_000, 300_000, 600_000, 2_000_000) +CHARITY = (0, 10_000) +MEDICAL = (0, 4_000) +# (investment interest paid, investment expenses). With expenses, California's +# FTB 3526 deduction is below the interest paid, so the federal and +# California amounts differ. +INVESTMENT = ((0, 0), (10_000, 0), (10_000, 4_000), (5_000, 20_000)) + +UNITS = list(product(STATES, FILING_STATUSES, AGIS, CHARITY, MEDICAL, INVESTMENT)) + + +def _simulation(year): + period = str(year) + people, tax_units, households = {}, {}, {} + for i, (state, status, agi, charity, medical, investment) in enumerate(UNITS): + interest, expenses = investment + person = f"p{i}" + people[person] = { + "age": {period: 50}, + "investment_interest_expense": {period: interest}, + "investment_expenses": {period: expenses}, + } + tax_units[f"t{i}"] = { + "members": [person], + "filing_status": {period: status}, + "adjusted_gross_income": {period: agi}, + "charitable_deduction": {period: charity}, + "ca_charitable_deduction": {period: charity}, + "medical_expense_deduction": {period: medical}, + } + households[f"h{i}"] = {"members": [person], "state_code": {period: state}} + return Simulation( + situation={"people": people, "tax_units": tax_units, "households": households} + ) + + +def _index(state, status, agi, charity, medical, investment): + return UNITS.index((state, status, agi, charity, medical, investment)) + + +@pytest.mark.parametrize("year", YEARS) +def test_itemized_limitation_invariants(year): + period = str(year) + simulation = _simulation(year) + deduction = { + "CA": simulation.calculate("ca_itemized_deductions", period), + "MN": simulation.calculate("mn_itemized_deductions", period), + } + ca_pre = simulation.calculate("ca_itemized_deductions_pre_limitation", period) + + for i, (state, status, agi, charity, medical, investment) in enumerate(UNITS): + label = f"{year} {UNITS[i]}" + pre = deduction[state][_index(state, status, 0, charity, medical, investment)] + if state == "CA": + assert ca_pre[i] == pytest.approx(pre, abs=0.01), label + value = deduction[state][i] + + # 1. The deduction lies between 0 and the pre-limitation total. + assert value >= 0, label + assert value <= pre + 0.01, label + + # 2. Investment interest does not change the reduction. + j = _index(state, status, agi, charity, medical, (0, 0)) + k = _index(state, status, 0, charity, medical, (0, 0)) + reduction_without = deduction[state][k] - deduction[state][j] + assert pre - value == pytest.approx(reduction_without, abs=0.01), label 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 a6847243c65..aaae4fd3a9b 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 @@ -18,7 +18,9 @@ medical_expense_deduction: 10_000 ca_investment_interest_expense_deduction: 5_000 output: - ca_itemized_deductions: 12_000 # medical expenses not subject to limitation + # Medical expenses and investment interest are both left out of the + # limitation, so nothing is subject to it. + ca_itemized_deductions: 15_000 - name: Single filer with low AGI and large charitable_deduction period: 2021 @@ -74,3 +76,42 @@ # 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 + +# 26 U.S.C. 68(c)(2), adopted by R&TC 17077, and the line 29 worksheet, line 2, +# leave investment interest out of the limitation. The amount left out is the +# FTB 3526 deduction that the pre-limitation total holds, not the interest paid. +# Both cases set that deduction as an input, so they test the exclusion list +# whatever formula computes FTB 3526. +- name: California's own investment interest deduction is left out of the high-AGI limitation + absolute_error_margin: 0.01 + period: 2024 + input: + state_code: CA + filing_status: SINGLE + adjusted_gross_income: 244_857 + 200_000 + investment_interest_expense: 10_000 + ca_investment_interest_expense_deduction: 6_000 + ca_charitable_deduction: 10_000 + output: + # 10,000 charity + 6,000 investment interest. + ca_itemized_deductions_pre_limitation: 16_000 + # Worksheet: line 2 = 6,000; line 3 = 10,000; line 4 = 80% x 10,000 = + # 8,000; line 8 = 6% x 200,000 = 12,000; line 9 = 8,000. 16,000 - 8,000. + # Leaving out the 10,000 paid instead would give line 4 = 80% x 6,000 = + # 4,800 and 11,200. + ca_itemized_deductions: 8_000 + +- name: California investment interest left out of the limitation when the 6% amount is smaller + absolute_error_margin: 0.01 + period: 2024 + input: + state_code: CA + filing_status: SINGLE + adjusted_gross_income: 244_857 + 50_000 + investment_interest_expense: 10_000 + ca_investment_interest_expense_deduction: 6_000 + ca_charitable_deduction: 10_000 + output: + ca_itemized_deductions_pre_limitation: 16_000 + # line 4 = 8,000; line 8 = 6% x 50,000 = 3,000; line 9 = 3,000. + ca_itemized_deductions: 13_000 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 1a70179489b..aadc46a547a 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 @@ -79,3 +79,36 @@ charitable_contributions_ald: 300 mn_itemizing: false mn_amt_taxable_income: 40_000 + +# 2024 Schedule M1MT line 10 subtracts investment interest (Schedule M1SA +# line 13) and line 14 unreimbursed employee expenses (Schedule M1SA line 23). +- name: MN AMT taxable income subtracts investment interest + period: 2024 + input: + adjusted_gross_income: 200_000 + mn_itemizing: true + investment_interest_expense: 10_000 + state_code: MN + output: + mn_amt_taxable_income: 200_000 - 10_000 + +- name: MN AMT taxable income subtracts the miscellaneous deduction + period: 2024 + input: + adjusted_gross_income: 200_000 + mn_itemizing: true + misc_deduction: 3_000 + state_code: MN + output: + mn_amt_taxable_income: 200_000 - 3_000 + +- name: MN AMT taxable income keeps investment interest for a non-itemizer + period: 2024 + input: + adjusted_gross_income: 200_000 + mn_itemizing: false + investment_interest_expense: 10_000 + misc_deduction: 3_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_itemized_deductions.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/tax/income/mn_itemized_deductions.yaml index 5746bbfd9a0..9c75f9292f5 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 @@ -201,3 +201,36 @@ deductible_mortgage_insurance_premiums: 1_800 interest_deduction: 11_800 mn_itemized_deductions: 10_000 + +# Minn. Stat. 290.0122, subd. 2(d), and the 2024 M1SA line 26 worksheets +# (step 2 takes out lines 4, 13 and 19) leave investment interest (line 13) +# out of the reduction. +- name: 2024 MN investment interest is exempt from the 80% reduction + absolute_error_margin: 0.01 + period: 2024 + input: + filing_status: SINGLE + adjusted_gross_income: 2_000_000 + investment_interest_expense: 10_000 + charitable_deduction: 5_000 + state_code: MN + output: + # AGI is over $1,053,750, so the reduction is 80% of the deductions + # other than investment interest: 0.8 x 5,000 = 4,000. + # 15,000 - 4,000. Were the interest reduced too: 15,000 - 12,000 = 3,000. + mn_itemized_deductions: 11_000 + +- name: 2024 MN investment interest is left out of the 80% cap on the 3% reduction + absolute_error_margin: 0.01 + period: 2024 + input: + filing_status: SINGLE + adjusted_gross_income: 300_000 + investment_interest_expense: 10_000 + charitable_deduction: 1_000 + state_code: MN + output: + # 3% x (300,000 - 232,500) = 2,025; the cap is 80% x 1,000 = 800, so the + # reduction is 800 and the deduction 11,000 - 800. Were the interest in + # the cap base, the cap would be 8,800 and the deduction 11,000 - 2,025. + mn_itemized_deductions: 10_200 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 14b5f968ce9..874312cee15 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 @@ -33,6 +33,15 @@ def formula(tax_unit, period, parameters): # M1MT line 12 takes the casualty and theft loss from Schedule # M1SA line 19, Minnesota's own amount. "mn_casualty_loss_deduction", + # M1MT line 10 takes investment interest from M1SA line 13. + "investment_interest_expense", + # M1MT line 14 takes unreimbursed employee expenses above 2% of + # AGI from M1SA line 23. The model has no M1SA line 20-23 amount + # yet, so the federal misc_deduction stands in. Its switch turns + # it off from 2018, and a reform that turns it back on also adds + # the tax preparation fees that line 23 leaves out. + # Line 13 (impairment-related work expenses) has no input here. + "misc_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_itemized_deductions.py b/policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py index b7154dc4d8a..af304f84ea2 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 @@ -53,12 +53,17 @@ def formula(tax_unit, period, parameters): # ... 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. + # (step 2: lines 4, 13 and 19) leave medical expenses, investment + # interest and casualty and theft losses out of the reduction. M1SA + # line 13 is the investment interest that interest_deduction holds. exempt_deds = add( tax_unit, period, - ["medical_expense_deduction", "mn_casualty_loss_deduction"], + [ + "medical_expense_deduction", + "investment_interest_expense", + "mn_casualty_loss_deduction", + ], ) net_deds = max_(0, mn_itm_deds - exempt_deds) filing_status = tax_unit("filing_status", period) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 9d6856a0433..08dbcb7ee5b 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -1035,26 +1035,39 @@ candidates: the candidate runs from 2018; verify worksheet editions and affected years.' - id: us-ca:itemized-limit-investment-interest-exclusion program: income-tax + years: + from: 2021 + type: tax question: Does the itemized-deduction limitation exclude California investment interest or the federal Schedule A line 9 amount? sources: - kind: statute - citation: R&TC 17077, incorporating IRC 68(c)(2) - url: null + citation: R&TC ยง17077, adopting 26 U.S.C. 68(c)(2) + url: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17077 - kind: worksheet - citation: 540 Schedule CA itemized limitation worksheet, FTB Line 29 worksheet - url: null + citation: 2024 Schedule CA line 29 worksheet, line 2, Form 540 booklet PDF p.68 + url: https://www.ftb.ca.gov/forms/2024/2024-540-booklet.pdf#page=68 readings: - statute: Exclude California's investment-interest deduction from FTB 3526 line 10. + statute: Exclude California's investment interest expense deduction (FTB 3526 line 8), the amount the itemized + total holds. other: Exclude federal Schedule A line 9. + example: A 2024 single filer with AGI of $444,857 (6% of the excess over the threshold is $12,000), $10,000 of charity, + a $6,000 FTB 3526 deduction and $10,000 on federal Schedule A line 9 deducts $8,000 under the statute, which PE-US + follows, and $11,200 through line 9. pe_us: - follows: conflicting-source - code: [] + follows: statute + code: + - policyengine_us/parameters/gov/states/ca/tax/income/deductions/itemized/limit/excluded_deductions.yaml prs: - https://github.com/PolicyEngine/policyengine-us/pull/9977 + - https://github.com/PolicyEngine/policyengine-us/pull/10645 state: ruled - note: An October 2026 ruling followed the worksheet; the later statute-first rule requires changing to the California - deduction, with years and citations verified. + note: 'Ruled 2026-11-02 for the statute. R&TC 17077 applies IRC 68 without changing 68(c), and 68(c)(2) leaves out + "any deduction for investment interest". The worksheet names federal Schedule A line 9, which gives the same result + when FTB 3526 and federal Form 4952 agree. ca_itemized_deductions_pre_limitation holds + ca_investment_interest_expense_deduction, so the exclusion list names that variable. An October 2026 ruling followed + the worksheet; the statute-first rule replaced it, and the open PR that still lists the federal amount must take + this reading.' - id: us-ca:joint-childless-eitc-claimable-spouse program: income-tax years: