Repository navigation
Conversation
db5f656 to
1677937
Compare
|
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. |
…#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>
…e check (#454) #1199's rounds 2 and 3 changed the kernel this lane calls, with_us_esi_premium_pool_anchor. Before trusting a transferred row's zero wage, the kernel now resolves the ACS PUMS earnings universe (resolve_acs_pums_earnings_universe), and that needs raw WAGP and the clone index beside the wage. After the rebase onto #1199's head, the lane's view dropped the clone index, so every lane build refused. - The anchor view keeps the clone index. It still drops the support source id: lane ACS rows keep pre-remap source ids that can repeat a donor's, so without that id the kernel reads each row's own wages. - The kernel's resolver scopes on the stacked pool's ACS support channel ("acs"). This lane tags its ACS rows with the late spine's channel ("acs_2024_1yr"), so on the lane that check sees no rows. _wages_under_the_acs_universe now applies the rule itself, refusing as the resolver does: - a blank raw WAGP at 15 or older, or at an unknown age, however the mapped wage was filled (round 2's zero-fill case); - below 15, a raw WAGP that is not blank, or a mapped wage that is neither null nor zero. The receipt counts the rows it checked and says why the kernel's own receipt counts none. A test pins that, so the docs are revisited if the owner module ever learns the lane's channel. - The test frames carry raw WAGP. New tests cover the zero-filled adult, the child with a nonzero mapped wage, a child already holding the named zero, and missing WAGP or clone index. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a943ddc to
e7e85cf
Compare
|
Adopted from the #454 session; the original session is gone. Rebased onto #1199's current head
New commit
Checks on
Next: an independent review of this PR. When #1199 merges, retarget to |
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