Skip to content

Count each Social Security benefit once in Pennsylvania CCW countable income - #10408

Open
PavelMakarchuk wants to merge 4 commits into
mainfrom
pa-ccw-social-security-once
Open

PavelMakarchuk wants to merge 4 commits into
mainfrom
pa-ccw-social-security-once

Conversation

@PavelMakarchuk

@PavelMakarchuk PavelMakarchuk commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

Pennsylvania Child Care Works counted Social Security disability (SSDI) and survivor benefits twice. pa_ccw_countable_income adds the variables in pa/dhs/ccw/income/countable_income/sources.yaml, and that list named social_security together with social_security_disability and social_security_survivors. social_security already adds the dependents, disability, retirement and survivors components, so the two extra entries counted those benefits a second time. For example, a parent with $12,000 a year of survivor benefits had $2,000 a month of countable income instead of $1,000.

55 Pa. Code ch. 3042 Appendix A Part I.E (file page 59) includes "Social Security benefits, Supplemental Security Income, survivors' benefits and permanent disability insurance payments made by the Social Security Administration" in gross monthly income, and no Part III exclusion (file page 62) applies. Each SSA benefit counts once, so the list keeps social_security and drops the two components.

Changes:

  • sources.yaml: drop social_security_disability and social_security_survivors, and add a comment that social_security includes all four components. The reference title now says #page 59-60, because Part I runs from item A on file page 59 to item U on file page 60.
  • tests/code_health/test_social_security_source_lists.py (new, from DTrim99's review suggestion G1): fails when a parameter value block lists social_security together with one of its four components. It reads YAML text only and imports no model. On the pre-fix PA CCW list it flags lines 7-9.
  • pa_ccw_countable_income.yaml: new Cases 5 (survivor benefits), 6 (SSDI) and 7 (one member each with SSDI, dependents', survivor and retirement benefits). The header list now matches the parameter list.

Reviewer DTrim99 found this double count (suggestion G5) on #10317 and it was left out of that PR's scope.

#10317 merged while this PR was open. This branch merges main (latest merge 0e6e37d), so it keeps military_retirement_income from #10317 and lists social_security once.

Test plan

  • For policy changes: YAML tests whose expected values come from an external source. Cases 5-7 follow Appendix A Part I.E and the "Annually: Divide by 12" row of the conversion table (file page 61). On main they return 2,000, 1,600 and 3,900 instead of 1,000, 800 and 2,800. In Case 7 the retirement benefit is above the federal SSI benefit rate, so computed ssi is 0.
  • Source conflicts: not applicable.
  • make format
  • Changelog fragment in changelog.d/

Local runs on the tree merged with main: PA CCW YAML folder (test_batched.py --mode per-subdir, 21 files) passes. test_social_security_source_lists.py, test_military_retirement_readers.py, test_unemployment_compensation_readers.py, test_source_conflicts.py, test_parameter_yaml_keys.py and test_rest_python_groups.py pass (202 tests). The new guard lands in the remaining Rest Python group.

axiom: TheAxiomFoundation/rulespec-us#1737 queued

rulespec-us#1737 is the queued pe-parity issue for Appendix A, and it has no module yet. Its scope does not cover Part I.E. A companion comment asks to add Part I.E and the Case 5-7 expectations to its scope.

🤖 Generated with Claude Code

PavelMakarchuk and others added 2 commits October 23, 2026 12:46
social_security already adds the dependents, disability, retirement and
survivors components, so listing social_security_disability and
social_security_survivors next to it counted SSDI and survivor benefits
twice. 55 Pa. Code ch. 3042 Appendix A Part I.E counts each SSA benefit
once. Keep social_security, drop the two components, and add tests that
set each component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y-once

# Conflicts:
#	policyengine_us/tests/policy/baseline/gov/states/pa/dhs/ccw/pa_ccw_countable_income.yaml
@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Count each Social Security benefit once in Pennsylvania CCW countable income

PR #10408 (author: PavelMakarchuk), head 4f339ee096. This is a first review. The PR fixes the double count that our #10317 review found (suggestion G5). It drops social_security_disability and social_security_survivors from the Pennsylvania Child Care Works income list, because social_security already adds them. It also adds three test cases, refreshes the test-file header and the reference title, and adds a fixed changelog fragment. The PR 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:

  • The list now counts each SSA benefit once, and nothing is lost.
    • social_security is adds = ["social_security_" + i for i in ["dependents", "disability", "retirement", "survivors"]].
    • pa_ccw_countable_income is a plain adds over the list.
    • With the two components gone, all four benefits count exactly once. Retirement and dependents' benefits were already counted only through social_security, so no benefit drops out. ssi stays as its own Part I.E item.
  • It follows the rule. 55 Pa. Code ch. 3042 Appendix A Part I.E counts "Social Security benefits, Supplemental Security Income, survivors' benefits and permanent disability insurance payments made by the Social Security Administration before deductions of health insurance premiums." No Part III item (A–U) excludes an SSA benefit. Part III.A excludes only "Employment earnings of an individual who is an unemancipated minor", so a child's dependents' or survivor benefit counts.
  • PA CCW was the only list with this double count. No other parameter list on main names social_security together with any of its four components. The three Python readers that use both (MD PAA, MO Section C, UT homeowner/renter relief) subtract SSDI from the total; they don't add it twice.
  • Tests. I hand-checked all 7 expectations in the file and the 3 pre-fix values the body gives. All are correct, and CI passed them.
  • Batch checks. The Count military SBP annuities in state benefit income lists #10317 military guard, the Integrate modeled state unemployment insurance into income flows #9558 UC guard and the source-conflict ratchet all pass on the tree merged with main. The NH Add New Hampshire state supplementation for 2026 #10156 state-gated key, OK Add Oklahoma state supplementation for 2026 #10165, Document disjoint health premium inputs and correct consumer treatment #10046 and the LIHEAP floor checks don't apply.

There are no critical and no should-address findings. There are four optional suggestions:

  • G1: a cheap static guard against this pattern.
  • G2 and G3: two related gaps outside this PR's scope.
  • G4: tidying the queued axiom issue.

Status.

Critical (must fix)

None.

Should address

None.

Suggestions

G1. Consider a static guard so that no list names social_security together with one of its components.

  • Why. This bug came from listing an aggregate next to its parts. Several merged lists already name the components on their own: AR SRA, DE POC, FL SR, HI CCAP, NJ CCAP and RI CCAP. If someone later adds - social_security to one of them, the same double count comes back without any test failing.
    • Notes already in the code. The IN CCDF, ND CCAP and MS CCPP lists already carry "do not also add social_security_disability" comments.
  • Fix (optional). A test in tests/code_health/, modelled on test_military_retirement_readers.py:
    • What it fails on: a parameter YAML file with an uncommented social_security list entry and an uncommented social_security_{dependents,disability,retirement,survivors} entry.
    • It passes at this head. On the merged tree, no file has both.
    • Cost. It reads text only and imports no model, so it adds almost nothing to CI.

G2. Out of scope: seven lists name the Social Security components but leave out social_security_dependents.

  • Where:
    • DE POC (P/de/dss/poc/income/countable_income/sources.yaml);
    • FL SR (P/fl/doe/sr/income/sources.yaml);
    • HI CCAP (P/hi/bessd/ccap/income/countable_income/sources.yaml);
    • NJ CCAP (P/nj/njdhs/ccap/income/countable_income/unearned_sources.yaml);
    • RI CCAP (P/ri/dhs/ccap/income/countable_income/sources.yaml);
    • AR SRA (P/ar/ade/oec/sra/income/sources.yaml), whose formula also drops children's Social Security;
    • GA CAPS (P/ga/decal/caps/income/countable_income/sources.yaml), which lists only social_security_retirement.
  • Effect. A dependents' benefit paid to a counted family member (for example, a spouse's auxiliary benefit) counts $0 in these programs. I did not check each state's rule, so some of these omissions may be deliberate.
  • Fix. Mention it in a follow-up issue. It doesn't block this PR.

G3. Out of scope: PA CCW does not count survivor_benefits, the non-SSA survivor pensions.

  • The rule. Part I.I counts "retirement benefits paid to a retired person or their survivors by a former employer or a union", and Part I.J counts government pensions paid "to former employees, including members of the armed forces, or their survivors" (file pages 59–60).
  • The variable. Its documentation reads "Survivor benefits other than Social Security survivor benefits."
  • The gap. The list counts pension_income and military_retirement_income, but not survivor_benefits. A survivor's employer or union pension entered there therefore counts $0. No child care list in the repo names survivor_benefits (about two dozen other state lists do, mostly LIHEAP and TANF), so this is a convention gap and not something this PR introduced.
  • Fix. Consider adding it in a follow-up, with a test and a companion row on rulespec-us#1737.

G4. Tidy the queued rulespec-us issue.

  • Pin the head. The Count each Social Security benefit once in Pennsylvania CCW countable income #10408 companion comment on rulespec-us#1737 names the PR but not the head SHA. The earlier SBP comment pinned f297b6ac67. Add 4f339ee096.
  • Update the issue body. It still scopes the module to Part I(A) and II(C) only ("Keep unrelated income inclusions … outside this correction"). Its corpus-blocker section also says the corpus has no Chapter 3042, but the SBP comment reports that the pinned ref now carries the chapter PDF. Consider folding the Part I.E and I.J companions into the body, so that a dispatcher reading only the body doesn't miss them.

Verified correct

Rule (55 Pa. Code ch. 3042 Appendix A, official PDF):

  • Part I heading and items A–I are on file page 59, and items J–U are on file page 60. The new title "55 Pa. Code Chapter 3042 Appendix A Part I #page 59-60", with #page=59 in the href, follows the CLAUDE.md format.
  • Part I.E (file page 59) is quoted in the Summary.
  • Conversion table (file page 61): "Annually — Divide by 12."
  • Part III heading is on file page 61 and items A–U are on file page 62. None excludes a Social Security, SSI, survivor or disability payment.
  • "Before deductions of health insurance premiums." PolicyEngine's social_security is the gross benefit, and Medicare premiums are a separate expense, so gross is what the list reads.

Code:

  • P/pa/dhs/ccw/income/countable_income/sources.yaml has 15 entries at the head. The comment on lines 7–8 is accurate.
    • Test header. The list in T/pa/dhs/ccw/pa_ccw_countable_income.yaml (lines 5–9) now matches all 15 entries.
    • G5 tail fixed. The header no longer names military_retirement_pay, social_security_disability, social_security_survivors or raw unemployment_compensation. Those were the stale header entries Count military SBP annuities in state benefit income lists #10317's G5 noted.
  • Callers. The list's only reader is pa_ccw_countable_income, and that variable's only reader is pa_ccw_adjusted_income. No other PA CCW variable reads a Social Security variable.
  • Merged-tree guards.
  • Changelog. changelog.d/pa-ccw-social-security-once.fixed.md is a top-level file, and fixed is the right type.

Hand-checked expectations (all at 2025-01; annual inputs ÷ 12):

  1. Case 1: 36,000 / 12 = 3,000.
  2. Case 2: (24,000 + 6,000 + 3,600 + 1,200) / 12 = 2,900.
  3. Case 3: 0.
  4. Case 4: (30,000 + 18,000) / 12 = 4,000. The child has no income.
  5. Case 5 (survivors). The person is 30 and not aged, blind or disabled, so ssi is 0. 12,000 / 12 = 1,000.
    • Before the fix: the list counted social_security plus the survivors component, so (12,000 + 12,000) / 12 = 2,000, as the body says.
  6. Case 6 (SSDI). meets_ssi_disability_criteria is an input that defaults to false, so ssi is 0. 9,600 / 12 = 800.
    • Before the fix: 19,200 / 12 = 1,600.
  7. Case 7 (mixed, four members). (9,600 + 2,400 + 3,600 + 18,000) / 12 = 2,800.
    • Before the fix: (33,600 + 9,600 + 3,600) / 12 = 3,900.
    • SSI is 0 on every path. The 68-year-old's $1,500 a month exceeds the 2025 federal benefit rate of $967 (and the couple rate of $1,450). Combined with the parent's $800, the total also exceeds the couple rate. The children are not disabled, and the parent is not aged or disabled.
    • Coverage. This case pins all four components, including retirement and dependents' benefits.

Axiom line assessment

axiom: TheAxiomFoundation/rulespec-us#1737 queued is valid.

  • The issue. rulespec-us#1737 is OPEN and labelled pe-parity. Its body records the signed-encoder blocker ("The signed encoder is unavailable in this workspace; each billed run requires separate approval").
  • The companion comment (2026-10-23) is dispatch-ready:
    • module path us-pa/regulations/55/3042/appendix-a.yaml, with its test companion;
    • corpus citation us-pa/regulation/title-55/chapter-3042-pdf;
    • verbatim Part I and I.E text;
    • a Part III exclusion check;
    • the required output (gross monthly income, with each SSA benefit counted once);
    • three companion tests derived from Part I.E and the conversion table.
  • It matches the head. The three companion tests are exactly Cases 5–7.
  • Optional tidy-ups are in G4: pin the head SHA, and fold the I.E and I.J scope into the body.

CI status

gh pr checks 10408, re-checked at the end of this review: 27 pass, 3 skipping, 0 failed, 0 pending.

Branch status

  • Position. 2 ahead (7620b4ecc4 and the merge commit 4f339ee096) and 52 behind PolicyEngine/main (dbfc017665).
  • Merge. git merge-tree --write-tree PolicyEngine/main 4f339ee096 exits 0 with no conflicts.
  • The merged tree is main plus exactly the PR's three files. Nothing has landed on main since the merge base in PA CCW, the Social Security or SSI variables, or the guard tests that matter here. The test_source_conflicts.py changes on main touch only other files' KNOWN_UNMARKED entries.
  • Sibling note. Open PR Fix stale comments and references in ten benefit income lists #10410 (stale comments in ten lists, same author) makes the identical reference-title edit to P/pa/dhs/ccw/income/countable_income/sources.yaml. A trial merge of the two heads is clean.

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.

The fix is right: social_security already adds all four SSA components, so dropping the two duplicates counts each benefit once and drops nothing, per 55 Pa. Code 3042 App. A Part I.E. All 7 expectations hand-check (1,000 / 800 / 2,800, down from 2,000 / 1,600 / 3,900), rulespec-us#1737 is valid, and the #10317, #9558 and ratchet guards pass on the merged tree. Four suggestions, including a static guard against listing social_security with its parts. Full report in the comment above.

PavelMakarchuk and others added 2 commits October 26, 2026 12:36
A parameter value block that lists social_security together with one of
its dependents, disability, retirement or survivors components counts
that benefit twice, as PA CCW did. The check reads YAML text only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Count each Social Security benefit once in Pennsylvania CCW countable income

Summary

Head 0e6e37dc8b. After our round-1 approval (4f339ee096), the PR gained one commit, 3a479a6010, which takes our suggestion G1, plus a clean merge of main. The commit adds tests/code_health/test_social_security_source_lists.py (128 lines). Nothing is open.

The new guard

  • What it checks. It reads parameter YAML as text and doesn't import the model, so its CI cost is negligible. Within each value block (the entries under one date key), it fails when a list has an uncommented social_security entry alongside social_security_dependents, _disability, _retirement or _survivors, in block or flow style.
  • What it ignores. Comments, names that merely contain the word (taxable_social_security, ..._insurance_premiums), and files that switch from the components to the total in a later block.
  • Self-test. It covers each of those cases: the flagged lines in the first fixture are [3, 4, 5, 6], and the other three fixtures are clean.
  • Scope. It deliberately skips Python readers, since those that read both subtract a component from the total (MD, MO and UT exclude SSDI that way), as round 1 found.
  • Results. It passes on this head, which fixes PA CCW, so no other list on main double-counts. It would have caught the PA CCW lists from before this PR.

CI and branch

32/32 pass at 0e6e37dc8b. The branch is 4 ahead and 0 behind main.

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 new code-health guard (our G1) reads YAML text only. It flags a list naming social_security together with any of its components, skips comments and look-alike names, and self-tests each case. It passes on this head, and CI is 32/32 green. Approving again. Full note 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