From bf0f863bf6ac30dcf5c4588908bc0d9a72555bb0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 22:14:14 -0400 Subject: [PATCH 1/3] 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/3] 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/3] 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: