Skip to content

Add KFF eligible-uninsured moments and Medicaid 100% take-up reform rows - #69

Open
MaxGhenis wants to merge 14 commits into
mainfrom
us/kff-medicaid
Open

Add KFF eligible-uninsured moments and Medicaid 100% take-up reform rows#69
MaxGhenis wants to merge 14 commits into
mainfrom
us/kff-medicaid

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Summary

  • Add KFF's 2024 state-indicator shares and 2022 flagship-brief counts for nonelderly uninsured people eligible for Medicaid or CHIP to the additive moments pipeline.
  • Compute 2024 PolicyEngine counterparts on the certified Populace Build P bundle, retaining reported-uninsured as the primary construct and modeled-uninsured as an annotated alternative.
  • Add a policyengine_us_inputs ReformRef route and stage the reform that forces annual Medicaid take-up to 100%, including enrollment, spending, and marginal-enrollee bridge rows for the US and every state.
  • Preserve the descriptive Scorecard register: KFF joins are different_model, held_out, and either concept_mismatch for 2024 or not_computed with a dated 2024 reference for the 2022 claims.

Results

Measure Result
Baseline Medicaid eligible, all ages 77.325M
Baseline Medicaid enrolled, all ages 72.336M
Baseline Medicaid spending $880.019B
KFF 2024 US eligible share among remaining uninsured 24.9%
PE reported-uninsured eligible share, under 65 43.15% (34.921M / 80.932M)
PE modeled-uninsured alternative, under 65 5.71% (2.785M / 48.796M)
Enrollment change at 100% take-up +4.989M
Medicaid spending change at 100% take-up +$71.527B
Marginal reported-uninsured enrollees 2.792M (56.0%)
Marginal enrollees with other coverage 2.196M (44.0%)

The national eligible and enrolled estimates differ from the lane anchors by 0.03% and 0.05%, respectively. Enrollment change equals eligible minus enrolled, and the two bridge components sum to the enrollment change within floating-point precision.

The three largest absolute state differences in the primary eligible-share comparison are California (+31.99 percentage points PE minus KFF), Connecticut (+31.74), and Maryland (+28.25). These rows carry named construct differences rather than accuracy labels.

Construct annotations

  • KFF applies MAGI Medicaid pathways and includes CHIP for children. PE includes SSI-linked, medically needy, optional senior/disabled, and other Medicaid pathways, while this construction does not add standalone CHIP.
  • The primary PE uninsured definition is none of has_esi, Marketplace-at-interview, non-Marketplace direct-purchase-at-interview, Medicaid-at-interview, TRICARE, VA, CHAMPVA, other-means-tested, or IHS coverage, plus not modeled medicare_enrolled. The under-65 restriction is applied during moment aggregation.
  • The modeled alternative additionally removes medicaid_enrolled, including anchor-and-fill enrollees presumed to have survey underreporting. KFF states no analogous underreporting correction.
  • KFF uses ACS records and its own immigration and ESI-offer imputations. PE uses CPS-ASEC-based Populace; the certified artifact lacks years_since_us_entry, so the engine's five-year-bar leg uses that variable's default.
  • KFF applies 2025 eligibility levels to the 2024 state indicator and 2023 levels to the 2022 brief. PE applies 2024 law to the single-period 2024 artifact.
  • The reform bridge is all ages and all PE Medicaid pathways, while the KFF population is nonelderly MAGI Medicaid plus CHIP.
  • Reform spending holds the baseline Medicaid state-allocation denominator fixed, matching policyengine-us baseline-branch semantics while the baseline and reform simulations run in separate processes.

Staging and ingest

  • Regenerated app/public/data/moments.json: 603 total rows, including 52 KFF concept_mismatch rows and five KFF not_computed rows with 2024 references.
  • Committed US/state diagnostic CSVs and 214 PE counterpart rows under data/pe/.
  • Staged 208 reform rows under sources/campaign-20260818/us/, covering four components across the US plus 50 states and DC.
  • Ingested those rows into data/scorecard.db as 208 exhibits under canonical ReformRef key 849973669b6526d6; offline re-ingest is idempotent.
  • Person-level baseline and reform extracts remain outside the repository under /private/tmp.

Ready-to-run ingest command:

PYTHONPATH=. .venv-pe/bin/python -m scorecard_db.ingest_campaign data/scorecard.db sources/campaign-20260818/us

Validation

  • Required deterministic 5,000-household sample first; the bounded-memory eligibility path matched the ordinary native engine output exactly across all 14,254 person rows and every extracted column.
  • Full baseline and reform ran in separate OS processes through pe.us.managed_microsimulation() on policyengine-us 1.764.6 and certified bundle populace-us-2024-buildp-sparse-rmloss100-cae8640-20260728T011454Z (us-5.0.2).
  • uv run --offline --no-project --active pytest tests/ -q: 179 successful tests.
  • Repository-wide Ruff lint and formatting completed successfully (51 Python files).
  • The app lint/build was not run because this worktree has no installed app/node_modules; network access was prohibited, so no dependency install was attempted.

Review findings (main session)

A post-lane probe of the certified artifact's person inputs found the
reported-uninsured denominator is not survey-faithful: 80.9M people under 65
carry none of the nine reported-coverage inputs, versus roughly 27M
ACS-reported nonelderly uninsured in 2024. Employer (146.9M under 65) and
Marketplace (21.4M) inputs sit near survey levels, but the reported
Medicaid-at-interview input sums to 24.6M under 65 against roughly 60M
survey-reported Medicaid — the sparse build thins it, and CMS-calibrated
modeled enrollment does not backfill the reported flag. Consequences, now
annotated on the joined rows:

  • The primary 43.15% reported-uninsured share should be read with that
    denominator context; the modeled-uninsured variant (5.71%) and the
    eligible-and-not-enrolled counts (2.79M uninsured marginal enrollees) are the
    informative PE rows for this join.
  • The large state deltas (CA +31.99pp, CT +31.74pp, MD +28.25pp) combine this
    denominator effect with a genuine construct difference: PE's eligibility
    formula includes state coverage regardless of immigration status (e.g.
    CA's is_ca_medicaid_immigration_status_eligible leg), while KFF's ACA-lens
    categories class those people as ineligible due to immigration status.
  • Artifact-side follow-up to file in microcosm: reported-coverage input
    thinning in the sparse build (adjacent to the untargeted-input zeroing class,
    microcosm#361).

Follow-ups

  • Re-verify the rendered KFF table's first-column label against KFF's downloadable CSV before narrowing Medicaid/Other Public to a more specific metric label.
  • Complete the Medicaid/CHIP concept split tracked in microcosm#321, then add a CHIP-inclusive PE child counterpart.
  • Add a 2025-rules PE variant when the certified artifact supports that policy period.

🤖 Generated with Claude Code

MaxGhenis and others added 13 commits August 18, 2026 15:22
…ct probe

The reported Medicaid-at-interview input sums to 24.6M under-65 on the sparse
Build P artifact versus roughly 60M survey-reported Medicaid, leaving an 80.9M
reported-uninsured denominator against roughly 27M ACS-reported nonelderly
uninsured. Named on the joined rows so the 43.15% primary share reads with
that context; modeled-uninsured and eligible-not-enrolled rows flagged as the
informative counterparts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI runs pytest without pandas; the builder now reads extracts with
csv/gzip and computes masks as boolean lists, matching the other
pipeline modules. Rerunning it on the full-file extracts reproduces all
208 committed staged rows (worst relative value difference 1.7e-9, every
non-numeric field identical).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolves the three blockers from the merge audit:
- Blank or non-finite values in age/person_weight/medicaid now raise at
  parse (the denominator column keeps NaN, used only in the paired
  retention check); identity/anchor/bridge gates use NaN-proof inverted
  comparisons; JSON writes set allow_nan=False.
- The full path requires the exact certified provenance triple via a
  shared require_certified_provenance (engine 1.764.6 / us-5.0.2 /
  Build P id) instead of trusting any self-consistent sidecar; the
  extractor calls the same pin.
- The extractor validates provenance before any computation or write,
  and publishes CSV + metadata sidecar together via temp-and-rename, so
  a rejected rerun can no longer leave a fresh CSV beside a stale
  certified sidecar.

New tests cover blank-cell rejection, denominator NaN tolerance, the
anchor-passing full path, and rejection of a wrong-but-self-consistent
triple (179 -> 183). Committed artifacts are unaffected: they are all
finite, and under the previous lenient parser any blank in a strict
column would have propagated NaN into them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — KFF eligible-uninsured moments + 100% take-up rows

Ran the branch locally (179 passed / 4 skipped, ruff clean, CI green) and verified the external side against the live source:

Verified:

  • KFF values re-fetched from the live indicator page: US 24.9 / AL 9.5 / CA 32.5 / AK 40.8 / MD 23.3 all match raw/state_indicator_2024acs_2025levels.md exactly, vintage confirmed (2024 ACS, 2025 eligibility levels). Bonus: the page's own column header reads "Medicaid/Other Public Eligible" — matching the staged STATE_SOURCE_COLUMN, so the kff-column-label-verbatim caution (annotations.json:47, source.json diagnosis_upstream) can be retired.
  • Take-up vocabulary is the engine's real input: takes_up_medicaid_if_eligible (compute_kff_medicaid_extract.py:30) is in compute_counterparts.py's proven fulltakeup set (line 60), not a guessed name.
  • Arithmetic identities hold: 77.325 − 72.336 = 4.989M enrollment change; bridge 2.792 + 2.196 = 4.988 ≈ Δ within fp.
  • reform_ref exhibit route is fail-loud: mutual exclusion with external_claim_match, framework pin, reform_key cross-check all raise (ingest_campaign.py:176-199); idempotency stays exact-run_id, no prefix sweep.
  • The 80.9M reported-uninsured denominator anomaly is measured and honestly diagnosed (annotations.json kff-medicaid-reported-denominator-sanity: thinned Medicaid-at-interview input, 24.6M vs ~60M survey).

Should consider:

  1. Primary/alternative designation contradicts the lane's own sanity annotation. The denominator annotation concludes the reported-uninsured share "is not a survey-faithful uninsured measure on this artifact" and names the modeled variant + eligible-and-not-enrolled counts as "the informative PolicyEngine rows for this join" — yet reported-uninsured (43.15%) is the primary construct and modeled (5.71%) the annotated alternative. A consumer surfacing primary rows shows exactly the comparison the lane says is uninformative. Either swap primacy, or carry the annotation's verdict onto the primary rows' display status so the 43.15%-vs-24.9% pairing can't render unqualified.
  2. Minor: meta["note"] (ingest_campaign.py:205,240) raises a bare KeyError on a note-less exhibit row rather than the labeled ValueError every sibling contract violation gets.

Solid lane overall — the construct-difference annotations are the most complete of any source in the repo.

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.

2 participants