Skip to content

Count reported SNAP and WIC receipt in categorical eligibility lists - #9248

Open
hua7450 wants to merge 3 commits into
mainfrom
receives-flags-categorical-eligibility
Open

Count reported SNAP and WIC receipt in categorical eligibility lists#9248
hua7450 wants to merge 3 commits into
mainfrom
receives-flags-categorical-eligibility

Conversation

@hua7450

@hua7450 hua7450 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Categorical-eligibility parameter lists referenced only the modeled benefit amounts (snap, wic) and omitted the reported-receipt bare inputs (receives_snap, receives_wic). A household that reports receiving SNAP or WIC but whose modeled amount is $0 was therefore dropped from categorical eligibility in seven programs.

Fixes #9247

Mechanism

  • receives_snap is a bare input: bool, SPMUnit, MONTH, no formula. receives_wic is the same at Person level.
  • snap is defined_for = "takes_up_snap_if_eligible"; wic is defined_for = "takes_up_wic_if_eligible".

So the modeled amount is $0 for a reporting household whenever the takeup draw is negative, income lands just over the limit, or member composition makes it ineligible. Formulas that hardcode the check already handle this by ORing both arms — is_head_start_categorically_eligible.py:26-31, meets_wic_categorical_eligibility.py:16-17, medicaid_community_engagement_pass_through_eligible.py:31-32. The parameter lists carried only the amount variable.

Precedent for a receipt flag in a parameter list: receives_medicaid in gov/aca/qualifying_non_marketplace_health_coverage.yaml and gov/hhs/chip/disqualifying_health_coverage.yaml.

Changes

receives_snap added to six lists, receives_wic to four:

Parameter list Added Consumer
gov/usda/school_meals/categorical_eligibility.yaml receives_snap meets_school_meal_categorical_eligibility
gov/fcc/lifeline/categorical_eligibility.yaml receives_snap is_lifeline_eligible, is_acp_eligible
gov/fcc/acp/categorical_eligibility.yaml receives_wic is_acp_eligible
gov/states/ma/dot/mbta/.../applicable_programs.yaml receives_snap ma_mbta_enrolled_in_applicable_programs
gov/states/ca/cpuc/care/eligibility/categorical.yaml both ca_care_categorically_eligible
gov/states/tx/dart/qualifying_programs.yaml both tx_dart_reduced_fare_program_eligible
gov/ed/pell_grant/efc/simplified/benefits.yaml both pell_grant_simplified_formula_applies

Entity aggregation was checked per file: receives_wic is Person-entity and every consumer aggregates person → SPMUnit / Household / TaxUnit; receives_snap is SPMUnit-entity and the two Person-entity consumers (MBTA, DART) project SPMUnit → Person. All consumers are YEAR-defined while both flags are MONTH, so add sums up to 12 monthly bools; every consumer either thresholds with > 0 or casts into a bool variable, so the result is correct in all cases.

The DART variable's own docstring already described its list as "(SNAP, Medicaid, Medicare, CHIP, TANF, WIC, etc.)", so receipt was the intent.

Deliberately not changed

These lists sum dollars, so a bool would contribute $1 of phantom income:

  • gov/states/ca/cpuc/income_sources.yaml (SNAP and WIC as counted income for CARE/FERA)
  • gov/local/ca/la/dwp/ez_save/income_sources.yaml (SNAP)
  • gov/states/ma/tax/income/credits/senior_circuit_breaker/income/disallowed_deductions.yaml (consumed by ma_scb_total_income)

Reporting aggregates (gov/household/household_benefits.yaml, cbo_means_tested_transfers.yaml, spm_unit_benefits.py) are also unchanged — their consumers are net-income and display variables, not program eligibility tests.

Impact

This expands categorical eligibility in microsimulation for households that report receipt but model to $0. No partner contract test expectations were edited. The MBTA partner edge-case file (tests/policy/baseline/partners/analytics_coverage/edge_cases/state/ma/mbta.yaml) passes unchanged, and the full 630-test partner suite passes with no expectations edited.

Test plan

  • 308 tests pass across the affected suites (school meals, FCC, Pell Grant, CA CPUC, TX DART, MA DOT)
  • New cases assert the reported-receipt-with-$0-modeled-amount path for all seven programs, including a new test file for ma_mbta_enrolled_in_applicable_programs, which previously had none
  • tests/.../partners/.../state/ma/mbta.yaml passes unchanged
  • Full partner suite green — 630 tests pass, no expectations edited
  • CI passes

Categorical-eligibility parameter lists referenced only the modeled
benefit amounts (snap, wic), omitting the reported-receipt bare inputs
(receives_snap, receives_wic). Households reporting receipt whose
modeled amount is $0 were dropped from categorical eligibility, unlike
the hardcoded formulas (Head Start, WIC, Medicaid community engagement)
which OR both arms.

Fixes #9247

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@DTrim99

DTrim99 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Program Review — multi-agent audit

PR #9248 — Reported-receipt flags (receives_snap / receives_wic) in categorical-eligibility lists

Scope

  • What the PR does: adds the reported-receipt bool inputs receives_snap (SPMUnit/MONTH) and receives_wic (Person/MONTH) to 7 categorical-eligibility parameter lists — receives_snap into 6 lists (school meals, Lifeline, CA CARE, MBTA, TX DART, Pell) and receives_wic into 4 (Pell, ACP, CA CARE, TX DART). Effect: a household that reports SNAP/WIC receipt but has a $0 modeled allotment (takeup draw negative, or income just over the limit) regains categorical eligibility across 7 programs (federal USDA/FCC/ED plus CA/MA/TX state and local).
  • PR type: bug fix. No .py variables changed — parameter-list wiring + tests + changelog only.
  • CI: 31/31 pass. Branch: 1 ahead / 2 behind main (not stale enough to block; a rebase is optional).
  • Review nature: structural wiring — no PDF / source-document audit ran (no statute values are introduced; this is program-membership plumbing, not parameter values). No reference-validator pass was required.

Verified correct (checks that passed)

  • SNAP/WIC is a genuine qualifier in all 7 programs. The base lists already carried snap/wic as members; the PR only mirrors the reported-receipt flag onto them. School meals / Lifeline / MBTA correctly get receives_snap only (WIC is not a qualifier for those). ACP / CA CARE / TX DART / Pell get the correct SNAP+WIC combos. ACP adds receives_wic to its own list and inherits the SNAP arm via the Lifeline categorical list it OR's in — no duplicate receives_snap needed. No flag was added where the program does not admit it. Authorities cited per program (42 USC 1758(b)/7 CFR 245.6; 47 CFR 54.409; 47 USC 1752(a)(6)(E); PUC 739.1(f); DART GoPass list; 2022-23 EFC Formula Guide; MBTA reduced-fare list).
  • The modeled-amount-vs-receipt gap is real. snap/wic amounts are defined_for/takeup-gated and return $0 for a reporting-but-untaken-up or just-over-income household, dropping it from categorical eligibility. The receipt flags are bare bool inputs (verified 0 formulas in each), carrying reported receipt independent of the modeled allotment.
  • All 7 consumers read the list via add(...) > 0 or adds on a bool var, so adding the flag reproduces exactly the (snap>0) | receives_snap both-arms behavior. The three cited hardcoded precedents confirm the pattern: is_head_start_categorically_eligible, meets_wic_categorical_eligibility, and medicaid_community_engagement_pass_through_eligible all OR both arms.
  • Entity aggregation correct for all 7 consumers, including the tricky ones. add()/adds project across entity levels in both directions automatically. The prompt's top worry — SPMUnit-level receives_snap feeding Person-level MBTA/DART consumers — is safe: pre-existing snap (SPMUnit) was already a member of those Person-level lists, so receives_snap is the identical shape and cannot introduce a new entity bug. Also verified: Person→SPMUnit (ACP), Person→Household + SPMUnit→Household (CA CARE), SPMUnit→TaxUnit + Person→TaxUnit (Pell).
  • MONTH-bool → YEAR consumer: no period bug. Both flags are MONTH bool; all consumers are YEAR. add()/adds sum the monthly bool to an int in 0..12, which every consumer reduces via > 0 or a bool-cast — no consumer treats the sum as a count, so a 12-vs-1 value never matters. Confirmed by live simulation ([12] > 0 → True). This is identical to how the pre-existing MONTH-period snap/wic amounts are already summed into these YEAR lists — no 12x inflation.
  • Completeness grep — no missed list. Grepping the entire parameters tree for bare - snap / - wic members returned 12 files. All 7 bool add()>0 eligibility lists carrying snap/wic are FIXED. The 5 dollar-sum/aggregate lists are correctly left untouched: household_benefits (float), cbo_means_tested_transfers (float), la/dwp/ez_save/income_sources (dollar income), ca/cpuc/income_sources (dollar income), and ma/.../senior_circuit_breaker/.../disallowed_deductions (dollar add-back). Adding a bool to any of these would inject a phantom $1. 0 missed, 0 wrongly touched.
  • Precedent confirmed. The receipt-flag-as-list-member pattern is already established: receives_medicaid sits alongside medicaid_enrolled in aca/qualifying_non_marketplace_health_coverage.yaml and hhs/chip/disqualifying_health_coverage.yaml.
  • No reinvented variables. Diff adds no new variable files; only new files are the changelog fragment and one new test YAML. receives_snap/receives_wic pre-exist and are reused.
  • List integrity. All 7 lists are flat single-start-date membership lists (not year-keyed dated blocks); every hunk is pure addition, no prior member dropped, no metadata disturbed.
  • Changelog present and correctly typed: changelog.d/receives-flags-categorical-eligibility.fixed.md (.fixed = patch bump), accurately naming all 7 programs.
  • New MBTA test file covers a previously-untested variable (ma_mbta_enrolled_in_applicable_programs) with positive-SNAP, reported-receipt-$0, and negative branches.

Critical (must fix)

None.

Should address

Both items are test-robustness gaps flagged by the test-coverage agent. Every wired program already has an isolated positive case and a negative control (no correctness gap), so these are hardening, not blockers.

  1. DART WIC case isolation is not pinned. tests/policy/baseline/gov/states/tx/dart/reduced_fare/eligibility/tx_dart_reduced_fare_program_eligible.yaml (WIC "mother" case, ~line 321). It sets wic:0, receives_wic:true but does not explicitly zero the other Person/SPMUnit qualifiers (medicaid_enrolled, is_medicare_eligible, snap, tanf, receives_snap) or add an explicit spm_units block. It passes today via WIC alone on defaults, but a future default change could mask a regression. Fix: mirror the SNAP case above it — explicit spm_units block and set the other qualifying programs to 0/false so only receives_wic can flip the result.

  2. No multi-person MBTA/DART case where one member's SPMUnit-level SNAP flows to a different Person. Both cross-entity tests use a single-person SPMUnit, so the SPMUnit→Person projection is exercised but not the strongest form. Fix: add a 2-person SPMUnit case with snap:0, receives_snap:true asserting the second member is also eligible, locking in the projection for multi-person units.

Suggestions

  1. IL IHWAP has the same bug class for SSI/TANF/housing — out of scope here, worth a follow-up. gov/states/il/dceo/ihwap/eligibility/categorical_eligibility.yaml is a genuine bool add()>0 test whose members (il_liheap, ssi, il_tanf, il_aabd, housing_assistance) are takeup/defined_for-gated modeled amounts, and receipt flags exist for three (receives_ssi, receives_tanf, receives_housing_assistance). This list contains no snap/wic, so it is correctly outside this PR's SNAP/WIC scope — not a defect of Count reported SNAP and WIC receipt in categorical eligibility lists #9248. Recommend a follow-up issue to mirror receipt flags here and audit other categorical lists referencing ssi/tanf/housing_assistance.
  2. Pell test — add a negative-flag control. pell_grant_simplified_formula_applies.yaml: add a mirror case with the flags false and all modeled benefits $0 asserting false, to lock in that the flag is what flips the result. (Existing pre-PR cases largely cover this; optional.)
  3. MBTA/DART/school_meals rely on the implicit numeric→bool cast via adds (whereas ACP/Lifeline/CARE make it explicit with > 0). Correct and pre-existing; noting for future readers — no change needed.
  4. Optional reference/comment polish. The inline per-item comments are accurate; optionally note the SPMUnit/Person + MONTH/YEAR detail. The Pell benefits.yaml reference (2022-23 EFC Formula Guide #page=4) could pin the exact qualifying-benefit worksheet page. Not blocking.
  5. Style: CARE/ACP tests mix 0/false and integer-truthy values (medicaid_enrolled: 0); harmless, matches existing file style.

Validation summary

Check Result
Regulatory / eligibility soundness (SNAP/WIC a genuine qualifier in all 7) PASS
Completeness (any list with the same bug missed?) PASS — 7 fixed, 5 correctly excluded, 0 missed
Entity / period aggregation (incl. SPMUnit→Person, MONTH→YEAR) PASS
Code patterns (list integrity, precedent, no reinvented vars) PASS
Test coverage (isolation + negative control per program) PASS with 2 robustness gaps (see Should address)
CI PASS (31/31)

Review severity: APPROVE

0 criticals; completeness verified (exactly the 7 snap/wic bool lists fixed, all 5 dollar-sum lists correctly left alone, none missed); entity and period aggregation confirmed for all 7 consumers including the SPMUnit→Person MBTA/DART projection. Strong, well-scoped bug fix — the two should-address items are test hardening, not correctness.

Next steps

  • Run /fix-pr 9248 to apply the two should-address test-robustness items (DART WIC isolation; multi-person SPMUnit→Person case).
  • Optionally open the IL IHWAP follow-up issue (Suggestion 1).

🤖 Generated by /review-program (Claude Code)

hua7450 and others added 2 commits August 7, 2026 11:41
Isolate the DART WIC case by zeroing every other qualifying program, and
add multi-person SPM unit cases to both cross-entity consumers (DART and
MBTA) so the SPMUnit-to-Person projection of receives_snap is locked in
for units with more than one member.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…into receives-flags-categorical-eligibility
@hua7450

hua7450 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough audit. Applied both should-address items.

1. DART WIC case isolation. The "mother" case now zeroes every other qualifier — chip, medicaid_enrolled, and an explicit spm_units block with snap: 0, receives_snap: false, tanf: 0 — so only receives_wic can flip the result. Left age: 28 / is_disabled: false with a comment noting those are what keep her out of the Medicare arm, since is_medicare_eligible is derived rather than settable.

2. Multi-person SPMUnit→Person projection. Added a 2-person case to both cross-entity consumers, not just DART: a parent + child SPM unit with snap: 0, receives_snap: true asserting [true, true] for DART, and the equivalent for ma_mbta_enrolled_in_applicable_programs. Also tightened the pre-existing single-person MBTA case to zero ma_eaedc / ma_tafdc / medicaid_enrolled, which had the same isolation looseness you flagged on DART.

19 tests pass across the two files. Branch is merged up to current main.

On Suggestion 1 — already done, and slightly wider than proposed. #9251 / #9252 cover the same bug class for receives_ssi and receives_tanf across seven lists, including IL IHWAP. #9252 is stacked on this branch, since four parameter files and four test files overlap on adjacent lines; merge this one first and that rebases onto main as a no-op.

Worth noting one thing your review surfaced that #9252 does not cover: IHWAP's list also carries il_liheap, il_aabd, and housing_assistance, all takeup-gated, and you note receives_housing_assistance exists. That is a residual gap I will take as a third follow-up rather than widen #9252.

Also flagging an asymmetry for #9252 rather than here: gov/states/dc/dhs/power/disqualifying_benefits.yaml is a disqualifying list, so adding receives_ssi there makes the model less generous, not more — opposite sign from every other item in that PR, and worth not rubber-stamping as a widening.

Suggestions 2–5 noted; leaving them as-is since the existing negative controls cover the Pell case.

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.

Categorical-eligibility parameter lists omit receives_snap and receives_wic

2 participants