Skip to content

Seed SNAP ABAWD discretionary exemptions at the statutory cap#353

Merged
MaxGhenis merged 1 commit into
mainfrom
snap-discretionary-exemption-stage
Jul 8, 2026
Merged

Seed SNAP ABAWD discretionary exemptions at the statutory cap#353
MaxGhenis merged 1 commit into
mainfrom
snap-discretionary-exemption-stage

Conversation

@daphnehanse11

Copy link
Copy Markdown
Collaborator

Addresses the SNAP half of #323; advances #248. (The Medicaid/CHIP half stays with #334/#337.)

Summary

Adds a snap_abawd_discretionary_exemption source stage seeding is_snap_abawd_discretionary_exempt — the state discretionary exemption from the ABAWD time limit (7 U.S.C. 2015(o)(6)) that PolicyEngine-US reads as a person-level, data-construction-time input (policyengine-us#8870). Without it the input defaults to False and the discretionary channel never fires.

Design

The statutory cap is a share of covered individuals (8% from FY2024; 15% FY1998–2019, 12% FY2020–23). Who is covered is engine logic — age band, hours, exemption stack — that the build must not duplicate. So the stage follows the pattern #294 established for SNAP take-up: seed with stable blake2b draws (source-identity keyed, clone-consistent) across the potentially covered population — adults 18–64, the post-OBBBA subject band and a superset of the pre-OBBBA 18–54 band — and let the engine intersect with modeled coverage. Because draws are independent of coverage, the exempt share among covered individuals lands at the statutory rate in expectation, under either regime.

Documented assumption: seeding at the cap assumes states fully use their exemptions. Actual usage varies (and historically ran below the cap); refining toward USDA exemption-usage reports is possible follow-up under #323.

The rate is data, not code — declared in source_stages.json with its statutory citation and consumed as a manifest operation parameter. A snap_discretionary_exemption_signal release gate requires a non-constant column and a weighted exempt share inside [0.015, 0.09] (expected ≈ 8% × the adult-18–64 population share ≈ 4.8%).

Testing

  • New test_us_snap_discretionary_exemption.py: 19 tests over manifest declaration (rate present with citation), the 18/64 inclusive band edges, ineligibility outside the band, per-seed determinism, source-identity clone consistency, guard paths, frame integration (idempotence, constant-default reseeding), and gate pass/fail behavior.
  • Builder test patches the stage alongside hours/snap; l0-refit export fixture extended (the column joins US_RELEASE_REQUIRED_PERSON_SOURCE_COLUMNS).
  • Full populace-build suite passes locally; ruff clean.

Note: #350, #352, and this PR extend the same wiring anchors; merge in any order and the later ones take 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 — statutory cap verified against the source and the PolicyEngine-US parameter; the seed-and-intersect design is right

Verified the seeded value against the statute, confirmed it is data rather than hardcoded, and checked the engine-intersection pattern against how PolicyEngine-US actually defines the input. Solid.

0.08 is the correct FY2024 statutory cap, and the historical values check out

The rate lives in source_stages.json as data (exemption_rate.value = 0.08) with its statutory citation, reached as a manifest parameter — not hardcoded. Good. The value and the historical schedule match the cited authority and the PolicyEngine-US parameter to the digit:

  • 8% from FY2024 — the Fiscal Responsibility Act of 2023 (P.L. 118-5, §312) permanently reduced the ABAWD discretionary-exemption cap from 12% to 8%, codified at 7 U.S.C. 2015(o)(6). PolicyEngine-US carries exactly this in gov.usda.snap.work_requirements.abawd.discretionary_exemption_rate (2023-10-01: 0.08), which also notes "OBBBA (P.L. 119-21, §10102) did not amend 2015(o)(6)."
  • 15% FY1998–2019 (Balanced Budget Act of 1997) and 12% FY2020–2023 (Agriculture Improvement Act of 2018, §4005) — both match the parameter's dated values.

The "seeding at the cap assumes full state usage" caveat is well-taken and, if anything, understated: USDA reports state agencies typically use well under the 8% allotment (only five instances of exceeding it across all states in FY2016–2019). Documenting the follow-up toward USDA exemption-usage reports is the right call.

The seed-and-intersect pattern matches the input's design intent

is_snap_abawd_discretionary_exempt is a bool person input in PolicyEngine-US whose own documentation says it "defaults to false and is intended to be assigned at data-construction time (e.g., in populace) among covered individuals," and the parameter is flagged economy: false ("like takeup, assigned during microdata construction rather than at simulation time"). So this PR is implementing exactly the contract the engine variable declares — and the #294 take-up analogy is apt: seed independent of coverage across the potentially-covered band (18–64, the superset of the pre/post-HR1 subject ages), let the engine intersect with modeled coverage, and the exempt share among covered individuals lands at the statutory rate in expectation under either regime. The gate band ([0.015, 0.09] around 8% × the adult-18–64 share) is a sensible signal check.

Plumbing confirmed — this closes the last ABAWD exemption input

is_snap_abawd_discretionary_exempt is read in meets_snap_abawd_work_requirements at the "State discretionary exemption — 7 U.S.C. 2015(o)(6)" line. With this seeded plus is_disabled/veterans_benefits (#350) and is_pregnant (#352), every ASEC-reachable exemption input the ABAWD formula consumes is now populated, so the next main build (under the #324 pe-us pin that carries this formula) is the first artifact where the ABAWD work requirement binds non-degenerately — the prerequisite for scoring the pre-OBBBA §10102 counterfactual. Before it, this input defaults False and the discretionary channel never fires. Sanity-check passes.

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

@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Thanks — took the 'if anything, understated' point directly: pushed 97f3e56 restating the full-usage assumption as an explicit upper bound in both the manifest notes and the module docstring, citing that USDA reporting shows usage typically runs well under the allotment (cap exceeded in only five state-year instances across FY2016-19), with the usage-report refinement tracked under #323.

@daphnehanse11

Copy link
Copy Markdown
Collaborator Author

Expanding on the earlier note with the specifics (97f3e56, CI green):

What changed. Both documentation surfaces now state the full-usage assumption as an explicit upper bound rather than a neutral "usage varies":

  • source_stages.json stage notes: "Seeding at the cap assumes full state usage, which overstates actual practice: USDA reporting shows states typically use well under the allotment (exemption usage exceeded the cap in only five state-year instances across FY2016-19), so this is an upper bound on the discretionary channel; refining toward USDA exemption-usage reports is follow-up under populace Assign data-construction inputs added by recent policyengine-us merges (SNAP ABAWD discretionary exemptions, CHIP take-up) #323."
  • The module docstring carries the same statement, so anyone reading either the manifest or the code gets the interpretation without cross-referencing.

What deliberately didn't change. The seeded behavior stays at the statutory cap (0.08) — the review point was about how to interpret the assumption, not the value, and moving below the cap toward observed usage is exactly the #323 follow-up (it needs a citable USDA usage rate, which would then live in the manifest the same way the cap does). The gate band, the 18–64 seed-and-intersect design your review validated against the engine variable's own contract, and all 19 tests are untouched.

One nuance the upper-bound framing buys for the OBBBA work: since discretionary exemptions are an exemption channel, seeding at the cap makes SNAP work-requirement bite estimates conservative (fewer people bound), which is the safer direction for the pre-OBBBA counterfactual comparison until the usage-calibrated rate lands.

@MaxGhenis

Copy link
Copy Markdown
Contributor

#350 and #352 are merged. #353 now conflicts with main after the #352 squash (same shared anchors) — one more rebase and it lands; happy to take it over if you'd rather.

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 daphnehanse11 force-pushed the snap-discretionary-exemption-stage branch from 496707a to 70ab6d6 Compare July 8, 2026 17:59
@MaxGhenis MaxGhenis merged commit 195782b into main Jul 8, 2026
4 checks passed
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.

2 participants