Recalibrate SNAP take-up per state to FNS household caseloads#376
Conversation
MaxGhenis
left a comment
There was a problem hiding this comment.
Review — design is sound and consistent with the merged #371, but two mechanical blockers stop a merge: the base is a merged branch and no CI has run.
Blockers (both mechanical)
- Base is
snap-caseload-targets, which merged today. That was #371's branch; #371 landed onmainas squash commit027355b(main is now further ahead). GitHub did not retarget this PR because the branch wasn't deleted, so as it stands the PR would merge intosnap-caseload-targets, notmain. Retarget the base tomainand rebase. - No CI has run ("no checks reported on the
snap-state-take-upbranch"). The Tests workflow ispull_request: branches: [main], so a PR based onsnap-caseload-targetsnever triggers it — the stale base is why there are no checks. Retargeting tomainwill run CI, which is the gate for merge.
What I verified locally (fresh clone, cherry-pick onto current main)
- The incremental commit (
e0e14c2) cherry-picks cleanly onto currentmain— no conflicts, exactly the +1113/−3 delta across the 7 files. The rebase is mechanical (#371's content is already inmain, and this was built directly on it). - On that rebased-onto-
mainstate, the delta is healthy:test_us_snap_state_take_up.py(14 tests),test_us_take_up_contract.py, and the register-consistency + take-up-contract preflight slices oftest_us_fiscal_refresh_builder.pyall pass — including the #384 preflights that this PR's own base predates.
Design — the seed/calibration double-pressure is resolved by construction
The stage calibrates the taker set to the FNS average_monthly_households facts — the same rows #371's snap_households weight-calibration targets compile from. So the seed (who takes up) and the weight-calibration objective (per-state household counts) pin the same administrative measure; they agree rather than fight. The take-up contract keeps takes_up_snap_if_eligible as out_of_scope (correctly not count_calibrated, since it retains a sourced national prior — that treatment forbids a sourced rate), so it stays out of the register-consistency signal-side registers and introduces no cross-register contradiction. Saturation-recorded-not-failed (CA at 1.05× its caseload) and persons-left-untargeted (SPM-unit overcount, per #371's second commit) are consistent with the merged surface.
One relationship note
This PR and #375 are two implementations of #372. This one is consistent with the merged #371 (FY2024 household caseloads); #375 uses a different basis (FY2022 eligible-person participation rates), which does not target the same quantity #371 now calibrates to. Worth settling which approach to carry before more investment — on the merged state, this is the consistent one.
Next step
Retarget base to main, rebase (trivial per the clean cherry-pick), and push so CI runs. With green checks it's a straightforward merge candidate; I did not merge because the base is non-main and no checks exist.
The snap_take_up stage (#294) anchors take-up on reported ASEC receipt and fills non-reporters to the national FNS participation rate. CPS SNAP receipt underreporting is strongly state-dependent, so the national fill starves the worst-underreporting states of taker households no reweighting can recover: the buildi-sparse release undershoots ten state benefit targets by 7-43% while a feasibility audit shows every state but California reachable under the weight cap, and state taker-household counts err from -38% to +325% against FNS caseloads (#372). Add a snap_state_take_up stage following the medicaid_take_up pattern (#331) at SPM-unit grain: reported recipients always take up; the fill draws at an in-build state rate (FNS average-monthly household count over weighted modeled-eligible units, an assignment prior, never cited as provenance) and is greedily calibrated to the FNS FY2024 state counts among eligible non-anchored units. The targets are the same snap_households rows the weight-calibration targets compile from, so the seed and the calibration objective agree. Saturation is recorded, not failed; a release gate enforces anchor preservation, per-state count fidelity within unit-weight granularity, full state target coverage, and the universal-take-up landmine. Persons caseloads stay untargeted: the SNAP assistance unit is often a subset of the SPM unit, so member counts overcount FNS participants by roughly half. Implements #372; stacked on the #371 caseload-target compilation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e0e14c2 to
a354ca2
Compare
|
Heads-up: since #371 merged to |
Integrates the SNAP state surface (#256 #371 #376), the PUMA ladder (#394), the loader-test decouple (#392), and --no-latest (#397) with the campaign's 93 restored input families. Six builder conflict hunks + the us_runtime export merges resolved keeping BOTH lineages: main's snap_state_take_up stage/diagnostics/telemetry sequenced alongside the campaign's other_health_insurance and ssi_take_up stages. Full populace-build suite passes on the merged tree (exit 0, 1 cred skip). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements #372. Stacked on #371 (base branch is
snap-caseload-targets) — the take-up calibration consumes the same compiledsnap_householdstarget specs that PR adds, so the seed and the weight-calibration objective agree by construction.Why
#294's national-rate fill bakes in CPS state underreporting: the buildi-sparse release undershoots ten state SNAP benefit targets by 7–43% while a per-record feasibility audit shows every state but California reachable under the 5× weight cap, and state taker-household counts err from −38% to +325% against FNS caseloads (full numbers in #372 and the #371 preview comment). The states that are short cannot be fixed by reweighting — they lack taker households; this stage supplies them.
What
A
snap_state_take_upsource stage following themedicaid_take_uppattern (#331) at SPM-unit grain, using the shared binary-assignment ops (#338):SPM_SNAPSUB > 0) always take up, identical to the national stage's anchor.calibrate_binary_assignmentgreedily calibrates among eligible non-anchored units so each unsaturated state hits its FNS FY2024 count within unit-weight granularity.The national
snap_take_upstage remains as the prior; this stage recomputes the final surface after target compilation (it needsis_snap_eligiblefrom the engine and the compiled targets, mirroring the Medicaid builder placement).Deliberate carry-overs from the Medicaid stage: off-domain units keep a draw-based propensity (no hard-coded zero reform response), and saturation is recorded rather than failed (CA's ceiling is 1.05× its caseload — an eligibility-undercount symptom, not an assignment bug). Persons caseloads stay untargeted: the SNAP assistance unit is often a subset of the SPM unit, so member counts overcount FNS participants ~50% (see #371's second commit).
Gates and diagnostics
us_snap_state_take_up_gate(release-blocking): every state must carry an FNS target; anchors must survive calibration in every state (unit-level check); unsaturated states must land on their count withinmax(2%, one unit weight); positive count with zero eligible weight fails as an eligibility-feed collapse; caseload==eligibility with a reachable floor below it fails as the universal-take-up landmine.us_snap_state_take_up.jsonrelease artifact + staging telemetry: per-state eligible/anchored/caseload weights, targets, saturation, plus national rollup.out_of_scopefor Bernoulli seeding (it keeps a sourced national rate, whichcount_calibratedforbids) withscope_ownerupdated to name this stage as the final surface owner.Tests
test_us_snap_state_take_up.py(14 tests): manifest declaration, anchor derivation from raw subsidy, state fill-rate prior, exact count calibration in unsaturated states, saturation, anchor survival under downward pressure, empty-target refusal, and gate failure modes (missing state target, eligibility collapse, dropped anchor, count miss, universal-take-up landmine).populace-buildsuite passes (517 tests).🤖 Generated with Claude Code