Skip to content

Add BEA NIPA Table 7.8 line 17 (B4923C): employer contributions for private group health insurance - #331

Open
MaxGhenis wants to merge 2 commits into
mainfrom
bea-nipa-private-group-health-insurance
Open

MaxGhenis wants to merge 2 commits into
mainfrom
bea-nipa-private-group-health-insurance

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds BEA NIPA Table 7.8 line 17, series B4923C, "Private group health insurance": employer contributions for private group health insurance, a supplement to wages and salaries. The package emits one fact per calendar year for 2023, 2024 and 2025.

Calendar year Published (millions) Fact value
2023 923,195 $923,195,000,000
2024 977,034 $977,034,000,000
2025 1,027,929 $1,027,929,000,000

The series is not an active-employee amount. BEA's State Personal Income and Employment: Concepts and Methods (December 2025), paragraphs 3.18 and 3.19, says the national estimates of employer contributions for group health insurance plans rest mainly on Medical Expenditure Panel Survey data covering insurance purchased by employers "for their active and retired employees" and self-insured plans, and, for "active and retired federal civilian employees", on Federal Employee Health Benefits data. Each fact carries those quotations, and the reading drawn from them marked as Chronicle's own, as its concept evidence (concept_alignment.evidence_notes and evidence_url).

How the series is tied to that text: BEA's series register lists B4923C, "Group health insurance", at T61100D:32|T70800:17. The NIPA Handbook (chapter 10, Table 10.B, line 32) names the same MEPS and OPM sources and does not say whether retired employees are covered. Table E of the state methodology gives 1,011,360 for group health in 2024, which matches neither NIPA vintage here. The other Table E lines I checked (compensation, supplements, employer contributions for pension and insurance funds, group life, workers' compensation) are not in the pinned NIPA flat file at 2024 either, so the gap is not particular to group health. The files support retiree coverage as a category and no numerical equivalence.

Consumer context: PolicyEngine/microcosm#454. An earlier draft of that work, and the first version of this PR's description, read B4923C as an active-worker series and planned to calibrate employer_sponsored_insurance_premiums to it. That reading was wrong, and no calibration target or feed re-pin follows from this PR. The fact is banked as a national-accounts cross-check, with the 2024 revision on the record. Facts are inert until a consumer re-pins its feed.

Source

  • File: https://apps.bea.gov/national/Release/XLS/Survey/Section7All_xls.xlsx, linked as "Section 7 Supplemental Tables" on BEA's NIPA file-download page (source_page in the manifest).
  • Fetched 2026-10-10T17:56:28Z with chronicle fetch-artifact: 944,273 bytes, SHA-256 de1c34e37da9b8b8d765efc0611cc653102373fe522fe9218c7689067b29b7fd, Last-Modified: Wed, 30 Sep 2026 12:30:04 GMT.
  • Sheet T70800-A reads "Data published September 30, 2026" and "[Millions of dollars]".
  • Published to ledger-raw with chronicle publish-raw at the manifest's content-addressed key, read back and hash-verified. Mirrored to chronicle-raw at the same key, as the pinned BEA flat file is, and read back and hash-verified there too.
  • The committed blob hashes to the publisher digest.

The two pinned BEA files differ at 2024. The flat file the other bea_nipa packages pin (fetched 11 May 2026) has B4923C at 1,002,920 for 2024; the workbook, published with BEA's 2026 annual update on 30 September 2026, has 977,034. BEA's Section 6 workbook of the same date carries the same series as Table 6.11D line 32, "Group health insurance", with the same three values (checked, not packaged).

What changes

  • db/data/bea/nipa_private_group_health_insurance/: the workbook and its manifest.
  • packages/bea/nipa_private_group_health_insurance/source_package.yaml: one record set, bea_nipa.cy{year}.private_group_health_insurance.
    • xlsx_used_range restricted to sheet T70800-A (the workbook has 46 sheets), artifact_year: 2025.
    • One row (26, line 17) and one measure, amount, with column_by_year CA/CB/CC and a header guard per year. Ids follow the bea_nipa convention: bea_nipa.cy2024.private_group_health_insurance.b4923c.amount.
    • Entity, role, domain and provenance class match the wages package: person / employee / compensation_of_employees / model_output. Concept bea_nipa.private_group_health_insurance, relation source_label, authority bea, with the coverage statement above as concept evidence.
    • Eleven guard cells: table title, unit banner, publication date, the line-number column header, line number 17, the line stub, the series code, the parent "Health insurance" line (label and code A2210C), the grandparent code B040RC, and footnote 3.
    • A cell-parsed package has no source row to take the series label from, so dimension_value_labels declares B4923C as "Private group health insurance", the guarded line stub without its indentation and footnote marker.
  • The alias bea-nipa-private-group-health-insurance is registered in SOURCE_PACKAGE_ALIASES. It is not added to the hard-target coverage contract (chronicle/targets/us_poverty.py), because no consumer targets it.

No existing package, schema or contract changes.

Chronicle governance

  • Approved agent role: ledger-source-ingestor. As in Add IRS SOI Historic Table 2 TY2023 state AGI-band facts #291, Add HMRC CGT Table 4 BADR and Investors’ Relief facts #288 and Add HMRC CGT Tables 7–9 source packages #273, the change also touches db/data and the alias map, which the role file does not list.
  • Deterministic checks, at 767c89e6 (the package files are unchanged in the merge head 08c229e9):
    • chronicle validate-package bea-nipa-private-group-health-insurance --year {2023,2024,2025}: valid at each year.
    • chronicle build-suite ... --year {2023,2024,2025}: valid at each year, with 1 fact, 3,483 source cells, lineage coverage 1.0 and 0 agent-acceptance errors.
    • chronicle build-bundle --year 2023 --source bea-nipa-private-group-health-insurance: valid, with 1 fact, no warnings and no duplicate keys.
    • Independent read: openpyxl on the publisher file gives row 26 as line 17, Private group health insurance\3\, B4923C, and 923195 / 977034 / 1027929 under the 2023 / 2024 / 2025 headers.
  • LLM judge verdicts, from an independent GPT-6.1 Sol review (subfleet, hard tier, read-only) of aa943a9f, summarised in a comment:
    • ledger-source-fidelity: PASS. It re-read the workbook with openpyxl and from the sheet XML, re-hashed it, and repeated the 76-year comparison with the flat file.
    • ledger-contract: PASS. No schema or identity change.
    • ledger-boundary: PASS. Source selection and unit normalisation only; the flat-file comparison is a test, and a supported coverage note is within remit, as in the CBO and CMS packages.
    • Overall: approve with three nits, all fixed in 767c89e6: one quotation's capitalisation, a test comment that attributed the scope inference to BEA, and wording that dated the older flat file more precisely than its files establish.
    • A second review of the aa943a9f to 767c89e6 delta returned approve for 767c89e6 as a whole, with all three verdicts PASS and no findings. Both are summarised in comments.

Invariants (tested in tests/test_chronicle_bea_private_group_health_insurance.py)

  1. One cell per fact. For each year the fact equals the published cell times 1,000,000, is an integer, and its lineage is the value cell, that column's header cell and the eleven guard cells.
  2. Year selects the column. A build at 2022 or 2026 is refused ("No source artifact for year"), and the artifact-year restamp guard reports no issue at any year.
  3. Guards bind. Moving the row to line 16 or line 18, or reading the 2023 column under the 2024 label, is refused. Each of the eleven guard cells fails the build when its expectation is changed.
  4. Differential against a second BEA publication. Row 26 of the workbook equals series B4923C in the earlier pinned flat file for every year from 1948 to 2023 (76 cells). The only differences are 2024 (1,002,920 against 977,034) and 2025 (absent from the flat file).
  5. Determinism. Two builds write equal consumer rows, with three distinct aggregate and semantic fact keys under one record_set_spec_id.
  6. Coverage travels with the fact. Every consumer row's concept evidence carries BEA's two quotations on active and retired employees and Chronicle's reading that the series is not an active-employee amount.

Tests

  • New tests/test_chronicle_bea_private_group_health_insurance.py (28 tests).
  • tests/test_chronicle_bundle.py: default-bundle pins moved by the one CY2023 fact the bundle year emits. Against main at 244a1281: facts 1,002,783 to 1,002,784, packages 321 to 322, bea 445 to 446, source tables 316 to 317, calendar_year:2023, country:0100000US and person each +1. I derived the increments from the single-source bundle above and did not run the full default bundle locally.
  • tests/test_chronicle_artifact_year_restamp.py: the package joins the representative pinned packages built on real bytes.
  • At the first head (042d5ffd, same selectors and guards, before the concept evidence was added): 406 passed over the new file plus test_chronicle_source_package.py, test_chronicle_consumer.py, test_chronicle_suite.py, test_chronicle_artifact_year_restamp.py, test_chronicle_pe_source_plan.py, test_source_files.py, test_chronicle_offline_fetch.py, test_issue_261_dimension_labels.py, test_issue_265_non_uk_labels.py, test_ots_anchor.py and test_ots_anchor_workflow.py.
  • At 767c89e6, and again at the merge head 08c229e9: 131 passed over the new file plus test_source_package_alias_drift.py, test_us_poverty_target_coverage.py, test_chronicle_governance.py, test_chronicle_boundaries.py, test_chronicle_facts_only.py, test_chronicle_artifact_year_restamp.py and test_chronicle_concepts.py.
  • ruff check chronicle policyengine_chronicle db scripts tests passes.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis force-pushed the bea-nipa-private-group-health-insurance branch from 042d5ff to aa943a9 Compare October 10, 2026 18:21
…oup health insurance

New source package bea-nipa-private-group-health-insurance reads series
B4923C from the Section 7 workbook BEA published with its 2026 annual
update on 30 September 2026 (Section7All_xls.xlsx, sha256 de1c34e3...), for
calendar years 2023 to 2025: $923,195M, $977,034M and $1,027,929M. The
flat file the other bea_nipa packages pin (fetched 11 May 2026) has
$1,002,920M for 2024.

The series is not an active-employee amount. BEA's State Personal Income
methodology (December 2025, paragraphs 3.18-3.19) says the national
estimates rest on survey data covering insurance purchased by employers for
their active and retired employees, and on Federal Employee Health Benefits
data for active and retired federal civilian employees. Each fact carries
those quotations and that reading as its concept evidence, so a consumer
cannot take the series for contributions for current employees only
(PolicyEngine/microcosm#454).

The package selects the year's own column (column_by_year) and guards the
table title, unit banner, publication date, line number, line label, series
code, both parent lines and footnote 3. A year without a declared column is
refused. The row agrees with the pinned flat file for every year from 1948
to 2023.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the bea-nipa-private-group-health-insurance branch from aa943a9 to 767c89e Compare October 10, 2026 18:37
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Independent review (GPT-6.1 Sol, subfleet hard tier, read-only) of aa943a9f

Verdict: approve with nits. ledger-source-fidelity PASS, ledger-contract PASS, ledger-boundary PASS. No blocker and no should-fix finding.

What the reviewer established by execution:

  • Workbook: 944,273 bytes, SHA-256 de1c34e3…b7fd, 46 sheets; T70800-A is 43 rows by 81 columns. Read with openpyxl and from the sheet XML: A1, A2 and A5 as guarded; row 26 is line 17, Private group health insurance\3\, B4923C; CA/CB/CC are 2023/2024/2025 with 923,195 / 977,034 / 1,027,929 as integer literals, no formulas.
  • The 76-year comparison with the pinned flat file (1948 to 2023 equal; 2024 and 2025 the only differences).
  • Builds at 2022 and 2026 raise No source artifact for year …. Substituting the quarterly sheet or another table's sheet fails.
  • 14 of the file's tests ran in its sandbox and passed; the rest write temporary files, which the read-only sandbox blocked. It re-ran those 14 mutations in memory and all were refused.

On coverage, the point of most risk: it re-extracted BEA's state methodology from the PDF, confirmed paragraphs 3.18 and 3.19, and found the mapping to B4923C supported by BEA's series register (T61100D:32|T70800:17) and by the NIPA Handbook's line 32 naming the same sources. The Handbook does not address retirees. Table E's 1,011,360 for 2024 matches neither NIPA vintage; the files do not identify why, so they support retiree coverage as a category and no numerical equivalence. It judged person / employee defensible, as for the parent series B040RC, given the note.

Nits, all fixed in 767c89e6:

  1. A test comment said the scope inference was "in BEA's words". It now says the note carries BEA's quotations and Chronicle's inference, and the note itself labels the inference "Chronicle's reading".
  2. The second quotation began with a lower-case "for" where BEA's sentence starts "For". The quotation is now the exact substring "active and retired federal civilian employees".
  3. "One vintage earlier" and the 30 April 2026 release date for the older flat file were more specific than the files establish. The wording now cites its manifest's fetch date, 11 May 2026.

I also took its suggestion to put the series-register and Handbook bridge into the evidence note, after fetching the register myself and finding B4923C,"Group health insurance",…,T61100D:32|T70800:17.

It could not verify the full default bundle, the R2 objects or the live file from its sandbox. I verified the last two (see the description); CI runs the first.

A confirmation review of the aa943a9f to 767c89e6 delta is running; I will post its verdict here.

Full text: ~/reviews/esi-b4923c-2026-10/chronicle-331-review-r2.md on the build machine.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Confirmation review of aa943a9f to 767c89e6 (GPT-6.1 Sol, subfleet hard tier, read-only)

Verdict: approve, for commit 767c89e6 as a whole. ledger-source-fidelity PASS, ledger-contract PASS, ledger-boundary PASS. No findings.

  • The saved delta matches Git's tree diff; only the package YAML and its test file changed. Parsed YAML outside the evidence note is identical, so selectors, guards, values, ids, other metadata, the bundle-count edits and the alias map are as first reviewed.
  • All three nits are resolved. Both quotations match BEA's paragraphs 3.18 and 3.19 exactly after normalising whitespace; the test comment separates BEA's words from Chronicle's inference; no wording dates the older flat file beyond its manifest's 11 May 2026 fetch.
  • The added bridge is supported: the series register entry (B4923C, "Group health insurance", T61100D:32|T70800:17), cell T70800-A!B26, and Handbook Table 10.B line 32, which names the MEPS and OPM sources without saying active or retired.
  • One limit it noted, which I am leaving as is: the note assertions check that the quotations and the attribution reach consumers. They would still pass if the note's table line or its Handbook sentence were edited to something false. Those two sentences were verified against the saved BEA files in this review, not by a committed test, because the BEA documents are not in the repository.

In its read-only sandbox 14 of the 28 tests ran and passed; the other 14 need tmp_path. All 28 pass locally at 767c89e6.

Full text: ~/reviews/esi-b4923c-2026-10/chronicle-331-review-r3.md on the build machine.

chronicle#328 (IRD wages) and chronicle#329 (Stats NZ household net worth)
landed on main and moved the default-bundle pins this branch also moves.
Each conflict in tests/test_chronicle_bundle.py is resolved as main's new
value plus this branch's one CY2023 BEA fact: facts 1,002,783 to 1,002,784,
packages 321 to 322, source tables 316 to 317 and person facts 496,035 to
496,036. Both sides' new assertions are kept.

Co-Authored-By: Claude Opus 5.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.

1 participant