diff --git a/changelog.d/mt-2024-base-gaps.fixed.md b/changelog.d/mt-2024-base-gaps.fixed.md new file mode 100644 index 00000000000..8eb6a331f59 --- /dev/null +++ b/changelog.d/mt-2024-base-gaps.fixed.md @@ -0,0 +1 @@ +Subtract interest on US government obligations from Montana taxable income from 2024 (MCA 15-30-2120(3)(b)(i)), and add end-to-end Montana Form 2 cases for 2024–2026. diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml index a2c38938f6a..279a308086d 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/subtractions/subtractions.yaml @@ -19,6 +19,11 @@ values: # (capped at MT earned income, (3)(n)(i)) and up to 50% of military survivor # benefits regardless of other income ((3)(n)(ii), Form WMRE); not yet modeled. - mt_interest_exemption_person + # MCA 15-30-2120(3)(b)(i); 2024 Form 2 Schedule I, Part I, line 11. + # The (3) lead-in allows the subtraction only to the extent federal + # taxable income included the interest. The model reads the input as + # entered and leaves that limit to issue #10780. + - us_govt_interest_person - mt_tuition_subtraction_person - mt_old_age_subtraction # 2021 Mont. Laws ch. 503 (SB 399) section 65 repealed 15-30-2110, MCA, @@ -32,6 +37,9 @@ values: # tip and overtime exclusions, and passenger-vehicle loan interest) # flow through into MT taxable income via mt_federal_obbba_subtraction. - mt_interest_exemption_person + # MCA 15-30-2120(3)(b)(i); 2025 Form 2 Schedule I, Part I, line 9. + # Issue #10780 tracks the (3) lead-in's inclusion limit, as in 2024. + - us_govt_interest_person - mt_tuition_subtraction_person - mt_old_age_subtraction # TODO: Remove this subtraction starting in 2029 because the federal @@ -52,9 +60,11 @@ metadata: href: https://web.archive.org/web/20220523051049/https://rules.mt.gov/gateway/Subchapterhome.asp?scn=42%2E15%2E2 - title: Montana Code Annotated, TITLE 15, CHAPTER 30, Part 21, 15-30-2110(3)(g) href: https://leg.mt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0100/0150-0300-0210-0100.html - - title: 2025 Montana Form 2 Instructions + - title: "2025 Montana Form 2 Instructions #page 21,23-24" href: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=21 - title: "2021 Montana Session Laws, chapter 503 (SB 399), section 65 (repealer) and section 70 (effective dates) #page 68,70" href: https://archive.legmt.gov/content/Publications/sales/2021SessionLaws_V3_ERRATA-VERSION.pdf#page=68 - - title: 2024 Montana Form 2 instructions, repealed deductions + - title: "2024 Montana Form 2 instructions, repealed deductions and Schedule I line 11 #page 5,25" 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=5 + - title: MCA 15-30-2120(3)(b)(i), interest from obligations of the United States government + href: https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0200/0150-0300-0210-0200.html diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_income.yaml new file mode 100644 index 00000000000..658f631a99a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_income.yaml @@ -0,0 +1,545 @@ +# Montana taxable income (Form 2 line 7) from household inputs, 2024 onward. +# Each case runs from wages, medical, mortgage, tax and interest inputs through +# the federal model, so it also checks which federal amounts Montana reads. +# Expectations are hand computed from: +# - 2024 Form 2 and instructions, with the revised Worksheet A (V1 1/2025): +# https://revenue.mt.gov/files/Forms/SALT-Cap-Instructions.pdf +# - 2025 Form 2 and instructions, Worksheet B (instructions PDF page 22). +# - 2026: statutory inference from MCA 15-30-2101(7), (14) and 15-30-2120 +# until a 2026 form exists. +# Worksheet line numbers: L1 Schedule A line 5e (capped SALT); L2 local income +# tax; L3 real estate tax; L4 personal property tax; L5 state income tax within +# the cap; L6 federal itemized deductions; L7 federal standard deduction; +# L8 = max(0, L6 - L7); L9 = min(L5, L8), the state income tax addback. +# Tax: 2024 single 5.9% x TI - 246 above 20,500 (joint - 492 above 41,000); +# 2025 single 5.9% x TI - 253.20 above 21,100 (joint - 506.40 above 42,200); +# 2026 single 4.7% to 47,500, then 5.65%. +# The 2025 instructions' rate table (PDF page 18) prints whole-dollar offsets +# of 253, 506 and 380. The brackets that MCA 15-30-2103(1) and (3) set give +# 253.20, 506.40 and 380.40, and these cases use the statutory amounts. + +- name: 2024 medical itemizer reaches the federal standard deduction floor + # Federal medical: 20,000 - 7.5% x 100,000 = 12,500; SALT = 5,000. + # Worksheet A L1=5,000; L2=L3=L4=0; L5=5,000; + # L6=17,500; L7=14,600; L8=2,900; L9=2,900; L10=14,600. + # Form 2 L1=100,000; L2=14,600; L3=85,400; L4=L5=L6=0; + # L7=85,400; tax=85,400 x 5.9% - 246 = 4,792.60. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 100_000 + other_medical_expenses: 20_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 5_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: true + itemized_taxable_income_deductions: 17_500 + mt_state_income_tax_itemized_add_back: 2_900 + mt_federal_deductions: 14_600 + mt_taxable_income: 85_400 + mt_income_tax: 4_792.60 + +- name: 2024 federal standard deduction controls despite medical expenses + # Federal medical: 16,000 - 7.5% x 100,000 = 8,500; + # itemized=8,500 + 3,000 SALT=11,500 < 14,600, so Worksheet A is unused. + # Form 2 L1=100,000; L2=14,600; L3=85,400; L4=L5=L6=0; + # L7=85,400; tax=4,792.60. Montana's repealed 2023 Itemized Deductions + # Schedule (2023 Form 2, page 7, lines 1a-1c) also floored medical expenses + # at 7.5% of Montana AGI, so it too would have allowed 8,500 here. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 100_000 + other_medical_expenses: 16_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 3_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + itemized_taxable_income_deductions: 11_500 + mt_state_income_tax_itemized_add_back: 0 + mt_federal_deductions: 14_600 + mt_taxable_income: 85_400 + mt_income_tax: 4_792.60 + +- name: 2024 joint itemizer attributes only the remaining SALT cap to state income tax + # Federal SALT=min(15,000 + 8,000, 10,000)=10,000; + # itemized=10,000 SALT + 20,000 mortgage + 5,000 charity=35,000. + # Worksheet A L1=10,000; L2=0; L3=8,000; L4=0; L5=2,000; + # L6=35,000; L7=29,200; L8=5,800; L9=2,000; L10=33,000. + # Form 2 L1=300,000; L2=33,000; L3=267,000; L4=L5=L6=0; + # L7=267,000; tax=267,000 x 5.9% - 492 = 15,261.00. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 300_000 + real_estate_taxes: 8_000 + home_mortgage_interest: 20_000 + charitable_cash_donations: 5_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + state_withheld_income_tax: 15_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + filing_status: JOINT + tax_unit_itemizes: true + salt_deduction: 10_000 + itemized_taxable_income_deductions: 35_000 + mt_state_income_tax_itemized_add_back: 2_000 + mt_federal_deductions: 33_000 + mt_taxable_income_joint: [267_000, 0] + mt_taxable_income: 267_000 + mt_income_tax: 15_261.00 + +- name: 2024 state income tax addback is limited by the standard deduction floor + # Federal SALT=3,000 state income tax + 3,000 real estate=6,000; + # itemized=6,000 + 9,000 mortgage=15,000 > 14,600. + # Worksheet A L1=6,000; L2=0; L3=3,000; L4=0; L5=3,000; + # L6=15,000; L7=14,600; L8=400; L9=400; L10=14,600. + # Form 2 L1=60,000; L2=14,600; L3=45,400; L4=L5=L6=0; + # L7=45,400; tax=45,400 x 5.9% - 246 = 2,432.60. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 60_000 + home_mortgage_interest: 9_000 + real_estate_taxes: 3_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 3_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: true + itemized_taxable_income_deductions: 15_000 + mt_state_income_tax_itemized_add_back: 400 + mt_federal_deductions: 14_600 + mt_taxable_income: 45_400 + mt_income_tax: 2_432.60 + +- name: 2024 Montana excludes the federal qualified business income deduction + # MCA 15-30-2120(2)(i). Itemized=3,000 < 14,600; Worksheet A is unused. + # Federal taxable income=80,000 - 14,600 - 10,000 QBI=55,400. + # Form 2 L1=80,000; L2=14,600 (QBI excluded); L3=65,400; + # L4=L5=L6=0; L7=65,400; tax=65,400 x 5.9% - 246 = 3,612.60. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 80_000 + tax_units: + tax_unit: + members: [head] + qualified_business_income_deduction: 10_000 + state_withheld_income_tax: 3_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + taxable_income: 55_400 + mt_federal_deductions: 14_600 + mt_taxable_income: 65_400 + mt_income_tax: 3_612.60 + +- name: 2024 US government interest is subtracted from federal taxable income + # Issue #9707. MCA 15-30-2120(3)(b)(i); 2024 Form 2 Schedule I, Part I, line 11. + # us_govt_interest_person classifies 1,000 of the taxable interest; it adds + # no federal income. Itemized=2,000 < 14,600; Worksheet A is unused. + # Form 2 L1=50,000 wages + 1,000 interest=51,000; L2=14,600; + # L3=36,400; L4=0; L5=1,000; L6=0; L7=35,400; + # tax=35,400 x 5.9% - 246 = 1,842.60. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + taxable_interest_income: 1_000 + us_govt_interest_person: 1_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 2_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + mt_subtractions: [1_000] + mt_taxable_income: 35_400 + mt_income_tax: 1_842.60 + +- name: 2024 US government interest cannot take taxable income below zero + # Form 2 L1=10,000 wages + 10,000 interest=20,000; L2=14,600; L3=5,400; + # L5=10,000; L7=max(0, 5,400 - 10,000)=0. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 10_000 + taxable_interest_income: 10_000 + us_govt_interest_person: 10_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 0 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + adjusted_gross_income: 20_000 + mt_subtractions: [10_000] + mt_taxable_income: 0 + +- name: 2025 state income tax moves to the Schedule I addition + # Federal medical=20,000 - 7.5% x 100,000=12,500; + # itemized=12,500 + 5,000 SALT=17,500 > 15,750. + # Worksheet B L1=5,000; L2=L3=L4=0; L5=5,000; L6=17,500; + # L7=15,750; L8=1,750; L9=1,750; L10=0; L11=1,750. + # Form 2 L1=100,000; L2=17,500; L3=82,500; L4=1,750; + # L5=L6=0; L7=84,250; tax=84,250 x 5.9% - 253.20 = 4,717.55. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 100_000 + other_medical_expenses: 20_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 5_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: true + additional_senior_deduction: 0 + mt_state_income_tax_itemized_add_back: 1_750 + mt_federal_deductions: 17_500 + mt_taxable_income: 84_250 + mt_income_tax: 4_717.55 + +- name: 2025 enhanced senior deduction reduces Montana taxable income exactly once + # Itemized=2,000 < standard 15,750 + 2,000 aged=17,750; + # Worksheet B is unused. Schedule 1-A senior deduction=6,000. + # Form 2 L1=50,000; L2=17,750 + 6,000=23,750 (Form 1040 lines 12e + # and 13b); L3=26,250; L4=L5=0; L6=5,660; L7=20,590; + # tax=20,590 x 4.7%=967.73. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 66 + employment_income: 50_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 2_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + standard_deduction: 17_750 + additional_senior_deduction: 6_000 + mt_taxable_income: 20_590 + mt_income_tax: 967.73 + +- name: 2025 phased SALT cap leaves no state income tax deduction to add + # 26 USC 164(b)(7): SALT cap=max(10,000, 40,000 - 30% x + # (600,000 - 500,000))=10,000; SALT=min(47,000, 10,000)=10,000. + # Itemized=10,000 SALT + 20,000 mortgage + 10,000 charity=40,000. + # Worksheet B L1=10,000; L2=0; L3=12,000; L4=0; L5=0; + # L6=40,000; L7=31,500; L8=8,500; L9=L10=L11=0. + # Form 2 L1=600,000; L2=40,000; L3=560,000; L4=L5=L6=0; + # L7=560,000; tax=560,000 x 5.9% - 506.40 = 32,533.60. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 600_000 + real_estate_taxes: 12_000 + home_mortgage_interest: 20_000 + charitable_cash_donations: 10_000 + spouse: + age: 40 + tax_units: + tax_unit: + members: [head, spouse] + state_withheld_income_tax: 35_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + filing_status: JOINT + tax_unit_itemizes: true + salt_cap: 10_000 + salt_deduction: 10_000 + itemized_taxable_income_deductions: 40_000 + mt_state_income_tax_itemized_add_back: 0 + mt_federal_deductions: 40_000 + mt_taxable_income: 560_000 + mt_income_tax: 32_533.60 + +- name: 2025 US government interest is subtracted from federal taxable income + # MCA 15-30-2120(3)(b)(i); 2025 Form 2 Schedule I, Part I, line 9. + # Itemized=2,000 < 15,750; Worksheet B is unused. + # Form 2 L1=51,000; L2=15,750; L3=35,250; L4=0; L5=1,000; L6=0; + # L7=34,250; tax=34,250 x 5.9% - 253.20 = 1,767.55. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + taxable_interest_income: 1_000 + us_govt_interest_person: 1_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 2_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + mt_subtractions: [1_000] + mt_taxable_income: 34_250 + mt_income_tax: 1_767.55 + +- name: 2025 joint return subtracts the spouse's US government interest + # The spouse holds the bonds; the joint return reports both spouses' income. + # Itemized=3,000 < 31,500; Worksheet B is unused. + # Form 2 L1=80,000 wages + 3,000 interest=83,000; L2=31,500; L3=51,500; + # L4=0; L5=3,000; L6=0; L7=48,500; + # tax=48,500 x 5.9% - 506.40 = 2,355.10. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 80_000 + spouse: + age: 40 + taxable_interest_income: 3_000 + us_govt_interest_person: 3_000 + tax_units: + tax_unit: + members: [head, spouse] + state_withheld_income_tax: 3_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + filing_status: JOINT + tax_unit_itemizes: false + mt_subtractions: [0, 3_000] + mt_taxable_income_joint: [48_500, 0] + mt_taxable_income: 48_500 + mt_income_tax: 2_355.10 + +- name: 2026 follows rolling federal conformity + # Statutory inference: no 2026 Montana form exists yet. Apply MCA + # 15-30-2101(14) rolling conformity and 15-30-2120(1), (2)(j). + # Federal medical=12,500; SALT=5,000; itemized=17,500 > 16,100. + # Inferred Worksheet B L1=5,000; L2=L3=L4=0; L5=5,000; + # L6=17,500; L7=16,100; L8=1,400; L9=1,400; L10=0; L11=1,400. + # Inferred Form 2 L1=100,000; L2=17,500; L3=82,500; L4=1,400; + # L5=L6=0; L7=83,900; tax=47,500 x 4.7% + 36,400 x 5.65%=4,289.10. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 100_000 + other_medical_expenses: 20_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 5_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: true + mt_state_income_tax_itemized_add_back: 1_400 + mt_federal_deductions: 17_500 + mt_taxable_income: 83_900 + mt_income_tax: 4_289.10 + +- name: 2026 federal itemized limitation and SALT phase-down flow into line 2 + # Statutory inference (MCA 15-30-2101(7) and (14); no 2026 Montana form yet). + # SALT cap: 40,400 - 30% x (1,000,000 - 505,000) < 10,000 floor -> 10,000. + # Charity after the 0.5%-of-AGI floor: 50,000 - 5,000 = 45,000. + # Itemized before 26 U.S.C. 68: 10,000 + 20,000 + 45,000 = 75,000. + # Section 68: 2/37 x min(75,000, 1,000,000 - 640,600) = 4,054.05; + # federal itemized = 70,945.95. + # Worksheet B L1=10,000; L3=15,000; L5=0, so no Schedule I addition. + # Form 2 L1=1,000,000; L2=70,945.95; L3=929,054.05; L7=929,054.05; + # tax = 47,500 x 4.7% + (929,054.05 - 47,500) x 5.65% = 52,040.30. + period: 2026 + absolute_error_margin: 1 + input: + people: + head: + age: 45 + employment_income: 1_000_000 + charitable_cash_donations: 50_000 + home_mortgage_interest: 20_000 + real_estate_taxes: 15_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 60_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + salt_cap: 10_000 + salt_deduction: 10_000 + itemized_taxable_income_deductions_reduction: 4_054.05 + itemized_taxable_income_deductions: 70_945.95 + tax_unit_itemizes: true + mt_state_income_tax_itemized_add_back: 0 + mt_federal_deductions: 70_945.95 + mt_taxable_income: 929_054.05 + mt_income_tax: 52_040.30 + +- name: 2026 US government interest is subtracted from federal taxable income + # Statutory inference (MCA 15-30-2120(3)(b)(i); no 2026 Montana form yet). + # Itemized=2,000 < 16,100; Worksheet B is unused. + # Form 2 L1=51,000; L2=16,100; L3=34,900; L4=0; L5=1,000; L6=0; + # L7=33,900; tax=33,900 x 4.7% = 1,593.30. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + employment_income: 50_000 + taxable_interest_income: 1_000 + us_govt_interest_person: 1_000 + tax_units: + tax_unit: + members: [head] + state_withheld_income_tax: 2_000 + state_sales_tax: 0 + local_sales_tax: 0 + local_income_tax: 0 + households: + household: + members: [head] + state_code: MT + output: + tax_unit_itemizes: false + mt_federal_deductions: 16_100 + mt_subtractions: [1_000] + mt_taxable_income: 33_900 + mt_income_tax: 1_593.30 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_us_govt_bond_interest.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_us_govt_bond_interest.yaml index a7e30dda507..46d8dd88cf2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_us_govt_bond_interest.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_us_govt_bond_interest.yaml @@ -3,6 +3,7 @@ input: people: person1: + taxable_interest_income: 5_000 us_govt_interest_person: 5_000 tax_units: tax_unit1: @@ -14,11 +15,14 @@ output: mt_subtractions: 5_000 -- name: MT does not subtract US government bond interest (2024) +- name: MT subtracts US government bond interest from federal taxable income (2024) + # MCA 15-30-2120(3)(b)(i) subtracts "interest from obligations of the United + # States government"; 2024 Form 2 Schedule I, Part I, line 11 (issue #9707). period: 2024 input: people: person1: + taxable_interest_income: 5_000 us_govt_interest_person: 5_000 tax_units: tax_unit1: @@ -28,4 +32,22 @@ members: [person1] state_code: MT output: - mt_subtractions: 0 + mt_subtractions: 5_000 + +- name: MT subtracts US government bond interest from federal taxable income (2025) + # MCA 15-30-2120(3)(b)(i); 2025 Form 2 Schedule I, Part I, line 9. + period: 2025 + input: + people: + person1: + taxable_interest_income: 5_000 + us_govt_interest_person: 5_000 + tax_units: + tax_unit1: + members: [person1] + households: + household1: + members: [person1] + state_code: MT + output: + mt_subtractions: 5_000 diff --git a/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py new file mode 100644 index 00000000000..45b01ee3206 --- /dev/null +++ b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py @@ -0,0 +1,259 @@ +"""Invariants of Montana's subtraction for US government interest from 2024. + +MCA 15-30-2120(3)(b)(i) subtracts "interest from obligations of the United +States government and exempt-interest dividends attributable to that +interest" from federal taxable income: +https://mca.legmt.gov/bills/mca/title_0150/chapter_0300/part_0210/section_0200/0150-0300-0210-0200.html +The 2024 Form 2 reports it on Schedule I, Part I, line 11 (instructions PDF +page 25) and the 2025 Form 2 on line 9 (instructions PDF page 23). Line 7 +(Montana taxable income) cannot fall below zero. + +Each generated household runs three times in one simulation. The bondholder's +taxable_interest_income and us_govt_interest_person are (I, u), (I, 0) and +(0, 0). The first two differ only in the classification input; the third +measures how much of the interest federal AGI includes. The generator keeps +to the input contract the model relies on: u <= I on the same person, with +none of the interest excluded under 26 U.S.C. 135 (us_bonds_for_higher_ed). + +Let TI_0 be Montana taxable income without the subtraction, and +u_in = min(u, AGI(I) - AGI(0)) the US interest that federal AGI includes. +u_in = u when the head or spouse holds the bonds. u_in = 0 when a dependent +does, because irs_gross_income leaves a tax-unit dependent's income off the +return. For every household and each year from 2024 to 2026: + +1. Same federal base: federal taxable income matches across (I, u) and (I, 0). +2. Accounting: Montana taxable income falls by min(u_in, TI_0). +3. Monotone: Montana income tax does not rise with the subtraction. +4. Bound: Montana taxable income is nonnegative. + +The (3) lead-in allows a subtraction only to the extent federal taxable income +included the amount. Under the input contract the subtraction never exceeds +u_in; issue #10780 tracks inputs outside it (US interest above taxable +interest, or interest that section 135 excludes). + +From 2025 the enhanced senior deduction phases out at 6% of MAGI, so taxable +income can carry cents. Amount comparisons allow the larger of one cent and +four float32 steps at the compared magnitude. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np + +from policyengine_us import Simulation + +YEARS = (2024, 2025, 2026) +VARIANTS = ("with", "without", "no_interest") +SLOW = [hypothesis.HealthCheck.too_slow, hypothesis.HealthCheck.data_too_large] + + +def tolerance(*values): + scale = max(abs(float(value)) for value in values) + return max(0.01, 4 * float(np.spacing(np.float32(scale)))) + + +def bondholder(case): + # A single filer has no spouse; put the spouse's bonds on the head. + if case["holder"] == "spouse" and not case["joint"]: + return "head" + return case["holder"] + + +def us_interest(case): + return round(case["interest"] * case["us_share"]) + + +def build_situation(cases, years): + people, tax_units, marital_units, households = {}, {}, {}, {} + + def by_year(value): + return {year: value for year in years} + + for i, case in enumerate(cases): + holder = bondholder(case) + inputs = { + "with": (case["interest"], us_interest(case)), + "without": (case["interest"], 0), + "no_interest": (0, 0), + } + for variant in VARIANTS: + interest, amount = inputs[variant] + key = f"{variant}_{i}" + head, spouse, dependent = ( + f"head_{key}", + f"spouse_{key}", + f"dependent_{key}", + ) + filers = [head, spouse] if case["joint"] else [head] + # Only a dependent bondholder joins the unit; a single filer with + # a dependent child files as head of household. + members = filers + ([dependent] if holder == "dependent" else []) + people[head] = { + "age": by_year(case["age"]), + "employment_income": by_year(case["wages"]), + "home_mortgage_interest": by_year(case["mortgage"]), + } + if case["joint"]: + people[spouse] = {"age": by_year(case["age"])} + if holder == "dependent": + people[dependent] = { + "age": by_year(10), + "is_tax_unit_dependent": by_year(True), + } + owner = {"head": head, "spouse": spouse, "dependent": dependent}[holder] + people[owner]["taxable_interest_income"] = by_year(interest) + people[owner]["us_govt_interest_person"] = by_year(amount) + tax_units[f"tax_unit_{key}"] = { + "members": members, + "state_withheld_income_tax": by_year(case["state_income_tax"]), + "state_sales_tax": by_year(0), + "local_sales_tax": by_year(0), + "local_income_tax": by_year(0), + } + marital_units[f"marital_unit_{key}"] = {"members": filers} + if holder == "dependent": + marital_units[f"dependent_marital_unit_{key}"] = { + "members": [dependent] + } + households[f"household_{key}"] = { + "members": members, + "state_code": by_year("MT"), + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "households": households, + } + + +VARIABLES = ( + "mt_taxable_income", + "mt_income_tax", + "taxable_income", + "adjusted_gross_income", +) + + +def calculate(cases, years=YEARS): + """Return {year: {variable: array}}, each array shaped [case, variant].""" + simulation = Simulation(situation=build_situation(cases, years)) + return { + year: { + variable: np.asarray(simulation.calculate(variable, year)).reshape( + len(cases), len(VARIANTS) + ) + for variable in VARIABLES + } + for year in years + } + + +def assert_properties(cases): + for year, results in calculate(cases).items(): + mt_ti = results["mt_taxable_income"] + tax = results["mt_income_tax"] + federal_ti = results["taxable_income"] + agi = results["adjusted_gross_income"] + for i, case in enumerate(cases): + ti_with, ti_without, _ = (float(x) for x in mt_ti[i]) + agi_with_interest, _, agi_no_interest = (float(x) for x in agi[i]) + u_in = min(us_interest(case), agi_with_interest - agi_no_interest) + context = (year, case, mt_ti[i], tax[i], federal_ti[i], agi[i]) + assert federal_ti[i][0] == federal_ti[i][1], context + assert ti_with >= 0 and ti_without >= 0, context + expected = min(u_in, ti_without) + assert abs((ti_without - ti_with) - expected) <= tolerance( + ti_without, ti_with + ), context + assert tax[i][0] <= tax[i][1] + tolerance(tax[i][1]), context + + +def case(joint, holder, wages, interest, us_share, **kwargs): + return { + "joint": joint, + "holder": holder, + "wages": wages, + "interest": interest, + "us_share": us_share, + "state_income_tax": kwargs.get("state_income_tax", 0), + "mortgage": kwargs.get("mortgage", 0), + "age": kwargs.get("age", 40), + } + + +# The subtraction exceeds taxable income before it (the zero floor binds), +# a dependent holds the bonds, interior cuts, single and joint, and a senior +# whose 2025-2026 deduction phase-out leaves cents in taxable income. +PINNED = [ + case(False, "head", 0, 30_000, 1.0), + case(True, "spouse", 5_000, 30_000, 1.0, age=70), + case(False, "dependent", 40_000, 10_000, 1.0), + case( + True, "dependent", 80_000, 10_000, 1.0, state_income_tax=4_000, mortgage=30_000 + ), + case(False, "head", 60_000, 5_000, 0.5), + case(True, "spouse", 120_000, 8_000, 0.75, state_income_tax=9_000, mortgage=30_000), + case( + False, + "head", + 200_000, + 30_000, + 1.0, + state_income_tax=15_000, + mortgage=30_000, + age=70, + ), + case(False, "head", 89_511, 5_000, 1.0, age=70), +] + + +def test_worked_subtractions(): + # 2024 single, 10,000 wages and 10,000 Treasury interest: Form 2 line 3 is + # 20,000 - 14,600 = 5,400, so the 10,000 subtraction stops at zero. + # 2025 joint, spouse holds 3,000 of bonds: 83,000 - 31,500 - 3,000 = 48,500. + # A dependent's 10,000 of bonds leaves the return unchanged; the parent + # files as head of household (2024 standard deduction 21,900). + cases = [ + case(False, "head", 10_000, 10_000, 1.0), + case(True, "spouse", 80_000, 3_000, 1.0), + case(False, "dependent", 40_000, 10_000, 1.0), + ] + results = calculate(cases, years=(2024, 2025)) + taxable_income_2024 = results[2024]["mt_taxable_income"][:, :2] + taxable_income_2025 = results[2025]["mt_taxable_income"][:, :2] + assert taxable_income_2024[0].tolist() == [0, 5_400] + assert taxable_income_2025[1].tolist() == [48_500, 51_500] + assert taxable_income_2024[2].tolist() == [40_000 - 21_900] * 2 + + +@hypothesis.settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=SLOW, +) +@hypothesis.example(PINNED) +@hypothesis.given( + st.lists( + st.fixed_dictionaries( + { + "joint": st.booleans(), + "holder": st.sampled_from(["head", "spouse", "dependent"]), + "wages": st.one_of( + st.integers(min_value=0, max_value=20_000), + st.integers(min_value=0, max_value=250_000), + ), + "interest": st.integers(min_value=0, max_value=30_000), + "us_share": st.one_of(st.just(1.0), st.floats(0, 1)), + "state_income_tax": st.integers(min_value=0, max_value=15_000), + "mortgage": st.sampled_from([0, 12_000, 30_000]), + "age": st.sampled_from([40, 70]), + } + ), + min_size=1, + max_size=6, + ) +) +def test_subtraction_properties(cases): + assert_properties(cases) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 68c442649aa..6326d1b322f 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -6554,6 +6554,41 @@ candidates: Montana AGI (Itemized Deductions Schedule line 1b), and from 2024 the federal floor on federal AGI applies (mt_medical_expense_deduction_joint.py, itemized/state_specific_deduction_applies.yaml); the joint standard deduction also uses pooled Montana AGI (mt_standard_deduction_joint.py). Verify the incorporated federal provision against the form''s AGI choice.' +- id: us-mt:rate-table-offset-rounding + program: income-tax + years: + from: 2025 + to: 2025 + type: tax + question: Does Montana's 2025 ordinary income tax subtract the statutory bracket amounts (253.20, 506.40 and 380.40) + or the whole-dollar amounts the rate table prints? + sources: + - kind: statute + citation: MCA 15-30-2103(1) and (3) (2023 MCA) + url: https://mca.legmt.gov/bills/2023/mca/title_0150/chapter_0300/part_0210/section_0030/0150-0300-0210-0030.html + - kind: instructions + citation: 2025 Form 2 instructions, 2025 Montana Income Tax Rates table, PDF page 18 + url: https://revenuefiles.mt.gov/files/Forms/Montana-Individual-Income-Tax-Return-Form-2-Instructions/2025_Montana_Individual_Income_Tax_Return_Form_2_Instructions.pdf#page=18 + readings: + statute: Tax the indexed bracket (21,100 single and separate, 42,200 joint and surviving spouse, 31,700 head of household) + at 4.7% and income above it at 5.9%, so a 5.9% shortcut subtracts 1.2% of the bracket, 253.20, 506.40 or 380.40. + other: Multiply taxable income by 5.9% and subtract $253, $506 or $380. + example: A 2025 single filer with $34,250 of Montana taxable income owes $1,767.55 on the statute's brackets and $1,767.75 + by the rate table. + pe_us: + follows: statute + code: + - policyengine_us/parameters/gov/states/mt/tax/income/main/single.yaml + - policyengine_us/parameters/gov/states/mt/tax/income/main/joint.yaml + - policyengine_us/parameters/gov/states/mt/tax/income/main/head_of_household.yaml + - policyengine_us/parameters/gov/states/mt/tax/income/main/separate.yaml + - policyengine_us/parameters/gov/states/mt/tax/income/main/surviving_spouse.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10772 + state: unreviewed + note: The 2024 table's offsets (246, 492 and 369) equal 1.2% of that year's brackets exactly, so only 2025 differs, + by 20 to 40 cents a return above the first bracket. PE-US applies the marginal rates to the brackets; the tests in + mt_taxable_income.yaml expect the statutory amounts. - id: us-nc:lieap-gross-versus-net-income program: liheap years: