From ddad751c2d668b5b141ee3a78ae75172be0b91a3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 9 Nov 2026 13:14:42 -0500 Subject: [PATCH 1/3] Subtract US government interest from Montana taxable income from 2024 MCA 15-30-2120(3)(b)(i) subtracts interest from obligations of the United States government from federal taxable income. The model's 2024 and 2025 subtraction lists omitted us_govt_interest_person, so from 2024 it taxed that interest (2024 Form 2 Schedule I, Part I, line 11; 2025 line 9). Restore it in both lists and correct the 2024 expectation in mt_us_govt_bond_interest.yaml, which recorded the bug. Add mt_taxable_income.yaml: end-to-end Form 2 cases for 2024-2026, hand computed from the forms and the revised Worksheet A / Worksheet B, run from household inputs through the federal model. Add test_mt_us_govt_interest_subtraction.py, a Hypothesis test of three invariants for 2024-2026: Montana taxable income falls by exactly min(u, taxable income before the subtraction) when the head or spouse holds the bonds and not at all when a dependent does; Montana tax does not rise; Montana taxable income stays nonnegative. Fixes #9707. Co-Authored-By: Claude Opus 5.5 --- changelog.d/mt-2024-base-gaps.fixed.md | 1 + .../tax/income/subtractions/subtractions.yaml | 10 +- .../mt/tax/income/mt_taxable_income.yaml | 509 ++++++++++++++++++ .../tax/income/mt_us_govt_bond_interest.yaml | 23 +- .../test_mt_us_govt_interest_subtraction.py | 220 ++++++++ 5 files changed, 759 insertions(+), 4 deletions(-) create mode 100644 changelog.d/mt-2024-base-gaps.fixed.md create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_income.yaml create mode 100644 policyengine_us/tests/test_mt_us_govt_interest_subtraction.py 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..98b94dac1ce 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,8 @@ 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. + - 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 +34,8 @@ 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. + - us_govt_interest_person - mt_tuition_subtraction_person - mt_old_age_subtraction # TODO: Remove this subtraction starting in 2029 because the federal @@ -52,9 +56,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..f2b9db002db --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_income.yaml @@ -0,0 +1,509 @@ +# 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%. + +- 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. The repealed Montana medical schedule + # would have deducted all 16,000. + 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 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..87e9387afb3 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 @@ -14,7 +14,9 @@ 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: @@ -28,4 +30,21 @@ 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: + 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..d87fa9f1bba --- /dev/null +++ b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py @@ -0,0 +1,220 @@ +"""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 twice in one simulation: once with +us_govt_interest_person = u on the bondholder and once with 0. Only that +classification input differs, so federal income and deductions match across +the pair. For every household and each year from 2024 to 2026: + +1. Accounting: Montana taxable income falls by exactly min(u_eff, TI_0), + where TI_0 is Montana taxable income without the subtraction. u_eff = u + when the head or spouse holds the bonds and 0 when a dependent does. + The dependent case is intended: irs_gross_income zeroes every income + source of a tax-unit dependent, so a dependent's interest never enters + the return, and the return subtracts none of it. +2. Monotone: Montana income tax does not rise with the subtraction. +3. Bound: Montana taxable income is nonnegative. + +Taxable income is integral here (integer inputs and deductions), so float32 +represents it exactly; tax comparisons allow one cent. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np + +from policyengine_us import Simulation + +YEARS = (2024, 2025, 2026) +AMOUNT_TOLERANCE = 0.01 +SLOW = [hypothesis.HealthCheck.too_slow, hypothesis.HealthCheck.data_too_large] + + +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): + 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) + for variant, amount in (("with", us_interest(case)), ("without", 0)): + 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(case["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, + } + + +def calculate(cases): + """Return {year: (taxable income, tax)}, each shaped [case, with/without].""" + simulation = Simulation(situation=build_situation(cases)) + results = {} + for year in YEARS: + taxable_income = np.asarray( + simulation.calculate("mt_taxable_income", year) + ).reshape(len(cases), 2) + tax = np.asarray(simulation.calculate("mt_income_tax", year)).reshape( + len(cases), 2 + ) + results[year] = taxable_income, tax + return results + + +def assert_properties(cases): + for year, (taxable_income, tax) in calculate(cases).items(): + for i, case in enumerate(cases): + u_eff = 0 if bondholder(case) == "dependent" else us_interest(case) + ti_with, ti_without = taxable_income[i] + context = (year, case, taxable_income[i], tax[i]) + assert ti_with >= 0 and ti_without >= 0, context + assert ti_without - ti_with == min(u_eff, ti_without), context + assert tax[i][0] <= tax[i][1] + AMOUNT_TOLERANCE, 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, and interior cuts, single and joint. +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, + ), +] + + +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) + taxable_income_2024 = results[2024][0] + taxable_income_2025 = results[2025][0] + 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) From 279e23d336b2eb8ad73d8969e4ee02c63716cb5c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 11 Nov 2026 00:42:36 -0500 Subject: [PATCH 2/3] Tighten Montana US-interest tests and record the 2025 rate-table rounding Review follow-ups for #9707: - Bond interest cases now enter the interest as taxable interest too, the model's input contract. - The property test runs a third, no-interest variant and derives the US interest that federal AGI includes, replacing the hard-coded dependent rule; it also checks that federal taxable income matches across variants and allows float32 rounding at the compared magnitude, since the 2025-2026 senior deduction phase-out leaves cents. A senior case with cents joins the pins. - New 2026 Form 2 case for the subtraction; corrected the medical comment (the repealed 2023 schedule also floored medical at 7.5% of Montana AGI). - Comment on the subtraction list and the test docstring point to #10780 for the (3) lead-in's inclusion limit. - source_conflicts/candidates.yaml: us-mt:rate-table-offset-rounding, the 2025 rate table's whole-dollar offsets against the statute's brackets. Co-Authored-By: Claude Opus 5.5 --- .../tax/income/subtractions/subtractions.yaml | 4 + .../mt/tax/income/mt_taxable_income.yaml | 40 +++++- .../tax/income/mt_us_govt_bond_interest.yaml | 3 + .../test_mt_us_govt_interest_subtraction.py | 126 ++++++++++++------ source_conflicts/candidates.yaml | 35 +++++ 5 files changed, 162 insertions(+), 46 deletions(-) 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 98b94dac1ce..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 @@ -20,6 +20,9 @@ values: # 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 @@ -35,6 +38,7 @@ values: # 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 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 index f2b9db002db..658f631a99a 100644 --- 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 @@ -14,6 +14,9 @@ # 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. @@ -52,8 +55,9 @@ # 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. The repealed Montana medical schedule - # would have deducted all 16,000. + # 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: @@ -507,3 +511,35 @@ 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 87e9387afb3..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: @@ -21,6 +22,7 @@ input: people: person1: + taxable_interest_income: 5_000 us_govt_interest_person: 5_000 tax_units: tax_unit1: @@ -38,6 +40,7 @@ input: people: person1: + taxable_interest_income: 5_000 us_govt_interest_person: 5_000 tax_units: tax_unit1: diff --git a/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py index d87fa9f1bba..c3e708a3c55 100644 --- a/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py +++ b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py @@ -8,22 +8,31 @@ 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 twice in one simulation: once with -us_govt_interest_person = u on the bondholder and once with 0. Only that -classification input differs, so federal income and deductions match across -the pair. For every household and each year from 2024 to 2026: - -1. Accounting: Montana taxable income falls by exactly min(u_eff, TI_0), - where TI_0 is Montana taxable income without the subtraction. u_eff = u - when the head or spouse holds the bonds and 0 when a dependent does. - The dependent case is intended: irs_gross_income zeroes every income - source of a tax-unit dependent, so a dependent's interest never enters - the return, and the return subtracts none of it. -2. Monotone: Montana income tax does not rise with the subtraction. -3. Bound: Montana taxable income is nonnegative. - -Taxable income is integral here (integer inputs and deductions), so float32 -represents it exactly; tax comparisons allow one cent. +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 +u <= I on the same person, the input contract the model relies on. + +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, such as US interest above taxable +interest or interest excluded under 26 U.S.C. 135. + +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 @@ -33,10 +42,15 @@ from policyengine_us import Simulation YEARS = (2024, 2025, 2026) -AMOUNT_TOLERANCE = 0.01 +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"]: @@ -48,15 +62,21 @@ def us_interest(case): return round(case["interest"] * case["us_share"]) -def build_situation(cases): +def build_situation(cases, years): people, tax_units, marital_units, households = {}, {}, {}, {} def by_year(value): - return {year: value for year in YEARS} + return {year: value for year in years} for i, case in enumerate(cases): holder = bondholder(case) - for variant, amount in (("with", us_interest(case)), ("without", 0)): + 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}", @@ -80,7 +100,7 @@ def by_year(value): "is_tax_unit_dependent": by_year(True), } owner = {"head": head, "spouse": spouse, "dependent": dependent}[holder] - people[owner]["taxable_interest_income"] = by_year(case["interest"]) + 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, @@ -106,30 +126,46 @@ def by_year(value): } -def calculate(cases): - """Return {year: (taxable income, tax)}, each shaped [case, with/without].""" - simulation = Simulation(situation=build_situation(cases)) - results = {} - for year in YEARS: - taxable_income = np.asarray( - simulation.calculate("mt_taxable_income", year) - ).reshape(len(cases), 2) - tax = np.asarray(simulation.calculate("mt_income_tax", year)).reshape( - len(cases), 2 - ) - results[year] = taxable_income, tax - return results +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, (taxable_income, tax) in calculate(cases).items(): + 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): - u_eff = 0 if bondholder(case) == "dependent" else us_interest(case) - ti_with, ti_without = taxable_income[i] - context = (year, case, taxable_income[i], tax[i]) + 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 - assert ti_without - ti_with == min(u_eff, ti_without), context - assert tax[i][0] <= tax[i][1] + AMOUNT_TOLERANCE, 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): @@ -146,7 +182,8 @@ def case(joint, holder, wages, interest, us_share, **kwargs): # The subtraction exceeds taxable income before it (the zero floor binds), -# a dependent holds the bonds, and interior cuts, single and joint. +# 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), @@ -166,6 +203,7 @@ def case(joint, holder, wages, interest, us_share, **kwargs): mortgage=30_000, age=70, ), + case(False, "head", 89_511, 5_000, 1.0, age=70), ] @@ -180,9 +218,9 @@ def test_worked_subtractions(): case(True, "spouse", 80_000, 3_000, 1.0), case(False, "dependent", 40_000, 10_000, 1.0), ] - results = calculate(cases) - taxable_income_2024 = results[2024][0] - taxable_income_2025 = results[2025][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 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: From 71a44844b19a1c38ca28ef7b8c3417b575aa538a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 11 Nov 2026 00:58:35 -0500 Subject: [PATCH 3/3] State the full input contract in the Montana US-interest test docstring Interest excluded under 26 U.S.C. 135 satisfies u <= taxable interest, so the contract also needs "none of it excluded under section 135" (review nit). Co-Authored-By: Claude Opus 5.5 --- .../tests/test_mt_us_govt_interest_subtraction.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py index c3e708a3c55..45b01ee3206 100644 --- a/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py +++ b/policyengine_us/tests/test_mt_us_govt_interest_subtraction.py @@ -12,7 +12,8 @@ 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 -u <= I on the same person, the input contract the model relies on. +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. @@ -27,8 +28,8 @@ 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, such as US interest above taxable -interest or interest excluded under 26 U.S.C. 135. +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