Skip to content

Count military SBP annuities in state benefit income lists - #10317

Open
PavelMakarchuk wants to merge 6 commits into
mainfrom
fix-sbp-benefit-income-sources
Open

PavelMakarchuk wants to merge 6 commits into
mainfrom
fix-sbp-benefit-income-sources

Conversation

@PavelMakarchuk

@PavelMakarchuk PavelMakarchuk commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

#10182 moved military Survivor Benefit Plan (SBP) annuities out of military_retirement_pay into a new input, military_retirement_pay_survivors, and added military_retirement_income as the sum of both. #10182 wired the new input into federal gross income, AGI, SNAP, SSI and state tax subtractions. The state benefit-program income lists still listed only military_retirement_pay, so an SBP annuity was no longer income for those programs.

This PR replaces military_retirement_pay with military_retirement_income in every benefit-program income list on main (43 entries in 41 files). Later commits merge main and add the LIHEAP lists that merged after the first commit: FL, OH, OK, RI and SD, then GA and AR. It also fixes the comments that #10182 made stale: the Oregon LIHEAP comment said military_retirement_pay includes survivor payments, and the Maryland MEAP comment said a survivor annuity could count twice through survivor_benefits.

Tax-side uses (for example the Missouri MO-A subtraction and the CO, SC, UT and VT military subtractions) are not changed.

A new code-health check, policyengine_us/tests/code_health/test_military_retirement_readers.py, keeps the lists from drifting back. Modelled on test_unemployment_compensation_readers.py (#9558), it fails when a parameter list outside the tax side (gov/irs/ or any tax/ folder) names military_retirement_pay without military_retirement_pay_survivors, or when Python outside the tax side reads the raw input. It reads text only and imports no model.

Rule check for each list

I checked each program's own income rule. Every program counts a recurring SBP annuity, and no exclusion covers it.

Program List (under parameters/gov/states/) Rule
AL LIHEAP al/adeca/liheap/income/sources/unearned.yaml FY2026 manual 5.5.1 (p. 18) counts government pensions, including military retirement pay, and regular annuity payments.
IN EAP in/ihcda/eap/income/sources.yaml PY2026 manual 6.1 (pp. 54-55) counts ongoing pension, annuity and retirement payments, including retirement funds paid to a surviving spouse or child.
KY LIHEAP ky/dcbs/liheap/unearned_income_sources.yaml FY2026 state plan 1.9 (p. 6) checks retirement/pension benefits; 921 KAR 4:116 §1(8) counts all unearned income.
MD MEAP md/dhs/meap/income/sources/unearned.yaml COMAR 07.03.21.04D(12) counts pensions and D(13) annuity disbursements; no 04E exclusion covers survivor annuities.
OR LIHEAP or/ohcs/liheap/income/sources/unearned.yaml PY2026 manual (p. 44) and PY2027 manual (p. 45) define a pension to include regular payments to surviving dependents for past services.
MO SAB mo/dss/ssp/sab/income/sources/unearned.yaml SAB manual 0410.015.10 counts military retirement (C2) and annuities (C11).
MO TANF mo/dss/tanf/income/sources/unearned.yaml TA manual 0210.015.05 marks military retirement and annuities as included.
TX TANF tx/tanf/income/sources/unearned.yaml Texas Works Handbook A-1324.20 counts "the gross amount of any SBP payment" as unearned income.
FL LIHEAP fl/commerce/liheap/income/sources/unearned.yaml FY2026 plan 1.9 (p. 6) checks retirement/pension benefits; manual 1200.01(F) (p. 49) counts all unearned income; the portal FAQ counts regular annuity payments.
OH HEAP oh/odjfs/liheap/income/sources/unearned.yaml EAP Guidelines Appendix XXIII (p. 73) lists military pensions and widow/widower benefits; E-2.3 (p. 8) counts any income not listed.
OK LIHEAP ok/dhs/liheap/income/sources/unearned.yaml OAC 340:20-1-11(a) counts all gross unearned income unless excluded; the incorporated SNAP exclusions do not cover annuities.
RI LIHEAP ri/dhs/liheap/income/sources/unearned.yaml LIHEAP Manual III.E (pp. 14-15) counts gross pension and retirement income; the III.G exclusions do not cover it.
SD LIEAP sd/dss/liheap/income/sources/unearned.yaml ARSD 67:15:01:01(4) counts all unearned income; the manual (p. 25) counts non-lump-sum annuity, pension and retirement payments.
GA LIHEAP ga/dfcs/liheap/income/sources/person.yaml Manual 1200.7 includes annuities (p. 65) and pensions "received regularly as a retirement benefit" (p. 66); no exclusion covers survivor annuities.
AR LIHEAP ar/ee/aeo/liheap/income/sources/unearned.yaml FFY 2025 manual Appendix F (p. 165) counts pensions paid to a person's "surviving dependents" and retirement pay from the military.
AR SRA ar/ade/oec/sra/income/sources.yaml FSU Procedural Manual 4.3.2 (p. 20) counts all unearned income unless excluded, including "Pensions, interest, and annuities".
CO CCAP co/ccap/income/countable_income/sources.yaml 8 CCR 1403-1 3.105.1(I)(8) counts pensions and annuities, and (e) any payment meant to provide for a retired person or their survivors.
DC CCSP dc/dhs/ccsp/income/countable_income/sources.yaml Policy Manual 2.5.2 (p. 13) counts military retirement/pensions and annuities; the 2.5.3 survivor exclusion covers Social Security only.
DE POC de/dss/poc/income/countable_income/sources.yaml DSSM 11003.9.1(4) counts all gross unearned income; (5)(B) lists pensions and annuities.
FL SR fl/doe/sr/income/sources.yaml Fla. Stat. 1002.81(7) counts gross income "derived from any source"; it excludes only food stamps and housing assistance.
HI CCAP hi/bessd/ccap/income/countable_income/sources.yaml HAR 17-798.3-10(b)(5) counts pensions and retirement benefits paid to a retired person or the person's survivors.
ID ICCP id/dhw/iccp/income/unearned_sources.yaml IDAPA 16.06.12.071 counts all gross unearned income, which .011.13 defines to include retirement.
KS CCAP ks/dcf/ccap/income/countable_income/unearned.yaml KEESM 6220 lists annuities, pensions, retirement and survivors benefits as countable.
KY CCAP ky/dcbs/ccap/income/countable_income/sources.yaml 922 KAR 2:160 8(2) counts all gross income; no 8(4) exclusion covers pensions or annuities.
MA CCFA (3 periods) ma/eec/ccfa/income/countable_income/sources/unearned.yaml Every period counts retirement income (2022 Policy Guide 4.2.C.3.d; Oct 2023 interim and May 2026 policies, Income); the survivor exclusion covers Social Security only.
MD CCS md/msde/ccs/income/countable_income/sources.yaml COMAR 13A.14.06.02B(28)(b)(v) counts pensions and annuities; (vii) counts military entitlements.
ME CCAP me/dhhs/ccap/income/countable_income/sources.yaml 10-148 CMR ch. 6 2.C.4.a counts pension and retirement benefits, including military retirement/pensions and annuities.
MI CDC mi/mdhhs/ccap/income/countable_income/sources.yaml BEM 503 "Retirement Income-Other" (all programs) counts annuities and military pensions at the gross amount.
MN CCAP mn/dcyf/ccap/income/countable_income/unearned.yaml Minn. Stat. 256P.06 subd. 3(2)(ix) counts retirement benefits.
MO CCS mo/dese/ccs/income/countable_income/sources.yaml 5 CSR 25-200.050(20)(E) counts pensions and annuities.
NE CCS ne/dhhs/child_care_subsidy/income/sources/unearned.yaml 392 NAC 2-010.01(A)(16) counts pension benefits paid to a retired person or their survivors; (22) counts annuities; 2-010.04 excludes only lump sums.
NM CCAP nm/ececd/ccap/income/countable_income/sources.yaml 8.15.2.11.C(5) NMAC counts pensions; C(6) does not exempt survivor annuities.
NV CCDP nv/dwss/ccdp/income/countable_income/sources.yaml Child Care Manual MS 306 (p. 54) counts any income not listed as exempt; MS 306.29 counts pensions; SBP is not exempt.
NY CCAP ny/ocfs/ccap/income/countable_income/sources.yaml 18 NYCRR 404.5(b)(5)(vii) counts pensions and retirement benefits paid to a retired person or his survivors, and periodic annuity receipts.
OH PFCC oh/dcy/ccap/income/countable_income/sources.yaml OAC 5180:2-16-03(H)(2) counts all non-earned income; no (I) exclusion covers SBP.
OR ERDC or/delc/erdc/income/countable_income/sources.yaml OAR 414-175-0035(52)(c)(A) counts monthly pension payments as unearned income.
PA CCW pa/dhs/ccw/income/countable_income/sources.yaml 55 Pa. Code ch. 3042 App. A Part I.J counts government pensions paid to former members of the armed forces or their survivors.
RI CCAP ri/dhs/ccap/income/countable_income/sources.yaml 218-RICR-20-00-4 4.2 "Income" (m) counts military retirement and annuities.
VT CCFAP vt/dcf/ccfap/income/sources.yaml CCFAP Regulations II B 2 (p. 9) counts all payments from any source; the only military exclusion is active-duty deployment pay.
WA WCCC wa/dcyf/wccc/sources.yaml WAC 110-15-0060(8) counts income types that WAC 110-15-0070 does not exclude; 0070 does not exclude SBP.
WV CCAP wv/dhhr/ccap/income/countable_income/sources.yaml Manual 5.2.4.4 (p. 55) counts pensions and annuities.

Tests

New cases show that an SBP annuity in military_retirement_pay_survivors now counts. Each case failed on main before the parameter change.

  • or_liheap_countable_income.yaml Case 8 (Oregon LIHEAP)
  • md_meap_countable_income.yaml Case 7 (Maryland MEAP; the annuity counts once)
  • in_eap_income.yaml Case 11 (Indiana EAP)
  • tx_tanf_gross_unearned_income.yaml (Texas TANF, retiree pay plus SBP)
  • ny_ccap_countable_income.yaml Case 16 (New York CCAP)
  • oh_liheap_countable_income.yaml Case 22 (Ohio HEAP)
  • sd_liheap_countable_income.yaml Case 24 (South Dakota LIEAP)
  • ga_liheap_income.yaml Case 16 (Georgia LIHEAP)
  • ar_liheap_countable_income.yaml Case 34 (Arkansas LIHEAP)
  • mo_sab_countable_income.yaml Case 39 (Missouri SAB; mirrors Case 33 with an SBP annuity: $1,000 + $500 = $1,500)

The code-health check pins the other lists statically. It passes on this head and fails on a list that is switched back.

Local run (macOS, policyengine-core test, one process per group of up to four program directories): all 4,144 YAML tests in the 34 program test directories of the first commit pass. After the merge, the tests in the five new LIHEAP directories and in IN EAP and MD MEAP (which the merge also changed) pass: 482 tests pass (302 in FL, OH, OK and RI LIHEAP; 180 in SD LIEAP, IN EAP and MD MEAP). After the merge of GA #9968 and AR #9973, all 156 tests in the GA and AR LIHEAP directories pass, and test_programs_registry.py, test_aggregate_list_integrity.py and code_health/test_source_conflicts.py pass (881 passed, 10 skipped). Without the GA and AR list change, the two new cases fail. After the review changes: 538 YAML tests pass in the MO SSP, PA CCW, WV CCAP and MN CCAP directories (the new MO SAB case fails without the list change), and the code-health and list checks pass (889 passed, 10 skipped), as do test_rest_python_groups.py and test_selective_test_batches.py (41 passed). make format makes no changes.

No files under policyengine_us/tests/policy/baseline/partners/ change.

Notes

axiom: TheAxiomFoundation/rulespec-us#1532, #1533, #1534, #1535, #1537, #1538, #1539, #1540, #1465, #1468, #1479, #1482, #1757, #1737, #1736 and #1870 queued

Each of the 15 existing pe-parity issues has an SBP companion note (input change, verbatim pension, annuity or survivor text, and a companion test from the same source). rulespec-us#1870 is a new dispatch-ready pe-parity issue for the other 26 lists (MO TANF, TX TANF, MO SAB and 23 child care programs). The signed encoder is blocked: several sources need ingest or are not in the pinned signed release, and each billed run needs separate approval.

🤖 Generated with Claude Code

#10182 moved Survivor Benefit Plan annuities into
military_retirement_pay_survivors. The benefit-program income lists
still listed only military_retirement_pay, so SBP annuities dropped out
of countable income. Replace military_retirement_pay with
military_retirement_income in 34 state child care, LIHEAP, TANF and SAB
lists, fix the OR LIHEAP and MD MEAP comments that #10182 made stale,
and add SBP cases for OR LIHEAP, MD MEAP, IN EAP, TX TANF and NY CCAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PavelMakarchuk and others added 2 commits October 21, 2026 12:21
These LIHEAP source lists merged after the first commit and still
listed only military_retirement_pay. Switch them to
military_retirement_income, update their comments, and add SBP cases
for OH HEAP and SD LIEAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PavelMakarchuk
PavelMakarchuk marked this pull request as ready for review October 21, 2026 16:29
@DTrim99

DTrim99 commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Count military SBP annuities in state benefit income lists

PR #10317 (author: PavelMakarchuk), head 10b2f1ca03. This is a first review. The PR switches 41 entries in 39 state benefit-program income lists from military_retirement_pay to military_retirement_income. An SBP (Survivor Benefit Plan) annuity entered in military_retirement_pay_survivors, the input #10182 split out, therefore counts again. The PR also adds seven test cases, fixes the stale Oregon LIHEAP and Maryland MEAP comments, and adds a fixed changelog fragment. It has no comments or reviews yet, and it touches no partner test file.

Path prefixes:

  • P/ = policyengine_us/parameters/gov/states/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/

Summary

What is right:

  • Every switch fits its program. The 39 files cover 10 LIHEAP lists, 26 child care lists, 2 TANF lists (MO, TX) and Missouri SAB. No program I checked excludes survivor annuities or treats them differently from the retiree's own pay. I confirmed Texas TANF (A-1324.20 names SBP), Washington WCCC and Minnesota CCAP against the sources; Minnesota's armed-forces clause is G6.
  • Coverage is complete for what the PR claims. On the tree merged with current main, no benefit-program list or formula names military_retirement_pay any more. Every remaining reader is on the tax side (MO MO-A, CA, CO, DE, GA, MI, NC, NE, SC, UT, VT), and the PR rightly leaves those alone.
  • No double count. military_retirement_income adds exactly military_retirement_pay and military_retirement_pay_survivors. No changed list keeps either part, and pension_income and survivor_benefits are separate inputs.
  • Tests. I hand-checked 10 expectations, including the no-double-count TX TANF case. CI passed all of them.
  • Batch checks. The source-conflict ratchet passes, and none of Integrate modeled state unemployment insurance into income flows #9558, Document disjoint health premium inputs and correct consumer treatment #10046, the state-gated keys or the 110% floor applies.

There are no critical findings and one should-address item:

  • S1. axiom: needed is not valid for a staff PR. Thirteen existing pe-parity issues cover these lists, and none mentions SBP. A concrete line is below.

There are also six suggestions. The main one is G1, a code-health guard.

Status.

Critical (must fix)

None.

Should address

S1. axiom: needed is not a valid line for a staff PR

Suggestions

G1. Add a code-health guard so that new benefit lists cannot name military_retirement_pay alone.

G2. Only 7 of the 41 changed entries have an SBP case, and the SAB family has none.

  • Pinned: IN, MD, OH, OR and SD LIHEAP, NY CCAP and TX TANF.
  • Unpinned:
    • MO SAB (Case 33 pins only the retiree half) and MO TANF;
    • AL, FL, KY, OK and RI LIHEAP;
    • the other 25 child care entries, counting Massachusetts's three periods.
  • Effect. Reverting any of them to military_retirement_pay would pass every test.
  • Fix. At least add a Missouri SAB case that mirrors Case 33. With social_security of 12,000 and military_retirement_pay_survivors of 6,000, 2026-01 gives $1,000 + $500 = $1,500 (§ 0410.015.10 lists annuities). G1 would pin the rest statically.

G3. Five merged LIHEAP lists count no military retirement at all.

  • Where:
    • P/ks/dcf/liheap/income/sources/unearned.yaml;
    • P/ms/mdhs/liheap/unearned_income_sources.yaml;
    • P/nc/ncdhhs/lieap/unearned_income_sources.yaml;
    • P/nj/dca/liheap/income/sources/unearned.yaml;
    • P/ny/otda/heap/income/sources/unearned.yaml.
  • Why it matters:
    • None of the five names military_retirement_pay or military_retirement_income.
    • Each counts pension_income, but that adds only taxable_pension_income and tax_exempt_pension_income.
    • The changed lists state the convention themselves: military retirement "is a separate input from the pension inputs that pension_income adds".
  • Example (New Jersey). The FY2026 LIHEAP Handbook, section 2.3.D (PDF p. 8), says: "Included in this definition are returns from capital investments such as dividends and interest, benefits and pensions, annuities, contributions, compensation payments, and receipts from the rental of property." Take a 70-year-old whose only income is $24,000 of military retirement pay, or the same amount as an SBP annuity. At the head, that person has $0 of countable income.
  • Why only a suggestion. The gap predates Wire military retirement pay and survivors into federal and state tax (#10181) #10182 and is outside the PR's stated scope, the lists that named military_retirement_pay. G1's guard would not catch an omission like this.
  • Fix: a follow-up that adds military_retirement_income to these five lists after checking each rule, or a sentence in the body.

G4. The PR body is incomplete.

G5. Pennsylvania CCW, a list this PR edits, already double counts two Social Security benefits on main.

  • Where: P/pa/dhs/ccw/income/countable_income/sources.yaml lists social_security and also social_security_disability and social_security_survivors.
  • Why it double counts. social_security adds the dependents, disability, retirement and survivors components, and pa_ccw_countable_income is a plain adds over the list. SSDI and Social Security survivor benefits therefore count twice.
  • Example. A parent with $12,000 a year of Social Security survivor benefits has $2,000 a month of countable income instead of $1,000. No test sets either component.
  • Fix: this is outside the PR's scope, so a follow-up issue is enough.
  • Stale test headers. Two still list military_retirement_pay as a source: T/pa/dhs/ccw/pa_ccw_countable_income.yaml (lines 5–7, which also list raw unemployment_compensation) and T/wv/dhhr/ccap/wv_ccap_countable_income.yaml (line 6).

G6. Minnesota's armed-forces clause.

  • The clause. Minn. Stat. 256P.06 subd. 3(2)(xi) counts "income from members of the United States armed forces unless excluded from income taxes according to federal or state law".
  • The tax side. Minnesota subtracts all military retirement income: mn_military_pension_subtraction reads military_retirement_income under 290.0132 subd. 21.
  • Why the switch is still right. The PR cites (ix), "retirement benefits", which has no tax condition. For an SBP annuity, which is paid to a survivor rather than a member, (ix) is the better reading.
  • An open question on main. Whether (xi) excludes a retiree's own pay is a separate question that main already raises.
  • Fix: consider a one-line comment in P/mn/dcyf/ccap/income/countable_income/unearned.yaml that records the reading.

Verified correct

Rules:

  • Texas TANF. Texas Works Handbook A-1324.20 says of military retirement that "The gross payment is counted as unearned income", and that "The gross amount of any SBP payment is counted as unearned income." That matches the new comment and the new case.
  • Washington WCCC. WAC 110-15-0070's only military exclusion is "Military housing and food allowance", so SBP is not excluded.
  • Minnesota CCAP. 256P.06 subd. 3(2)(ix) counts "retirement benefits" (G6).
  • The other 36 lists. I relied on the per-program citations in the PR body, and checked them against each list's own comments and exclusion notes:
    • DC CCSP excludes social_security and ssi but not pensions.
    • Massachusetts CCFA's exclusive 2023 and 2025 lists keep pension income.
    • The Missouri SAB comment says "Military retirement is listed separately because pension_income excludes it".
    • The KS, KY, MO CCS, NM, NV, NY, OH and WV comments place military retirement under pensions.
    • None excludes survivor annuities or separates them from retiree pay.

Coverage, double counting and wording:

  • Coverage. On the merged tree, the remaining military_retirement_pay readers are all tax files, with a military_retirement_pay_survivors companion where the tax rule covers survivors.
  • No list has both forms. No list holds military_retirement_income together with either of its parts. military_retirement_income is adds = ["military_retirement_pay", "military_retirement_pay_survivors"].
  • Comments.
    • The Oregon LIHEAP comment no longer says military_retirement_pay includes survivor payments.
    • The Maryland MEAP "could count twice" sentence is gone, correctly.
    • The new "belongs in military_retirement_pay_survivors, not survivor_benefits" guidance appears in all seven LIHEAP lists that also list survivor_benefits (AL, FL, IN, MD, OH, OR and SD).
  • Body counts. "41 entries in 39 files" and the seven listed cases match the diff.

Hand-checked expectations (10):

  1. IN EAP in_eap_income.yaml Case 11. Age 58 is an adult and not a dependent student. 8,400 + 9,360 = 17,760. The Medicare premium offsets only Social Security, which is 0.
  2. MD MEAP md_meap_countable_income.yaml Case 7.
    • Benefits. The benefits term, max(0 + 0 − Part B premium, 0), is 0, so the premium cannot reduce the annuity. The unearned annuity gives 9,600.
    • No double count. survivor_benefits, which is also on the list, is 0.
  3. NY CCAP ny_ccap_countable_income.yaml Case 16 (2025). 10,000 + 6,000 = 16,000. Adult SSI is 0, and no child earnings are excluded.
  4. OH HEAP oh_liheap_countable_income.yaml Case 22. The annuity counts 9,600. The medicare_part_b_premium deduction is 0 here, which CI's pass at 9,600 confirms: at $800 a month, the Medicare Savings Program pays the premium.
  5. OR LIHEAP or_liheap_countable_income.yaml Case 8. 9,600 unearned + 0 TANF + 0 earned + 0 interest = 9,600.
  6. SD LIEAP sd_liheap_countable_income.yaml Case 24. 9,600 unearned. The Part B premium nets only against Social Security (0), and TANF and support paid are 0, so 9,600.
  7. TX TANF new case (2026-01). (3,600 + 2,400) / 12 = 500.
    • No double count: listing both the pay and the total would give 800.
    • Old list: it would give 300.
  8. TX TANF "Military retirement and railroad payments". (2,400 + 3,600 + 1,200) / 12 = 600, unchanged.
  9. MO TANF, the retirement-payments case. (1,200 + 600 + 600 + 1,200) / 12 = 300, unchanged.
  10. MO SAB Case 33. 12,000 / 12 + 6,000 / 12 = 1,500, unchanged.

On main, the lists dropped each new case's SBP (IN would give 9,360, for example). That is consistent with the body's statement that each case failed on main.

Batch checks:

Axiom line assessment

Invalid: axiom: needed (S1).

CI status

27 pass, 3 skipping, 0 failed. Run 39635220801 (pull_request, started 2026-10-21 16:29 UTC) tested head 10b2f1ca03 merged into a6fafb06c2. I re-checked gh pr checks 10317 at 00:22 UTC on 2026-10-22, at the end of this review.

  • Passed:
    • Baseline: all four states shards; the household, irs, ssa-usda, rest-a, rest-b, reform and contrib-hhs shards.
    • Contrib: all nine shards.
    • Python: Rest (Python, shard-1 + variables), Rest (Python + variables) and Microsimulation.
    • Other: Household API Partners, PR validation, Package and compatibility, and Quick Feedback.
  • Skipping: the three wrapper checks (Python tests, Contrib tests, Baseline tests) from the companion skipped run.
  • Locally: I ran no tests or probes. The examples are hand traces of the code at the head.

Branch status

0 critical, 1 should and 6 suggestions are open at this head.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is right. All 41 entries in 39 lists (LIHEAP, child care, MO/TX TANF, MO SAB) fit their rules, no benefit list on the merged tree names military_retirement_pay alone any more, and nothing is double counted. One should-address item: 'axiom: needed' is invalid for staff. Please add an SBP companion to the 13 open pe-parity issues, file one issue for the other 26 lists, and mark them queued. Six suggestions; the main one is a #9558-style code-health guard, since nine open LIHEAP PRs add military_retirement_pay-only lists and #10332 would silently revert the HI fix. Full report in the comment above.

PavelMakarchuk and others added 3 commits October 22, 2026 12:43
Switch the Georgia and Arkansas LIHEAP income lists, which merged in
#9968 and #9973, from military_retirement_pay to
military_retirement_income. Georgia manual 1200.7 includes annuities and
pensions; Arkansas manual Appendix F counts pensions paid to surviving
dependents. Add a survivor annuity case to each income test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a code-health check, modelled on the unemployment compensation
readers check, that fails when a parameter list or Python reader outside
the tax side names military_retirement_pay without the survivor annuity
input. Address the review of #10317: add a Missouri SAB survivor annuity
case, record the Minnesota CCAP reading of 256P.06 subd. 3(2)(ix), and
update the stale source headers of the PA CCW and WV CCAP tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 22, 2026
@DTrim99

DTrim99 commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Count military SBP annuities in state benefit income lists

PR #10317 (author: PavelMakarchuk), head f297b6ac67. Our round 1 reviewed 10b2f1ca03 and requested changes (0 critical, 1 should, 6 suggestions). Since then the author has merged main (which brought in GA LIHEAP #9968 and AR LIHEAP #9973) and pushed two commits:

  • 168dc7c545 switches the new Georgia and Arkansas LIHEAP lists to military_retirement_income and adds an SBP case to each.
  • f297b6ac67 adds the code-health guard from G1, a Missouri SAB SBP case (G2), the Minnesota reading comment (G6) and corrected PA CCW / WV CCAP test headers (G5).

The PR body now has a concrete queued axiom line and lists every open PR the guard will flag. The merge commit b1cb1d9c78 had no hand-resolved conflicts. No partner test file changes.

Summary

  • Every round-1 item is resolved. S1 (the axiom line) is fixed. G1, G2, G4 and G6 are fixed. G3 and G5 are deferred as documented follow-ups.
    • All 16 rulespec-us issues on the line are OPEN and labelled pe-parity.
    • Each of the 15 existing issues now has an SBP companion comment against head f297b6ac67.
    • The new CA YCTC expanded in 2022 #1870 is dispatch-ready for the other 26 lists and records the encoder blockers.
  • The guard works and is cheap.
  • The new switches and cases are right. Georgia manual 1200.7 includes pensions and annuities, and Arkansas manual Appendix F (p. 165) counts pensions paid to "surviving dependents". Every new expectation hand-checks.
  • New findings: none critical and none should-address. There are three small suggestions.
  • CI: 15 pass and 3 pending at 22:29 UTC, with none failed. The guard's shard (Rest Python shard 1) has passed.
  • Branch: it merges cleanly with main.

Prior findings

Finding Status Evidence
S1. axiom: needed is invalid for a staff PR Fixed The line is now axiom: TheAxiomFoundation/rulespec-us#1532, #1533, #1534, #1535, #1537, #1538, #1539, #1540, #1465, #1468, #1479, #1482, #1757, #1737, #1736 and #1870 queued. All 16 issues are OPEN and labelled pe-parity. Each of the 15 existing issues has a 2026-10-22 "SBP companion" comment against f297b6ac67 (see Axiom). #1870 covers the other 26 lists.
G1. Add a code-health guard against raw military_retirement_pay in benefit lists Fixed policyengine_us/tests/code_health/test_military_retirement_readers.py is modelled on the UC readers check. It skips gov/irs/ and any tax/ folder. It flags block-list or flow-list YAML entries and exact Python string constants, and ignores comments. It allows one Python file, and a fourth test fails if that allowance goes stale. On the trial merge with #10332 (d318d73b78), the Hawaii CCAP file has the 2021-08-06 block's - military_retirement_pay at line 64, which the guard flags. It runs in the remaining group of Rest Python shard 1, where all four tests passed.
G2. Only 7 of 41 entries had an SBP case, and MO SAB had none Fixed mo_sab_countable_income.yaml Case 39 mirrors Case 33 with military_retirement_pay_survivors and gives $1,500. GA Case 16 and AR Case 34 cover the two new lists. The guard pins the rest statically.
G3. KS, MS, NC, NJ and NY LIHEAP list no military retirement input Declined with reason The body's Notes: "That gap predates #10182 and needs a rule check per state, so it is a follow-up." That is reasonable. No tracking issue exists yet (N1).
G4. The PR body omitted open PRs and four axiom issues Fixed The Notes name PA #9971, AK #10341, ND #10342, NH #10343, UT #10344, WA #10345, WY #10346 and the #10332 Hawaii block. The axiom line now includes #1540, #1535, #1537 and #1539, plus #1533 (GA) and #1538 (AR).
G5. PA CCW double counts SSDI and SS survivors; stale test headers Partly fixed; rest declined with reason Both headers are fixed. pa_ccw_countable_income.yaml and wv_ccap_countable_income.yaml now list exactly the sources in their parameter files, including military_retirement_income and total_unemployment_compensation. The double count is left as an out-of-scope follow-up in the Notes. No tracking issue exists yet (N1).
G6. Record the Minnesota (ix) versus (xi) reading Fixed mn/dcyf/ccap/income/countable_income/unearned.yaml now says that military retirement pay and SBP annuities count under 256P.06 subd. 3(2)(ix), and that the (xi) tax condition is not applied to them. #1870's Minnesota block records the same reading and the open doubt about a retiree's own pay.

New findings

Critical: none. Should address: none.

Suggestions:

N1. File tracking issues for the two deferred follow-ups. G3 (five LIHEAP lists that count no military retirement) and G5 (PA CCW counting SSDI and SS survivor benefits twice) are recorded only in the PR body's Notes, and a search of policyengine-us finds no issue for either. Once the PR merges, the body is easy to lose. Two short issues would keep them tracked. The New Jersey example from round 1 can seed the first, and the $12,000 survivor-benefit example the second.

N2. Stale comments and references that #1870 records are worth a follow-up. This PR did not create them, and it does not need to fix them.

  • Stale comments:
    • md/msde/ccs/income/countable_income/sources.yaml (lines 21–22) still lists "annuities" and "military entitlements" as COMAR sources "we don't model".
    • ar/ade/oec/sra/income/sources.yaml (line 25) still lists annuities among sources with "no matching variables".
    • After this PR, an SBP annuity is modeled through military_retirement_income.
  • Stale references in edited lists, as CA YCTC expanded in 2022 #1870 records:
    • OH PFCC cites OAC 5180:2-16-03, which is now 5180:6-1-03 (eff. 2026-08-01).
    • RI CCAP cites "Definition 30", which is now 29.
    • CO CCAP cites "Section 7.105", which is now 3.111(I).
    • NM CCAP cites 8.15.2 NMAC, replaced by 8.9.3 from 2022-11-01.
    • Page anchors: PA #page=59 should be 60, WV #page=51 should be 55, ME #page=13 should be 14, and DC #page=12 should be 13.
  • Fix: one reference-cleanup follow-up.

N3. The guard's survivor exemption is file-wide, not per list. raw_yaml_lines drops every raw entry in a file once any line in that file lists military_retirement_pay_survivors.

  • Example: a multi-period file whose new period lists both inputs would hide a raw-only entry in an older period. That older period would miss SBP, because the input is not year-gated.
  • Effect today: none. No non-tax file uses the exemption.
  • Fix (optional): scope the check to each period block, or say "file" in the docstring and failure message.

Verified

The new switches against the sources:

  • Arkansas. FFY 2025 LIHEAP manual, Appendix F, file p. 165:
    • "Pension": "Assistance paid at regular intervals to a person or to the person's surviving dependents in consideration of such things as past services, age, merit, poverty, or injury or loss sustained." It is marked Yes.
    • "Retirement/veteran benefits": "A monthly payment made to someone who is retired from work or the military." It is marked Yes.
  • Georgia. LIHEAP Policy Manual 1200.7 (file pp. 65–66):
    • "Annuity: Unearned payment received from an investment plan." It is marked Include.
    • "Pension: Unearned payment received regularly as a retirement benefit." It is marked Include.
    • No Exclude row covers a survivor annuity.
  • The new comments say that a survivor annuity "belongs in military_retirement_pay_survivors, not survivor_benefits". That matches the convention of the other seven LIHEAP lists.

Hand-checked new or changed expectations:

  1. GA LIHEAP ga_liheap_income.yaml Case 16 (2026).
    • Age 62 meets the adult minimum. The person sources sum only military_retirement_income = 9,600.
    • Social Security is 0, so the Part B netting is 0. Interest and ga_tanf are 0.
    • Result: 9,600.
  2. AR LIHEAP ar_liheap_countable_income.yaml Case 34 (2026).
    • Unearned = 9,600. Social Security, railroad and the premium net to 0, and tanf (with ar_tea 0) is 0.
    • Monthly = floor(9,600 / 12 + 0.5) = 800, × 12 = 9,600.
  3. MO SAB mo_sab_countable_income.yaml Case 39 (2026-01). 12,000 / 12 + 6,000 / 12 = 1,500. It is identical to Case 33, which passes with the same amounts as military_retirement_pay. The "$1,073 standard" in the comment matches consolidated_standard.yaml (2026-01-01: 1,073).
  4. Guard self-test, YAML detector.
    • Sample 1 flags lines [4, 5, 6]: a block entry, an entry with a trailing comment, and a flow entry.
    • Sample 2 gives []. military_retirement_income does not match, and the commented lines are stripped.
    • Sample 3 gives [], because survivors are listed.
    • Sample 4 gives [3], because the survivors line is commented out.
  5. Guard self-test, Python detector and path check.
    • The Python detector flags [1, 2, 3] in sample 1 and [] in sample 2. A class name, a label, a comment and longer names are not the exact constant.
    • is_tax_side gives True for gov/irs/..., .../mo/tax/... and variables/.../vt/tax/.... It gives False for TX TANF and for a file named tax.yaml under GA LIHEAP.
  6. test_benefit_source_lists_do_not_read_raw_military_retirement_pay on the merged tree. A grep of parameters/**/*.yaml outside /tax/ and gov/irs/ finds no military_retirement_pay entry, so it gives 0 violations.
  7. test_benefit_python_does_not_read_raw_military_retirement_pay on the merged tree. Every exact "military_retirement_pay" constant outside tests sits under a tax/ folder (CA, CO, DE, GA ×2, NC, NE, SC ×2, VT ×2). The only exception is military_retirement_income.py line 18, which is allowed. Result: 0 violations.
  8. test_allowed_python_files_still_read_the_raw_input. The allowed file still holds the constant (adds = ["military_retirement_pay", "military_retirement_pay_survivors"]), so not stale.

Counts and housekeeping:

  • The body's count is right. The merge-base diff adds 43 - military_retirement_income entries across 41 parameter files. It removes 42 bare - military_retirement_pay lines, plus Nevada's, which carried a trailing comment.
  • Test headers. The PA CCW and WV CCAP headers match their lists exactly.
  • Changelog. changelog.d/fix-sbp-benefit-income-sources.fixed.md is unchanged, top level and typed fixed.
  • Partners. No partner file changes.

Batch checks:

Axiom

Valid: axiom: TheAxiomFoundation/rulespec-us#1532, #1533, #1534, #1535, #1537, #1538, #1539, #1540, #1465, #1468, #1479, #1482, #1757, #1737, #1736 and #1870 queued.

  • State and labels. All 16 are OPEN and labelled pe-parity, and all were updated 2026-10-22 between 19:22 and 19:23 UTC.
  • Spot-checked SBP companions (6 of 15). Each names head f297b6ac67, the input change and the verbatim source text, and gives a companion test from the source.
  • CA YCTC expanded in 2022 #1870 (new), read in full. It is dispatch-ready on the Add AMT parameters for 2020-2023 #1467 model.
    • Structure. It has a block for each of the 26 programs (MO TANF, TX TANF, MO SAB and 23 child care programs). Each block gives the existing issue and module, a proposed module path, the corpus citation with pinned-ref status (or "none; needs ingest" for AR SRA, DE POC, FL SR and NY CCAP), the verbatim law with file pages, the exclusions checked, the required output with entity and period, and a source-derived $800-a-month companion test.
    • Blockers and dispatch. It records the blockers: sources missing from the corpus or the signed release, and billed runs that need separate approval. It gives a paste-ready review_finding template.
    • Matches the head. It names f297b6ac67, the guard and the MO SAB Case 39.
    • Interpretive limits. It records the MA CCFA closed-list risk from 2023-10-01 and the OR ERDC and NV residual-rule readings.

CI

15 pass, 3 pending and 0 failed at 22:29 UTC on 2026-10-22. Run 39838247295 (pull_request, created 19:13 UTC) tests head f297b6ac67 merged into 2bc4ccc970, which is current main.

  • Passed:
    • Baseline: household, irs, rest-a, rest-b, ssa-usda and states shards 1–4. The states shards hold the new GA, AR and MO SAB cases.
    • Python: Rest (Python, shard-1 + variables), which holds the guard and the source-conflict ratchet, and Microsimulation.
    • Other: Household API Partners, PR validation, Package and compatibility, and Quick Feedback.
  • Pending:
    • Baseline contrib-hhs and reform;
    • Python Rest (Python + variables).
  • Locally: I ran no tests or probes. The values above are hand traces of the code at the head.

Branch

0 critical, 0 should and 3 suggestions are open at this head. Let CI finish green before merging.

Verdict: APPROVE

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2: the axiom item is fixed. All 16 issues are open and labelled pe-parity, the 15 existing ones have SBP companion rows, and #1870 covers the other 26 lists. The new code-health guard passes on the merged tree and would catch #10332's HI block and the open LIHEAP PRs. The GA/AR switches match their manuals, and the new cases hand-check. Three small suggestions. CI was still finishing, so please let it go green before merging. Full report in the comment above.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants