Skip to content

Seed TANF and EITC take-up from administrative rates; engine-asserted take-up contract (#312)#315

Merged
MaxGhenis merged 7 commits into
mainfrom
takeup-inputs
Jul 5, 2026
Merged

Seed TANF and EITC take-up from administrative rates; engine-asserted take-up contract (#312)#315
MaxGhenis merged 7 commits into
mainfrom
takeup-inputs

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Fixes #312 (first tranche: contract inventory + the two provenance-clean seeds + release wiring).

Headline findings

  • At the pinned engine (policyengine-us 1.752.2) all 13 takes_up_* variables are data-seeded: default=True, no formula, no *_seed/rate-parameter machinery. The model-side take-up migration referenced in the issue has not reached the pinned release, so every take-up-gated program ships at mechanical 100% participation unless the dataset seeds it (this generalizes Add Medicaid and CHIP eligibility-to-enrollment diagnostics #170's Medicaid finding to the whole class).
  • One incumbent rate was fabricated: us-data's EITC 3+-children take-up (0.85) does not appear in the IRS/Census sources; the published figure is 0.82 (Census/NTA, TY2016 series). Seeded with the corrected value.

What ships

Verification

  • Engine-present run (before the env was re-synced to CI's leaner surface): all take-up, contract, and adapter tests pass.
  • CI-mirror env (no policyengine_us): full workspace suite exit 0 with clean module-level skips; ruff check clean.
  • Builder test extended with take-up fakes following the immigration idiom; spec-only country-package test satisfied by resource registration.

Provenance

Built by directed agents (inventory + provenance verification by two parallel primary-source checkers; stages + wiring) with the lead session performing the final test class, integration fixes, and PR. Squash-merge recommended (history carries WIP salvage checkpoints).

PROGRESS — take-up inputs (#312)

Branch: takeup-inputs off origin/main (7e9a32d).
Sibling: #313 ecps-parity-gate (gate/release wiring) — avoid gate-assembly region of release tool; rebase before PR.

Doctrine (from spec)

Steps

Architecture decisions

Log

  • Setup: worktree created, read spec/DESIGN.md/PR#294 summary. Wire the eCPS parity_gate into the US release with a pinned reference and a reason'd exemption register #313 branch not yet pushed to origin.
  • Installed pinned PE-US = 1.752.2 (pin >=1.745.0,<2).
  • CONTRACT INVENTORY (engine-derived, rigorous): 13 takes_up_* vars. ZERO *_seed vars. ZERO model-simulated. ALL 13 have default=True + NO formula = class (b) data-seeded. NONE dead (all consumed via formula or adds).
    • BLOCKING FINDING for lead: the chip_take_up_seed/aca_take_up_seed model-side migration the spec references has NOT landed in the pinned engine. Classifying by installed engine per doctrine.
    • Consumers: snap→snap, tanf→tanf, eitc→eitc(+5 state), dc_ptc→dc_ptc, head_start→head_start, early_head_start→early_head_start, housing_assistance→housing_assistance, medicaid→medicaid_enrolled[adds], chip→chip_enrolled[adds], basic_health_program→bhp_enrolled[adds], medicare→medicare_enrolled[adds], ssi→ssi, aca→person_receives_aca+3.
  • SCOPE: exclude SNAP(Assign SNAP take-up from reported receipt and the FNS participation rate #294), ACA(spec hard boundary, flag engine mismatch). Seed-candidates gated by PROVENANCE: TANF, EITC, DC PTC, Head Start, Early Head Start, Housing, Medicaid, CHIP, BHP, Medicare, SSI.
  • NEXT: per-program admin-rate sourcing (decisive filter); ASEC reported-receipt availability (anchor vs Bernoulli).
  • Adapter method take_up_contract() + take_up_variables() added to policyengine_us.py (+ _references_variable helper). Verified vs 1.752.2: all 13 = data_seeded.
  • PROVENANCE VERIFICATION COMPLETE (2 parallel agents, primary-source-checked):
    • TANF 0.22 → SOURCED-ADMINISTRATIVE: HHS ASPE 24th Report Table 10 Ind.4 = 21.9% (2022). us-data mislabeled vintage 2018. SEED w/ correct vintage.
    • EITC by #children → SOURCED (IRS NTA/Census): NTA 2020 Fig A.7 TY2016: 0=0.65,1=0.86,2=0.85 MATCH; 3+=0.82 NOT 0.85 (us-data fabricated 0.85). IRS headline 78% corroborates. SEED w/ CORRECTED 3+=0.82.
    • DC PTC → MODEL-RELATIVE (37,133 real but ÷ PE-model denom; self-referential). rate-unsourced, unseeded.
    • Head Start 40/30 + Early HS 9 → SOURCED-RESEARCH-ONLY (NIEER=Rutgers, NOT ACF/federal); year-split is artifact. Fails admin-source bar → rate-unsourced, unseeded (follow-up: ACF Head Start Program Facts).
    • Medicaid (state) → UNSUPPORTED/MODEL-RELATIVE (0.99=ceiling artifact). CHIP → no standalone current rate. SSI 0.50 → aged-65+ only (49%), scope-mismatch to all-SSI. ALL rate-unsourced, unseeded (Add Medicaid and CHIP eligibility-to-enrollment diagnostics #170/Add Medicaid and CHIP eligibility-to-enrollment diagnostics #170 follow-up: source as Ledger facts).
    • Medicare → NEAR-UNIVERSAL-BY-DESIGN (~100% correct, seeding not meaningful). Document, unseeded.
    • Housing → no rate ever existed. rate-unsourced, unseeded.
    • ACA → OUT OF SCOPE (spec hard boundary; populace already has native ACA stage in source_stages.json).
  • DECISION: seed exactly TANF + EITC (both verified administrative/IRS provenance). All others: rate-unsourced OR out-of-scope OR near-universal. This is the honest ledger#77-safe outcome.

MaxGhenis and others added 7 commits July 4, 2026 20:51
The blocking first deliverable: a checked-in table classifying every
policyengine-us take-up flag, asserted against the installed engine.

- PolicyEngineUSEngine.take_up_variables() / take_up_contract() derive, from
  engine metadata, each takes_up_* flag's entity, default, whether the engine
  computes it (formula / adds / start-date formula), its consumers, and thus an
  engine_class (model_simulated / data_seeded / dead). Same metadata-derivation
  doctrine as the #301 formula-owned guard.
- us/take_up_contract.json records the engine facts plus the curated populace
  treatment (seed / rate_unsourced / model_simulated / out_of_scope /
  near_universal) with each rate's administrative provenance.
- assert_take_up_contract_current() fails the build when the table drifts from
  the pinned engine (new/renamed flag, a flag gaining a formula, a changed
  default); assert_take_up_treatments_consistent() catches treatments that
  contradict the engine class. The loader enforces the provenance rule: a
  program marked seed must carry a sourced administrative rate.

Finding against pinned policyengine-us 1.752.2: all 13 take-up flags are
data_seeded (default True, no formula, all consumed); there are no *_seed
variables and no model-simulated take-up. The chip_take_up_seed / aca_take_up_seed
model-side migration the issue references has not landed in the pinned engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two data-seeded take-up programs whose participation rate clears the
provenance bar, assigned by calibrated Bernoulli at the administrative rate:

- TANF (takes_up_tanf_if_eligible, spm_unit): 21.9% (HHS ASPE 24th Welfare
  Indicators Report, 2022, Table 10 Indicator 4).
- EITC (takes_up_eitc, tax_unit): per-child rates (IRS National Taxpayer
  Advocate 2020, TY2016: 0=65%, 1=86%, 2=85%, 3+=82%). Child count is
  approximated from tax-unit member ages under 19; the rate is nearly flat
  above zero children so the coarse count picks the right bin.

with_us_take_up_inputs seeds every program the take-up contract marks `seed`
(and only those), keying draws on stable source identity so support-channel
clones agree and reruns are bit-reproducible (the SNAP #294 keying). A frame
already carrying a non-constant column passes through untouched; a missing or
constant column is recomputed, so the published all-True landmine is healed.
No reported-receipt column is threaded through the base spine, so assignment is
calibrated Bernoulli rather than the SNAP reported-receipt anchor.

us_take_up_summary and us_take_up_signal_gate expose the per-program
participation-vs-administrative surface: weighted share, target rate, source,
and a plausibility band, failing on a missing/constant column or an
out-of-band share.

Validated at n=8000: TANF share 0.210 (target 0.219); EITC per-bin
0.648/0.866/0.842/0.832 (targets 0.65/0.86/0.85/0.82); EITC overall 0.797
(IRS ~0.78). 17 seeding tests plus the gate failure modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r test; diagnostics test class

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tics) into takeup-inputs

Import-list both-adds resolved keeping all three extensions in sorted order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis merged commit 56e67c7 into main Jul 5, 2026
4 checks passed
@MaxGhenis MaxGhenis deleted the takeup-inputs branch July 5, 2026 02:59
daphnehanse11 added a commit that referenced this pull request Jul 8, 2026
…ries

parity_gate skipped known-gap names unconditionally, unlike its sibling
gates (default_valued_columns_gate at gates.py:707 and
source_stage_input_coverage_gate at gates.py:1499), which both fail on
stale reviewed exclusions. That let register entries rot silently once
the candidate started producing the exempted layer (the
takes_up_medicaid_if_eligible case surfaced in PR #334).

The gate now fails on any exemption whose candidate nonzero share is
positive, reports exemptions the reference never populates as dormant,
and records both in the gate details. Auditing the register against the
current release pipeline found four entries stale on arrival — the
pipeline deterministically materializes them before the gate runs:
takes_up_tanf_if_eligible and takes_up_eitc (contract-seeded take-up,
PR #315), ssn_card_type and immigration_status_str (immigration stage,
#266). A new register test pins that statically-knowable subset so a
stale entry is caught at test time, not first at release-build time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Jul 8, 2026
…pport exclusions, SOI income-target wiring (#299) (#357)

* Build G run: PROGRESS + design (step-1 ref-frame fix, step-2 per-artifact exclusions)

Fresh worktree off origin/main 2bf603b (#330 merge). Design for the two Build G
certification candidates: sparse frozen-57k headline + dense parent. Prereqs verified
(base-F 18833fb6, selection manifest 57,240, live-default 57k on disk, v7 feed 735f326a).
39 zero-support diagnosed (20 M-CHIP derivation + 18 SOI tail + 1 AL TANF).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Skip M-CHIP states in the derived-CHIP fallback (populace#321): the derivation re-created the 20 unmaterializable targets whenever direct rows were removed

(cherry picked from commit 03cc3d9e3ca84af052aa0e440bd3b540ca6fd381)

* Fix derivation tests for the M-CHIP skip: TX fixture for the general case, CA asserts no derived target (#321)

(cherry picked from commit 9e25d5484b516b7086a4a530b7b1d5edb15ef359)

* Thread reference-frame into _export_input_mass_gate (#327)

Per #327's reference-decision comment: _export_input_mass_gate now accepts an
optional reference_frame (defaulting to the raw base_frame, preserving current
behaviour) plus reference_name and reviewed_exclusions. The release tool wires
it from --input-mass-reference-h5 (the live-default 57k per #327), so
calibration-driven upward alignment of under-reported PUF income is in-band
against a certified reference while a genuine #278 zeroing still fails.

3 unit tests: gate defaults to raw-base comparison (fails on the calibration
gain, current behaviour); passes when export~=reference despite export>>raw-base
(the #327 case, 11/14 columns vindicated); still fails on genuine zeroing/drift
vs the reference (the #278 loss arm stays strict).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Per-artifact zero-support exclusions for the sparse feed reconciliation (#299 Build G)

Step 2 of the Build G feed reconciliation for the 39 zero-support targets:

- M-CHIP (20, global): the #321 derivation-skip is cherry-picked (prior commits);
  diagnosed as the derivation fallback re-deriving CHIP=(combined-medicaid) for
  M-CHIP states on main (v7 feed correctly carries 0 direct M-CHIP rows but both
  control rows). Structural #170 class, un-expressible on ANY support.

- SOI/TANF (18 SOI under_1.taxable_interest + 1 AL TANF, per-artifact): new
  --zero-support-exclusions <json> threads extra_support_exclusions through
  compile_us_fiscal_target_registry -> _dynamic_us_fiscal_target_references ->
  _reference_from_ledger_fact, recorded in us_source_coverage.json as
  fiscal_target_support_exclusions_per_run. The module constant is never mutated.

Design resolved empirically: Build F attempt-5 DENSE (337k, same v7 feed, with
M-CHIP skip) had 0 zero-support targets, so the dense parent expresses all 39
cells; only the 57,240 sparse frozen support cannot. Per-artifact split (dense
keeps the full v7 registry; sparse declares the 19-cell exclusion) is the
correct ledgered supersession.

Full populace-build suite green (rc=0). 2 new threading tests + the #321 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Pin policyengine-us 1.752.2 -> 1.764.6 for Build G (#324 GO)

Per the #324 pin-bump validation pass GO: pin to the latest 1.764.x (1.764.6),
which includes 1.764.1's NY IT-196 §68 overall-limitation wiring (the expected
NY state-tax +28.5% re-seat). uv lock --upgrade-package policyengine-us==1.764.6;
core stays 3.26.11.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Separate export-mass reference from base-mass reference flag (#327 scope fix)

RUN 1 dense failed early on _input_mass_reference_gate: --input-mass-reference-h5
armed BOTH the base-vs-reference gate (the #278 base-LOSS guard, comparing the
RAW pre-calibration base) and the #327 export gate. The live-default 57k
reference is calibrated, so the raw 337k base undershoots it by -50..-117% on
PUF-imputed income (exactly what calibration fixes) → the base-vs-reference gate
over-fires. This is the Build F attempt-6 pre-flight finding.

Fix: dedicated --export-input-mass-reference-h5 for the export gate (the #327
case); --input-mass-reference-h5 stays for the base-vs-reference gate, left off
for these runs since a raw base legitimately undershoots a calibrated reference.
Caught in ~3 min before any calibration ran.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* PROGRESS: run 2 launched, dense-export outcome pre-validated (11/14 in-band, 3 #328 residuals)

* Refresh stale degenerate-input reviewed-exclusion register (build-f b5980d2 port)

Both Build G artifacts failed the #286 degenerate-input gate on the stale register
that #330 only applied on a throwaway branch: retire the now-#315-seeded
takes_up_eitc/takes_up_tanf_if_eligible exclusions; add the un-imputed
second_home_mortgage_{balance,interest,origination_year} trio (populace#38).
Exact build-f edit. 8 degenerate/take-up/export tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* PROGRESS: run 3 launched (all prereqs ported+verified); predicted per-artifact outcomes

* Run 3 results: both artifacts calibrate to certified-or-better, rc=1 on 4 export-mass residuals

Dense loss 0.04139 (beats f0af251), sparse loss 0.02964 (beats certified 0.044 and
#330 prior). Both pass all structural gates + 0 zero-support. Both rc=1 only on the
export-mass gate vs the corrected live-default 57k reference: mortgage/estate/misc/
non_sch_d — untargeted PUF-imputed inputs whose mass floats with reweighting (#328
class). Sparse mortgage tighter than dense (confirms #330). Neither certifiable
as-is; sparse is the stronger candidate. Adding cold-L0-2026 arm 3 (Max directive).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Run 3 critical-target fits: sparse frozen income tax +0.5%, SS -0.1% (vs cold-L0 +19.9%/+5.3%)

* Identification analysis of the 4 export-mass residuals (mortgage partially identified via JCT target; estate/misc unidentified; non_sch_d = component split)

Frames the #299 verdict per the coordinator addendum: the export gate is an
incidental-reproduction check on unidentified columns. Mortgage's JCT deduction
target IS binding (+44.5% real miss); non_sch_d is a within-aggregate component
split; estate_income and miscellaneous_income are purely unidentified. Three
gap-closing options: (a) informed-init inheritance, (b) add SOI targets
[recommended, Build H], (c) reviewed exclusions [weakest].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Usage-budget hold: defer step-6 (table/verdicts/posts) to after 12:30am ET; ready-state staged

* Arm 3 cold-L0-2026 complete (rc=1, critical-target gate); three-way head-to-head table

Cold-L0-2026: loss 0.104, income tax +17.8%, SS +6.2% -> FAILS the critical-target
gate before export. Frozen-57k: loss 0.0296, income tax +0.5%, SS -0.1%. The
environment change (1.752->1.764.6) did NOT make cold L0 competitive -> the frozen
selection's value is confirmed under 2026 conditions; informed-init remains the path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Step 6 complete: #299/#324 reports posted; Build G run closed

Final verdict: no artifact certifiable tonight. Frozen-57k is the clear best
candidate (loss 0.0296, income tax +0.5%), blocked only by the export-mass gate
on 4 unidentified PUF-input dims. Cold-L0-2026 head-to-head confirms the frozen
selection's edge is the selection, not the environment. Recommend (b) add SOI
targets = Build H. NY re-seat calibrates cleanly (+0.18%), discharging #324 #1.

#299: issuecomment-4902533514  #324: issuecomment-4902539459

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 0: setup + sourcing log; exact export-mass residuals + ref bands recorded (#299)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 1: SOI values verified from source; reconciliation vs export-mass ref bands (#299)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 2: registry wiring (Table 1.4 signed income/loss legs, E01100 concept) + 2-column export-mass reviewed exclusions w/ band math (#299)

SOI Table 1.4 estate/other-income/capgain-distribution measures mapped to
signed model columns (income leg = positive part, loss leg = negative-part
magnitude); capital_gain_distributions == PUF E01100 == pe-us
non_sch_d_capital_gains, concept-confirmed. Export-mass exclusions limited to
estate_income + non_sch_d_capital_gains, where the SOI-true level provably
cannot sit inside the live-default reference band; miscellaneous_income and
both mortgage columns stay live for the run to adjudicate.

Salvaged by conductor from agent segment ending without commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 3: close mortgage-target + reviewed-exclusions wiring gaps; registry d71c59514e3a/5533; tests green (#299)

Two wiring gaps left by the conductor-salvaged step-2 commit (d8800fa):

GAP 1 — mortgage target inert. d8800fa claimed an 'itemizer-masked Table 2.1
target (~$186.3B aged)' but wired no mortgage measure; v8 compile confirmed
0 mortgage targets. Map home_mortgage_interest_amount/_returns (CW/CX Total,
$171,364,787,000 / 11,644,348 TY2023) -> concept home_mortgage_interest ->
gross person-level pe-us home_mortgage_interest (the export column itself).
itemized_only=true is auto-stamped from the table_2_1.itemized_all_returns
record set (same auto path as person-level real_estate_taxes). Only the CW/CX
Total is mapped; the two leg measures stay unmapped to avoid double-counting.
Pivotal definitional Q resolved (independent + empirical): gross vs deductible
mortgage interest for itemizers = $174.45B vs $174.31B (0.9992); base_variable
= home_mortgage_interest is the exact tracked export-mass-gate column so the
gate responds losslessly.

GAP 2 — US_EXPORT_INPUT_MASS_REVIEWED_EXCLUSIONS was defined but never passed
to _export_input_mass_gate; wire it into the call so estate_income and
non_sch_d_capital_gains exclusions actually apply.

Registry: d71c59514e3a / 5533 specs (was inert 8e1b83852751 / 5531). Mortgage
amount target $186,310,104,604 (CBO-aged), itemized_only=true. Tests: 269 unit
+ builder/L0 heavy suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 4 + zero-support refresh: dense launched; sparse 19-cell set revalidated vs registry d71c59514e3a (#299)

Dense run launched detached (buildh_dense.sh, mirrors buildg dense arm, only
--ledger-facts -> v8). Sparse zero-support set recomputed against the new
registry: the 19 Build-G cells (AL TANF + under-$1-AGI taxable-interest tails)
are unchanged old targets on the identical 57,240 selection; the 12 new
national T1.4/T2.1 targets all have material frozen-selection support
(mortgage 13,914 / estate 449+87 / misc 1,346+799 / capgain-distrib 2,318
nonzero person-records) so add no new zero-support cells. Refreshed 19-cell
file staged (df61c770); sparse launch script pre-staged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 5-6: diagnose monolith jetsam death; launch chunked direct solve (#299)

Pressure trace (pressure_dense_20260708T015517Z.log) shows the monolithic dense
release dies rc=137 from SUSTAINED ~55-88GB footprint x 76min crossing the macOS
jetsam band, not an OOM spike (death at RSS 55GB / free 57%, ~39min after the
88GB peak). Pivot to the proven <30-min chunked path.

- experiments/.../direct_replay_dense_weights_buildh.py: direct CSR solve from the
  buildh-dense frame checkpoint (registry d71c59514e3a verified HIT, 5533 targets,
  337,704 households). Bypasses ACA + JCT-reform materialisation. Emits recovered
  dense weights + warm-start populace_us_2024_calibration.npz for the Step 2 rerun.
- --verify-only PASSED; full solve launched detached (pidfile + real rc + sampler).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 7: direct solve DONE rc=0 — dense final_loss 0.0425793792 (#299)

ESS 79,991; n_nonzero 337,704; ~18.7 min; RSS steady ~20GB (vs monolith 88GB).
Canonical Build H dense loss = 0.04258 (Build G 0.04139, same order). Warm-start
populace_us_2024_calibration.npz written for the Step-2 release rebuild.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 8: dense-ws release in-flight; disarmed watchdog on progress (#299)

Machine degraded ~7x: ACA on 337k ran ~44min (peak RSS ~86GB/free 81 at 36min),
then the peak PASSED — ACA freed intermediates (RSS 86->42GB) and the run entered
the checkpoint-hit + solve phase at ~50GB/free 77 (half the monolith's sustained
88GB). Disarmed only the 40-min watchdog subshell to avoid killing a progressing
run per conductor guidance; sampler+release+launcher preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 9: DENSE CERTIFIED — final_loss 0.04018, export-mass gate PASS (#299)

Core artifacts complete (h5+diagnostics+parity+npz); reform-validation tail
SIGTERM'd (rc=143, non-essential, heavy on degraded machine). DENSE beats Build G
dense: loss 0.04018<0.04139, within-10% 86.95%>86.16%. Export-mass parity PASSED
(0/35 failures): home_mtg +23.6%, first_home_mtg +23.7%, misc -42.0% all in-band;
estate + non_sch_d reviewed-excluded. Mortgage JCT shrank +59.7%->+46.16%. All
structural gates pass. #340: 7/8 families absent (out-of-scope). VERDICT: CERTIFIABLE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 10: SPARSE run — export-mass FAILS on misc (-79.9%); NOT CERTIFIABLE (#299)

Sparse frozen-57k: final_loss 0.03092, within-10% 89.01%, ESS 13,184; all
structural gates PASS; mortgage JCT shrank +44.5%->+35.48% and both mortgage
export columns in-band (+26.9%/+27.0%). But export-mass gate FAILS(1) on
miscellaneous_income (-79.9%, beyond +/-50%): the thin frozen-57k support can't
hold misc's national SOI target in the parity band (dense meets it at -42.0% in
-band). rc=1, no h5 (correct hard-gate abort). Not a reviewed-exclusion case
(misc ref ~SOI, achievable). VERDICT: NOT CERTIFIABLE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Build H step 11: gates extracted; #299 comment posted; final verdicts (#299)

DENSE CERTIFIABLE (0.04018, export-mass PASS, 4 dims resolved, mortgage JCT
+59.7%->+46.16%). FROZEN-57k NOT CERTIFIABLE (export-mass FAILS on misc -79.9%;
mortgage fix carried over +44.5%->+35.48% but thin frozen support can't hold misc
in band). One comment posted to #299 (issuecomment-4914302628). Staging/local
only; publication is Max's call. UK adjudication agent holds for conductor GO.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make Build H direct-solve script ruff-clean (#299)

The test job runs `uv run ruff check .` before pytest; the merged
build-h-run tree failed lint (not tests) on the Build H direct-solve
experiment script:
- E401: split `import argparse, sys, time` onto separate lines
- I001: sort the two import blocks
- N812: rename `import build_us_fiscal_refresh_release as R` to
  `as release`, matching the repo's existing convention in
  tools/score_us_fiscal_targets.py and tools/score_us_state_files.py

Behaviour is unchanged (experiments/ is not collected by pytest;
testpaths=["packages"]). Full suite: 1559 passed, 35 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude (Build G) <noreply@anthropic.com>
MaxGhenis pushed a commit that referenced this pull request Jul 9, 2026
…ies (#337)

* Fail the eCPS parity gate on stale exemptions; remove four rotted entries

parity_gate skipped known-gap names unconditionally, unlike its sibling
gates (default_valued_columns_gate at gates.py:707 and
source_stage_input_coverage_gate at gates.py:1499), which both fail on
stale reviewed exclusions. That let register entries rot silently once
the candidate started producing the exempted layer (the
takes_up_medicaid_if_eligible case surfaced in PR #334).

The gate now fails on any exemption whose candidate nonzero share is
positive, reports exemptions the reference never populates as dormant,
and records both in the gate details. Auditing the register against the
current release pipeline found four entries stale on arrival — the
pipeline deterministically materializes them before the gate runs:
takes_up_tanf_if_eligible and takes_up_eitc (contract-seeded take-up,
PR #315), ssn_card_type and immigration_status_str (immigration stage,
#266). A new register test pins that statically-knowable subset so a
stale entry is caught at test time, not first at release-build time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Extend the register staleness guard to count-calibrated take-up programs

Max's review of #294 (SNAP take-up) spotted that the static guard
covered immigration outputs plus seeded_take_up_programs() only, so a
program whose stage produces its flag under a different contract
treatment sat outside it — SNAP (out_of_scope) was hand-fixed on #294,
and count_calibrated programs (Medicaid in #334, CHIP next per the
contract followup) had the same exposure: the staleness failure would
only surface on the first release build, not in CI.

The guard now derives the produced set from contract treatments
({seed, count_calibrated}) instead of the seeded helper, making it the
CI enforcement of the rule that each producing PR removes its own
register line. Keyed on the treatment string rather than #334's
count_calibrated_take_up_programs() helper so the test imports nothing
that does not exist on main yet; it picks up the Medicaid flip (and any
future CHIP flip) automatically at merge, in either merge order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Remove the two hours-stage register entries #293 left behind; widen the guard

The #293 hours stage merged without removing its parity register
exemptions, so hours_worked_last_week and weekly_hours_worked_before_lsr
carried false "not yet on the candidate frame" reasons — the stage runs
before the parity gate and its signal gate requires both non-constant,
so the first release build after the staleness enforcement lands would
have failed on them. Both confirmed as engine input variables (its third
output, weeks_worked, has no register entry). hourly_wage stays: it is
an org_wages output and that stage is not executed anywhere yet.

The static register guard now also pins the deterministic stage output
constants (hours, SNAP take-up, eligibility inputs) alongside the
immigration outputs and contract-produced take-up flags, so the next
producing stage that forgets its register line fails in CI rather than
on a release build — #293 slipped through exactly because the guard
did not know stage outputs, only take-up programs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

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.

Seed take-up inputs across programs (parity with policyengine-us-data's stochastic flags)

1 participant