Repository navigation
Conversation
The stacked ASEC + ACS pool did not run the meps_esi_premiums stage, so a pool-lineage release carried no employer_sponsored_insurance_premiums and failed us_release_input_coverage_gate and us_esi_premiums_anchor_gate. Stage on the CPS rows. with_us_esi_premium_inputs joins the pool's pre-clone source operators. The pool passes anchor_share, the household mass of the rows the stage runs on over the pool's household mass, read from the live frame. The factor becomes anchor x share / sum(w x raw), so each ASEC person carries the dollars a single-source build assigns. anchor_share defaults to 1 and leaves the base build as it was. ACS rows. The column enters the early gap-fill as family source_operator_esi_premiums, so the QRF sees measured wages on donors and recipients. with_us_esi_premium_pool_anchor then runs in the derive stage. On rows without the raw ASEC coverage columns it clears the premium where the person's clone-0 source record has no wages and scales the rest by one factor, so transferred rows carry the same weighted premium per unit of household mass as source-derived rows. It never rewrites a source-derived row and refuses a row with only some raw columns. Invariants, property-tested in test_us_esi_premiums_pool.py: the pool-wide weighted column equals the source rows' total over their mass share; both kinds of row carry equal premium per unit of mass; source rows are immutable; transferred people without source wages carry nothing on every clone; the rest move by one common multiple; a pool already on scale is returned unchanged; rescaling every weight moves no value. Differential tests hold the pool's CPS rows to a single-source build's dollars. Gates. Both ESI gates grade a pooled frame by row kind and band the transferred rows against the source rows. The anchor gate records the assumption it makes about the ACS half's employed share. Sealed pools. The legacy v11 stacked authority reconstruction excludes families added after v11, and a test pins its digest and components to their previous values. H3 graph. The post-transfer graph declares the premium, the raw coverage columns and the wage column as source cells and the premium as a derive rewrite, and the derive kernel's implementation hash covers esi_premiums. The parity fixture is regenerated; every pre-existing cell is byte-identical. The fixture generator's helper path, stale since the test split, is fixed. Pins. Counts and digests the new early family and derive consumer move are re-pinned at observed values: 135 battery targets, 49 early targets in 14 families, 119 pool transfer targets, a 1,074-row remaining-stage manifest, the stacked authority and its components, the gap-fill schedule, the checkpoint identity, the spec bundle, field-usage claims and the coverage evidence. The late producer DAG, the seed protocol and the seed map do not move. The legacy publication golden's pool H5 and agreement bytes are unchanged; its manifest differs only in checkpoint identity digests. Evidence. experiments/us-esi-454/run_pool_fill_on_pinned_inputs.py runs the stage, the real early gap-fill and the anchor on the pinned ASEC and ACS inputs for a household sample. Receipts for 1% and 5% samples are committed (aggregates only) and a test pins the documented figures to them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/us-esi-employer-premiums.md gains a stacked-pools section: the mass share rule, the transfer to ACS rows, the derive-stage anchor and its invariants, the anchor-universe assumption, why the column has no post-transfer calibration spec, and what the pinned-input runs measured. docs/us-multispine-operator-ordering.md adds the stage and the anchor to the ordering, the hard-consumer audit and the operator registry, and moves the surface counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Missing wages. The anchor read a missing or non-numeric clone-0 wage as no wages and cleared the premium. It now refuses a transferred row whose source record has no finite wage. The stacked pool's ACS earnings-universe producer writes the explicit zeros below age 15 before the derive stage, and the QBI reconciliation that follows the anchor already refuses a blank ACS earnings value there, so the rule adds no refusal a pool would otherwise survive. Row kind. Nulls in every raw coverage field made a row transferred, so a CPS record that lost its codes passed as a transfer, in a pool or in a single-source frame. The CPS record id (PERIDNUM) now corroborates it: a row with the id must carry every field, a row with the fields must carry the id, and a frame without the id column cannot hold transferred rows. PERIDNUM is a declared anchor input (remaining-stage manifest, 1,075 rows; H3 graph cell). One premium per person. The legacy two-spine order transfers after it clones, so its transfer draws each support clone separately. The anchor now gives every transferred clone its clone-0 record's draw before it clears and scales, and the signal gate fails on transferred clone disagreement. No transferred mass. The anchor no longer refuses a pool whose transferred rows hold no household weight, and both gates skip the two-sided comparison then. Mass share. Widening the property tests to zero weights and support clones found that the share was taken against the sum of all household weights, so "no transferred mass" depended on summation order and rescaling every weight could turn a valid pool into a refusal. Each side is now summed on its own. The experiment runs the earnings-universe producer before the anchor, as the pool does. Rerun at 1% and 5%, it reproduces every documented figure: no eligible ACS record was missing a wage and no clone needed resetting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#454) The lane pools a dense donor release with the ACS spine. The donor carries employer_sponsored_insurance_premiums from the meps_esi_premiums stage; ACS rows did not, so fill_reviewed_nulls refused the frame. A lane-local one-family transfer fits the premium on the donor's ASEC observation role, whose wages are measured, and the pooled frame is anchored with the stacked pool's kernel: an ACS person without wages carries no premium and the rest are scaled so both spines carry the same weighted premium per unit of household mass. The lane-wide weighted column equals the donor rows' total divided by their share of household mass. The declared ACS transfer plan and the stacked pool's plan are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e ACS local lane (#454) Staging qualifies the donor before the ACS sources are fetched and runs both ESI premium gates on the pooled frame. Finalize runs them on the calibrated artifact and records esi_premiums_signal and esi_premiums_anchor bound to the artifact bytes; input_coverage passes only if both do, where it was a fixed pass. Package refuses a report without a passing verdict for the bytes it packages. --allow-esi-premium-gaps records a red verdict for a diagnostic build, and the Modal stage plan forwards it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Max's ruling d1317 on #1189 moves the stage's scale anchor from CMS NHE Table 24 to BEA NIPA 7.8 (B4923C). This branch reads the anchor from the stage and does not restate it: the comments, help text and test fixtures that named NHE now name the stage's anchor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
esi-premiums-pool-operator
branch
from
October 12, 2026 15:32
db5f656 to
1677937
Compare
Contributor
Author
|
The base of this PR, #1199, is rebased onto #1189's newest head ( Behavior changes since
Counts this branch may pin:
A third review of #1199 is running, so one more change is possible. If anything above moves again, I'll post it here. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The ACS local release lane stopped building once the
meps_esi_premiumsstage landed (#1189): its donor release carriesemployer_sponsored_insurance_premiums, its ACS rows did not, andfill_reviewed_nullsrefuses a frame whose premium is null on some rows. The lane also had no verdict on the column: itsinput_coveragegate entry was a fixed"passed": True.This PR puts the premium on every row of the lane's frame, holds it to the donor's scale, and grades it with both ESI gates at staging and on the calibrated artifact. Part of #454.
Stacked on #1199 (
esi-premiums-pool-operator, which is stacked on #1189). It uses #1199'swith_us_esi_premium_pool_anchor,us_esi_premiums_household_mass_shareand the pooled grading in both gates, and changes none of them.No data release was built or published.
The three decisions
1. How the lane gets the column: a lane-local transfer, fitted on ASEC observations
acs_local_esi_premium_transfer_target_families()is a one-family plan (source_operator_esi_premiums) that only the lane's staging build uses, the same shape as the #765 usual-hours repair. I did not add the column to_DECLARED_ACS_TRANSFER_TARGET_FAMILIES:ACS_DONOR_CHANNEL_AUTO), the wrong donor for this column (below).source_operator_esi_premiumsfamily (multispine_pool.pool_transfer_target_families()adds asource_operator_*family only for outputs the declared plan does not own), and it would enter the frozen v11 reconstruction (stacked_spine._legacy_stacked_authority_receipt()drops post-v11 targets by(entity, family)), whichvalidate_stacked_post_puf_transfer_receiptcompares an attested schema-9 pool against. A new declared family namedsource_operator_esi_premiumswould avoid those two, not the donor problem.spine_agreementuses the declared plan as its default metric registry.Taking the lane's ACS rows from the stacked pool would replace the lane rather than repair it: both tools read the legacy spine tags, legacy support roles and the staging summary's null register, and the hours repair refuses an assembled frame.
Donor. The transfer fits on the donor's ASEC observation role (support clone 0), not on the PUF-detail role that
ACS_DONOR_CHANNEL_AUTOgives the lane's tax-detail families. On the observation roleemployment_income_before_lsris the measured CPS wage. PUF support imputation writes the clone's wages (PUF_TAX_DETAIL_DEFAULT_PERSON_OUTPUTS), while the clone's premium is a copy of its source person's, so on those rows the premium no longer follows the wage. The recipient side is the measured ACS wage (WAGP).What that does to the structural zero, computed on the test fixture (synthetic; 320 ASEC households cloned once, 280 ACS people of whom 112 report no wages; only wages tell the employed from the rest):
With the PUF-detail role the fit cannot tell workers from non-workers (31.3% against 31.0%), the anchor then clears two fifths of what was drawn and loads the total onto fewer workers. Both variants end on the same lane total, because the anchor holds it. These are fixture numbers, not real data: no certified donor release carries the column yet.
2. Scaling
with_optional_acs_spinemultiplies every donor household weight by1 - acs_shareand gives ACS the rest of the donor's household mass (half each by default). After pooling,with_acs_local_esi_premium_anchorruns the stacked pool's kernel,with_us_esi_premium_pool_anchor, which reads each side's share from the live frame. On the ACS rows it clears the premium where the person reports no wages and scales the rest by one factor. It never rewrites a donor row.Three lane-specific points:
Row kind and missing wages. The kernel tells the donor rows from the ACS rows by the CPS record id (
PERIDNUM) and the seven raw coverage fields, and it refuses a transferred row whose wage is missing. ACS PUMS leavesWAGPblank below age 15, outside its earnings universe; the legacy lane keeps that blank in the staging frame, where the reviewed-null register fills it for the engine pass. The lane does not write zeros into the frame. In the anchor's view only, a transferred row whose mapped wage is null, whose age is below 15 and whose rawWAGPis blank reads as zero wages underacs_income_universe's named rule (acs_2024_pums_wagp_age_15_plus). Any other missing wage stays missing and stops the build. The donor qualifier also requiresPERIDNUM.Source ids. The kernel finds a support clone's wages by
person_source_idacross the whole frame. The stacked pool makes that id unique at assembly. This lane does not:base_pool._prepared_source_viewgives ACS rows their pre-remap ids as source ids, and those can repeat a donor's. Run on the lane frame directly, the kernel raises (source records repeat a 'person_source_id'). Every ACS row here is its own clone-0 source record, so the lane hands the kernel a view without the lookup's two columns and it reads each row's own wages. The wrapper refuses a transferred row that is not clone 0.Memory. The kernel copies every table it is given and each gate copies the person table. The lane runs them on a narrow view (ids, memberships and the columns they read) and rebuilds the pooled frame once. By the accounting
estimate_optional_acs_pool_peak_bytesuses for pooling (both input frames plus two copies of the pooled tables), that one rebuild holds the donor plus two copies of the pooled frame, which is below the pooling peak the existing guard already budgets for.3. Gates
tools/_legacy/build_us_acs_multispine_base.py): the donor is qualified before the ACS sources are fetched (column, raw coverage columns, wages, legacy support roles, the stage's signal gate). Both gates run on the pooled frame before the staging H5 is written; a red gate fails the build. Verdicts and receipts go in the summary underlocal_esi_premiums.esi_premiums_signalandesi_premiums_anchor, bound to the artifact's SHA-256. Either one red fails finalize.input_coverage.passedis now the conjunction of the two instead of a fixedTrue.--allow-esi-premium-gapson both tools records a red verdict without failing, for a diagnostic build from a donor that predates the stage. A release build leaves it unset.fill_reviewed_nullskeeps its refusal as a backstop for a staging H5 built before this change.Invariants
For every lane frame (any
acs_share, any household weights on either spine, multi-person ACS households, children with blank wages, any transfer draws):1 - acs_share) = the donor release's own weighted premium.Intended non-invariant: the lane total is not pinned to the stage's national anchor. It is the donor release's total, and a calibrated release sits within the release tolerance of the anchor, not on it.
Tests
packages/microcosm-build/tests/engine_free/us/test_us_acs_local_esi_premiums.py(51 tests):with_optional_acs_spine.build_optional_acs_multispinewith the real QRF): every row carries the premium, the donor spine is the release byte for byte, both gates pass, andrefuse_unassigned_us_esi_premiumshas nothing to refuse.WAGPbelow 15 reads as zero in the anchor only; at 15 or older, at an unknown age, or with a non-blank rawWAGPit is refused; end to end, ACS children get no premium and keep their blank wage.PERIDNUM, wages or legacy roles, or with a row whose raw fields lack the record id; assembled donors; a donor failing the signal gate; transferred clones; a missing anchor input; an untransferred spine.Also: staging-tool tests (qualification before fetch, waiver, coverage plan, red gates abort before export), release-tool tests (finalize records and enforces both gates; package refuses a missing, unbound, stale or red verdict), and an engine-contract test that runs the real
fill_reviewed_nullson the lane frame against policyengine-us 2.2.1 (the pass runs and leaves the premium; an untransferred spine is still refused).Verification
This PR is stacked on #1199, so CI does not run until #1189 and #1199 merge and it is retargeted to
main(both workflows trigger onpull_requesttomainonly). Local runs at head6ced20bb9, Python 3.14:test_us_acs_local_esi_premiums.py(engine-free)test_us_acs_local_release_tool.py,test_us_acs_multispine_legacy_builder.py,test_us_spine_blindness.py,test_us_acs_multispine.py,test_us_acs_local_hours.py,test_us_acs_multispine_base_builder.py,test_us_modal_stage_plan_tool.py,test_us_esi_premiums_pool.py,test_us_esi_premiums.pyengine_contract/us/test_us_acs_local_release_tool.pyagainst policyengine-us 2.2.1ruff check .,ruff format --checkon touched files,tools/ci_test_plan.py verifypackages/microcosm-build/tests/engine_free/uspackages/microcosm-build/tests/engine_contract/usagainst policyengine-us 2.2.1packages/microcosm-build/tests/engine_free/sharedThe one shared failure is
test_telemetry_emitter.py::test_subprocess_exchanges_ambient_token_and_delivers_eventsatassert emitter.available. The emitter waitsDEFAULT_STARTUP_TIMEOUT_SECONDS(3.0 s) for its service subprocess to become ready, and on this host under tonight's load (load average over 300) the service's cold start took about 13 s. The test fails identically at the prerequisite headdb5f656d2(same line, 4.83 s), and this PR touches neither the emitter nor its test.Anchor (d1317). Max ruled on #1189 that the stage's scale anchor moves from CMS NHE Table 24 to BEA NIPA 7.8 (B4923C). That change lands in #1189. This PR reads the anchor from the stage (
EMPLOYER_PREMIUM_ANCHOR) and restates no figure, so the lane's scaling and gates follow it unchanged. Commita943ddc5aremoved the NHE naming from this PR's comments, help text and test strings. The gate table row in the doc keeps #1189's wording, and #1189 will update it.Independent review: GPT-6.1 Sol (
subfleet run --task review --tier hard), four rounds. Round 1 requested changes: the Modal stage plan did not forward the new waiver, and one doc claim overstated what declaring the column would break. Both were fixed. Rounds 2 to 4 approved, the last at6ced20bb9after the adaptation to #1199's anchor rules (record id, missing-wage refusal, clone reset).Not in this PR
axiom: n/a: data-pipeline change in microcosm (a microsimulation input on one release lane); no PolicyEngine country-model policy changed.
🤖 Generated with Claude Code