From b825dabcfa4025169c2cde533d42485c268fff67 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Mon, 13 Jul 2026 10:53:38 -0400 Subject: [PATCH] Apply MT taxable Social Security base amounts once per return (#9011) mt_taxable_social_security ran the 2021-2023 Form 2 Taxable Social Security Benefits Schedule per person but took the line 10/12 base amounts by tax-unit filing status, so a married-joint couple got $32,000/$12,000 each ($64,000/$24,000 combined) and the separate-on-same-form columns never got their $16,000/$6,000 amounts. Now the person-level variable (separate columns) uses the half-of-joint base amounts, and a new mt_taxable_social_security_joint runs the schedule once on combined income with the full joint base amounts for the single-column joint path. Co-Authored-By: Claude Opus 4.8 --- .../mt-taxable-ss-thresholds-9011.fixed.md | 1 + .../gov/states/mt/tax/income/integration.yaml | 28 +++++++ .../income/mt_taxable_social_security.yaml | 24 +++++- .../states/mt/tax/income/base/mt_agi_joint.py | 2 +- .../income/base/mt_taxable_social_security.py | 22 +++++- .../base/mt_taxable_social_security_joint.py | 78 +++++++++++++++++++ 6 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 changelog.d/mt-taxable-ss-thresholds-9011.fixed.md create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py diff --git a/changelog.d/mt-taxable-ss-thresholds-9011.fixed.md b/changelog.d/mt-taxable-ss-thresholds-9011.fixed.md new file mode 100644 index 00000000000..b16cf8f18bb --- /dev/null +++ b/changelog.d/mt-taxable-ss-thresholds-9011.fixed.md @@ -0,0 +1 @@ +Fixed the Montana taxable Social Security schedule to apply the filing-status base amounts once per return rather than in full per spouse, using the half-of-joint amounts per column for married filers and the full joint amounts for the single-column joint computation. diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml index 42a09bb5f67..5d22075dccc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml @@ -511,3 +511,31 @@ mt_regular_income_tax_joint: 1_918 mt_capital_gains_tax_joint: 1_022.8 mt_income_tax: 2_940.8 + +- name: MT 2021 joint elderly couple taxable SS uses the schedule thresholds once, not per spouse (taxsim 1078) + period: 2021 + absolute_error_margin: 5 + input: + people: + head: + age: 70 + employment_income: 22_432.51 + taxable_interest_income: 18_181.87 + taxable_private_pension_income: 4_484.76 + social_security_retirement: 40_195.39 + spouse: + age: 71 + employment_income: 670.88 + taxable_interest_income: 18_181.86 + taxable_private_pension_income: 4_484.75 + social_security_retirement: 40_195.38 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + # TaxAct status 2a columns: 34,166 + 20,539 + mt_taxable_social_security: [34_166, 20_539] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security.yaml index 8c06e4a846c..f8622cf7218 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/mt_taxable_social_security.yaml @@ -1,4 +1,7 @@ -- name: Test case 1, filing jointly, uses amount on line 18. +# Married filers compute the schedule per column (married filing separately +# on the same form), so each spouse's column uses the half-of-joint base +# amounts ($16,000/$6,000), fully taxing this benefit at 85% ($850). +- name: Test case 1, married per-column computation uses the separate base amounts. period: 2021 input: social_security: 1_000 @@ -11,7 +14,24 @@ filing_status: JOINT state_code: MT output: - mt_taxable_social_security: 500 + mt_taxable_social_security: 850 + +# Joint filers run the schedule once with the full joint base amounts +# ($32,000/$12,000), so the same income is only partially taxed ($500). +- name: Test case 1b, joint single-column computation uses the full joint base amounts. + period: 2021 + input: + social_security: 1_000 + irs_gross_income: 50_000 + taxable_social_security: 200 + tax_exempt_interest_income: 100 + mt_applicable_ald_deductions: 5_000 + student_loan_interest_ald: 300 + mt_subtractions: 2_000 + filing_status: JOINT + state_code: MT + output: + mt_taxable_social_security_joint: 500 - name: Test case 2, filing single. period: 2021 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py index 96e49ffc3c1..56cdef43d71 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_agi_joint.py @@ -31,7 +31,7 @@ def formula(tax_unit, period, parameters): if p.applies: # 2023 and before: apply lines 21-24 adjustment for social security taxable_ss = add(tax_unit, period, ["taxable_social_security"]) - mt_taxable_ss = add(tax_unit, period, ["mt_taxable_social_security"]) + mt_taxable_ss = tax_unit("mt_taxable_social_security_joint", period) adjusted_mt_ss_difference = mt_taxable_ss - taxable_ss tax_unit_mt_agi = max_(reduced_agi + adjusted_mt_ss_difference, 0) else: diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security.py index ee6d393b9c7..0e99c6213ce 100644 --- a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security.py +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security.py @@ -47,13 +47,31 @@ def formula(person, period, parameters): ) # line 10: get amount based on filing status filing_status = person.tax_unit("filing_status", period) - threshold_amount = p.lower[filing_status] + statuses = filing_status.possible_values + # Montana's Taxable Social Security Benefits Schedule computes married + # taxpayers in separate columns, applying half the joint base amounts + # to each spouse's column (Form 2, page 6, lines 10 and 12: married + # filing separately enters $16,000/$6,000 in columns A and B). Map + # joint filers to the married-filing-separately thresholds so the base + # amounts are applied once per couple rather than in full per spouse. + uses_separate_threshold = filing_status == statuses.JOINT + lower_amount = where( + uses_separate_threshold, + p.lower["SEPARATE"], + p.lower[filing_status], + ) + upper_amount = where( + uses_separate_threshold, + p.upper["SEPARATE"], + p.upper[filing_status], + ) + threshold_amount = lower_amount # line 11: line 9 - line 10 (income_reduced_by_subtractions - threshold_amount) income_reduced_by_subtractions_and_threshold = max_( 0, income_reduced_by_subtractions - threshold_amount ) # line 12: get amount based on filing status - amount_lower = p.upper[filing_status] - p.lower[filing_status] + amount_lower = upper_amount - lower_amount # lien 13: line 11 - line 12 minimum_tax_threshold = max_( 0, income_reduced_by_subtractions_and_threshold - amount_lower diff --git a/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py new file mode 100644 index 00000000000..9527fc68d2c --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/base/mt_taxable_social_security_joint.py @@ -0,0 +1,78 @@ +from policyengine_us.model_api import * + + +class mt_taxable_social_security_joint(Variable): + value_type = float + entity = TaxUnit + label = "Montana taxable social security benefits for joint filers" + defined_for = StateCode.MT + definition_period = YEAR + reference = ( + "https://mtrevenue.gov/wp-content/uploads/mdocs/form%202%202021.pdf#page=6" + ) + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.mt.tax.income.social_security.amount + p_irs = parameters(period).gov.irs.social_security.taxability.rate + # Joint filers run the Taxable Social Security Benefits Schedule once + # in a single column, combining both spouses' lines 1-9 and applying + # the joint base amounts once (Form 2, page 6). + # line 1 total net SS amount + social_security = add(tax_unit, period, ["social_security"]) + # line 2 SS multiplied by the base rate + social_security_benefits_fraction = social_security * p_irs.base.benefit_cap + # line 3: irs_gross_income - taxable_social_security + gross_income = add(tax_unit, period, ["irs_gross_income"]) + taxable_ss = add(tax_unit, period, ["taxable_social_security"]) + reduced_gross_income = max_(gross_income - taxable_ss, 0) + # line 5: tax exempt interest income + tax_exempt_interest_income = add( + tax_unit, period, ["tax_exempt_interest_income"] + ) + # line 6: Sum of line 2, 3, 4, 5 + income_increased_by_ss_and_interest = ( + social_security_benefits_fraction + + reduced_gross_income + + tax_exempt_interest_income + ) + # line 7: Remove the student loans from the above the line deductions + ald_less_student_loan = add( + tax_unit, period, ["mt_applicable_ald_deductions"] + ) - add(tax_unit, period, ["student_loan_interest_ald"]) + # line 8: Montana subtractions + ald + subtractions = add(tax_unit, period, ["mt_subtractions"]) + increased_subtractions = subtractions + ald_less_student_loan + # line 9: line 6 - line 8, if line 8 >= line 6, return 0 + income_reduced_by_subtractions = max_( + 0, income_increased_by_ss_and_interest - increased_subtractions + ) + # line 10: joint base amount applied once + filing_status = tax_unit("filing_status", period) + threshold_amount = p.lower[filing_status] + # line 11: line 9 - line 10 + income_reduced_by_subtractions_and_threshold = max_( + 0, income_reduced_by_subtractions - threshold_amount + ) + # line 12: joint span applied once + amount_lower = p.upper[filing_status] - p.lower[filing_status] + # line 13: line 11 - line 12 + minimum_tax_threshold = max_( + 0, income_reduced_by_subtractions_and_threshold - amount_lower + ) + # line 14 & 15 + capped_reduced_income = min_( + income_reduced_by_subtractions_and_threshold, amount_lower + ) + minimum_tax_threshold_fraction = capped_reduced_income * p_irs.base.excess + # line 16 + smaller_fraction = min_( + minimum_tax_threshold_fraction, social_security_benefits_fraction + ) + # line 17 & 18: line_13 * 0.85 + line_16 + adjusted_tax_amount = ( + minimum_tax_threshold * p_irs.additional.benefit_cap + smaller_fraction + ) + # line 19: line_1 * 0.85 + adjusted_taxable_amount = social_security * p_irs.additional.excess + # line 20 + return min_(adjusted_taxable_amount, adjusted_tax_amount)