Require state-level SNAP benefit hard targets#256
Conversation
42d651f to
19f77c6
Compare
|
@PavelMakarchuk one open item for merge timing (not code review): this gate makes the 51 state SNAP rows mandatory, so it should land only once the production grep -c 'state_benefits' consumer_facts.jsonl # expect >= 51If the artifact predates the state record sets, merging this will (correctly) fail the next build until the artifact is refreshed — that's the gate doing its job, but better to sequence it deliberately. |
|
Independent review verdict: the mechanism is sound (presence-of-key matching validated non-empty; state SNAP already maps to an active hard target on main, so this enforces an existing surface; orthogonal to #245's CD posture) and local gate/target tests pass. Two things block merge, matching the PR's own unchecked boxes: (1) run one fiscal-refresh build on a current artifact to confirm the deployed consumer_facts carries the state_benefits record sets, and (2) record any zero-support states as reviewed exclusions (TN-style). With per-state diagnostics from that run attached, this is merge-ready. |
|
Gate-review evidence (independent review pass, 2026-07-06): Code review: clean. The Your first unresolved checklist item is now resolved with evidence: the current curated feed (the v6 surface used in tonight's Build F staging run) carries 53 state-level Second checklist item lands tomorrow: tonight's Build F staging run produces exactly the per-state SNAP calibration diagnostics you flagged as needed. Merge sequencing: once those diagnostics confirm no zero-support state (the TN-style case), this merges. One design note for a follow-up (non-blocking): the failure message reports only the count ("has 50 match(es), needs 51") without naming the missing state, and the only exemption mechanism is requirement-wide — a per-state diagnostic in the failure line would make future breakage self-explaining. |
e5ec306 to
9ce73ed
Compare
MaxGhenis
left a comment
There was a problem hiding this comment.
Review — still valuable; do not close. Needs a rebase and its two rollout items resolved before it's merge-ready.
I checked whether the merged SNAP train (#350/#352/#353) or the state suites (#345/#319) already cover this. They don't:
- SNAP train (#350/#352/#353) is entirely about SNAP input columns — carrying eligibility/exemption inputs through outputs, seeding pregnancy, seeding ABAWD exemptions. Nothing about calibration-target coverage.
- State suites (#345 7 state-program benchmarks + EITC-by-state cross-check; #319 legislative-reform validation) are backtest/validation suites, not target-profile coverage requirements.
- Current
mainhas neither piece of this PR:gates.pyhas norequired_metadata_keys(only key-valuerequired_metadata, line ~357), andfiscal_targets.pyhas nosnap_state_benefitsrequirement — SNAP is still enforced only at the nationalsnap_total. A build can still silently ship national-only SNAP calibration, which is exactly the gap this PR closes (#255).
The mechanism is genuinely needed, not redundant with the existing state coverage. The existing state_income_tax requirement gets away with bare min_matches=44 because that family has no national row. SNAP is different: the usda_snap/target_role=snap_total family carries both a national row and the per-state rows, so a plain count would let the national row (or a partial state surface) pass. required_metadata_keys=("state_fips",) is the right primitive — it demands state-dimensioned rows (state_fips present) without enumerating 50 FIPS values, and the matching logic is correct (AND of value-matches then key-presence; __post_init__ rejects empty keys). The two profile tests pin exactly the failure modes: a national row and a 50-of-51 surface both fail. min_matches=51 is right (50 states + DC; GU FIPS 66 / VI resolve no PE state FIPS and are skipped — the compile test proves CA→06 maps and Guam is dropped).
What remains before this can go ready (the PR's own checkboxes, still open, and correct):
- Rebase — CONFLICTING/DIRTY.
fiscal_targets.pynow carries ~30TargetCoverageRequirementblocks and has moved a lot since this branch's base; the changes here are additive (one dataclass field, one match clause, one requirement), so the rebase should be mechanical, but it's required. - Confirm the deployed
consumer_facts.jsonlcarries theusda_snap.fy2024.state_benefits.*record sets. This gate makes the build fail if they're absent, so this is a hard data dependency (same class as #334's Ledger-fact dependency) — merging it against an artifact that lacks the state rows would red the release build. - Run a fiscal-refresh build and review per-state SNAP diagnostics — any zero-support state needs a reviewed exclusion (the TN pattern) rather than a gate failure.
The deferred follow-up (state participation counts as indicator targets, pending an average-monthly vs annual-ever bridge) is correctly out of scope here.
Recommendation: keep open and pursue — rebase, tick the two data/rollout boxes, then it's a clean, self-contained hardening. Not closing (and not merging) — review-only per request.
9ce73ed to
e1e0911
Compare
|
Addressing the three items: 1. Rebase — done (e1e0911, CI running). The rebase surfaced one real interaction beyond the mechanical conflict: the complete-profile coverage tests that landed with the Build G work ( 2. Deployed-artifact confirmation — verified at the Ledger declaration level; the artifact-pin check remains. The 3. Per-state diagnostics / zero-support exclusions — open, needs a build run. Noting that the Build G per-run So: item 1 closed, item 2 four-fifths closed (declaration + ETL verified; pin grep outstanding), item 3 gated on the next fiscal-refresh run. |
MaxGhenis
left a comment
There was a problem hiding this comment.
Review — the code is clean and merges green, but holding the merge: the required gate has no working escape hatch for a legitimately-missing or zero-support state, and rollout items 2 & 3 are still open.
I re-reviewed against current main. This branch is 6 commits behind and its green CI predates #384 (cross-register/take-up preflights), #387 (Medicaid substitution register), and #389 (Build J re-cert). Merging main in is conflict-free, and on that merged tree the touched suites pass locally (test_gates.py + test_us_fiscal_targets.py, 226 tests green; ruff clean), so the code itself is sound and would not red CI.
Genuinely resolved
- Item 1 (rebase + fixtures) is in the diff and holds on the merged tree:
complete_snap_state_rows()is wired at all eight complete-profile sites and the profile-coverage suite passes with the new requirement active. - Mechanism is correct:
required_metadata_keysis presence-only, empty keys are rejected in__post_init__, andmin_matches=51is right (50 + DC; GU/VI resolve no PE state FIPS and drop at reference compilation). - No interaction with the #387 reviewed-substitution register: that path is
cms_medicaid/medicaid_enrollment; this requirement isusda_snap/snap_total. Different family — no double-handling of a state.
Why it is not merge-ready — the escape-hatch pincer (the substantive item)
The coverage gate runs on target_specs after support exclusions are applied. In tools/build_us_fiscal_refresh_release.py the registry is compiled with extra_support_exclusions=... (~L5667) and only then does target_profile_coverage_gate(target_specs, ...) run (~L5692). A source_record_id in extra_support_exclusions (or the standing US_FISCAL_TARGET_SUPPORT_EXCLUSIONS) makes _reference_from_ledger_fact return None (fiscal_targets.py ~L1983–1991) — the target is dropped from the registry, not zeroed.
So the per-run zero-support exclusion proposed in item 3 as the vehicle for a thin-support state is mutually exclusive with this requirement: excusing one SNAP-benefit state drops the usda_snap + state_fips count to 50 and fails snap_state_benefits (needs 51). There is no reviewed way to excuse a genuinely-unreported or zero-support SNAP-benefit state — the only remedy is to repopulate the feed. That is exactly the register-vs-register pincer register_consistency.py (#377) was built to forbid for input columns, reintroduced here for target rows. Contrast #387: the Medicaid gate injects a substitute spec so the count survives; SNAP-benefit rows have no equivalent, so this gate fails closed with only a bare count in the message (has 50 match(es), needs 51) rather than naming the missing state.
Two rollout items still open (per the latest PR comment)
- Deployed feed not confirmed against the current pin. The Ledger source package declares 53
state_benefitsrows and the ETL produces them, and the Build F feed was confirmed to carry 53 on 2026-07-06 — but nobody with artifact access has grepped the current pinnedconsumer_facts.jsonl(Build J vintage) forusda_snap.fy2024.state_benefits.>= 51. This gate makes absence a hard build-red, so that one-line check is a prerequisite. - No fiscal-refresh run under this gate has confirmed every SNAP-benefit state is support-expressible. The Build J zero-support register carries only TANF + SOI cells (no SNAP), which is encouraging, but that run predates this gate.
To make it merge-ready, either
- (a) confirm the current pinned
consumer_facts.jsonlcarries >= 51usda_snap.fy2024.state_benefits.*rows and a fiscal-refresh build under this requirement shows zero SNAP-benefit states in the support-exclusion set — closing items 2 + 3 against the live pin and proving the pincer is unreachable on this artifact; or - (b) give the requirement a reviewed escape hatch the coverage gate honors — e.g. subtract a named reviewed-exclusion set from the effective
min_matches, or a substitution register in the #387 mold — so a genuinely-unreported or zero-support state degrades to a reviewed, reasoned exclusion instead of a red release.
Path (a) with the pin-grep and per-state diagnostics attached is the smaller step; (b) is the durable fix if the feed can ever legitimately lack a state. Either closes it out.
The Ledger's usda-snap-fy69-to-current package ships per-state total_benefits facts, and the direct-reference path already compiles state-geography usda_snap rows into state_fips-scoped hard targets. Nothing required them, though: a build consuming a facts artifact that predates the state record sets silently ships national-only SNAP calibration. Add a snap_state_benefits coverage requirement (50 states + DC) so the target-profile gate fails loudly when the compiled registry lacks the state SNAP surface, and extend TargetCoverageRequirement with required_metadata_keys so a requirement can demand state-dimensioned rows by metadata-key presence instead of enumerating FIPS values. Territory rows (GU, VI) resolve no PolicyEngine state FIPS and are skipped at reference compilation, so they never count toward the gate. Closes #255 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main's profile-coverage tests (added since this branch's base) assert a complete target surface passes the requirements; the new snap_state_benefits requirement makes 51 state-dimensioned SNAP rows part of completeness, so the fixtures gain complete_snap_state_rows(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
e1e0911 to
85e3e00
Compare
Merge-path (a) executed and verified — both conditions hold on the certified pin; merging.Following up on my review comment: I ran the feed-completeness and support-expressibility verification directly against the artifacts the release build actually consumes. 1. The deployed feed carries the full state SNAP surface. The pinned consumer feed ( 2. No SNAP-benefit state is zero-support or excluded. No 3. The gate itself passes end-to-end. On this branch rebased onto current Also rebased the branch onto current The design follow-up from my review stands as future work, now non-blocking: a reviewed exclusion/substitution hatch (the #387 mold) would make the gate degrade gracefully if a future feed vintage ever legitimately lacks a state, and the failure line could name the missing state rather than just the count. |
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>
Summary
Closes #255.
SNAP currently calibrates to a single national hard target. This PR makes the 51 state-level SNAP benefit totals (50 states + DC) a required part of the US target profile, so a build can no longer silently ship national-only SNAP calibration.
Why the change is this small
Investigation for the 2026-07-01 SNAP research readout found the pipeline is already almost fully wired:
usda-snap-fy69-to-currentalready ships per-statetotal_benefitsfacts (usda_snap.fy2024.state_benefits.<region>record sets,geography_level: state, all 53 state agencies)._direct_reference_from_factalready accepts state-geography facts for mapped families and stampsstate_fipsmetadata — the same path per-state TANF targets use today.state_fips-scoped targets by masking households to the state.So with a current
consumer_facts.jsonl, state SNAP hard targets activate with zero mapping changes. The only gap was enforcement.Changes
gates.py: addrequired_metadata_keystoTargetCoverageRequirement— presence-of-key matching, so a requirement can demand state-dimensioned rows (state_fipspresent) without enumerating 50 FIPS values. Validated non-empty in__post_init__.fiscal_targets.py: add thesnap_state_benefitscoverage requirement (familyusda_snap, rolesnap_total,state_fipskey required,min_matches=51).required_metadata_keys(present/absent/empty-key rejection); compile test proving a state-levelusda_snapfact becomes astate_fips-scoped spec while a Guam row is skipped; profile tests proving 50 state rows or a national-only surface fail the gate; state SNAP rows added to the complete-profile fixture.Draft status / rollout note
This gate makes builds fail if the consumer-facts artifact lacks the state SNAP record sets. Before marking ready:
consumer_facts.jsonlincludes thestate_benefitsrecord sets (the source package parses them from the FNS FY24 workbook, but the deployed artifact vintage needs checking).Follow-up (not in this PR)
State participation counts (
average_monthly_households/average_monthly_persons) as indicator targets — needs an explicit average-monthly vs annual-ever bridge before becoming a hard constraint.Test plan
uv run pytest packages/populace-build/tests— full suite passes (269 tests).ruff check/ruff format --checkclean on changed files.🤖 Generated with Claude Code