Repository navigation
Produce ESI employer premiums scaled to BEA B4923C from restored ASEC policyholder fields (#454) - #1189
Produce ESI employer premiums scaled to BEA B4923C from restored ASEC policyholder fields (#454)#1189MaxGhenis wants to merge 11 commits into
Conversation
AHRQ publishes the MEPS-IC State tables only as PDF. The tool pins four PDFs by URL, byte length and SHA-256, extracts the cells the meps_esi_premiums stage reads with pdftotext, and writes them with their provenance to us_runtime/data/meps_ic_esi_premium_cells.json: - 2025 Series II, private sector by State and firm size: average total premium and employee contribution, single / employee-plus-one / family; - 2024 Series III, State and local government by census division (2025 is not published), plus the 2024 private national rows that age it; - 2025 Series I, pretax-contribution and health-insurance offer rates by firm size. Values are copied as published; a suppressed cell stays null. The tool refuses a reissued PDF, a label that matches no row or two, a table for another survey year, and a United States row that differs from the AHRQ national figure. --check fails if the committed file is stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The declared meps_esi_premiums stage could not run: every pinned ASEC input omits the Census current employment-based coverage fields, so employer_sponsored_insurance_premiums was a recorded parity gap and pre_tax_health_insurance_premiums had no producer at all. Source fields. asec_census_person_columns restores NOW_OWNGRP, NOW_HIPAID, NOW_GRPFTYP, NOW_GRPFTYP2, PEMLR and NOEMP from the pinned Census person members by exact PERIDNUM join, for every pooled vintage (none of the four pinned inputs carries them). Domains are the Census API codebooks for survey years 2023-2026, checked against all four pinned members. Stage (us_runtime/esi_premiums.py), run in the base build's pre-clone enrichment, both build paths: - Employer premium: an employed current policyholder whose job has an employer takes the MEPS-IC cell of their tier and employer (private: State x firm size, 2025; State government and local/federal: census division, 2024 aged to 2025). Employer paid all -> premium; some -> premium minus employee contribution; none -> 0. Dependents, non-employed policyholders and the unincorporated self-employed carry 0. One factor scales the weighted total to BEA NIPA 7.8 line 17 for the build year ($977.034B for 2024 in the 2026-09-30 vintage). - Pre-tax premium: an employed policyholder with wages, a reported premium and an employee share pays PHIP_VAL through payroll with the MEPS-IC firm-size pretax-offer probability. The blake2b draw is a new registered site of the seed protocol. The stage refuses missing, non-integer or out-of-codebook source values and the Census universe relations the members satisfy; it never proxies or defaults. Anchor. NHE Table 24 ($1,047.0B) counts COBRA and retiree enrollees; the same cells over every current policyholder reproduce it to 1.5% ($1,031.0B), 11.5% of that being non-employed policyholders. An employed-only column therefore scales to BEA's compensation series and holds NHE as an upper bound. Gates. us_esi_premiums_signal_gate (structure, clone agreement, common cell multiple, raw-share band) runs in the base build before and after the clone and in the release tool on base and export. us_esi_premiums_anchor_gate runs on the calibrated export for both the dense and sparse defaults: red when either column is absent or zero-mass, when the employer total leaves 5% of BEA or exceeds NHE. Its verdict ships as the esi_premiums gate evidence; --allow-esi-premium-gaps records without failing a diagnostic build. Registers. The parity known-gap entry is removed; both inputs are required release columns with two binding reform-coverage probes; the cms_nhe ESI target families become deferred reviewed exclusions and BEA's series is recorded source-absent; the source manifest, spec bundle, seed-site inventory and spec identity pins are regenerated or re-pinned at their observed values. Measured on the real pinned pools (experiments/us-esi-454), before calibration: 74.0M weighted policyholders with a positive employer premium, scale factor 1.070, mean $13,209, pre-tax premiums $181.1B on income years 2023-2025; all gates pass before and after the PUF clone. Not wired: the multispine pool, whose operators run on CPS rows carrying part of the stacked weight. Its releases fail the new gates until then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/us-esi-employer-premiums.md: what the two inputs feed in policyengine-us 2.2.1, the source fields, the cell method, the BEA anchor and why NHE is only an upper bound, the pre-tax share assumptions, the measurements on both pinned pools, the gates, lineage, and what the change does not do. Adds the six restored columns to the Census person-column review, the operator to the base-build ordering note, and a changelog fragment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pre-tax premium column overlaps the other premium columns; the engine contract is disjointFrom the session that took the "engine's medical-expense base" follow-up in this PR's "Not in this PR" list. The fix for that double count belongs in this stage, not in the engine. What the engine expects. PolicyEngine-US treats the premium inputs as disjoint: a premium is reported in What this stage writes. At Why the engine can't absorb it. Under the disjoint contract, federal Schedule A is already correct: it counts the non-pre-tax inputs in full and never reads the pre-tax input. If the engine subtracted the pre-tax input there, a caller who follows the contract ($2,000 pre-tax, $3,000 after tax) would get $1,000 on Schedule A instead of $3,000. PolicyEngine/policyengine-us#10067 adds tests that pin this, and makes no formula change. What overlapping columns do, by engine version:
Ask. Before any release carries this column:
I have not changed this branch. |
|
Three corrections to my comment above, from the independent review of PolicyEngine/policyengine-us#10067. Asks 3 and 4 stand.
|
The first independent review asked for changes on five points; each held up against sources. Anchor and universe. BEA NIPA 7.8 line 17 is not an active-worker series: BEA's State Personal Income methodology (December 2025, paragraphs 3.18 and 3.19) says it covers active and retired employees, as CMS NHE Table 24 does. The stage now prices every current policyholder, scales that universe to NHE Table 24 (the anchor #454 chose), and writes the column for employed policyholders only. Retiree and COBRA dollars are priced and left out, not loaded onto workers. BEA and MEPS-IC's own private-sector enrollment total are recorded beside the anchor verdict. Payment status. "Employer pays some" subtracts the contribution of an employee who pays one: MEPS-IC's average contribution divided by one minus the published share of enrollees with no contribution (Tables II.C/D/E.4.a). A cohort with MEPS-IC's own mix reproduces the published cell mean. Gates. Both gates fail when a raw ASEC coverage column is missing. The anchor gate recomputes the anchor-universe total at release weights and bands the employed share. The evidence file carries the base and export signal verdicts, so a waived failure stays on the record. An ACS local build refuses a frame whose input is null on some rows instead of default-filling it. The output family is registered at the raw-stage operator boundary. Pre-tax premium. Removed from this change. PolicyEngine-US treats the premium inputs as disjoint (PolicyEngine/policyengine-us#10046), so a producer must move those dollars out of the reported premium on an engine that includes that change. The earlier derivation is kept on branch esi-pre-tax-premiums-454. The stage now has one output and no seeded draw; the seed-protocol registration is reverted. Measured on the default pinned pool, target year 2024, before calibration: $925.7B over 74.0M employed policyholders with a positive premium, mean $12,515. Receipts regenerated for both pools. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reform-probe count drops to 42 with the pre-tax probe gone, the pool tool's import graph reaches esi_premiums through the operator boundary (75 modules), and the ESI stage no longer reads PHIP_VAL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Round 2 is pushed (head 4110519). On the comment above about the pre-tax premium column: The disjoint contract is right, and the fix is on the data side. This PR no longer produces I closed PolicyEngine/policyengine-us#10064, which I had filed on the premise that the engine should subtract the pre-tax input. The first independent review's five blocking findings and what changed for each are in the PR description. A second review is running against this head. |
The second independent review approved with nits and no blocking findings. - Target parity: the NHE private-employer family no longer borrows the all-employer text. Nothing holds the private part of the column to that fact; it is recorded against MEPS-IC's active-employee total. - L0/refit export: the ESI column joins the required person source columns, so the standalone export refuses a frame where it is absent or constant. The doc says that path still runs neither ESI gate. - Doc: NOEMP is the employer of last year's longest job while PEIO1COW is the current job; an unmeasured "most" is removed; the unincorporated self-employed are priced and left out; the NHE digest is of the zip archive. - Cell table: --check compares published values, titles and pins and no longer depends on the installed poppler version. - Tests: every tier x State x employer cell reconciles to its published mean under the published mix; the anchor gate is exercised without each raw column and without the household State. The spec bundle is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Max ruled d1317: anchor on BEA NIPA 7.8 (B4923C), not NHE Table 24Ruled 2026-10-11 ( Why: both national totals include retirees, so the only independent check on the active-worker piece is MEPS-IC's private active-employee total for 2024 ($668.6B). The private part of the column is $713.2B under NHE (+6.7%) and $665.6B under BEA (−0.4%). What this needs on this PR:
Unchanged: d1264 (whether and how to calibrate the column against a national total) and d1318 (the release) are still open. This ruling only picks the producer's scale anchor. Context: CRFB's Medicare for All work prices the wage offset from this column, so the anchor sets its size (about 6.7% smaller under BEA). |
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>
…1317) Decision d1317 (2026-10-11) chose BEA NIPA Table 7.8 line 17 (B4923C) over CMS NHE Table 24 as the scale anchor, with the US Chronicle feed re-pinned to carry the banked fact. Stage. EMPLOYER_PREMIUM_ANCHOR is now B4923C for 2023 to 2025 ($923,195M, $977,034M, $1,027,929M, the 2026-09-30 vintage); NHE Table 24 becomes the recorded cross-check beside MEPS-IC's active-employee total. The universe design is unchanged: every current policyholder is priced and scaled, and the employed part is written. On the default pool the column is $863.9B (mean $11,679 per policyholder with a positive premium); its private part is 0.4% below MEPS-IC's own active-employee total. Feed. The pin moves from Chronicle 51aa40f to cf9ece2 (tag microcosm-us-feed-454-v1): 51aa40f plus the digit-text guard match from aa3e4de3 and the B4923C package as chronicle#331 merged it. All 40,178 previous rows are byte-identical, exactly three facts are added, all 33,250 compiled targets are identical, and two rebuilds give the same digest. The release tool refuses a feed whose B4923C values differ from the stage's anchor (us_esi_premiums_anchor_feed_mismatches). The family is a deferred reviewed exclusion, not a target (calibration is d1264). Also answers the round-3 review's nits: the NHE private-employer family has its own verdict basis, the NOEMP sentence is qualified, and the L0 export's generic input-mass parity check is disclosed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The d1317 change is at head
d1264 and d1318 are untouched. |
The review approved with no blocking findings; this answers its nits. - Four places still called NHE Table 24 the anchor: the US_DONORS note, the --allow-esi-premium-gaps help, a release-tool comment and a test name. - A waived anchor-versus-feed mismatch left no record. The check is now _check_us_esi_anchor_against_feed: it stops a release build, lets a diagnostic run continue under --allow-unpinned-feed or --allow-esi-premium-gaps, and its result is written to esi_premiums.json (anchor_feed block, schema_version 3). - Nothing in CI tied the anchor to the pinned feed. A test now compares EMPLOYER_PREMIUM_ANCHOR with the three facts the committed re-pin report says the pinned feed added, and with the pin's digest and row count. The release tool's check has direct tests for pass, refuse and each waiver. - The two cms_nhe families shared an origin text that described the BEA series; they have their own. The manifest note uses BEA's wording for what the anchor counts. - The registry comparison now compares ordered lists with target aging on and off (identical; 33,250 distinct names). - Docs: status date, the two checks that hold the anchor to the feed, the waiver wording, and what a later feed re-pin must carry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view's nits (#454) Main's #1178 changed puf_support's attested source bytes, and this branch adds the meps_esi_premiums stage, so the seed protocol and owner-map digests and the golden semantic hash are recomputed for both; the source-manifest digest is this branch's, the US bundle spec_sha256 is 41d89d1b..., and the coverage attestation is regenerated (42217/42217, 41/41). The fifth review approved with no blocking findings: - --allow-unpinned-feed's help now says it also lets through a feed whose B4923C facts differ from the ESI anchor. - A dry run stops before esi_premiums.json is written, so its report now carries the anchor-versus-feed verdict and lists allow_unpinned_feed among its waivers; tested with a waived mismatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes the code side of #454: the US base build now produces
employer_sponsored_insurance_premiums, scaled to BEA NIPA Table 7.8 line 17 (B4923C) as decision d1317 ruled, with gates that go red when it is absent, zero-mass, misassigned or off its anchor. The data release is a separate call (d1318).axiom: n/a: data pipelineWhat changed since the first review
The first independent review (GPT-6.1 Sol) asked for changes on five points. All five held up when checked against sources, and the design changed. A second review found all five resolved and nothing blocking, and a delta review of its nits did too. A fourth review, of the BEA anchor and the feed re-pin, approved with nits; they are answered in the last commit.
fill_reviewed_nullsrefuses a frame whose input is null on some rows.PHIP_VALwas also deductible as a medical expense.esi-pre-tax-premiums-454.With the matched universe in place, the anchor was a methodology choice between two retiree-inclusive totals. Max ruled d1317 on 2026-10-11: BEA, not NHE Table 24.
Method
NOW_OWNGRP,NOW_HIPAID,NOW_GRPFTYP,NOW_GRPFTYP2,PEMLRandNOEMPare restored from the sha-pinned Census person files for every pinned income year (2022 to 2025), by exactPERIDNUMjoin. The stage refuses a frame that breaks the Census universe relations.Full method, limits and sources:
docs/us-esi-employer-premiums.md.The feed re-pin
d1317 asked for the US Chronicle feed to carry the banked B4923C fact (PolicyEngine/chronicle#331). The pin moves from
51aa40ftocf9ece2:51aa40fplus the digit-text guard match from Chronicle main'saa3e4de3and the B4923C package as #331 merged it (branchfeed/us-51aa40f-esi-b4923c). Built there:deferredreviewed exclusion, not a target (calibration is d1264);us_esi_premiums_anchor_feed_mismatches). A diagnostic run under--allow-unpinned-feedor--allow-esi-premium-gapscontinues and records the difference inesi_premiums.json.Scripts and reports:
experiments/us-esi-454/feed-repin/; procedure:docs/us-chronicle-feed-repin.md.#1192 also re-pins the feed (from the older
c5e5bf8base) and conflicts with main. Whichever lands second re-pins onto a Chronicle base that holds both changes, carries the three B4923C scope pairs, and redoes the row comparison. Until then the feed check fails safe: a feed without B4923C is refused.Measured on the pinned pools (target year 2024, before calibration)
Both gates pass on each pool, before and after the PUF-support clone. Receipts:
experiments/us-esi-454/receipts/.Readings not taken
Invariants
Each is a property test over generated populations (Hypothesis, 120 examples), in
tests/engine_free/us/test_us_esi_premiums.py:cscales each value by1/cand leaves the totals unchanged.One more identity is held by example tests and a release check, not a property test: the stage's pinned B4923C values equal the feed's banked facts. A test checks them against the committed re-pin report, and the release tool checks the feed it compiles from.
Gates
us_esi_premiums_signal_gate: base build (before and after the clone) and release tool (base and calibrated export). Red on an absent, constant, negative or non-finite column, a premium outside the universe, a value that is not the common cell multiple, clone disagreement, or a missing raw column.us_esi_premiums_anchor_gate: release tool, on the calibrated export, dense and sparse. Recomputes the anchor-universe total at release weights and requires it within 5% of BEA NIPA 7.8 line 17, with employed policyholders carrying 80% to 95% of it. Records the NHE Table 24 and MEPS-IC cross-checks (NHE is 7.2% above BEA for 2024, so a release scaled to NHE would fail).requiredin the release input-coverage manifest, with a reform-coverage probe (neutralizing it must lowercbo_household_market_incomeby at least $500B).esi_premiums.jsonis required release evidence and carries the anchor verdict, both signal verdicts and the anchor-versus-feed verdict.tools/export_us_l0_refit_h5.py) refuses a frame whose column is absent or constant. It runs neither ESI gate.Limits, stated in the doc: the raw-mean band catches unit errors in the cell table, not a level error inside the band; the 5% tolerance is a chosen criterion, since no release has calibrated this column yet.
What this does not do
Test plan
engine-freeandengine-usCI jobs greentests/engine_free/us/test_us_esi_premiums.pyand the feed, register, release-tool and spec-engine tests locallytests/engine_contract/us/test_us_esi_premiums_engine.pyin policyengine-us 2.2.1tools/build_us_meps_ic_esi_cells.py --checkagainst the pinned AHRQ PDFscf9ece2(twice, same digest), compared row by row and target by target with the previous feed🤖 Generated with Claude Code