Skip to content

Seed pregnancy at the national rate; document the never-carried SNAP exemption inputs#352

Merged
MaxGhenis merged 2 commits into
mainfrom
pregnancy-and-absent-inputs
Jul 8, 2026
Merged

Seed pregnancy at the national rate; document the never-carried SNAP exemption inputs#352
MaxGhenis merged 2 commits into
mainfrom
pregnancy-and-absent-inputs

Conversation

@daphnehanse11

Copy link
Copy Markdown
Collaborator

Closes #351; advances #248.

Summary

Two deliverables for the unsourced SNAP exemption inputs, split by whether the retired pipeline had an answer:

1. Seed is_pregnant (ported from the retired pipeline). The CPS ASEC does not measure pregnancy, so the retired enhanced-CPS pipeline seeded it: women 15–44 drawn against CDC natality-derived pregnancy rates (point-in-time rate = annual births × 39/52 over the female 15–44 population). This PR adds a pregnancy source stage doing the same with stable blake2b draws keyed on source identity (clone-consistent, bit-reproducible — the #294 pattern). One honest divergence, documented in the module and manifest notes: the retired pipeline varied rates by state via live CDC VSRR + Census ACS fetches with a 4.1% national fallback; populace builds are hermetic, so this stage seeds at the cited national rate and state-level variation stays follow-up work under #351. The rate is data, not code — it lives in source_stages.json with its citation and reaches the handler as a manifest parameter. A pregnancy_signal release gate enforces non-constant output and a weighted pregnant-share band ([0.002, 0.02], bracketing rate × female-15–44 share ≈ 0.8%). The is_pregnant entry leaves the parity known-gaps register since the stage now produces it.

2. Document the three never-carried inputs. is_homeless, was_in_foster_care, and is_incapable_of_self_care have no ASEC source and are not persisted columns, so the degenerate-input gate cannot see them (and its anti-rot rule means register entries for unstored columns would break it — checked before writing). Instead, a US_DOCUMENTED_ABSENT_INPUTS register is emitted into every release manifest as documented_absent_inputs, naming each input, why it cannot fire (the CPS samples the housed population; no foster-history or incapacity items), and the tracking issue. That satisfies #248's acceptance criterion that unsupported inputs be explicitly documented in release diagnostics rather than defaulting invisibly.

Why now

The pre-OBBBA SNAP counterfactual (PolicyEngine/policyengine-us#8944) restores the pregnancy, homeless, and former-foster-youth exemption channels. With this PR, pregnancy contributes realistically to population estimates, and the other two are visibly documented as absent instead of silently zero — so OBBBA work-requirement research can cite exactly which channels are modeled.

Testing

  • New test_us_pregnancy.py: 18 tests over manifest declaration (rate present with citation), eligibility bounds (sex and 15–44 age edges), per-seed determinism, source-identity clone consistency, guard paths (missing raw columns, missing rate, unexpected parameters), frame integration (idempotence, constant-default reseeding), and gate pass/fail behavior.
  • Builder test patches the stage alongside hours/snap; l0-refit export fixture extended (is_pregnant joins US_RELEASE_REQUIRED_PERSON_SOURCE_COLUMNS).
  • Full populace-build suite passes locally; ruff clean.

Note: this branch and #350 (eligibility_inputs) both extend the same wiring anchors; whichever merges second gets the usual rebase.

🤖 Generated with Claude Code

@MaxGhenis MaxGhenis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — seeded rate verified against the cited source; the absent-inputs register is the right mitigation for the #340 blind spot

Reviewed the seeded value against its citation, checked that nothing computed is hardcoded, and traced both the pregnancy channel and the three documented-absent inputs into the pinned PolicyEngine-US ABAWD formula. It holds up well.

The 4.1% rate is exactly corroborated by the cited CDC source

The seed lives in source_stages.json as data (pregnancy_rate.value = 0.041) with its citation, reached as a manifest parameter — not hardcoded in Python. Good. And the value is right: the point-in-time formula is annual births × 39/52 ÷ female-15-44 population, which equals the general fertility rate × 39/52. CDC final 2023 natality (NVSR 74-1) reports a general fertility rate of 54.5 births per 1,000 women aged 15–44, so the point-in-time pregnant share is 0.0545 × (39/52) = 0.0409 ≈ 4.1%. Equivalently, 3,596,017 births × 39/52 ÷ ~66.0M women 15–44 = 4.09%. The 39/52 gestation factor (≈0.75, a 9-month pregnancy as a share of the year) is the standard construction. Value verified.

The one divergence from the retired pipeline — national uniform rate instead of the retired live-fetched state VSRR/ACS rates — is documented honestly in both the module and the manifest notes, with the hermetic-build rationale and the #351 follow-up. That is the right way to handle a scope reduction (and a good contrast with #350, where two mapping refinements are still framed as parity).

Plumbing confirmed — is_pregnant and the three absent inputs all feed the ABAWD formula

  • is_pregnant is read at exemption (E) Pregnant — 7 U.S.C. 2015(o)(3)(E) in meets_snap_abawd_work_requirements, matching the citation in the stage notes exactly. Seeding women 15–44 (others get False) is correct since only that population can carry the flag.
  • The three documented_absent_inputsis_homeless, was_in_foster_care, is_incapable_of_self_care — are genuinely consumed downstream: is_homeless and was_in_foster_care (as former_foster_youth) are both read in the pre-HR1 exemption branch of the same formula. So documenting them as absent-and-defaulting is not bookkeeping — those exemption channels silently do not fire, exactly the #340 failure mode, and the register makes that visible in release diagnostics rather than invisible.

Suggestion (not blocking): connect the register to the #340 column families

documented_absent_inputs is the first release-diagnostics home for "an input the engine reads but the build cannot populate." That is precisely the surface #340 asked for. The two are complementary but distinct: this register covers inputs with no survey source (structurally unfixable from ASEC), whereas #340's families (tip_income, overtime, auto-loan, education-credit, retirement-contribution, casualty, misc-itemized, childcare) have a source and were merely never persisted (build/persistence-scope — and #350 fixes five of them). Consider whether these should converge into one "inputs not reaching the engine" diagnostics block with two clearly-labeled sub-registers (no-source vs not-persisted), or at least cross-reference #340 here, so a release has a single place to answer "what isn't modeled and why." Worth a look, not a merge condition.

CI is green on test/wheels for 3.13 and 3.14; 18 tests cover the seeding bounds, determinism, clone consistency, guards, and gate behavior. Not merging — review only.

@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Agreed the no-source vs not-persisted distinction deserves one diagnostics home. Pushed d57abe9 adding the scope note to the register: this block is the no-survey-source class (structurally unfixable from ASEC), the #340 families are persistence-scope (and #350 fixes five of them), and if a not-persisted register lands later the two should surface as labeled sub-blocks of one 'inputs not reaching the engine' diagnostic. Left the structural convergence itself to #340 rather than widening this PR.

@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Expanding on the earlier note with the specifics of how the review suggestion was taken (d57abe9, CI green):

What changed. The US_DOCUMENTED_ABSENT_INPUTS register in tools/build_us_fiscal_refresh_release.py now carries a scope note stating the class boundary you drew: this register is for the no-survey-source class — inputs structurally unfixable from ASEC (the CPS samples the housed population; there are no foster-history or incapacity items) — whereas the #340 column families (tips, overtime, education credits, retirement contributions, …) have a source and were merely never persisted, i.e. build/persistence-scope work (of which #350 fixes five). The note ends with the convergence design: if/when #340 adds a not-persisted register, the two should surface as labeled sub-blocks of a single "inputs not reaching the engine" diagnostic, so a release manifest has one place answering "what isn't modeled and why."

What deliberately didn't change. The structural convergence itself stays with #340 — building the combined block here would mean this PR defining #340's register shape before #340's own work lands, and the suggestion was explicitly non-blocking. The seeded rate (0.041, whose CDC corroboration in your review — GFR 54.5 × 39/52 — is a nice independent check), the manifest citation, the gate band, and all 18 tests are untouched.

If you'd rather the combined block land now with a placeholder not-persisted sub-register, happy to do that here instead — say the word.

@MaxGhenis

Copy link
Copy Markdown
Contributor

#350 is merged (thanks for the docstring fix). #352/#353 now conflict with main after the #350 squash — 24 hunks, mostly tools/build_us_fiscal_refresh_release.py — so leaving the rebases to you as offered rather than racing your active branches; happy to take them over if you'd rather. #358 merged independently. Once #352/#353 land, the next build cut picks up the full work-requirement surface.

daphnehanse11 and others added 2 commits July 8, 2026 11:59
…exemption inputs

Add a pregnancy source stage porting the retired pipeline's seeding
(women 15-44, CDC natality-derived rate, stable blake2b draws keyed on
source identity) at the national 4.1% rate — state-level variation is
documented follow-up under #351 since populace builds cannot make live
CDC/ACS fetches. Remove the is_pregnant parity-register entry.

Add US_DOCUMENTED_ABSENT_INPUTS, emitted into every release manifest as
documented_absent_inputs, for the three inputs with no ASEC source
(is_homeless, was_in_foster_care, is_incapable_of_self_care) — they are
not persisted columns, so the degenerate-input gate cannot document
them, and this register makes the always-False assumption visible per
the #248 acceptance criteria.

Closes #351; advances #248.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post-rebase wiring: pregnancy slots after eligibility_inputs in the
stage manifest, runtime registry, gate plumbing, and release manifest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@daphnehanse11 daphnehanse11 force-pushed the pregnancy-and-absent-inputs branch from d57abe9 to c5802bc Compare July 8, 2026 16:01
@MaxGhenis MaxGhenis merged commit a07223b into main Jul 8, 2026
4 checks passed
daphnehanse11 added a commit that referenced this pull request Jul 8, 2026
Add a snap_abawd_discretionary_exemption source stage seeding
is_snap_abawd_discretionary_exempt with stable blake2b draws across
potentially covered adults 18-64 at the 7 U.S.C. 2015(o)(6) cap rate
(8% from FY2024); the engine intersects with modeled coverage so the
exempt share among covered individuals lands at the statutory rate in
expectation. Full-cap seeding is documented as an explicit upper bound
(USDA reports usage typically runs well under the allotment; cap
exceeded in only five state-year instances across FY2016-19), with
usage-report refinement tracked under #323.

Rebuilt on main after #350/#352 merged; stage order runs
eligibility_inputs -> pregnancy -> snap_abawd_discretionary_exemption.

Addresses the SNAP half of #323; advances #248.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
daphnehanse11 added a commit that referenced this pull request Jul 8, 2026
Add the three work-program inputs (is_snap_work_program_participant,
weekly_snap_work_program_hours, is_snap_workfare_participant) to the
documented-absent-inputs register: no ASEC item measures SNAP E&T,
qualifying work-program, or workfare participation, so the always-False
defaults are now visible in every release manifest with the compliance
channels they suppress. USDA reports E&T reaches a small minority of
participants, so the default understates compliance only modestly.
Add contract tests pinning the register's coverage, issue citations,
disjointness from the degenerate exclusions, and non-overlap with
seeded inputs.

Closes #249; advances #248. Stacked on #352.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis pushed a commit that referenced this pull request Jul 8, 2026
Add a snap_abawd_discretionary_exemption source stage seeding
is_snap_abawd_discretionary_exempt with stable blake2b draws across
potentially covered adults 18-64 at the 7 U.S.C. 2015(o)(6) cap rate
(8% from FY2024); the engine intersects with modeled coverage so the
exempt share among covered individuals lands at the statutory rate in
expectation. Full-cap seeding is documented as an explicit upper bound
(USDA reports usage typically runs well under the allotment; cap
exceeded in only five state-year instances across FY2016-19), with
usage-report refinement tracked under #323.

Rebuilt on main after #350/#352 merged; stage order runs
eligibility_inputs -> pregnancy -> snap_abawd_discretionary_exemption.

Addresses the SNAP half of #323; advances #248.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Document or seed the unsourced SNAP exemption inputs (pregnancy, homelessness, foster-care history, incapacity)

2 participants