Repository navigation
Conversation
ff1d52e to
db5f656
Compare
|
The base of this PR, #1189, moved to head
A round-4 independent review is running on #1189. |
|
One more commit on the base, #1189, now at head
|
|
The base, #1189, is now at head
|
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>
Wage evidence. The legacy two-spine PUF pass fills missing mapped earnings with zero, so the round-1 wage check could not tell a filled zero from a measured one there. The anchor now resolves the ACS earnings universe (resolve_acs_pums_earnings_universe) over the transferred rows: an eligible person (15 or older) with a blank raw WAGP is refused whatever the mapped column holds, and a person below 15 must carry the named universe zero. WAGP and the universe inputs (age, tax-unit membership, support provenance) are declared anchor inputs: the remaining-stage manifest grows to 1,079 rows (54 derive), and the H3 graph gains the raw wage cell. Masses. The anchor and the gates took the transferred side's mass as one minus the source share, which loses a tiny side to rounding, so the factor depended on weight scale. Each side's household mass is now summed on its own; the target is source total x transferred mass / source mass, and the gates compare each side against its own mass. The property strategy spans weights from 1e-9 to 1e10. PERIDNUM in the H3 graph is a string cell. Writing a real-shaped 21-digit id into the fixture showed that the generic csv-tables source codec inferred numbers before casting to the declared dtype, so long numeric ids came back rounded. It now reads declared string columns as strings; a codec test covers long ids, leading zeros, blanks and whitespace. The base now anchors the stage to BEA NIPA 7.8 line 17 (d1317). The pinned-input experiment was rerun at that anchor; the documented figures and their pinning test follow it. In the 5% sample every transferred wage was evidence: the earnings-universe producer wrote the named zero on the 25,511 ACS records below 15, and Census reported WAGP for the 145,870 others. Re-pinned at observed values: the remaining-stage manifest and engine ABI lock, the spec bundle, field usage and coverage evidence, the checkpoint identity, the H3 fixture and graph counts, and the legacy publication golden (pool H5 and agreement bytes unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
db5f656 to
1677937
Compare
Summary
The stacked ASEC + ACS pool now carries
employer_sponsored_insurance_premiumson every row, so a pool-lineage (--exact-k) release has the input thatus_release_input_coverage_gaterequires andus_esi_premiums_anchor_gategrades. Follow-up to #1189 for #454;docs/us-esi-employer-premiums.mdnamed this gap under "What this does not do".Stacked on #1189 (
esi-employer-premiums-454). This repo's CI runs only on PRs that targetmain, so checks start when #1189 merges and this PR is retargeted. Until then the test evidence is local; see the test plan.axiom: n/a: data/microsim pipeline
The two design questions
1. Scaling. A
cps_sourceoperator sees only the rows with raw CPS evidence, at pool weights.assemble_stacked_spinegives each source half of ASEC's household mass, so those rows hold half the population. Run unchanged, the stage would hold half the policyholders to the whole anchor and double every premium.The pool passes the stage
anchor_share: the household mass of the rows it runs on over the pool's household mass, read from the live frame (us_esi_premiums_household_mass_share). The factor isanchor x share / sum(w x raw). Assembly multiplies every ASEC household weight by that same share, so the factor equals the single-source factor and each ASEC person carries the dollars a single-source build assigns.anchor_sharedefaults to 1, which leaves the base build as it was.2. ACS rows. ACS has no policyholder flag, payment status, tier or firm size, so the stage cannot run there. The column enters the pool's QRF transfer as family
source_operator_esi_premiums, on the transfer's fixed predictor surface (age, sex and State always; wages, self-employment, Social Security, retirement and investment income, household head and tenure where both sources observe them).A QRF draw keeps no total and can put a premium on a non-worker.
with_us_esi_premium_pool_anchorruns in the derive stage, once every row is filled. On transferred rows only, it:A transferred person's wage must be evidence. The anchor resolves the ACS earnings universe over the transferred rows. An eligible person (15 or older) needs a reported raw
WAGP, even if a producer filled the mapped wage. A person below 15 needs the named universe zero. A missing or non-numeric mapped wage is refused too. In the stacked pool nothing real is refused: the late ACS earnings-universe producer writes the zeros below 15 before the derive stage, and Census reportsWAGPfor everyone else.It tells the two kinds of row apart by whether the raw ASEC coverage columns are present, corroborated by the CPS record id (
PERIDNUM), not by a source channel. Soesi_premiums.pystays spine-blind and moves to the registered operator set. A CPS record that lost its coverage codes is refused by the anchor and fails both gates; it is never graded as a transfer.Where this departs from the brief
The brief suggested a post-clone operator, since the draw is keyed by source identity. I placed the stage pre-clone:
Invariants
For every valid pool, stated in the module and property-tested with Hypothesis in
tests/engine_free/us/test_us_esi_premiums_pool.py:The strategies generate zero-weight households and support clones (index 1 and the tail's index 2), with per-clone draws on the transferred side.
Differential tests: the pool's CPS rows equal a single-source build's dollars row for row, and the pool total equals the single-source total for any mass split. The anchor-universe total the release gate recomputes equals the anchor on the pooled fixture, before and after the PUF clone.
Assumption. The anchor (BEA NIPA 7.8 line 17 since #1189's d1317 change) counts every policyholder, and ACS rows have no policyholder flag to price the non-employed ones from. The gates take the pool's anchor-universe total as the ASEC rows' total plus the transferred column over the ASEC rows' employed share. That assumes employed policyholders carry the same share of employer premiums on the ACS half as on the ASEC half. The anchor gate records the assumption and fails if the transferred rows' column per unit of household mass leaves 0.8 to 1.25 times the source rows'.
Measured on the pinned inputs
experiments/us-esi-454/run_pool_fill_on_pinned_inputs.pyruns the part of the pool that decides the column on the real inputs (pooled 2023-2025 ASEC, ACS 2024 1-year PUMS, the real assembly, stage, early gap-fill QRF, anchor operator, by-origin battery and both gates) on a household sample. It is not a pool build, and no artifact was built or published. Receipts hold aggregates only.Figures are at #1189's BEA anchor ($977.0B for 2024).
The fill reproduces ASEC's premium per holder and gives slightly fewer ACS people one, and ACS rows carry fewer weighted people at equal household mass, so the factor is above one and ACS holders end about 5% above ASEC holders in the 5% sample. The factor fell from 1.10 to 1.05 as the donor sample grew fivefold. A full build was not run. In the 5% sample the earnings-universe producer wrote the named zero wage on the 25,511 ACS records below 15, and Census reported
WAGPfor all 145,870 others.Sealed pools and pinned surfaces
_legacy_stacked_authority_receiptrebuilds the version-11 authority from live surfaces to score attested schema-9 pools, so a new family would have changed it. The reconstruction now excludes families added after v11 (_POST_V11_AUTHORITY_TARGET_FAMILIES), and a new test pins the legacy authority digest and its components to their previous values.test_legacy_entrypoint_publication_matches_origin_main_goldenmoves only in checkpoint identity digests. Run against the base branch with the same fixture, the pool H5 and agreement bytes are identical and the manifest differs only instage_checkpoints.*identity_sha256.us_runtime/graph.pydeclares the premium, the seven raw coverage columns, the CPS record id (a string cell), the mapped and raw ACS wages as source cells, and the premium as a derive rewrite. The derive kernel's implementation hash now coversesi_premiums.py. The parity fixture was regenerated withtools/graph_us_post_transfer_fixture.py, whose helper path was stale since the test split. Every pre-existing source and expected cell is byte-identical. Writing a real-shaped 21-digitPERIDNUMinto the fixture exposed a defect in the genericcsv-tablessource codec. It inferred numbers before casting to the declared dtype, so a declared string column of long numeric ids came back rounded. It now reads declared string columns as strings, and a codec test covers long ids, leading zeros, blanks and whitespace. The new expected column shows the transferred rows moving from the drawn $6,000 to $8,000, the ASEC row's premium at equal household mass.Independent review
GPT-6.1 Sol reviewed this twice, and both reviews asked for changes. The last commit answers the second.
Round 1
PERIDNUM. The anchor refuses such a record and both gates fail on it.Round 2
WAGPis refused whatever the mapped column holds, and a person below 15 must carry the named universe zero.WAGPand the universe inputs are declared anchor inputs.1 - share, which loses a tiny side to rounding, so the factor depended on weight scalesource total x transferred mass / source mass, and the gates compare each side against its own mass. The property strategy now spans weights from 1e-9 to 1e10.PERIDNUMas a floatMy own widened property tests found one more defect in round 1: the mass share depended on floating-point summation order. Round 2's separate masses replace that code.
Not in this PR
declared_acs_transfer_target_familiesstill does not name the column, andfill_reviewed_nullsrefuses that frame. A separate branch,esi-premiums-acs-local-lane-454, is stacked on this one.HINS1,COW,ESR) for the fill.Test plan
Local, on the pushed head:
uv run ruff check .andpython3 tools/ci_test_plan.py verifytools/generate_us_bundle_from_constants.py --check,tools/spec_engine_coverage.py --check,tools/repin_us_pool_engine_contracts.py --checkCI, once this targets
main:🤖 Generated with Claude Code