Repository navigation
Wire military retirement pay and survivors into federal and state tax (#10181) - #10182
jindal-git wants to merge 5 commits into
Conversation
hua7450
left a comment
There was a problem hiding this comment.
Thanks for taking this on. Putting military_retirement_pay and military_retirement_pay_survivors into federal gross income is right. Military retired pay is taxed as a pension (Pub 525 (2024)), and survivor annuities "must be included in your gross income" (Pub 575 (2024)). The SNAP and SSI placements are right too.
The difficulty is what this does downstream. Before this PR, the pay never reached federal AGI, so every state that starts from federal AGI or taxable income left it untaxed. That happened to be correct wherever the state exempts military retirement, so those states never needed a modeled exclusion. Now the pay reaches their base, and only the states that already had a military subtraction were updated. The survivor additions in AR, AZ, CT, LA, MI (line 11), ND, NM, OK, UT, VA and WV do match state law.
Must fix
1. household_net_income counts the tax on the pay but not the pay
household_market_income sums gov/household/market_income_sources.yaml:3-29, which has neither military input. The PR added them only to the person-level market_income.py:30-31. So household_net_income now subtracts the federal tax and the lost SNAP but never adds the income, while spm_unit_net_income does count it.
Example: a single 60-year-old in TX, 2024, with $30,000 of military_retirement_pay. Federal tax goes from $0 to $1,616, and SNAP goes from $3,495 to $0, but household market income stays $0. household_net_income falls by $5,111 when it should rise by $24,889.
Fix: add both inputs to market_income_sources.yaml, and assert household_net_income in the new federal test.
2. States that exempt military retirement pay now tax it
In each of these states, the pay now enters the state base through federal AGI or gross income and nothing removes it. Unless noted, the examples are a 2025 single filer aged 65 whose only income is $30,000 of the stated input; "law" is the result under that state's rules.
| State | Law | Model now → law | Fix |
|---|---|---|---|
| NY | Tax Law § 612(c)(3)(ii); IT-201 line 26 "(including the military)" | $1,045 → $0 | Add both inputs to ny/tax/income/agi/subtractions/sources.yaml |
| IL | 35 ILCS 5/203(a)(2)(F); Pub 120 "military plans" | $1,294.43 → $0 | Add both to il/tax/income/base/subtractions.yaml |
| MA | c. 62 § 2(a)(2)(E): uniformed-services retired pay and SBP | $1,245 → $0 | Subtract both in ma_gross_income.py next to public_pension |
| PA | PA-40 instructions: "Military pension benefits" not taxable; SBP to a beneficiary | $921 → $0 | Add both to pa/.../nontaxable_income_sources.yaml; add survivors to the forgiveness eligibility income |
| KY | KRS 141.019; Schedule P excludes up to $31,110 | $1,029.20 → $0 | Include both in the pension amount at ky_pension_income_exclusion.py:16 |
| KS | K.S.A. 79-32,117(c)(vii) | $852.02 → $0 | Add both in ks_agi_subtractions.py:29-41; update the proxy comment |
| IN | IC 6-3-2-4, deductible in full, incl. surviving spouse (IB 27) | $825 plus county tax → $0 | Add a deduction for both inputs; mind double claims via in_other_deductions |
| HI | HRS § 235-7(a)(2)-(3); N-11 line 13 | about $815 before credits → $0 | Add both to both blocks of hi/tax/income/subtractions/subtractions.yaml |
| WI | Wis. Stat. § 71.05(1)(am); Schedule SB line 12 names SBP | $604.62 → $0 | Add both to both blocks of wi/tax/income/subtractions/sources.yaml |
| MO | RSMo 143.121.3(12) (MO-A line 10); SBP qualifies for the public pension exemption | $392.46 → $0 | Add the pay to agi_subtractions.yaml; add survivors to the Section A public pension amount |
| OH | R.C. 5747.01(A)(23): uniformed-services retired pay and SBP | $314.63 → $0 | Give oh_uniformed_services_retirement_income_deduction a formula (head/spouse), still overridable |
| ME | 36 M.R.S. § 5122(2)(M-2): includes "the surviving spouse"; instructions "including survivor benefits" | survivors: $455.30 → $0 | Read both inputs at me_pension_income_deduction.py:40 |
| DC | D.C. Code § 47-1803.02(a)(2)(N)(ii): survivor benefits at 62+ (D-40 line 12) | survivors: $580 → $0 | Add a survivors-at-62+ subtraction to dc/tax/income/subtractions/sources.yaml |
| DE | 30 Del. C. § 1106(b)(3)b: up to $12,500 of U.S. pensions at 60+, incl. a deceased taxpayer's | $742 → $125 | Add both inputs to the 60+ income basket; treat survivors under 60 as a pension |
| MD | § 10-209(d)(1) and Worksheet 13A: pay above the § 10-207(q) cap counts toward the 65+ pension exclusion | $50,000 of pay: $694.11 → $0 | Pass the excess above the military cap into md_pension_subtraction_amount |
| CA | R&TC §§ 17132.9–17132.10: up to $20,000 each of retired pay and SBP, 2025–2029, federal AGI ≤ $125,000 single / $250,000 joint | wages $60,000 plus $20,000 pay: about $1,555 too much | Model the exclusion with its AGI limits; add a 2025 test (the new CA test is 2024 only) |
| MN | 290.091 subd. 2(a)(iv) removes the subd. 21 military subtraction from AMT income | $100,000 of pay: AMT $1,921.05 → $0 | Add mn_military_pension_subtraction to AMT_SUBTRACTIONS |
| OR | ORS 316.157(2)(e)(C): military pay is pension income for the retirement income credit | $15,000 of pay: $396.14 before refundable credits → $0 | Include both inputs in line 1 of or_retirement_credit.py (taxing the pay itself is right) |
| NJ | NJ-1040 lines 37a–37c test NJ gross income, which excludes military pensions | wages $185,000, pay $30,000 and a $10,000 529 deposit: deduction lost, about $637 | Test nj_agi instead of federal AGI in nj_529_deduction.py:21-22; can be a follow-up |
Montana (MCA 15-30-2120(3)(n)) and North Carolina (the years_in_military default) are under "Should fix".
3. Survivor changes that don't match state law, and Iowa 2023
- CO:
co_pension_subtraction_indv.py:32,40gives filers under 55 onlypension_survivors. Book 104 line 4 lets a secondary beneficiary under 55 subtract up to $20,000 (Book 104 (2025)).- Example: wages $40,000 plus $18,000 of SBP at age 45 now owes $792 more.
- Fix: use
pension_survivors + military_retirement_pay_survivorsin that branch.
- ID (2025+): survivors were added only to the 2021–2024 list, and
id_military_retirement_deduction.py:20reads only the retiree's pay. Idaho Code 63-3022A(1)(b) covers "the unremarried widow or widower".- Example: $20,000 of SBP gives $1,060 too much tax.
- Fix: add survivors to
id_military_retirement_deduction.
- VT (2022–2024): survivors now feed the $10,000 cap path and the eligibility gate in every year. 32 V.S.A. 5830e(d) covered only "U.S. military retirement income" until Act 71 (S.51) added survivor benefit income from 2025.
- Example: TY2024, $10,000 of SBP is exempted, so tax is about $335 too low.
- Fix: gate survivors on
income_based_structure.in_effect.
- NE (2021):
ne_military_retirement_subtraction.py:26-41adds survivors with no year gate. Form 1040N-MIL says the pre-2022 election excludes "annuity payments to a spouse, former spouse, or child".- Example: TY2021, $20,000 of SBP gives an $8,000 subtraction instead of $0.
- Fix: add a dated survivor-eligibility parameter, true from 2022.
- IA (2023): the 2021-01-01 block of
ia/tax/income/taxable_income/subtractions.yamlhas neither input. Iowa Code 422.7(20)-(21) exempted both since 2014; only the form line moved in 2024.- Example: TY2023, age 50, $30,000 of pay now owes $713.23 instead of $0.
- Fix: add both inputs to that block, and correct the "added in 2024" comment.
4. Georgia excludes the same military dollars twice
ga/tax/income/agi/exclusions/retirement/sources.yaml:31-32 adds the full military pay to the retirement-exclusion income. ga_military_retirement_exclusion_person already excludes up to $35,000 of it for filers under 62.
Ga. Comp. R. & Regs. r. 560-7-4-.02(3) says "Only retirement income that is included in Georgia taxable income shall be included when computing the retirement income exclusion".
Example: 2025, disabled single filer aged 50, $60,000 of wages and $40,000 of pay.
- Exclusions are $70,000 instead of $45,000.
- Tax before credits is $934.20 instead of $2,231.70.
Fix: count only the pay left after ga_military_retirement_exclusion_person, plus survivor pay.
Should fix
- Input definitions overlap.
military_retirement_pay.py:10says "including survivor benefits", yet a separate survivors input exists.- The survivors label says surviving spouses, but the new test gives it to an 18-year-old dependent.
- Neither says whether it is the taxable 1099-R amount, net of the SBP cost reduction, CRSC and the § 104 disability portion.
- Where SBP is entered changes state results. For example, Maine deducts SBP entered as retired pay but not as survivors.
- Suggest: define retired pay as the retiree's own taxable pay, and survivors as SBP/RCSBP annuities to any annuitant. Point CRSC and VA DIC to
veterans_benefits.
- MI: the PR puts survivors on Schedule 1 line 11, but the worksheets that reduce limits by line 11 still read only
military_retirement_pay. These aremi_standard_deduction_tier_two.py:35,..._tier_three.py:34, the tier one, expanded and tier three retirement worksheets, andmi_interest_dividends_capital_gains_deduction.py:37.- Example: TY2025, born 1950, $20,000 of SBP and $20,000 of interest. The model gives $0 tax; the law gives $603.50.
- RI (TY2021–2022): the (c)(9) pension modification sources leave out military pay, which RI Pub 2024-01 says qualifies. The (c)(11) military line starts in 2023.
- Fix: add both inputs to the (c)(9) sources for 2021–2022 only.
- MT (2024+): MCA 15-30-2120(3)(n) subtracts the lesser of Montana earned income and 50% of military pay, plus up to 50% of SBP, for qualifying residents. It isn't modeled.
- Example: with $20,000 of Montana wages, the model gives $967.73 instead of $262.73.
- Fix: model it with an explicit residency assumption, or document it as a limitation.
- NC: the deduction logic matches G.S. 105-153.5(b)(5a). But
years_in_militaryand the survivor flag default to 0 and false, so pay entered alone is now taxed: $733.13 instead of $0.- Suggest: document on the military inputs that NC needs the service record.
- Tests:
- The new federal file doesn't use the
Case N, description./person1conventions, and it usesabsolute_error_margin: 1. - Cases 1–2 mention taxable income but don't assert it.
- No case asserts
household_net_income. - Please add a survivors case for each changed state.
- The new federal file doesn't use the
Minor
- Pensions are § 61(a)(10) since P.L. 115-97, not (a)(11) (new test header). The Pub 525 and Pub 575 pages above would be good references for
sources.yaml. - The new CA case uses 5,363, the 2023 deduction; the 2024 value is 5,540.
- The changelog fragment describes only the federal wiring. It omits the state survivor additions, the CO and GA source changes and the NE file rename.
|
Thank you @hua7450 for the thorough and detailed review! We have addressed all points raised across federal wiring, state calculations, survivor benefits gating, and test conventions in the latest commit: 1. State Law & Exclusions Missing
2. Worksheets & Edge Cases
3. Survivor Benefits (SBP/RCSBP) Gating & Timing
4. Georgia Double-Exclusion Fix
5. Definitions, Worksheets & Tests
|
hua7450
left a comment
There was a problem hiding this comment.
Thanks for working through the whole list. Almost all of the first review is fixed at 10199be03c: 25 of the 26 must-fix items, plus the MI, RI and test-convention items. I traced each fix from the input to the final state tax:
- Federal and household: household net income; the MN AMT.
- Exclusions and subtractions: CA (single filers), HI, IL, IN, KS, NY, MA, MO, PA, WI, KY, OH, DC, DE, MD (65+), ME.
- Credits: the OR credit.
- Survivors: CO, ID, IA 2023, VT and NE.
- Georgia: the double exclusion.
The head and spouse gating in 10199be03c also matches state law. A few of the fixes have side effects, and New Jersey is still open.
Must fix
1. California: the exclusion is capped per spouse instead of per return
ca_military_retirement_exclusion.py:27-32 caps each spouse's pay at $20,000 and then adds them, so a joint return can exclude $40,000 of retired pay, and another $40,000 of SBP. FTB says the exclusion is "not to exceed $20,000 for each type of payments … The exclusion is capped at $20,000 for a surviving spouse, spouses filing a joint return, and any other individual". Sources: the 2025 Schedule CA (540) instructions, line 5a/5b, and FTB Pub 1032 (2025).
Example: 2025, joint return, both spouses 62, each with $60,000 of military_retirement_pay. The model excludes $40,000 and gives $1,471.38 of tax before credits. Excluding $20,000 gives $2,385.06, so California tax is $913.68 too low.
Fix: cap each payment type on the tax unit's total, and update the "per category per person" description in cap.yaml.
2. Pennsylvania: tax-forgiveness eligibility income now counts military pension
pa/tax/income/forgiveness/eligibility_income_sources.yaml:11-12 adds military_retirement_pay. Schedule SP says "Do not include the following types of nontaxable income in Line 10 … Military pension benefits" (2025 PA-40 instructions). Survivor annuities (Code 7, line 4) are correctly included.
Example: 2025, single, age 65, $30,000 of pension and $5,000 of wages. Eligibility income should be $5,000, which gives full forgiveness and $0 tax. The model counts $35,000, gives no forgiveness, and charges $153.50.
Fix: remove military_retirement_pay from that list.
3. Massachusetts: the Senior Circuit Breaker now leaves out military pay
Excluding the pay from ma_gross_income (the earlier fix) also drops it from ma_scb_total_income. § 6(k)(1) adds pensions back: total income is AGI "increased by, to the extent they are excluded or subtracted from adjusted gross income … retirement, pension, or annuities".
Example: 2025, single, age 65, renter paying $12,000, with $30,000 of pay. The credit is $2,820 instead of $70.
Fix: add both inputs to ma/.../senior_circuit_breaker/income/disallowed_deductions.yaml.
4. Georgia 2021: a military exclusion that didn't exist yet reduces the retirement exclusion
ga_retirement_income_exclusion_retirement_income.py:28-30 now subtracts ga_military_retirement_exclusion_person before computing retirement income. The military exclusion parameters (military_retirement/base.yaml, additional/amount.yaml) start in 2022 with no earlier value, so 2021 picks up the 2022 amount. Georgia's 2021 subtraction list doesn't include that exclusion.
Example: TY2021, disabled filer aged 50, $30,000 of pay. The retirement exclusion becomes $12,500 instead of $30,000, and Georgia tax is $414 instead of $0.
Fix: add 2021-01-01: 0 to both parameters.
5. Maryland: wrong cap age for disabled filers aged 55–64
md_pension_subtraction_amount.py:17-18,30-35 computes the excess over the military cap using the pension exclusion's age of 65. The military subtraction itself (md_military_retirement_subtraction.py:34-40) switches caps at 55. So for a disabled filer aged 55–64, the excess is measured against $12,500 after $20,000 was already subtracted. § 10-209(d)(1) bars counting military income "included in the subtraction under § 10–207(q)".
Example: 2024, single, age 60, disabled, $40,000 of pay and $30,000 of wages. Military-related subtractions total $47,500 against $40,000 of pay. That is $7,500 too much, or $10,000 in 2021–2022.
Fix: compute each person's capped military subtraction once, and subtract that amount here.
6. New Jersey 529 deduction (still open from the first review)
nj_529_deduction.py:21-22 still tests federal adjusted_gross_income against the $200,000 limit. The NJ-1040 instructions use NJ gross income, "If your gross income was $200,000 or less" (2025 NJ-1040 instructions). NJ gross income excludes "United States military pensions and survivor's benefit payments" (same booklet, page 10).
Example: 2025, single NJ resident, $185,000 of wages, $30,000 of pay and $10,000 of investment_in_529_plan_indv. The model denies the deduction, so NJ tax is $637 too high.
Fix: test nj_agi, which is NJ-1040 line 29, instead of federal AGI.
Should fix
-
Tests for this round's fixes. These paths have no test setting the input each fix touches:
- GA, IL, KS, KY, MA, MD, MN AMT, MO line 10, NY, OR and PA;
- CO under 55, ID 2025+ survivors and IA 2021–23;
- the seven MI worksheets, NE 2021, RI 2021–22 and VT 2022–24.
Several of the problems above would have shown up as test failures. Please add one case per changed formula or list, plus NE 2021/2022 and VT 2024/2025 boundary cases.
-
CA exclusion tests. The only test is one single filer at $60,000 AGI. Please add:
- the AGI cliff, using "does not exceed": $125,000 excludes and $125,001 doesn't;
- a joint return at $250,000;
- a dependent recipient;
- a 2030 case;
- the joint case from item 1.
-
The federal test locks in a food-package benefit the filer isn't eligible for. Case 1's
household_net_income: 28_915includes $531 of CSFP. That's becausegov/usda/school_meals/income/sources.yamlomits military pay, while 7 CFR 247.9(d)(1) uses gross income. The correct value is 28,384.- Please either assert the components instead of the net figure, or use an age under 60 as Case 3 does.
- The school-meal income list can be a separate PR.
-
The same "head plus spouse military pay" sum is repeated. This round adds 17 inline sums (DE, ID, IN, KS, KY, MA, MD, ME, OH, OR and the seven MI worksheets) and 10 parameter lists. A single person-level
addsvariable combining the two inputs would let these name one variable. States that treat survivors differently would keep reading the separate inputs: CA (a separate cap per type), CO under 55, NE, VT, GA, DC, NC and MO. -
Parameter metadata.
- These files have no
label:ca/.../military_retirement/cap.yamlandagi_limit.yaml;dc/.../survivor_benefits/min_age.yaml;in/.../military_retirement/base.yamlandexcess_rate.yaml.
- Values need underscores (
125_000), and1.0should be1. - The CA files cite §§ 17132.9 and 17132.10, but link only 17132.9.
- These files have no
-
NE
survivors_eligible.yaml. Its reference titled "2021 Form 1040N-MIL" links to the 2021 booklet page, which says nothing about survivors. Please cite Form 1040N-MIL for the false value and Information Guide 8-364 for the 2022 value. -
Input definitions, partly done. The "including survivor benefits" overlap is removed from the docstrings. Still needed:
- Say that both inputs are the taxable amount, meaning after the SBP premium reduction, excluding CRSC and the § 104 disability portion (Pub 525 (2024)).
- Point CRSC and VA DIC to
veterans_benefits. - Relabel the survivors input: it still says "paid to surviving spouses", but Case 4 is an 18-year-old annuitant.
- Two NC files still say
military_retirement_paycan hold survivor benefits "as its documentation permits":nc_military_retirement_deduction.py:41-43andnc_military_retirement_survivor_eligible.py:22-23.
-
Montana (MCA 15-30-2120(3)(n)) and the NC service-record default are still open from the first review. Documenting them as known limitations is fine.
Minor
- The changelog's state list leaves out WI, which has non-test changes.
in_military_retirement_deduction.py:33repeats thehead_or_spousegate.ne_military_retirement_subtraction.py:27-30can multiply byp.survivors_eligible.- The 2021 and 2024 IA subtraction lists are now identical, so the 2024 block can go.
- The NY sources file dropped the note that
taxable_public_pension_incomeis a proxy. - The DE "capped at 2,000" test uses $1,500, so it never reaches the cap.
- Several test files end with extra blank lines.
…PolicyEngine#10181) - Add military_retirement_pay and military_retirement_pay_survivors to federal irs_gross_income sources - Add military_retirement_pay and military_retirement_pay_survivors to market_income, SNAP unearned income, and SSI unearned income - Harmonize state military retirement subtractions and senior pension exclusions to include military_retirement_pay_survivors (AZ, AR, CO, CT, GA, IA, ID, LA, MI, ND, NE, NM, OK, RI, UT, VA, VT, WV) - Fix typo in ne_military_retirement_subtraction filename - Add YAML test for federal gross income, AGI, market income, and SNAP/SSI unearned income - Add CA taxable income test verifying military retirement pay inclusion - Update NC and VT baseline tests to reflect wiring into AGI - Add changelog fragment
…ing across states
…dge cases, and test overhaul
10199be to
6e7667f
Compare
|
Thanks for the thorough second review @hua7450! We have addressed all the feedback and cleanly rebased on Must-Fix
Shared Person-Level Adds Variable
Should-Fix & Minor Improvements
|
Closes #10181.
Summary
This PR wires
military_retirement_payandmilitary_retirement_pay_survivorsinto federal gross income (irs_gross_income),adjusted_gross_income,market_income, SNAP unearned income, and SSI unearned income. It also harmonizes state-level military retirement subtractions and senior pension exclusions so that both retiree pay and military survivor benefits are recognized according to state law.Why separate variables are kept
military_retirement_payandmilitary_retirement_pay_survivorsremain separate variables. In states like North Carolina (G.S. § 105-153.5(b)(5a)) and South Carolina (S.C. Code § 12-6-1170/1171), eligibility and worksheets differ between retiree pay and survivor benefits (a civilian surviving spouse hasyears_in_military = 0, but qualifies based on deceased spouse's service).taxable_pension_incomeortaxable_public_pension_income, because 20+ state programs (e.g. Arkansas Form AR1000F line 17 vs 18, Michigan interest/dividends deduction, Nevada CCDP, Oregon LIHEAP, CCAP in multiple states) already list bothpension_incomeandmilitary_retirement_payside-by-side. Adding military retirement intotaxable_pension_incomewould cause double-counting in those states.Changes
Federal Gross Income & AGI:
military_retirement_payandmilitary_retirement_pay_survivorstopolicyengine_us/parameters/gov/irs/gross_income/sources.yamlunder IRC § 61(a)(11).COMPONENTSinpolicyengine_us/variables/household/income/person/general/market_income.py.parameters/gov/usda/snap/income/sources/unearned.yaml) and SSI (parameters/gov/ssa/ssi/income/sources/unearned.yaml).State Subtraction & Exemption Harmonization:
military_retirement_pay_survivorsalongsidemilitary_retirement_pay:parameters/gov/states/ar/tax/income/gross_income/sources/individual.yaml,joint.yaml, andar_military_retirement_income_person.pyvariables/gov/states/az/tax/income/subtractions/az_military_retirement_subtraction.pyparameters/gov/states/co/tax/income/subtractions/pension/income_sources.yamlparameters/gov/states/ct/tax/income/subtractions/subtractions.yamlparameters/gov/states/ga/tax/income/agi/exclusions/retirement/sources.yamlparameters/gov/states/id/tax/income/deductions/retirement_benefits/income_sources.yamlparameters/gov/states/ia/tax/income/taxable_income/subtractions.yamlparameters/gov/states/la/tax/income/exempt_income/sources.yamlparameters/gov/states/mi/tax/income/subtractions.yamlne_military_retirement_subtraction.py(and fixed typone_military_retirement_substraction.py->ne_military_retirement_subtraction.py)nm_armed_forces_retirement_pay_exemption_person.pyparameters/gov/states/nd/tax/income/taxable_income/subtractions/sources.yamlok_military_retirement_exclusion.pyparameters/gov/states/ri/tax/income/agi/subtractions/subtractions.yamlut_military_retirement_credit_eligible.pyandut_military_retirement_credit_potential.pyva_military_benefit_subtraction_person.pyvt_military_retirement_pay_exclusion.py,vt_military_retirement_cap_based_exemption.py,vt_military_retirement_income_based_exemption.py, andvt_retirement_income_exemption_eligible.pyparameters/gov/states/wv/tax/income/subtractions/subtractions.yamlandsenior_citizen_disability_deduction/modification_sources.yamlTests:
tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/military_retirement_pay.yamlverifyingirs_gross_income,adjusted_gross_income,market_income,snap_unearned_income,ssi_unearned_income, and dependent exclusion.tests/policy/baseline/gov/states/ca/tax/income/ca_taxable_income.yamlconfirming state taxation through federal AGI.nc_military_retirement_deduction.yaml) and VT (vt_retirement_income_exemption.yaml) that previously supplied workaroundtaxable_public_pension_incomeor higher wages to compensate for lack of federal AGI wiring.test_dependent_retirement_income_state_invariants.pypassed across 2025 and 2026.tests/policy/baseline/partners/**.axiom: needed