Make SNAP work requirement disqualification person-level - #8961
Conversation
21581c7 to
712402b
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8961 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 17 3 -14
Lines 279 49 -230
==========================================
- Hits 279 49 -230
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…strants Under 7 CFR 273.7, working 30+ hours weekly is exemption (b)(1)(vii) from work registration, not an affirmative requirement; non-exempt registrants remain eligible unless they affirmatively fail to comply without good cause. Those events are unobservable in survey data, so the baseline assumes compliance, with a new is_snap_work_registration_noncompliant input as a sanction hook. Inline the 30-hour test in the Medicaid community engagement pass-through to preserve its behavior, and rewrite YAML tests that encoded the over-strict household-level reading. Fixes PolicyEngine#8862 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
42 CFR 435.554(c)(7) and the interim final rule preamble provide that the SNAP exclusion from the Medicaid community engagement requirement turns on SNAP receipt plus being subject to (not exempt from) a SNAP work requirement; unlike the TANF prong, states do not confirm actual compliance. Drop the hours/ABAWD compliance conjunct from the pass-through accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Setting monthly_age at a year period divides the value across months, which made an 18-year-old read as a household child under the 273.24(c)(4) any-member exception after rebasing onto main. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
712402b to
77453dc
Compare
|
Ran a first release-scale reform smoke of this branch (head 77453dc) against the Build K Populace artifact (populace-us-2024-buildk-snapval, 57,240 households, calibrated on pe-us 1.764.6 — the artifact that fits all 51 FNS state caseload targets). Baseline = 2024 law (
Smoke script and log available; happy to turn (2) into a failing YAML test if useful. 🤖 Generated with Claude Code |
7 U.S.C. 2015(o) is an eligibility limitation. Excluding a noncompliant member shrinks snap_unit_size AND prorates his income (273.11(c)(2)), so a household over the limits at full size can qualify on half the earner's income once he fails the time limit — +$288/yr in Case 2, and at release scale the mechanism inverts ABAWD reform signs (+$0.12B for switching the time limit ON). Case 1 pins the correct zero-income-member direction (passes); Cases 2-3 pin the no-gain invariant (Case 2 fails). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Follow-up on finding (2): the wrong sign is now pinned to a minimal household and encoded as a YAML test (committed locally as The mechanism, isolated: a TX household of a 66-year-old ($9,600 Social Security) and a 30-year-old earning $30,000 is SNAP-ineligible — until the earner drops below 20 hours/week. Then Test file:
Design question for the fix: 7 CFR 273.11(c) governs treatment of a disqualified member within a participating household; it isn't an eligibility pathway for households that don't qualify at full composition. One shape: apply the (c)(2) proration to the benefit computation only when the full-composition unit is otherwise eligible (or gate 🤖 Generated with Claude Code |
…ility expansion) Excluding a noncompliant member shrank snap_unit_size AND prorated his income (273.11(c)(2)), letting full-composition-ineligible households qualify at reduced size on halved income: failing the ABAWD time limit created a ~$288/yr benefit for an over-income household, and at release scale switching the time limit ON raised SNAP spending (+$0.12B). Count the income in full (273.11(c)(1) treatment) so disqualification can only reduce eligibility and benefits; drop the proration variable; repin the two proration tests to full counting; the invariant test from the prior commit now passes (Case 2: eligibility no longer flips). The proration predates this branch (it shipped with the SNAP wave on main); this fix rides the person-level rework since the two must agree. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fix drafted and validated; the wrong-sign smoke fully decomposed into two independent causes. Both commits are local on this branch ( Cause 1 — eligibility expansion via proration (real bug, pre-dates this branch, now fixed). At pure current law, a household over the income limits at full composition qualified at reduced size on the disqualified earner's halved income ( Cause 2 — Coherent validation (2026: baseline HR1-on vs repeal), on the Build K artifact with the fix:
🤖 Generated with Claude Code |
Resolves conflicts with main's reviewed ineligible-member income work (PolicyEngine#8372 follow-ups), which splits the treatment this branch had unified: 273.7 registration sanctions count income in full (273.11(c)(1)) while 273.24 ABAWD time-limit ineligibles are prorated (273.11(c)(2)). - snap_earned_income: take main's version (SE attribution at the unit level plus the counted-share regime split); drop this branch's blanket full-count formula and its changelog fragment, both superseded. - Rewrite the eligibility guard test as snap_work_requirement_disqualification_income_treatment.yaml: the no-gain invariant is pinned for sanctions via the new is_snap_work_registration_noncompliant hook, and the reg-prescribed outcome that time-limit proration can newly qualify an over-income household is pinned explicitly. - Keep this branch's Medicaid CE status test (CMS-2454-IFC) and its person-level integration tests (renumbered snap.yaml Cases 8-10). - Set the noncompliance hook on main's sanctioned-member test cases, which relied on zero hours implying a failed general requirement; baseline now assumes registration compliance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…into snap-work-req-person-level
Program Review — PR #8961 (Make SNAP work-requirement disqualification person-level)Author: daphnehanse11 · Fixes #8862 · Logic/semantics rewrite (no parameter-value changes) Source DocumentsFederal regulations (verified against primary sources where reachable):
Branch Status⚠ PR branch is 70 commits behind main (7 ahead). Recommend rebasing before merge. Review was scoped strictly to the PR's merge-base diff, so the staleness did not cause false-positive findings. SummaryGenuine correction of two prior misreadings of SNAP work-requirement law, verified by four independent validators. All agree: 0 critical. The general work requirement no longer treats "not working 30+ hrs" as an affirmative failure (it's an exemption from registration per 273.7(b)(1)(vii)); disqualification now attaches to the individual (removing them from the unit with prorated/ full-counted income per 273.11(c)) rather than zeroing the household; and the Medicaid community-engagement SNAP prong is a status test (SNAP receipt + non-exemption, no compliance check) per the June-2026 interim final rule, while the TANF prong retains its compliance check. CI green (35/35; PR reports 377 SNAP + 373 Medicaid passing). The four legal claims — all CONFIRMED against primary sources:
Entity levels (the crux) are correct: both new/changed variables are Critical (Must Fix)None. Should Address (non-blocking)
Suggestions
Validation Summary
Review Severity: COMMENTA well-researched, regulatorily-sound correction of two real misreadings, with strong test coverage and correct person-level entity semantics. No blocking issues. Before merge: update the stale PR body (item 1), confirm the new IFR text (item 2), and ideally tighten Case 1's margin (item 3). Rebase recommended given the 70-commit lag. Next StepsTo auto-apply the actionable items: Review generated with Claude Code via /review-program |
…into snap-work-req-person-level
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Program-review items (PR PolicyEngine#8961 review comment): - Tighten absolute_error_margin in the disqualification income treatment tests from 1 to 0.01, pinning the exact currency values so the boolean and unit-size assertions stand on their own. - Add a pre-2027 negative case pinning the Medicaid community engagement applicability-date boundary in is_medicaid_ineligible_due_to_work_requirement. - Add subsection deep links to the 7 CFR 273.7 reference tuple, a direct eCFR 435.554(c)(7) link to the Medicaid CE pass-through variable, and a 7 CFR 273.11(c)(1) cite to is_snap_prorated_income_member. - Pair the incapacitated-person exemption with the sanction hook in the general work requirements tests (Case 12b). - Wrap long single-line documentation strings. Cross-member conflation (PolicyEngine#9145, flagged in hua7450's PR PolicyEngine#9118 review): - is_snap_eligible now requires a single member to pass the student, immigration, and work-requirement filters simultaneously via spm_unit.any(~is_snap_excluded_member), replacing the separate unit-level meets_snap_work_requirements reduction. A fully excluded unit no longer draws the minimum allotment (regression test added). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #8862
Summary
Issue #8862 identified two misreadings of SNAP work requirement law. The first — household-wide disqualification when any member fails — was largely resolved by #8372, which made
snap_unit_sizeremove failing individuals, added income proration, and gave the household-level variable any-member semantics. This PR completes the issue's scope by fixing the second misreading: the general work requirement treated working 30+ hours/week as an affirmative requirement, disqualifying every non-exempt adult who simply was not working. It also corrects the Medicaid community engagement pass-through, whose SNAP prong the interim final rule defines as a status test rather than a compliance test.Regulatory authority
Verified against primary sources (eCFR, U.S. Code, enrolled H.R. 1, Federal Register):
Changes
meets_snap_general_work_requirementsnow returns exemption status OR the absence of affirmative noncompliance. In the baseline everyone meets the requirement (compliance assumed); the exemption logic is unchanged and still governs who can be sanctioned.is_snap_work_registration_noncompliant(new person-level input, default false) is the sanction-modeling hook. When set true, only exempt people — including 30+ hour workers per 273.7(b)(1)(vii) — still meet the general requirement, matching 273.7(f)(1), under which only non-exempt registrants can be sanctioned.medicaid_community_engagement_pass_through_eligiblepreviously required actual work activity (30-hour general test plus ABAWD compliance). Per the statute and interim final rule, the SNAP prong is a status test: SNAP receipt plus being subject to (not exempt from) a SNAP work requirement, with no compliance check. The formula now tests SNAP receipt and non-exemption only; the TANF prong retains its compliance requirement per the rule's explicit asymmetry. Documented simplifications: the 273.7(b)(1)(vii) working-30+-hours exemption is not netted out of subject-to status (immaterial to CE outcomes, since such workers satisfy the requirement directly), and post-HR1 adults 60-64 subject only to the ABAWD requirement are not separately captured.Income treatment implements the two-regime 273.11(c) split (
is_snap_prorated_income_member): members whose only failing prong is the ABAWD time limit have their income prorated per 273.11(c)(2), while members sanctioned under the general work requirements have their income counted in full per 273.11(c)(1). Both regimes remove the member from the SNAP unit size.snap_work_requirement_disqualification_income_treatment.yamlpins both regimes and the no-gain invariant for (c)(1) sanctions.Behavioral effect
Test coverage
meets_snap_general_work_requirements.yaml: 7 → 12 cases; the two cases encoding the over-strict reading flip to true, and five new cases exercise the sanction hook against each exemption.meets_snap_work_requirements.yaml: failure cases now derive from organic ABAWD facts or the noncompliance hook; added an end-to-end sanction case.snap.yaml: acceptance-criteria integration tests — the issue example (full 535 allotment for two with TANF zeroed for isolation), a mixed no-children household (unit of one, still eligible), and a single failing ABAWD (unit size zero).snap_unit_size.yaml,federal_work_study_income_exclusion.yaml: override-based premises converted to organic ABAWD facts; expected outputs unchanged.medicaid_community_engagement_pass_through_eligible.yaml: all five existing cases unchanged; new case asserts a non-working, non-exempt SNAP member passes through without a compliance check.Validation
make formatclean🤖 Generated with Claude Code
Rebase note (2026-07-09)
Rebased onto main past #8857, #8858, #8860, #8861, #8869, #8870, #8871, #8919, #8942. Semantic resolutions:
meets_snap_general_work_requirements: merged with Support qualifying work-program participation in SNAP ABAWD work requirement #8857's work-program path — the formula is nowexempt | is_snap_work_program_participant | ~is_snap_work_registration_noncompliant. Participation under 7 CFR 273.7(a)(1)(ii) is affirmative compliance and overrides the sanction hook (new test case 14).medicaid_community_engagement_pass_through_eligible: kept the status test; carried forward Cast bool-bracket .calc() results to bool before bitwise-NOT #8919's.astype(bool)cast on the age-bracket result. Main's cases 10–11 (ABAWD hours binding on the pass-through) were updated: under the 42 CFR 435.554(c)(7) status test the hours test does not bind, so case 11's expected output flips to[true, true].meets_snap_work_requirements.yaml: main's cases 11–12 relied on the affirmative-30-hour reading; re-anchored their failure path on the noncompliance hook. Cases 1–4 now setagerather thanmonthly_age— a year-periodmonthly_ageinput divides by 12, which made adults read as household children under Fix benefit parameter accuracy (CO SS cap increase, KY CCAP SMI, KY K-TAP citation, SNAP ABAWD gate) #8942's any-member 273.24(c)(4) semantics.Post-rebase validation: SNAP YAML 377 passed, Medicaid YAML 373 passed, ACA pays_aca_premium 8 passed, microsimulation 6 passed, ruff format clean.
Review round (2026-07-27)
Addressed @DTrim99's program review and the cross-member conflation @hua7450 flagged in the #9118 review (tracked as #9145):
is_snap_eligiblenow requires a single member to satisfy the student, immigration, and work-requirement filters simultaneously, viaspm_unit.any(~is_snap_excluded_member), replacing the separate unit-levelmeets_snap_work_requirementsreduction. A fully excluded unit no longer draws the minimum allotment. Regression test added (immigration-ineligible worker + citizen ABAWD, no single member passes all three → ineligible,snap: 0).snap_work_requirement_disqualification_income_treatment.yamlCases 1–2 toabsolute_error_margin: 0.1with exact currency values (reconciled with the equivalent fix Ziming pushed concurrently), so the boolean and unit-size assertions bind.is_medicaid_ineligible_due_to_work_requirement.yaml.Validation: SNAP YAML 487 passed, Medicaid YAML 378 passed, WA ECEAP 113 passed, TX CEAP + DC SNAP local benefit 31 passed, microsimulation 6 passed, ruff format/check clean.