From bf0f863bf6ac30dcf5c4588908bc0d9a72555bb0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 22:14:14 -0400 Subject: [PATCH 1/6] Restrict Michigan military survivor subtraction to spouses --- .../mi-military-survivor-spouse-only.fixed.md | 1 + .../states/mi/tax/income/subtractions.yaml | 4 +- .../mi/tax/income/mi_taxable_income.yaml | 45 +++++++++++++++++++ ...erest_dividends_capital_gains_deduction.py | 2 +- ..._expanded_retirement_benefits_deduction.py | 2 +- ...ment_benefits_deduction_tier_one_amount.py | 2 +- ...uction_tier_three_ss_exempt_not_retired.py | 4 +- ..._deduction_tier_three_ss_exempt_retired.py | 4 +- .../mi_standard_deduction_tier_three.py | 4 +- .../mi_standard_deduction_tier_two.py | 2 +- .../mi_military_retirement_subtraction.py | 22 +++++++++ 11 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 changelog.d/mi-military-survivor-spouse-only.fixed.md create mode 100644 policyengine_us/variables/gov/states/mi/tax/income/mi_military_retirement_subtraction.py diff --git a/changelog.d/mi-military-survivor-spouse-only.fixed.md b/changelog.d/mi-military-survivor-spouse-only.fixed.md new file mode 100644 index 00000000000..7e3443d443f --- /dev/null +++ b/changelog.d/mi-military-survivor-spouse-only.fixed.md @@ -0,0 +1 @@ +Restrict Michigan's military survivor retirement subtraction to surviving spouses and use the eligible subtraction in retirement deduction worksheets. diff --git a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml index 4710ac468a7..b9d9ff8abd2 100644 --- a/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/mi/tax/income/subtractions.yaml @@ -2,7 +2,7 @@ description: Michigan subtracts these sources to federal adjusted gross income w values: 2017-01-01: - us_govt_interest # Line 10 - - military_retirement_income # Line 11 + - mi_military_retirement_subtraction # Line 11 - taxable_social_security # Line 14 - military_service_income # Line 14 - section_22_income # Line 22 @@ -14,7 +14,7 @@ values: - mi_other_subtractions 2026-01-01: - us_govt_interest # Line 10 - - military_retirement_income # Line 11 + - mi_military_retirement_subtraction # Line 11 - taxable_social_security # Line 14 - military_service_income # Line 14 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_taxable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_taxable_income.yaml index 9bc049de866..13cb0d4198b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_taxable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_taxable_income.yaml @@ -19,3 +19,48 @@ state_code: MI output: # 20_000 - 19_000 - 5_000 < 0 mi_taxable_income: 0 + +- name: A non-spouse military survivor pays Michigan tax on benefits from a deceased parent. + period: 2024 + input: + age: 20 + employment_income: 40_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + state_code: MI + output: + adjusted_gross_income: 50_000 + mi_military_retirement_subtraction: 0 + mi_subtractions: 0 + mi_taxable_income: 44_400 + mi_income_tax_before_non_refundable_credits: 1_887 + +- name: A surviving spouse subtracts military survivor retirement benefits. + period: 2024 + input: + age: 40 + employment_income: 40_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: true + state_code: MI + output: + adjusted_gross_income: 50_000 + mi_military_retirement_subtraction: 10_000 + mi_subtractions: 10_000 + mi_taxable_income: 34_400 + mi_income_tax_before_non_refundable_credits: 1_462 + +- name: A military retiree subtracts their own pay without surviving-spouse status. + period: 2024 + input: + age: 40 + employment_income: 40_000 + military_retirement_pay: 10_000 + is_surviving_spouse: false + state_code: MI + output: + adjusted_gross_income: 50_000 + mi_military_retirement_subtraction: 10_000 + mi_subtractions: 10_000 + mi_taxable_income: 34_400 + mi_income_tax_before_non_refundable_credits: 1_462 diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py index 298e32bf081..b7ad93fae0d 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.py @@ -35,7 +35,7 @@ def formula(tax_unit, period, parameters): person, period, [ - "military_retirement_income", + "mi_military_retirement_subtraction", "taxable_pension_income", ], ) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.py index ad383d90463..761fee0a84a 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.py @@ -54,7 +54,7 @@ def formula(tax_unit, period, parameters): # Line 1 tier_one_cap = p.tier_one.amount[filing_status] head_or_spouse_military_retirement_pay = ( - person("military_retirement_income", period) * is_head_or_spouse + person("mi_military_retirement_subtraction", period) * is_head_or_spouse ) # Line 2 total_military_retirement_pay = tax_unit.sum( diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.py index e38dd186796..8e6aea6f8ee 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.py @@ -32,7 +32,7 @@ def formula(tax_unit, period, parameters): # Line 10 is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) military_retirement_pay = ( - person("military_retirement_income", period) * is_head_or_spouse + person("mi_military_retirement_subtraction", period) * is_head_or_spouse ) total_military_retirement_pay = tax_unit.sum(military_retirement_pay) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.py index bdc3aa18b3a..bde1d0eff4b 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.py @@ -63,7 +63,9 @@ def formula(tax_unit, period, parameters): # the tier one or tier three deduction amount # Worksheet 3.2 counts only military retirement income on the filer's Schedule 1, line 11, # which excludes a dependent's pay (not in the filer's AGI). - military_pay = person("military_retirement_income", period) * is_head_or_spouse + military_pay = ( + person("mi_military_retirement_subtraction", period) * is_head_or_spouse + ) military_retirement_pay_received = tax_unit.sum(military_pay) > 0 # Line 8 tier_one_amount = tax_unit( diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.py index 5a7ed0ced0f..87d5cac3b9f 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.py @@ -78,7 +78,9 @@ def formula(tax_unit, period, parameters): # the tier one or tier three deduction amount # Worksheet 3.1 counts only military retirement income on the filer's Schedule 1, line 11, # which excludes a dependent's pay (not in the filer's AGI). - military_pay = person("military_retirement_income", period) * is_head_or_spouse + military_pay = ( + person("mi_military_retirement_subtraction", period) * is_head_or_spouse + ) military_retirement_pay_eligible = tax_unit.sum(military_pay) > 0 # Line 8 tier_one_amount = tax_unit( diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.py index 059d5935ad1..cf03976a763 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.py @@ -31,7 +31,9 @@ def formula(tax_unit, period, parameters): person = tax_unit.members is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) # Line 2: enter military retirement pay and survivor benefits - military_retirement_income = person("military_retirement_income", period) + military_retirement_income = person( + "mi_military_retirement_subtraction", period + ) # Line 3: enter military service income or taxable social security taxable_ss = person("taxable_social_security", period) military_service_income = person("military_service_income", period) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.py b/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.py index d97feba92af..b4d341417a8 100644 --- a/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.py +++ b/policyengine_us/variables/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.py @@ -33,7 +33,7 @@ def formula(tax_unit, period, parameters): person, period, [ - "military_retirement_income", + "mi_military_retirement_subtraction", "military_service_income", ], ) diff --git a/policyengine_us/variables/gov/states/mi/tax/income/mi_military_retirement_subtraction.py b/policyengine_us/variables/gov/states/mi/tax/income/mi_military_retirement_subtraction.py new file mode 100644 index 00000000000..079de197a01 --- /dev/null +++ b/policyengine_us/variables/gov/states/mi/tax/income/mi_military_retirement_subtraction.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class mi_military_retirement_subtraction(Variable): + value_type = float + entity = Person + label = "Michigan military retirement subtraction" + unit = USD + definition_period = YEAR + defined_for = StateCode.MI + reference = ( + "https://www.legislature.mi.gov/documents/mcl/pdf/mcl-206-30.pdf", # (1)(e), (8)(c) + "https://www.michigan.gov/taxes/questions/iit/accordion/military/are-military-survivor-retirement-benefits-that-pass-to-a-beneficiary", + "https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2024/MI-1040-Instructions.pdf#page=13", + "https://www.michigan.gov/taxes/-/media/Project/Websites/taxes/Forms/IIT/TY2025/MI-1040-Book.pdf#page=12", + ) + + def formula(person, period, parameters): + own_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + return own_pay + survivor_pay * surviving_spouse From b2acaeb65a123d4b6a5fd1dec5135e239504d1ca Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 22:25:14 -0400 Subject: [PATCH 2/6] Test Michigan survivor income and retirement worksheet invariants --- ..._mi_military_survivor_income_invariants.py | 192 ++++++++++++++++++ .../mi_military_retirement_subtraction.yaml | 117 +++++++++++ 2 files changed, 309 insertions(+) create mode 100644 policyengine_us/tests/core/test_mi_military_survivor_income_invariants.py create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_military_retirement_subtraction.yaml diff --git a/policyengine_us/tests/core/test_mi_military_survivor_income_invariants.py b/policyengine_us/tests/core/test_mi_military_survivor_income_invariants.py new file mode 100644 index 00000000000..13828a3c576 --- /dev/null +++ b/policyengine_us/tests/core/test_mi_military_survivor_income_invariants.py @@ -0,0 +1,192 @@ +"""Non-spouse military survivor benefits stay in Michigan's tax base. + +Each year uses one vectorized simulation of paired tax units. The pairs +share every input except increasing military survivor benefits, including +benefits paid to dependents. Seeded incomes span the retirement deduction +caps and ages span Michigan's retirement tiers. This catches an exemption +that incorrectly leaves taxable income flat as well as deductions that +incorrectly count taxable survivor benefits against another deduction. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + + +SEED = 20261022 +TOLERANCE = 0.05 # dollars; model calculations use float32 arrays +FILER_AGES = [20, 40, 59, 62, 65, 67, 70, 75, 80, 85] + + +def _draw_units(year): + rng = np.random.default_rng(SEED + year) + units = [] + for i in range(24): + joint = i % 2 == 1 + + def filer(age): + return { + "age": age, + "employment_income": float(rng.integers(0, 80_001)), + "taxable_private_pension_income": float(rng.integers(0, 100_001)), + "military_retirement_pay": ( + 0.0 if i % 3 == 0 else float(rng.integers(0, 50_001)) + ), + "taxable_interest_income": float(rng.integers(0, 20_001)), + "retirement_benefits_from_ss_exempt_employment": ( + 10_000.0 if age >= 59 and i % 3 != 0 else 0.0 + ), + "year_of_retirement": 2008 if i % 4 == 0 else 2018, + "military_retirement_pay_survivors": float(rng.integers(0, 30_001)), + # Unchanged pairs check that units cannot affect each other. + "increment": 0.0 if i % 6 == 0 else float(rng.integers(1, 30_001)), + } + + head = filer(FILER_AGES[i % len(FILER_AGES)]) + spouse = filer(FILER_AGES[(i + 3) % len(FILER_AGES)]) if joint else None + dependents = [ + { + "age": 8 + j * 4, + "military_retirement_pay_survivors": float(rng.integers(0, 10_001)), + "increment": 0.0 if i % 6 == 0 else float(rng.integers(1, 10_001)), + } + for j in range(i % 3) + ] + units.append({"head": head, "spouse": spouse, "dependents": dependents}) + + # Guarantee a young filer above the zero floor, independently of the draw. + units[0] = { + "head": { + "age": 20, + "employment_income": 40_000.0, + "military_retirement_pay_survivors": 0.0, + "increment": 10_000.0, + }, + "spouse": None, + "dependents": [], + } + # This pair changes only a dependent's benefits, which belong on their + # own return and must not change the filer's AGI or Michigan deductions. + units[1]["head"]["increment"] = 0.0 + units[1]["spouse"]["increment"] = 0.0 + units[1]["dependents"][0]["increment"] = 10_000.0 + # Guarantee a pair whose small survivor increase remains below the tax + # base's zero floor, alongside the positive-base example above. + units[2]["head"] = { + "age": 20, + "employment_income": 0.0, + "military_retirement_pay_survivors": 0.0, + "increment": 1_000.0, + } + return units + + +def _paired_situation(units, year): + people, tax_units, marital_units = {}, {}, {} + households, families, spm_units = {}, {}, {} + for increased in (False, True): + for i, unit in enumerate(units): + key = f"{int(increased)}_{i}" + members, couple = [], [] + + def add_person(role, record): + name = f"{role}_{key}" + members.append(name) + person = { + variable: {year: amount} + for variable, amount in record.items() + if variable != "increment" + } + benefits = record["military_retirement_pay_survivors"] + person["military_retirement_pay_survivors"] = { + year: benefits + increased * record["increment"] + } + person.update( + { + "is_surviving_spouse": {year: False}, + "is_tax_unit_head": {year: role == "head"}, + "is_tax_unit_spouse": {year: role == "spouse"}, + "is_tax_unit_dependent": {year: role.startswith("dependent")}, + } + ) + people[name] = person + return name + + couple.append(add_person("head", unit["head"])) + if unit["spouse"] is not None: + couple.append(add_person("spouse", unit["spouse"])) + marital_units[f"couple_{key}"] = {"members": couple} + for j, record in enumerate(unit["dependents"]): + name = add_person(f"dependent_{j}", record) + marital_units[f"dependent_{key}_{j}"] = {"members": [name]} + tax_units[f"tax_unit_{key}"] = {"members": members} + households[f"household_{key}"] = { + "members": members, + "state_name": {year: "MI"}, + } + families[f"family_{key}"] = {"members": members} + spm_units[f"spm_unit_{key}"] = {"members": members} + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "households": households, + "families": families, + "spm_units": spm_units, + } + + +@pytest.mark.parametrize("year", [2024, 2025, 2026]) +def test_nonspouse_survivor_benefits_never_reduce_michigan_taxable_income(year): + units = _draw_units(year) + sim = Simulation(situation=_paired_situation(units, year)) + count = len(units) + + def paired(variable): + amounts = sim.calculate(variable, year) + return amounts[:count], amounts[count:] + + increments = np.array( + [ + unit["head"]["increment"] + + (unit["spouse"]["increment"] if unit["spouse"] is not None else 0) + for unit in units + ] + ) + # Child benefits are excluded from the filer's federal AGI; every dollar + # paid to the head or spouse is federally taxable on this return. + agi_before, agi_after = paired("adjusted_gross_income") + np.testing.assert_allclose( + agi_after - agi_before, increments, rtol=0, atol=TOLERANCE + ) + for variable in ("mi_subtractions", "mi_exemptions"): + before, after = paired(variable) + np.testing.assert_allclose( + after, + before, + rtol=0, + atol=TOLERANCE, + err_msg=f"Non-spouse survivor benefits changed {variable}", + ) + + before, after = paired("mi_taxable_income") + assert (after >= before - TOLERANCE).all() + above_floor = before > TOLERANCE + np.testing.assert_allclose( + after[above_floor] - before[above_floor], + increments[above_floor], + rtol=0, + atol=TOLERANCE, + err_msg="Non-spouse benefits must increase a positive Michigan tax base", + ) + unchanged_filer_income = increments == 0 + np.testing.assert_allclose( + after[unchanged_filer_income], + before[unchanged_filer_income], + rtol=0, + atol=TOLERANCE, + ) + assert (above_floor & (increments > 0)).any() + assert ((before == 0) & (after == 0) & (increments > 0)).any() + assert unchanged_filer_income.any() diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_military_retirement_subtraction.yaml new file mode 100644 index 00000000000..3eee26f1030 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/mi_military_retirement_subtraction.yaml @@ -0,0 +1,117 @@ +- name: Michigan subtracts the retiree's own military retirement pay + period: 2024 + input: + state_code: MI + military_retirement_pay: 10_000 + military_retirement_pay_survivors: 0 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 10_000 + +- name: Michigan subtracts military survivor benefits received by a surviving spouse + period: 2024 + input: + state_code: MI + military_retirement_pay: 0 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: true + output: + mi_military_retirement_subtraction: 10_000 + +- name: Michigan taxes military survivor benefits received by a non-spouse + period: 2024 + input: + state_code: MI + military_retirement_pay: 0 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 0 + +- name: Michigan evaluates survivor eligibility separately for each filer and excludes dependent pay + period: 2025 + input: + people: + head: + age: 40 + employment_income: 40_000 + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: true + spouse: + age: 40 + military_retirement_pay: 5_000 + military_retirement_pay_survivors: 15_000 + is_surviving_spouse: false + dependent: + age: 15 + military_retirement_pay: 3_000 + military_retirement_pay_survivors: 5_000 + is_surviving_spouse: true + tax_units: + tax_unit: + members: [head, spouse, dependent] + marital_units: + couple: + members: [head, spouse] + child: + members: [dependent] + households: + household: + members: [head, spouse, dependent] + state_code: MI + output: + mi_military_retirement_subtraction: [30_000, 5_000, 8_000] + mi_subtractions: 35_000 + +- name: Taxable non-spouse survivor benefits do not reduce the 2024 expanded retirement cap + period: 2024 + input: + state_code: MI + age: 62 + taxable_private_pension_income: 50_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 0 + # Worksheet 3.3 uses only the military subtraction on Schedule 1 line 11. + # The full $64,040 cap times 50% remains available for civilian pensions. + mi_expanded_retirement_benefits_deduction: 32_020 + mi_retirement_benefits_deduction_tier_one_amount: 50_000 + +- name: Taxable non-spouse survivor benefits do not reduce the 2025 expanded retirement cap + period: 2025 + input: + state_code: MI + age: 62 + taxable_private_pension_income: 50_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 0 + # $65,897 times 75%, with no military subtraction to offset the cap. + mi_expanded_retirement_benefits_deduction: 49_422.75 + +- name: Taxable non-spouse survivor benefits do not reduce a senior's private pension cap + period: 2024 + input: + state_code: MI + age: 82 + taxable_private_pension_income: 70_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 0 + mi_retirement_benefits_deduction_tier_one_amount: 64_040 + +- name: Taxable non-spouse survivor benefits do not reduce a senior's investment income deduction + period: 2024 + input: + state_code: MI + age: 82 + taxable_interest_income: 20_000 + military_retirement_pay_survivors: 10_000 + is_surviving_spouse: false + output: + mi_military_retirement_subtraction: 0 + mi_interest_dividends_capital_gains_deduction: 14_274 From db7b2a15014af651a272cee6e73e552f12cfd29e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 22:33:56 -0400 Subject: [PATCH 3/6] Apply state-specific military survivor eligibility rules --- .../mi-military-survivor-spouse-only.fixed.md | 2 +- .../retirement_benefits/income_sources.yaml | 2 +- .../taxable_income/subtractions/sources.yaml | 6 +- .../survivors_eligible.yaml | 12 +++ .../income/agi/subtractions/subtractions.yaml | 2 +- .../taxable_retirement_income/sources.yaml | 6 +- .../az_military_retirement_subtraction.yaml | 29 +++++++ .../id_retirement_benefits_deduction.yaml | 33 ++++++++ .../id_military_retirement_deduction.yaml | 83 +++++++++++++++++++ .../in_military_retirement_deduction.yaml | 30 +++++++ ...est_dividends_capital_gains_deduction.yaml | 1 + ...xpanded_retirement_benefits_deduction.yaml | 1 + ...nt_benefits_deduction_tier_one_amount.yaml | 1 + ...tion_tier_three_ss_exempt_not_retired.yaml | 1 + ...eduction_tier_three_ss_exempt_retired.yaml | 1 + .../mi_standard_deduction_tier_three.yaml | 1 + .../mi_standard_deduction_tier_two.yaml | 1 + .../nd_military_retirement_subtraction.yaml | 57 +++++++++++++ ...orces_retirement_pay_exemption_person.yaml | 64 ++++++++++++++ .../ri_military_retirement_subtraction.yaml | 59 +++++++++++++ .../ri_retirement_income_subtraction.yaml | 43 ++++++++++ .../sc_military_deduction_survivors.yaml | 41 ++++++++- .../sc_retirement_deduction_survivors.yaml | 18 ++++ .../va_military_benefit_subtraction.yaml | 68 +++++++++++++++ .../az_military_retirement_subtraction.py | 8 +- .../id_eligible_military_retirement_income.py | 26 ++++++ .../id_military_retirement_deduction.py | 4 +- .../in_military_retirement_deduction.py | 7 +- .../nd_military_retirement_subtraction.py | 20 +++++ ..._forces_retirement_pay_exemption_person.py | 13 ++- .../ri_military_retirement_subtraction.py | 24 ++++++ .../ri_retirement_income_subtraction.py | 5 ++ .../sc_military_deduction_survivors.py | 8 +- .../sc_retirement_deduction_survivors.py | 10 ++- .../va_military_benefit_subtraction_person.py | 14 ++-- 35 files changed, 675 insertions(+), 26 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/nm/tax/income/exemptions/armed_forces_retirement_pay/survivors_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.yaml create mode 100644 policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_eligible_military_retirement_income.py create mode 100644 policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.py create mode 100644 policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.py diff --git a/changelog.d/mi-military-survivor-spouse-only.fixed.md b/changelog.d/mi-military-survivor-spouse-only.fixed.md index 7e3443d443f..1a8ce543d78 100644 --- a/changelog.d/mi-military-survivor-spouse-only.fixed.md +++ b/changelog.d/mi-military-survivor-spouse-only.fixed.md @@ -1 +1 @@ -Restrict Michigan's military survivor retirement subtraction to surviving spouses and use the eligible subtraction in retirement deduction worksheets. +Restrict military survivor retirement tax exemptions to eligible surviving spouses in Arizona, Idaho, Indiana, Michigan, North Dakota, New Mexico, Rhode Island, South Carolina, and Virginia. Apply New Mexico's 2025 survivor eligibility date and use Michigan's eligible subtraction in retirement deduction worksheets. diff --git a/policyengine_us/parameters/gov/states/id/tax/income/deductions/retirement_benefits/income_sources.yaml b/policyengine_us/parameters/gov/states/id/tax/income/deductions/retirement_benefits/income_sources.yaml index e640086326a..f4a8c8f7f81 100644 --- a/policyengine_us/parameters/gov/states/id/tax/income/deductions/retirement_benefits/income_sources.yaml +++ b/policyengine_us/parameters/gov/states/id/tax/income/deductions/retirement_benefits/income_sources.yaml @@ -2,7 +2,7 @@ description: Idaho counts the following income sources under the retirement bene values: 2021-01-01: - public_pension_income - - military_retirement_income + - id_eligible_military_retirement_income 2025-01-01: - public_pension_income diff --git a/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml b/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml index 1529671dcf1..09aaf7f05b6 100644 --- a/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml +++ b/policyengine_us/parameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yaml @@ -5,7 +5,7 @@ values: - nd_ltcg_subtraction # long-term capital gains - nd_qdiv_subtraction # qualified dividends - taxable_social_security - - military_retirement_income + - nd_military_retirement_subtraction - nd_529_deduction - nd_stillborn_subtraction - nd_other_subtractions @@ -15,7 +15,7 @@ values: - nd_ltcg_subtraction # long-term capital gains - nd_qdiv_subtraction # qualified dividends - taxable_social_security - - military_retirement_income + - nd_military_retirement_subtraction - nd_529_deduction - nd_stillborn_subtraction - nd_other_subtractions @@ -25,7 +25,7 @@ values: - nd_ltcg_subtraction # long-term capital gains - nd_qdiv_subtraction # qualified dividends - taxable_social_security - - military_retirement_income + - nd_military_retirement_subtraction - military_service_income # military pay exclusion - nd_529_deduction - nd_stillborn_subtraction diff --git a/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/armed_forces_retirement_pay/survivors_eligible.yaml b/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/armed_forces_retirement_pay/survivors_eligible.yaml new file mode 100644 index 00000000000..ca0d874063e --- /dev/null +++ b/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/armed_forces_retirement_pay/survivors_eligible.yaml @@ -0,0 +1,12 @@ +description: New Mexico allows surviving spouses to claim the armed forces retirement pay exemption when this is true. +values: + 2022-01-01: false + 2025-01-01: true + +metadata: + label: New Mexico armed forces retirement surviving spouse eligibility + unit: bool + period: year + reference: + - title: 2024 HB 252, Sections 32 and 42 (PDF pages 102 and 147), amending NMSA 7-2-5.13 and setting applicability + href: https://www.nmlegis.gov/Sessions/24%20Regular/final/HB0252.PDF#page=102 diff --git a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml index ea4b9bf49ca..afb8cd1d13c 100644 --- a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yaml @@ -13,7 +13,7 @@ values: - ri_tuition_saving_program_contribution_subtraction - ri_retirement_income_subtraction - us_govt_interest - - military_retirement_income + - ri_military_retirement_subtraction - ri_other_subtractions metadata: diff --git a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/sources.yaml b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/sources.yaml index b9e9db7b587..d004981f19e 100644 --- a/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/sources.yaml +++ b/policyengine_us/parameters/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/sources.yaml @@ -5,10 +5,6 @@ values: - military_retirement_income - taxable_401k_distributions - taxable_403b_distributions - 2023-01-01: - - taxable_pension_income - - taxable_401k_distributions - - taxable_403b_distributions metadata: unit: list period: year @@ -18,3 +14,5 @@ metadata: href: https://webserver.rilegislature.gov/Statutes/TITLE44/44-30/44-II/44-30-12.htm - title: Rhode Island Division of Taxation Publication 2021-02 Line 5b href: https://tax.ri.gov/sites/g/files/xkgbur541/files/notice/Pub_2021_02_pension_income_guide_04_06_21.pdf#page=5 + - title: Rhode Island Division of Taxation Publication 2024-01, pension and annuity income modification + href: https://tax.ri.gov/sites/g/files/xkgbur541/files/2024-03/PUB_2024-01_retirement_income_guide.pdf#page=7 diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml index 6cfdf96d24a..bb7f985438e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml @@ -119,3 +119,32 @@ state_code: AZ output: az_military_retirement_subtraction: 9_000 + +- name: Arizona excludes a surviving spouse's military survivor benefits. + period: 2024 + input: + state_code: AZ + is_surviving_spouse: true + military_retirement_pay_survivors: 10_000 + output: + az_military_retirement_subtraction: 10_000 + +- name: Arizona does not exclude a non-spouse survivor's military benefits. + period: 2024 + input: + state_code: AZ + age: 20 + is_surviving_spouse: false + military_retirement_pay_survivors: 10_000 + output: + az_military_retirement_subtraction: 0 + +- name: Arizona retains own military retirement pay without excluding non-spouse survivor benefits. + period: 2024 + input: + state_code: AZ + is_surviving_spouse: false + military_retirement_pay: 30_000 + military_retirement_pay_survivors: 10_000 + output: + az_military_retirement_subtraction: 30_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/retirement_benefits/id_retirement_benefits_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/retirement_benefits/id_retirement_benefits_deduction.yaml index e219b830b1f..72f0567011f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/retirement_benefits/id_retirement_benefits_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/deductions/retirement_benefits/id_retirement_benefits_deduction.yaml @@ -102,3 +102,36 @@ state_code: ID output: id_retirement_benefits_deduction: 30_000 + +- name: Idaho general retirement deduction excludes non-spouse military survivor benefits before 2025. + period: 2024 + input: + state_code: ID + age: 65 + is_surviving_spouse: false + military_retirement_pay_survivors: 10_000 + output: + id_retirement_benefits_deduction_relevant_income: 0 + id_retirement_benefits_deduction: 0 + +- name: Idaho general retirement deduction includes an unremarried surviving spouse before 2025. + period: 2024 + input: + state_code: ID + age: 65 + is_surviving_spouse: true + military_retirement_pay_survivors: 10_000 + output: + id_retirement_benefits_deduction_relevant_income: 10_000 + id_retirement_benefits_deduction: 10_000 + +- name: Idaho general retirement deduction retains own military pay before 2025. + period: 2024 + input: + state_code: ID + age: 65 + is_surviving_spouse: false + military_retirement_pay: 10_000 + output: + id_retirement_benefits_deduction_relevant_income: 10_000 + id_retirement_benefits_deduction: 10_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.yaml index 901804ef8e0..3bb0e02ec91 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.yaml @@ -88,6 +88,7 @@ people: person1: is_tax_unit_head_or_spouse: true + is_surviving_spouse: true military_retirement_pay_survivors: 25_000 age: 65 tax_units: @@ -101,3 +102,85 @@ output: id_military_retirement_deduction: 25_000 +- name: Idaho military retirement deduction excludes a non-spouse survivor. + period: 2025 + input: + state_code: ID + age: 65 + is_surviving_spouse: false + military_retirement_pay_survivors: 25_000 + output: + id_military_retirement_deduction: 0 + +- name: Idaho military retirement deduction keeps own pay when non-spouse survivor benefits are present. + period: 2025 + input: + state_code: ID + age: 65 + is_surviving_spouse: false + military_retirement_pay: 30_000 + military_retirement_pay_survivors: 25_000 + output: + id_military_retirement_deduction: 30_000 + +- name: Idaho military survivor deduction excludes a remarried survivor filing separately. + period: 2025 + input: + people: + survivor: + is_surviving_spouse: true + military_retirement_pay_survivors: 25_000 + new_spouse: {} + families: + family: + members: [survivor, new_spouse] + marital_units: + couple: + members: [survivor, new_spouse] + tax_units: + survivor_return: + members: [survivor] + filing_status: SEPARATE + spouse_return: + members: [new_spouse] + filing_status: SEPARATE + households: + household: + members: [survivor, new_spouse] + state_code: ID + output: + id_military_retirement_deduction: [0, 0] + +- name: Idaho unmarried widow remains eligible when a married adult child shares the family. + period: 2025 + input: + people: + survivor: + age: 65 + is_surviving_spouse: true + military_retirement_pay_survivors: 25_000 + adult_child: + age: 40 + child_spouse: + age: 40 + families: + family: + members: [survivor, adult_child, child_spouse] + marital_units: + widow: + members: [survivor] + couple: + members: [adult_child, child_spouse] + tax_units: + survivor_return: + members: [survivor] + filing_status: SINGLE + child_return: + members: [adult_child, child_spouse] + filing_status: JOINT + households: + household: + members: [survivor, adult_child, child_spouse] + state_code: ID + output: + id_military_retirement_deduction: [25_000, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_military_retirement_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_military_retirement_deduction.yaml index 5d7d2005361..033fd22097c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_military_retirement_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/in/tax/income/deductions/in_military_retirement_deduction.yaml @@ -21,6 +21,7 @@ person1: military_retirement_pay: 30_000 person2: + is_surviving_spouse: true military_retirement_pay_survivors: 10_000 tax_units: tax_unit: @@ -31,3 +32,32 @@ state_code: IN output: in_military_retirement_deduction: 40_000 + +- name: Indiana non-spouse survivor benefits do not qualify for the military retirement deduction. + period: 2024 + input: + age: 20 + state_code: IN + is_surviving_spouse: false + military_retirement_pay_survivors: 10_000 + output: + in_military_retirement_deduction: 0 + +- name: Indiana service member can deduct own pay without deducting non-spouse survivor benefits. + period: 2024 + input: + state_code: IN + is_surviving_spouse: false + military_retirement_pay: 30_000 + military_retirement_pay_survivors: 10_000 + output: + in_military_retirement_deduction: 30_000 + +- name: Indiana surviving spouse benefits retain the 2021 phase-in. + period: 2021 + input: + state_code: IN + is_surviving_spouse: true + military_retirement_pay_survivors: 10_000 + output: + in_military_retirement_deduction: 6_250 + 0.75 * (10_000 - 6_250) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml index b58c60f5e55..b57dc766323 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/interest_dividends_capital_gains/mi_interest_dividends_capital_gains_deduction.yaml @@ -127,6 +127,7 @@ taxable_pension_income: 10_000 military_retirement_pay: 500 military_retirement_pay_survivors: 500 + is_surviving_spouse: true taxable_interest_income: 5_000 dividend_income: 400 capital_gains: 100 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.yaml index 3839bf0b58a..7ffcd0ae033 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/expanded/mi_expanded_retirement_benefits_deduction.yaml @@ -206,6 +206,7 @@ person1: taxable_pension_income: 20_000 military_retirement_pay_survivors: 1_000 + is_surviving_spouse: true is_tax_unit_head_or_spouse: true tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.yaml index c6e1780f293..68d3aba2430 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_one/mi_retirement_benefits_deduction_tier_one_amount.yaml @@ -470,6 +470,7 @@ taxable_private_pension_income: 50_000 military_retirement_pay: 10_000 military_retirement_pay_survivors: 10_000 + is_surviving_spouse: true is_tax_unit_head_or_spouse: true tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.yaml index 040f0f15a38..66a380bc2af 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/not_retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_not_retired.yaml @@ -303,6 +303,7 @@ person1: taxable_pension_income: 40_000 military_retirement_pay_survivors: 1_000 + is_surviving_spouse: true is_tax_unit_head_or_spouse: true tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.yaml index a4dc58d4a61..b7104cc8fd9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/retirement/tier_three/ss_exempt/retired/mi_retirement_benefits_deduction_tier_three_ss_exempt_retired.yaml @@ -162,6 +162,7 @@ person1: taxable_pension_income: 40_000 military_retirement_pay_survivors: 1_000 + is_surviving_spouse: true is_tax_unit_head_or_spouse: true tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.yaml index cb513bc35af..dc3b0247fec 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_three/mi_standard_deduction_tier_three.yaml @@ -165,6 +165,7 @@ person1: military_retirement_pay: 1_000 military_retirement_pay_survivors: 500 + is_surviving_spouse: true military_service_income: 500 taxable_social_security: 100 is_tax_unit_head_or_spouse: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.yaml b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.yaml index f62ff181535..bd4819556fd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mi/tax/income/deductions/standard/tier_two/mi_standard_deduction_tier_two.yaml @@ -76,6 +76,7 @@ person1: military_retirement_pay: 3_000 military_retirement_pay_survivors: 2_000 + is_surviving_spouse: true is_tax_unit_head_or_spouse: true tax_units: tax_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.yaml new file mode 100644 index 00000000000..ab91d1bdb8a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.yaml @@ -0,0 +1,57 @@ +- name: North Dakota does not subtract a nonspouse survivor's benefits on their own return. + period: 2024 + input: + state_code: ND + age: 20 + is_surviving_spouse: false + employment_income: 40_000 + military_retirement_pay_survivors: 10_000 + output: + adjusted_gross_income: 50_000 + nd_military_retirement_subtraction: 0 + nd_subtractions: 0 + nd_taxable_income: 35_400 + +- name: North Dakota subtracts a surviving spouse's military survivor benefits. + period: 2024 + input: + state_code: ND + age: 50 + is_surviving_spouse: true + military_retirement_pay_survivors: 10_000 + output: + nd_military_retirement_subtraction: 10_000 + nd_subtractions: 10_000 + +- name: North Dakota subtracts own military retirement pay without requiring surviving spouse status. + period: 2024 + input: + state_code: ND + age: 60 + is_surviving_spouse: false + military_retirement_pay: 20_000 + output: + nd_military_retirement_subtraction: 20_000 + nd_subtractions: 20_000 + +- name: North Dakota keeps own retirement pay separate from a nonspouse survivor annuity. + period: 2025 + input: + state_code: ND + is_surviving_spouse: false + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + output: + nd_military_retirement_subtraction: 20_000 + nd_subtractions: 20_000 + +- name: North Dakota combines own retirement and eligible surviving spouse benefits. + period: 2025 + input: + state_code: ND + is_surviving_spouse: true + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + output: + nd_military_retirement_subtraction: 30_000 + nd_subtractions: 30_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.yaml index 439b2215b0e..91544b953c2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.yaml @@ -33,3 +33,67 @@ is_tax_unit_head_or_spouse: false output: nm_armed_forces_retirement_pay_exemption_person: 0 + +- name: New Mexico does not exempt a nonspouse survivor annuity in 2024. + period: 2024 + input: + state_code: NM + age: 20 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 0 + +- name: New Mexico does not exempt a surviving spouse's annuity before 2025. + period: 2024 + input: + state_code: NM + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 0 + +- name: New Mexico exempts a surviving spouse's annuity starting in 2025. + period: 2025 + input: + state_code: NM + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 10_000 + +- name: New Mexico does not extend the 2025 survivor exemption to nonspouses. + period: 2025 + input: + state_code: NM + age: 20 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 0 + +- name: New Mexico keeps own retirement pay separate from nonspouse survivor benefits. + period: 2025 + input: + state_code: NM + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay: 25_000 + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 25_000 + +- name: New Mexico applies one cap to own pay and eligible surviving spouse benefits. + period: 2025 + input: + state_code: NM + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay: 25_000 + military_retirement_pay_survivors: 10_000 + output: + nm_armed_forces_retirement_pay_exemption_person: 30_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.yaml new file mode 100644 index 00000000000..fcd6f492584 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.yaml @@ -0,0 +1,59 @@ +- name: Rhode Island does not subtract a nonspouse survivor annuity on the survivor's own return. + period: 2024 + input: + state_code: RI + age: 20 + is_surviving_spouse: false + employment_income: 40_000 + military_retirement_pay_survivors: 10_000 + output: + adjusted_gross_income: 50_000 + ri_military_retirement_subtraction: 0 + ri_subtractions: 0 + ri_agi: 50_000 + +- name: Rhode Island subtracts surviving spouse annuities starting in 2023. + period: 2023 + input: + state_code: RI + age: 50 + is_surviving_spouse: true + military_retirement_pay_survivors: 10_000 + output: + ri_military_retirement_subtraction: 10_000 + ri_subtractions: 10_000 + +- name: Rhode Island subtracts own military retirement pay without surviving spouse status. + period: 2024 + input: + state_code: RI + age: 60 + is_surviving_spouse: false + military_retirement_pay: 20_000 + output: + ri_military_retirement_subtraction: 20_000 + ri_subtractions: 20_000 + +- name: Rhode Island's separate military subtraction is unavailable before 2023. + period: 2022 + input: + state_code: RI + age: 50 + is_surviving_spouse: true + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + output: + ri_military_retirement_subtraction: 0 + ri_subtractions: 0 + +- name: Rhode Island keeps own retirement pay separate from a nonspouse survivor annuity. + period: 2025 + input: + state_code: RI + age: 60 + is_surviving_spouse: false + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + output: + ri_military_retirement_subtraction: 20_000 + ri_subtractions: 20_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.yaml index cf0fb9772ce..5d99228c5bb 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.yaml @@ -148,3 +148,46 @@ military_retirement_pay_survivors: 5_000 output: ri_retirement_income_subtraction: 15_000 + +- name: 2024 - Nonspouse survivor benefits remain eligible under the general pension rules. + period: 2024 + input: + state_code: RI + age: 75 + is_surviving_spouse: false + ri_retirement_income_subtraction_eligible: true + taxable_pension_income: 15_000 + military_retirement_pay_survivors: 10_000 + output: + ri_military_retirement_subtraction: 0 + ri_retirement_income_subtraction: 20_000 + ri_subtractions: 20_000 + +- name: 2024 - Own military pay is excluded once and nonspouse survivor benefits use the general pension cap. + period: 2024 + input: + state_code: RI + age: 75 + is_surviving_spouse: false + ri_retirement_income_subtraction_eligible: true + taxable_pension_income: 10_000 + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 5_000 + output: + ri_military_retirement_subtraction: 20_000 + ri_retirement_income_subtraction: 15_000 + ri_subtractions: 35_000 + +- name: 2025 - Surviving spouse military benefits cannot be counted again in the general pension subtraction. + period: 2025 + input: + state_code: RI + age: 75 + is_surviving_spouse: true + ri_retirement_income_subtraction_eligible: true + taxable_pension_income: 10_000 + military_retirement_pay_survivors: 20_000 + output: + ri_military_retirement_subtraction: 20_000 + ri_retirement_income_subtraction: 10_000 + ri_subtractions: 30_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.yaml index e0b3ea4a9fa..6a7bb85b71b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.yaml @@ -2,6 +2,7 @@ period: 2022 input: military_retirement_pay_survivors: 11_000 + is_surviving_spouse: true state_code: SC output: sc_military_deduction_survivors: 11_000 @@ -35,13 +36,14 @@ sc_military_deduction_survivors: [0, 0] sc_military_deduction: 0 -- name: The head's survivor benefits are deducted and the dependent's are not. +- name: The surviving spouse head's benefits are deducted and the dependent's are not. period: 2025 input: people: person1: age: 60 military_retirement_pay_survivors: 9_000 + is_surviving_spouse: true person2: age: 15 is_tax_unit_dependent: true @@ -61,3 +63,40 @@ output: sc_military_deduction_survivors: [9_000, 0] sc_military_deduction: 9_000 + +- name: South Carolina does not subtract a nonspouse survivor annuity on the survivor's own return. + period: 2024 + input: + state_code: SC + age: 20 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + sc_military_deduction_survivors: 0 + sc_military_deduction: 0 + +- name: South Carolina's spouse condition also applies before the 2022 military deduction expansion. + period: 2021 + input: + state_code: SC + age: 20 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + sc_military_deduction_survivors: 0 + +- name: South Carolina keeps own retirement pay separate from a nonspouse survivor annuity. + period: 2025 + input: + state_code: SC + age: 60 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay: 20_000 + military_retirement_pay_survivors: 10_000 + output: + sc_military_deduction_indv: 20_000 + sc_military_deduction_survivors: 0 + sc_military_deduction: 20_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml index fd7fdae44c7..35c7e56f1da 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml @@ -3,6 +3,7 @@ input: pension_survivors: 1_000 military_retirement_pay_survivors: 4_000 + is_surviving_spouse: true age: 70 state_code: SC output: @@ -13,6 +14,7 @@ input: pension_survivors: 4_000 military_retirement_pay_survivors: 6_000 + is_surviving_spouse: true age: 64 state_code: SC output: @@ -23,6 +25,7 @@ input: pension_survivors: 2_000 military_retirement_pay_survivors: 4_000 + is_surviving_spouse: true age: 70 state_code: SC output: @@ -33,11 +36,26 @@ input: pension_survivors: 4_000 military_retirement_pay_survivors: 6_000 + is_surviving_spouse: true age: 64 state_code: SC output: sc_retirement_deduction_survivors: 0 +- name: Military survivor income that was not deducted does not reduce the general pension cap. + period: 2021 + input: + pension_survivors: 4_000 + military_retirement_pay_survivors: 6_000 + # Isolate the worksheet offset from the separately tested military + # eligibility calculation; line p-6 contains only an allowed deduction. + sc_military_deduction_survivors: 0 + is_surviving_spouse: true + age: 64 + state_code: SC + output: + sc_retirement_deduction_survivors: 3_000 + # Only the head's and spouse's survivor benefits are in the filer's federal # AGI; a dependent reports their own on their own return. - name: A dependent's survivor pension is not deducted from the filer's income. diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction.yaml index 5aee17fc803..e683997670a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction.yaml @@ -249,3 +249,71 @@ output: # Under $40,000 limit, full amount subtracted va_military_benefit_subtraction: 35_000 + +- name: Virginia does not subtract a nonspouse survivor annuity on the survivor's own return. + period: 2024 + input: + state_code: VA + age: 20 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + va_military_benefit_subtraction: 0 + +- name: Virginia subtracts an eligible surviving spouse annuity without an age minimum in 2024. + period: 2024 + input: + state_code: VA + age: 40 + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 10_000 + output: + va_military_benefit_subtraction: 10_000 + +- name: Virginia's surviving spouse subtraction retains the age minimum in 2023. + period: 2023 + input: + state_code: VA + age: 54 + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 25_000 + output: + va_military_benefit_subtraction: 0 + +- name: Virginia applies the 2023 cap to an age-eligible surviving spouse. + period: 2023 + input: + state_code: VA + age: 55 + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay_survivors: 25_000 + output: + va_military_benefit_subtraction: 20_000 + +- name: Virginia keeps own retirement pay separate from a nonspouse survivor annuity. + period: 2025 + input: + state_code: VA + age: 60 + is_surviving_spouse: false + is_tax_unit_head_or_spouse: true + military_retirement_pay: 30_000 + military_retirement_pay_survivors: 20_000 + output: + va_military_benefit_subtraction: 30_000 + +- name: Virginia applies one cap to own pay and eligible surviving spouse benefits. + period: 2025 + input: + state_code: VA + age: 60 + is_surviving_spouse: true + is_tax_unit_head_or_spouse: true + military_retirement_pay: 30_000 + military_retirement_pay_survivors: 20_000 + output: + va_military_benefit_subtraction: 40_000 diff --git a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py index c4f78c6d0ee..b830c6c2824 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py +++ b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py @@ -10,6 +10,7 @@ class az_military_retirement_subtraction(Variable): "https://www.azleg.gov/ars/43/01022.htm", "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2022_140BOOKLET.pdf#page=25", "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2021_140BOOKLET.pdf#page=27", + "https://azdor.gov/sites/default/files/document/FORMS_INDIVIDUAL_2025_140i.pdf", ) definition_period = YEAR defined_for = StateCode.AZ @@ -18,7 +19,12 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.az.tax.income.subtractions.military_retirement person = tax_unit.members head_or_spouse = person("is_tax_unit_head_or_spouse", period) - military_retirement_income = person("military_retirement_income", period) + # Form 140 line 29b extends the military retirement subtraction to + # payments received by a deceased veteran's surviving spouse. + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + military_retirement_income = retirement_pay + survivor_pay * surviving_spouse eligible_military_pay = military_retirement_income * head_or_spouse capped_military_pay = min_(eligible_military_pay, p.max_amount) return tax_unit.sum(capped_military_pay) diff --git a/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_eligible_military_retirement_income.py b/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_eligible_military_retirement_income.py new file mode 100644 index 00000000000..972031a7113 --- /dev/null +++ b/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_eligible_military_retirement_income.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class id_eligible_military_retirement_income(Variable): + value_type = float + entity = Person + label = "Idaho eligible military retirement and survivor income" + unit = USD + definition_period = YEAR + defined_for = StateCode.ID + reference = ( + "https://legislature.idaho.gov/statutesrules/idstat/title63/t63ch30/sect63-3022a/", + "https://legislature.idaho.gov/wp-content/uploads/sessioninfo/2025/legislation/H0479.pdf", + ) + + def formula(person, period, parameters): + # Section 63-3022A covers the retired member and the member's + # unremarried widow or widower, including under the 2025 amendment. + surviving_spouse = person("is_surviving_spouse", period) + # Marital units contain one unmarried person or a married couple, + # independently of other relatives and of how spouses file taxes. + married = person.marital_unit.nb_persons() > 1 + eligible_survivor = surviving_spouse & ~married + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + return retirement_pay + survivor_pay * eligible_survivor diff --git a/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.py b/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.py index 8d1c74736b6..1551022b6f8 100644 --- a/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.py +++ b/policyengine_us/variables/gov/states/id/tax/income/subtractions/military_retirement/id_military_retirement_deduction.py @@ -17,7 +17,9 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.id.tax.income.subtractions.military_retirement person = tax_unit.members head_or_spouse = person("is_tax_unit_head_or_spouse", period) - military_retirement_income = person("military_retirement_income", period) + military_retirement_income = person( + "id_eligible_military_retirement_income", period + ) eligible_military_pay = military_retirement_income * head_or_spouse # Multiply by applies parameter (False when military retirement diff --git a/policyengine_us/variables/gov/states/in/tax/income/deductions/in_military_retirement_deduction.py b/policyengine_us/variables/gov/states/in/tax/income/deductions/in_military_retirement_deduction.py index c2f10bb42c1..e779f327503 100644 --- a/policyengine_us/variables/gov/states/in/tax/income/deductions/in_military_retirement_deduction.py +++ b/policyengine_us/variables/gov/states/in/tax/income/deductions/in_military_retirement_deduction.py @@ -10,6 +10,7 @@ class in_military_retirement_deduction(Variable): reference = ( "http://iga.in.gov/legislative/laws/2021/ic/titles/006#6-3-2-4", "https://forms.in.gov/Download.aspx?id=16915#page=20", + "https://www.in.gov/dor/files/ib27.pdf", ) defined_for = StateCode.IN @@ -23,7 +24,11 @@ def formula(tax_unit, period, parameters): # Dependents' income is not in federal AGI; they report it on their # own return. head_or_spouse = person("is_tax_unit_head_or_spouse", period) - military_retirement = person("military_retirement_income", period) + # IC 6-3-2-4(a)(2) covers the service member and surviving spouse. + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + military_retirement = retirement_pay + survivor_pay * surviving_spouse excess = max_(0, military_retirement - p.base) person_deduction = min_(military_retirement, p.base) + p.excess_rate * excess return tax_unit.sum(person_deduction * head_or_spouse) diff --git a/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.py b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.py new file mode 100644 index 00000000000..1cd20c8e999 --- /dev/null +++ b/policyengine_us/variables/gov/states/nd/tax/income/subtractions/nd_military_retirement_subtraction.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class nd_military_retirement_subtraction(Variable): + value_type = float + entity = Person + label = "North Dakota military retirement subtraction" + unit = USD + definition_period = YEAR + defined_for = StateCode.ND + reference = ( + "https://ndlegis.gov/cencode/t57c38.pdf#page=35", # 57-38-30.3(2)(r) + "https://www.tax.nd.gov/military-service-members", + ) + + def formula(person, period, parameters): + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + return retirement_pay + survivor_pay * surviving_spouse diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.py index 96301c66b50..d34111f6f19 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_armed_forces_retirement_pay_exemption_person.py @@ -7,14 +7,23 @@ class nm_armed_forces_retirement_pay_exemption_person(Variable): label = "New Mexico armed forces retirement pay exemption per person " unit = USD definition_period = YEAR - reference = "https://law.justia.com/codes/new-mexico/2024/chapter-7/article-2/section-7-2-5-13/" + reference = ( + # NMSA 7-2-5.13; HB 252 sections 32 and 42 (PDF pages 102, 147) + # extend eligibility to surviving spouses for tax years beginning in 2025. + "https://www.nmlegis.gov/Sessions/24%20Regular/final/HB0252.PDF#page=102", + ) defined_for = StateCode.NM def formula(person, period, parameters): p = parameters( period ).gov.states.nm.tax.income.exemptions.armed_forces_retirement_pay - armed_forces_retirement_pay = person("military_retirement_income", period) + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + armed_forces_retirement_pay = ( + retirement_pay + survivor_pay * surviving_spouse * p.survivors_eligible + ) capped_amount = min_(armed_forces_retirement_pay, p.cap) qualifies = person("is_tax_unit_head_or_spouse", period) diff --git a/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.py b/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.py new file mode 100644 index 00000000000..5dda1163ea7 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/ri_military_retirement_subtraction.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class ri_military_retirement_subtraction(Variable): + value_type = float + entity = Person + label = "Rhode Island military service pension subtraction" + unit = USD + definition_period = YEAR + defined_for = StateCode.RI + reference = ( + # R.I. Gen. Laws 44-30-12(c)(11); the Division of Taxation's + # interpretation extends the exemption to surviving spouses. + "https://webserver.rilegislature.gov/Statutes/TITLE44/44-30/44-II/44-30-12.htm", + "https://tax.ri.gov/sites/g/files/xkgbur541/files/2024-03/PUB_2024-01_retirement_income_guide.pdf#page=16", + "https://tax.ri.gov/sites/g/files/xkgbur541/files/2025-01/PUB_2025-01_Retirement_Income_Guide.pdf#page=17", + "https://tax.ri.gov/sites/g/files/xkgbur541/files/2026-02/PUB_2026-01_Retirement_Income_Guide.pdf#page=15", + ) + + def formula_2023_01_01(person, period, parameters): + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + return retirement_pay + survivor_pay * surviving_spouse diff --git a/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.py b/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.py index b2c3eb06faa..c89f5ead2cf 100644 --- a/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.py +++ b/policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction.py @@ -18,6 +18,11 @@ def formula(tax_unit, period, parameters): ).gov.states.ri.tax.income.agi.subtractions.taxable_retirement_income person = tax_unit.members retirement_income = add(person, period, p.sources) + # Military pensions deducted under the separate military subtraction + # cannot also use the general pension exclusion. Other survivor + # annuities remain eligible for the general age and income rules. + military_subtraction = person("ri_military_retirement_subtraction", period) + retirement_income = max_(retirement_income - military_subtraction, 0) head_or_spouse = person("is_tax_unit_head_or_spouse", period) # Cap applies per person, not per tax unit capped_per_person = min_(retirement_income, p.cap) * head_or_spouse diff --git a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.py b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.py index debbbc42f47..b9e7754b2b5 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/military_retirement/sc_military_deduction_survivors.py @@ -9,6 +9,7 @@ class sc_military_deduction_survivors(Variable): unit = USD reference = ( "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1171(C) + "https://dor.sc.gov/sites/dor/files/policies/RR22-11.pdf", "https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=17", ) definition_period = YEAR @@ -17,4 +18,9 @@ def formula(person, period, parameters): # Only the head's and spouse's survivor benefits are in the filer's # federal AGI; a dependent reports their own on their own return. head_or_spouse = person("is_tax_unit_head_or_spouse", period) - return person("military_retirement_pay_survivors", period) * head_or_spouse + surviving_spouse = person("is_surviving_spouse", period) + return ( + person("military_retirement_pay_survivors", period) + * head_or_spouse + * surviving_spouse + ) diff --git a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py index 2bd3dc6155d..96367cc68ee 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py @@ -9,6 +9,7 @@ class sc_retirement_deduction_survivors(Variable): unit = USD reference = ( "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1170(A)(1) + "https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2021.pdf", "https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=17", ) definition_period = YEAR @@ -18,13 +19,14 @@ def formula(person, period, parameters): age = person("age", period) # line 1 max_deduction_allowed = p.cap.calc(age) - # line 2 - military_retirement_pay_survivors = person( - "military_retirement_pay_survivors", period + # Line 2 uses the military deduction claimed on line p-6, rather + # than taxable survivor benefits that were not deducted. + military_retirement_deduction = person( + "sc_military_deduction_survivors", period ) # line 3 retirement_deduction_available = max_( - max_deduction_allowed - military_retirement_pay_survivors, 0 + max_deduction_allowed - military_retirement_deduction, 0 ) # line 4: only the head's and spouse's survivor benefits are in the # filer's federal AGI; a dependent reports their own on their own diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction_person.py index e969fff99c7..1d75ab16025 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_military_benefit_subtraction_person.py @@ -8,14 +8,18 @@ class va_military_benefit_subtraction_person(Variable): defined_for = StateCode.VA unit = USD definition_period = YEAR - reference = "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/" + reference = ( + "https://law.lis.virginia.gov/vacode/title58.1/chapter3/section58.1-322.02/", + "https://www.tax.virginia.gov/military-benefits-faq", + ) def formula(person, period, parameters): p = parameters(period).gov.states.va.tax.income.subtractions.military_benefit - subtractable = min_( - person("military_retirement_income", period), - p.amount, - ) + retirement_pay = person("military_retirement_pay", period) + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + military_benefits = retirement_pay + survivor_pay * surviving_spouse + subtractable = min_(military_benefits, p.amount) eligible = person("is_tax_unit_head_or_spouse", period) if p.availability: From 294069c0687ef3e31214707a14873f9814af4b91 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 22:30:46 +0000 Subject: [PATCH 4/6] Gate SC's survivor retirement deduction on surviving-spouse status 12-6-1170 gives the survivor deduction to the original owner's surviving spouse (RR21-13 denies it to other beneficiaries); once the military gate zeroed the 2021 offset, a non-spouse with survivor pension got up to $3,000. The 2021 worksheet's reduction by the military deduction is dropped because 12-6-1170(C)(1) and RR22-11 exclude a military retiree's surviving spouse from it, and the unused toggle parameter goes with it. The maximum follows the deceased spouse's would-be age (2021-2022 worksheet line p-3, line 1) through a new deceased_spouse_age input that defaults to the person's own age. The SC and RI conflicts go to the candidate backlog. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FjKTA223kkKGWSie6 --- .../mi-military-survivor-spouse-only.fixed.md | 2 +- .../retirement/subtract_military.yaml | 27 ------- .../code_health/test_source_conflicts.py | 1 + .../sc_retirement_deduction_survivors.yaml | 76 ++++++++++++++++++- .../sc_retirement_deduction_survivors.py | 43 +++++------ .../demographic/person/deceased_spouse_age.py | 17 +++++ source_conflicts/candidates.yaml | 59 ++++++++++++++ 7 files changed, 172 insertions(+), 53 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/sc/tax/income/subtractions/retirement/subtract_military.yaml create mode 100644 policyengine_us/variables/household/demographic/person/deceased_spouse_age.py diff --git a/changelog.d/mi-military-survivor-spouse-only.fixed.md b/changelog.d/mi-military-survivor-spouse-only.fixed.md index 1a8ce543d78..b5a01fd9d57 100644 --- a/changelog.d/mi-military-survivor-spouse-only.fixed.md +++ b/changelog.d/mi-military-survivor-spouse-only.fixed.md @@ -1 +1 @@ -Restrict military survivor retirement tax exemptions to eligible surviving spouses in Arizona, Idaho, Indiana, Michigan, North Dakota, New Mexico, Rhode Island, South Carolina, and Virginia. Apply New Mexico's 2025 survivor eligibility date and use Michigan's eligible subtraction in retirement deduction worksheets. +Restrict military survivor retirement tax exemptions to eligible surviving spouses in Arizona, Idaho, Indiana, Michigan, North Dakota, New Mexico, Rhode Island, South Carolina, and Virginia. Apply New Mexico's 2025 survivor eligibility date and use Michigan's eligible subtraction in retirement deduction worksheets. Limit South Carolina's general survivor retirement deduction to surviving spouses, base its maximum on the deceased spouse's age, and drop the 2021 reduction by the military survivor deduction. diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/retirement/subtract_military.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/retirement/subtract_military.yaml deleted file mode 100644 index 207fc239a9c..00000000000 --- a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/retirement/subtract_military.yaml +++ /dev/null @@ -1,27 +0,0 @@ -description: South Carolina subtracts the survivors retirement deduction from the military retirement deduction if this is true. -metadata: - unit: bool - label: South Carolina choice to subtract survivors retirement deduction from military retirement deduction - reference: - - title: SC 1040 tax form instructions (2025) - href: https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2025.pdf#page=12 - - title: Instructions for Form SC-1040 (line 3p) - href: https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=17 - - title: Instructions for Form SC-1040 (line 3p) - href: https://dor.sc.gov/forms-site/Forms/IITPacket_2022.pdf#page=19 - - title: SC-1040 2021 - href: https://dor.sc.gov/forms-site/Forms/SC1040_2021.pdf#page=2 - - title: SC-1040 2022 - href: https://dor.sc.gov/forms-site/Forms/SC1040_2022.pdf#page=2 - # The law indicates the 2022 case. We model calculation of the survivors retirement deduction in 2021 based on the tax form, - # where the retirement deduction is subtracts from military retirement deduction. - - title: South Carolina State SECTION 12-6-1170(C)(1) - href: https://www.scstatehouse.gov/code/t12c006.php - - title: SC 1040 tax form instructions (2023) - href: https://dor.sc.gov/forms-site/Forms/IITPacket_2023.pdf#page=25 - - title: SC 1040 tax form instructions (2024) - href: https://dor.sc.gov/forms-site/Forms/SC1040Instr_2024.pdf#page=12 - -values: - 2021-01-01: true - 2022-01-01: false diff --git a/policyengine_us/tests/code_health/test_source_conflicts.py b/policyengine_us/tests/code_health/test_source_conflicts.py index 901b7b37215..90fcbd1fdfa 100644 --- a/policyengine_us/tests/code_health/test_source_conflicts.py +++ b/policyengine_us/tests/code_health/test_source_conflicts.py @@ -164,6 +164,7 @@ "policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.py": 1, "policyengine_us/variables/gov/states/mn/tax/income/subtractions/mn_k12_education_subtraction.py": 1, "policyengine_us/variables/gov/states/mo/dese/ccs/eligibility/mo_ccs_income_eligible.py": 1, + "policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py": 1, "policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.py": 1, "policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_categorical_eligible.py": 1, "policyengine_us/variables/gov/territories/pr/tax/income/pr_agi.py": 1, diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml index 35c7e56f1da..93e451c162e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml @@ -31,6 +31,9 @@ output: sc_retirement_deduction_survivors: 2_000 +# 12-6-1170(C)(1) excludes a military retiree's surviving spouse from the +# 2021 worksheet's reduction for military retirement, so the $6,000 military +# survivor deduction does not reduce the $3,000 maximum. - name: Case 4, in 2021, below age threshold period: 2021 input: @@ -40,7 +43,9 @@ age: 64 state_code: SC output: - sc_retirement_deduction_survivors: 0 + sc_military_deduction_survivors: 6_000 + # min(4_000, 3_000) + sc_retirement_deduction_survivors: 3_000 - name: Military survivor income that was not deducted does not reduce the general pension cap. period: 2021 @@ -83,3 +88,72 @@ state_code: SC output: sc_retirement_deduction_survivors: [0, 0] + +- name: A 2021 filer who is not a surviving spouse gets no survivor deduction. + period: 2021 + input: + employment_income: 40_000 + pension_survivors: 4_000 + military_retirement_pay_survivors: 6_000 + is_surviving_spouse: false + age: 64 + state_code: SC + output: + sc_military_deduction_survivors: 0 + sc_retirement_deduction_survivors: 0 + +- name: The same 2021 filer as a surviving spouse keeps the survivor deduction. + period: 2021 + input: + employment_income: 40_000 + pension_survivors: 4_000 + military_retirement_pay_survivors: 6_000 + is_surviving_spouse: true + age: 64 + state_code: SC + output: + sc_military_deduction_survivors: 6_000 + # min(4_000, 3_000); no military reduction for a surviving spouse. + sc_retirement_deduction_survivors: 3_000 + +- name: A 2021 surviving spouse over 65 is not reduced by military survivor benefits. + period: 2021 + input: + pension_survivors: 12_000 + military_retirement_pay_survivors: 6_000 + is_surviving_spouse: true + age: 70 + state_code: SC + output: + sc_military_deduction_survivors: 6_000 + # min(12_000, 10_000). The 2021 worksheet would give + # min(12_000, 10_000 - 6_000) = 4_000. + sc_retirement_deduction_survivors: 10_000 + sc_retirement_deduction: 10_000 + sc_military_deduction: 6_000 + +- name: The survivor maximum follows the deceased spouse's age, below 65. + period: 2022 + input: + pension_survivors: 5_000 + is_surviving_spouse: true + age: 70 + deceased_spouse_age: 60 + state_code: SC + output: + # The deceased spouse would be 60, so the maximum is $3,000 though the + # survivor is 70. + sc_retirement_deduction_survivors: 3_000 + +- name: The survivor maximum follows the deceased spouse's age, 65 or older. + period: 2022 + input: + pension_survivors: 5_000 + is_surviving_spouse: true + age: 60 + deceased_spouse_age: 66 + state_code: SC + output: + # The deceased spouse would be 66, so the maximum is $10,000 though the + # survivor is 60. + sc_retirement_deduction_survivors: 5_000 diff --git a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py index 96367cc68ee..a056c3bd1f1 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py @@ -8,7 +8,9 @@ class sc_retirement_deduction_survivors(Variable): defined_for = StateCode.SC unit = USD reference = ( - "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1170(A)(1) + "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1170(A)(1), (C)(1) + "https://dor.sc.gov/sites/dor/files/policies/RR21-13.pdf#page=11", + "https://dor.sc.gov/sites/dor/files/policies/RR22-11.pdf#page=39", "https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2021.pdf", "https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=17", ) @@ -16,30 +18,23 @@ class sc_retirement_deduction_survivors(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.sc.tax.income.subtractions.retirement - age = person("age", period) - # line 1 - max_deduction_allowed = p.cap.calc(age) - # Line 2 uses the military deduction claimed on line p-6, rather - # than taxable survivor benefits that were not deducted. - military_retirement_deduction = person( - "sc_military_deduction_survivors", period - ) - # line 3 - retirement_deduction_available = max_( - max_deduction_allowed - military_retirement_deduction, 0 - ) - # line 4: only the head's and spouse's survivor benefits are in the + # Line 1: the surviving spouse's maximum follows the age the deceased + # spouse would have reached had they lived. + deceased_spouse_age = person("deceased_spouse_age", period) + max_deduction_allowed = p.cap.calc(deceased_spouse_age) + # Source conflict: the 2021 SC1040 worksheet (line p-3, line 2) + # reduces this maximum by the military retirement deduction, but + # 12-6-1170(C)(1) and RR22-11 exclude a military retiree's surviving + # spouse from that reduction. Following the statute, no reduction + # applies in any year (candidate + # us-sc:2021-survivor-offset-surviving-spouse). + # Line 4: only the head's and spouse's survivor benefits are in the # filer's federal AGI; a dependent reports their own on their own - # return. + # return. The deduction passes only to the original owner's surviving + # spouse, not to other beneficiaries of an inherited account. head_or_spouse = person("is_tax_unit_head_or_spouse", period) + surviving_spouse = person("is_surviving_spouse", period) retirement_income_survivors = ( - person("pension_survivors", period) * head_or_spouse - ) - # In 2021, South Carolina subtracts the survivors retirement deduction from the military retirement deduction - # In 2022, it does not - cap = ( - retirement_deduction_available - if p.subtract_military - else max_deduction_allowed + person("pension_survivors", period) * head_or_spouse * surviving_spouse ) - return min_(retirement_income_survivors, cap) + return min_(retirement_income_survivors, max_deduction_allowed) diff --git a/policyengine_us/variables/household/demographic/person/deceased_spouse_age.py b/policyengine_us/variables/household/demographic/person/deceased_spouse_age.py new file mode 100644 index 00000000000..d735be35ea7 --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/deceased_spouse_age.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class deceased_spouse_age(Variable): + value_type = float + entity = Person + label = "Age the deceased spouse would have reached" + documentation = ( + "For a surviving spouse, the age the deceased spouse would have " + "reached by the end of the year had they lived. Without an input, " + "this takes the person's own age." + ) + unit = "year" + definition_period = YEAR + + def formula(person, period, parameters): + return person("age", period) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 8338e796c45..d445b890d64 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -5047,6 +5047,65 @@ candidates: state: unreviewed note: 'PE-US follows the manual; verify whether the unchecked insurance category covers these payments. Code not yet on main: ri_liheap countable income.' +- id: us-ri:pension-modification-agi-threshold-strict + program: income-tax + years: + from: 2021 + type: tax + question: Does a filer whose federal AGI equals the threshold qualify for the pension and annuity modification? + sources: + - kind: statute + citation: R.I. Gen. Laws § 44-30-12(c), income test for the taxable retirement income modification + url: https://webserver.rilegislature.gov/Statutes/TITLE44/44-30/44-II/44-30-12.htm + - kind: worksheet + citation: RI-1040 instructions, line 1t worksheet + url: https://tax.ri.gov/sites/g/files/xkgbur541/files/2025-12/2025%201040R%20Instructions%20122025.pdf#page=10 + readings: + statute: Federal AGI must be less than the threshold; AGI equal to it does not qualify. + other: The worksheet allows AGI less than or equal to the threshold. + example: A 2025 single filer born in 1950 with AGI of exactly $107,000 gets no modification under the statute and + qualifies under the worksheet. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/ri/tax/income/agi/subtractions/taxable_retirement_income/ri_retirement_income_subtraction_eligible.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10398 + state: ruled + note: The code already uses a strict comparison and the eligibility tests pin AGI equal to the threshold as + ineligible. Verify the statute's and the worksheet's wording before registering. +- id: us-sc:2021-survivor-offset-surviving-spouse + program: income-tax + years: + from: 2021 + to: 2021 + type: tax + question: Is a surviving spouse's 2021 retirement deduction maximum reduced by the military retirement deduction + attributable to the deceased spouse? + sources: + - kind: statute + citation: S.C. Code § 12-6-1170(C)(1) + url: https://www.scstatehouse.gov/code/t12c006.php + - kind: guidance + citation: SCDOR Revenue Ruling 22-11 + url: https://dor.sc.gov/sites/dor/files/policies/RR22-11.pdf#page=39 + - kind: worksheet + citation: 2021 SC1040 retirement deduction worksheet, line p-3, line 2 + url: https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=17 + readings: + statute: The reduction for military retirement does not apply to a military retiree's surviving spouse. + other: The 2021 worksheet subtracts the surviving spouse's military retirement deduction from the survivor + retirement deduction maximum. + example: A 2021 surviving spouse whose deceased spouse would be 64, with $4,000 of survivor pension and $6,000 of + military survivor benefits, deducts $3,000 under the statute and $0 under the worksheet. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10398 + state: ruled + note: RR22-11 agrees with the statute. PR 10398 removes the 2021 reduction and the parameter that toggled it. - id: us-sd:liheap-2025-published-income-limit program: liheap years: From 36d6029ce59f8c305d81432bcca8f0cf126f3893 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 00:06:59 +0000 Subject: [PATCH 5/6] Count SBP payments under Arizona's 43-1022(2) subtraction except a 2023+ surviving spouse's ITR 11-3 puts SBP benefits under 43-1022(2)'s $2,500 subtraction for federal plans. From 2023 the Form 140 instructions move a deceased veteran's surviving spouse's payments to line 29b. The previous head put every surviving spouse's payments on line 29b in every year and gave other recipients nothing, below what (2) allows. A new parameter dates the line 29b move, az_public_pension_exclusion now counts every other SBP payment, and the reading is recorded as a source-conflict candidate. Also cite 12-6-1170(A)(3) for South Carolina's deceased-spouse age rule and pin its 65 boundary with a case. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FM4VSMYJYahmqhhBo --- .../surviving_spouse_eligible.yaml | 15 +++++++ .../az_military_retirement_subtraction.yaml | 41 ++++++++++++++++++- .../az_public_pension_exclusion.yaml | 24 +++++++++++ .../sc_retirement_deduction_survivors.yaml | 12 ++++++ .../az_military_retirement_subtraction.py | 10 +++-- .../az_public_pension_exclusion.py | 14 ++++++- .../sc_retirement_deduction_survivors.py | 8 ++-- source_conflicts/candidates.yaml | 40 ++++++++++++++++++ 8 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/az/tax/income/subtractions/military_retirement/surviving_spouse_eligible.yaml diff --git a/policyengine_us/parameters/gov/states/az/tax/income/subtractions/military_retirement/surviving_spouse_eligible.yaml b/policyengine_us/parameters/gov/states/az/tax/income/subtractions/military_retirement/surviving_spouse_eligible.yaml new file mode 100644 index 00000000000..2a55ba932c1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/az/tax/income/subtractions/military_retirement/surviving_spouse_eligible.yaml @@ -0,0 +1,15 @@ +description: Arizona includes a deceased veteran's surviving spouse's military survivor benefit payments in the military retirement subtraction if this is true. +values: + 2018-01-01: false + 2023-01-01: true +metadata: + unit: bool + period: year + label: Arizona military retirement subtraction surviving spouse eligibility + reference: + - title: A.R.S. 43-1022(2), (26) + href: https://www.azleg.gov/ars/43/01022.htm + - title: Arizona Individual Income Tax Ruling ITR 11-3 + href: https://azdor.gov/sites/default/files/2023-03/RULINGS_INDV_2011_itr11-3.pdf#page=2 + - title: 2025 Arizona Form 140 Resident Personal Income Tax Instructions, line 29b + href: https://azdor.gov/sites/default/files/document/FORMS_INDIVIDUAL_2025_140i.pdf diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml index bb7f985438e..0735717adcf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml @@ -129,7 +129,7 @@ output: az_military_retirement_subtraction: 10_000 -- name: Arizona does not exclude a non-spouse survivor's military benefits. +- name: Arizona leaves a non-spouse survivor's SBP payments off line 29b and counts them toward the $2,500 public pension subtraction. period: 2024 input: state_code: AZ @@ -138,8 +138,9 @@ military_retirement_pay_survivors: 10_000 output: az_military_retirement_subtraction: 0 + az_public_pension_exclusion: 2_500 -- name: Arizona retains own military retirement pay without excluding non-spouse survivor benefits. +- name: Arizona subtracts own military retirement pay in full and a non-spouse survivor's SBP payments up to $2,500. period: 2024 input: state_code: AZ @@ -148,3 +149,39 @@ military_retirement_pay_survivors: 10_000 output: az_military_retirement_subtraction: 30_000 + az_public_pension_exclusion: 2_500 + +- name: In 2021, Arizona counts a surviving spouse's SBP payments toward the $2,500 public pension subtraction. + period: 2021 + input: + state_code: AZ + is_surviving_spouse: true + military_retirement_pay_survivors: 6_000 + output: + az_military_retirement_subtraction: 0 + # min(6,000, 2,500) = 2,500 under 43-1022(2) and ITR 11-3. ADOR moved a surviving spouse's payments to line 29b in 2023. + az_public_pension_exclusion: 2_500 + +- name: In 2022, Arizona counts a surviving spouse's SBP payments and public pension toward one $2,500 subtraction. + period: 2022 + input: + state_code: AZ + is_surviving_spouse: true + military_retirement_pay_survivors: 1_000 + taxable_public_pension_income: 2_000 + output: + az_military_retirement_subtraction: 0 + # min(1,000 + 2,000, 2,500) = 2,500. + az_public_pension_exclusion: 2_500 + +- name: In 2023, Arizona subtracts a surviving spouse's SBP payments in full on line 29b. + period: 2023 + input: + state_code: AZ + is_surviving_spouse: true + military_retirement_pay_survivors: 6_000 + output: + # Arizona sets no line 29b cap from 2021 (43-1022(26)) and leaves these payments out of the $2,500 subtraction. + az_military_retirement_subtraction: 6_000 + az_public_pension_exclusion: 0 + diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml index 5eb5a626fd8..8ad17b5d4b1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml @@ -96,3 +96,27 @@ state_code: AZ output: az_public_pension_exclusion: 5_000 + +- name: In 2021, Arizona counts a non-spouse survivor's SBP payments toward the $2,500 public pension subtraction. + period: 2021 + input: + state_code: AZ + age: 20 + is_surviving_spouse: false + military_retirement_pay_survivors: 6_000 + output: + az_military_retirement_subtraction: 0 + # min(6,000, 2,500) = 2,500 under 43-1022(2). Arizona puts no non-spouse survivor's payments on line 29b. + az_public_pension_exclusion: 2_500 + +- name: In 2023, Arizona still counts a non-spouse survivor's SBP payments toward the $2,500 public pension subtraction. + period: 2023 + input: + state_code: AZ + age: 20 + is_surviving_spouse: false + military_retirement_pay_survivors: 6_000 + output: + az_military_retirement_subtraction: 0 + # ADOR moved only a surviving spouse's payments to line 29b, so min(6,000, 2,500) = 2,500 under 43-1022(2). + az_public_pension_exclusion: 2_500 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml index 93e451c162e..51226624c68 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.yaml @@ -157,3 +157,15 @@ # The deceased spouse would be 66, so the maximum is $10,000 though the # survivor is 60. sc_retirement_deduction_survivors: 5_000 + +- name: A survivor gets the $10,000 maximum when the deceased spouse would have reached 65. + period: 2022 + input: + pension_survivors: 12_000 + is_surviving_spouse: true + age: 60 + deceased_spouse_age: 65 + state_code: SC + output: + # min(12_000, 10_000) + sc_retirement_deduction_survivors: 10_000 diff --git a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py index b830c6c2824..6f4787d8cd5 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py +++ b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py @@ -11,6 +11,7 @@ class az_military_retirement_subtraction(Variable): "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2022_140BOOKLET.pdf#page=25", "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2021_140BOOKLET.pdf#page=27", "https://azdor.gov/sites/default/files/document/FORMS_INDIVIDUAL_2025_140i.pdf", + "https://azdor.gov/sites/default/files/2023-03/RULINGS_INDV_2011_itr11-3.pdf#page=2", ) definition_period = YEAR defined_for = StateCode.AZ @@ -19,12 +20,15 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.az.tax.income.subtractions.military_retirement person = tax_unit.members head_or_spouse = person("is_tax_unit_head_or_spouse", period) - # Form 140 line 29b extends the military retirement subtraction to - # payments received by a deceased veteran's surviving spouse. + # In ITR 11-3, ADOR puts SBP benefits under 43-1022(2) + # (az_public_pension_exclusion). From 2023, ADOR moves a deceased + # veteran's surviving spouse's payments to line 29b in its Form 140 + # instructions. retirement_pay = person("military_retirement_pay", period) survivor_pay = person("military_retirement_pay_survivors", period) surviving_spouse = person("is_surviving_spouse", period) - military_retirement_income = retirement_pay + survivor_pay * surviving_spouse + on_line_29b = surviving_spouse & p.surviving_spouse_eligible + military_retirement_income = retirement_pay + survivor_pay * on_line_29b eligible_military_pay = military_retirement_income * head_or_spouse capped_military_pay = min_(eligible_military_pay, p.max_amount) return tax_unit.sum(capped_military_pay) diff --git a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.py b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.py index 6391cec2579..d564b3f0c30 100644 --- a/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.py +++ b/policyengine_us/variables/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.py @@ -11,6 +11,7 @@ class az_public_pension_exclusion(Variable): "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2022_140BOOKLET.pdf#page=18", "https://azdor.gov/sites/default/files/2023-03/FORMS_INDIVIDUAL_2021_140BOOKLET.pdf#page=23", "https://www.azleg.gov/ars/43/01022.htm", + "https://azdor.gov/sites/default/files/2023-03/RULINGS_INDV_2011_itr11-3.pdf#page=2", ) defined_for = StateCode.AZ @@ -18,7 +19,18 @@ def formula(tax_unit, period, parameters): p = parameters(period).gov.states.az.tax.income.subtractions.pension person = tax_unit.members head_or_spouse = person("is_tax_unit_head_or_spouse", period) - pension_income = person("taxable_public_pension_income", period) + # Arizona counts SBP annuities as benefits from a plan established by + # federal law (43-1022(2); ITR 11-3). From 2023, a surviving spouse + # reports these payments on line 29b instead. + p_military = parameters( + period + ).gov.states.az.tax.income.subtractions.military_retirement + survivor_pay = person("military_retirement_pay_survivors", period) + surviving_spouse = person("is_surviving_spouse", period) + on_line_29b = surviving_spouse & p_military.surviving_spouse_eligible + pension_income = person("taxable_public_pension_income", period) + where( + on_line_29b, 0, survivor_pay + ) eligible_pension_income = pension_income * head_or_spouse total_allowed_pension_exclusion = min_( p.public_pension_cap, eligible_pension_income diff --git a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py index a056c3bd1f1..3d84d79aba2 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/subtractions/retirement/sc_retirement_deduction_survivors.py @@ -8,7 +8,7 @@ class sc_retirement_deduction_survivors(Variable): defined_for = StateCode.SC unit = USD reference = ( - "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1170(A)(1), (C)(1) + "https://www.scstatehouse.gov/code/t12c006.php", # SECTION 12-6-1170(A)(1), (A)(3), (C)(1) "https://dor.sc.gov/sites/dor/files/policies/RR21-13.pdf#page=11", "https://dor.sc.gov/sites/dor/files/policies/RR22-11.pdf#page=39", "https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2021.pdf", @@ -18,8 +18,10 @@ class sc_retirement_deduction_survivors(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.sc.tax.income.subtractions.retirement - # Line 1: the surviving spouse's maximum follows the age the deceased - # spouse would have reached had they lived. + # Line 1: a surviving spouse applies the deduction "in the same manner + # that the deduction applied to the deceased spouse" (12-6-1170(A)(3)), + # so the survivor takes the maximum for the age the deceased spouse + # would have reached. deceased_spouse_age = person("deceased_spouse_age", period) max_deduction_allowed = p.cap.calc(deceased_spouse_age) # Source conflict: the 2021 SC1040 worksheet (line p-3, line 2) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index a0e33f978a4..22bf330252a 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -883,6 +883,46 @@ candidates: prs: [] state: unreviewed note: Verify the legal basis for the amended-return remedy and treatment of exactly $3,000. +- id: us-az:military-survivor-benefit-recipient + program: income-tax + years: + from: 2021 + type: tax + question: Does Arizona subtract Survivor Benefit Plan (SBP) payments under 43-1022(2)'s $2,500 subtraction for + federal plans or in full on line 29b under 43-1022(26), and does the answer differ for a deceased veteran's + surviving spouse and for other recipients? + sources: + - kind: statute + citation: A.R.S. 43-1022(2)(a) and (26) + url: https://www.azleg.gov/ars/43/01022.htm + - kind: guidance + citation: Arizona Individual Income Tax Ruling ITR 11-3 + url: https://azdor.gov/sites/default/files/2023-03/RULINGS_INDV_2011_itr11-3.pdf#page=2 + - kind: instructions + citation: 2025 Arizona Form 140 instructions, line 29b + url: https://azdor.gov/sites/default/files/document/FORMS_INDIVIDUAL_2025_140i.pdf + readings: + statute: Paragraph (2)(a) allows up to $2,500 of benefits from "any other retirement system or plan established + by federal law", except "retired or retainer pay of the uniformed services of the United States that qualifies + for a subtraction under paragraph 26". Paragraph (26) names no survivors. ITR 11-3 says "Arizona allows a + subtraction of up to $2,500 for uniformed services retired pay and SBP benefits." + other: From 2023, the Form 140 instructions put the payments of "the surviving spouse of a deceased military + veteran" on line 29b, which has no cap from 2021. + example: A 2021 surviving spouse with $6,000 of SBP payments and no other income subtracts $2,500 under (2) and + $0 on line 29b. In 2023 the same filer subtracts $6,000 on line 29b and $0 under (2). A non-spouse recipient + subtracts $2,500 under (2) in both years. + pe_us: + follows: gap-fill + code: + - policyengine_us/variables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.py + - policyengine_us/variables/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.py + - policyengine_us/parameters/gov/states/az/tax/income/subtractions/military_retirement/surviving_spouse_eligible.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10398 + state: ruled + note: PolicyEngine puts a surviving spouse's SBP payments under (2) through 2022 and on line 29b from 2023, where + paragraph (26) is silent on survivors and ADOR's instructions fill the gap. Every other recipient's payments stay + under (2) in every year, as its text and ITR 11-3 provide. - id: us-ca:capi-payment-standard-offset program: capi years: From e74e561be813296fc3898e723fc3e3ff1f409d9b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 4 Nov 2026 23:10:27 -0500 Subject: [PATCH 6/6] Pin dependents out of Arizona's public pension subtraction Add the case that fails if az_public_pension_exclusion drops its head or spouse filter: a child's $4,000 of SBP payments stays off the filer's subtraction, which takes only the head's $1,000. In the us-az:military-survivor-benefit-recipient candidate, move the ITR 11-3 quote from the statute's reading to the other sources, and give the note the textual reason for the gap-fill label: neither paragraph says whether a survivor's SBP payments count as annuities received as retired or retainer pay. Co-Authored-By: Claude Opus 5.5 --- .../az_military_retirement_subtraction.yaml | 1 - .../az_public_pension_exclusion.yaml | 22 +++++++++++++++++++ source_conflicts/candidates.yaml | 15 ++++++++----- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml index 0735717adcf..25768be8c5b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.yaml @@ -184,4 +184,3 @@ # Arizona sets no line 29b cap from 2021 (43-1022(26)) and leaves these payments out of the $2,500 subtraction. az_military_retirement_subtraction: 6_000 az_public_pension_exclusion: 0 - diff --git a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml index 8ad17b5d4b1..ee30d2576e6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/az/tax/income/subtractions/az_public_pension_exclusion.yaml @@ -120,3 +120,25 @@ az_military_retirement_subtraction: 0 # ADOR moved only a surviving spouse's payments to line 29b, so min(6,000, 2,500) = 2,500 under 43-1022(2). az_public_pension_exclusion: 2_500 + +- name: Arizona leaves a dependent's SBP payments out of the filer's $2,500 public pension subtraction. + period: 2023 + input: + people: + head: + age: 45 + taxable_public_pension_income: 1_000 + child: + age: 10 + is_tax_unit_dependent: true + military_retirement_pay_survivors: 4_000 + tax_units: + tax_unit: + members: [head, child] + households: + household: + members: [head, child] + state_code: AZ + output: + # Only the head's 1,000 counts; the child's 4,000 goes on the child's own return. + az_public_pension_exclusion: 1_000 diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 22bf330252a..f6e4cf3826e 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -904,9 +904,10 @@ candidates: readings: statute: Paragraph (2)(a) allows up to $2,500 of benefits from "any other retirement system or plan established by federal law", except "retired or retainer pay of the uniformed services of the United States that qualifies - for a subtraction under paragraph 26". Paragraph (26) names no survivors. ITR 11-3 says "Arizona allows a - subtraction of up to $2,500 for uniformed services retired pay and SBP benefits." - other: From 2023, the Form 140 instructions put the payments of "the surviving spouse of a deceased military + for a subtraction under paragraph 26". Paragraph (26) covers "annuities and pensions received as retired or + retainer pay" and names no survivors. + other: ITR 11-3 says "Arizona allows a subtraction of up to $2,500 for uniformed services retired pay and SBP + benefits." From 2023, the Form 140 instructions put the payments of "the surviving spouse of a deceased military veteran" on line 29b, which has no cap from 2021. example: A 2021 surviving spouse with $6,000 of SBP payments and no other income subtracts $2,500 under (2) and $0 on line 29b. In 2023 the same filer subtracts $6,000 on line 29b and $0 under (2). A non-spouse recipient @@ -920,9 +921,11 @@ candidates: prs: - https://github.com/PolicyEngine/policyengine-us/pull/10398 state: ruled - note: PolicyEngine puts a surviving spouse's SBP payments under (2) through 2022 and on line 29b from 2023, where - paragraph (26) is silent on survivors and ADOR's instructions fill the gap. Every other recipient's payments stay - under (2) in every year, as its text and ITR 11-3 provide. + note: Neither paragraph says whether a survivor's SBP payments count as annuities "received as retired or retainer + pay", and (2)(a)'s exception turns on that question, so PolicyEngine follows ADOR's reading for each year. A + surviving spouse's payments stay under (2) through 2022, as ITR 11-3 provides, and move to line 29b from 2023, as + the Form 140 instructions provide. ADOR has moved no other recipient's payments, so those stay under (2) in every + year. - id: us-ca:capi-payment-standard-offset program: capi years: