Skip to content
Merged
1 change: 1 addition & 0 deletions changelog.d/mi-military-survivor-spouse-only.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +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. 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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

This file was deleted.

1 change: 1 addition & 0 deletions policyengine_us/tests/code_health/test_source_conflicts.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"policyengine_us/variables/gov/states/mn/dcyf/mfip/income/earned/mn_mfip_dependent_care_deduction.py": 1,
"policyengine_us/variables/gov/states/mn/tax/income/deductions/mn_itemized_deductions.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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Loading
Loading