Reviewed CMS Medicaid enrollment substitution register for the take-up gate (#386)#387
Merged
Conversation
Apply the reviewed CMS Medicaid enrollment substitutions once after the target-registry compile, before the dense/sparse split, so the injected RI spec flows through target_specs into the materialized calibration registry that both the dense (calibrate) and sparse (calibrate_l0_refit) arms consume, into the take-up target table, and into the build manifest. Thread the substitution records into _with_medicaid_take_up_outputs -> with_us_medicaid_take_up so they ride the take-up diagnostics: the gate rot-checks a backfilled (stale) substitution and the us_medicaid_take_up release artifact records which states shipped a substituted point-in-time count. Surface the applied records in build_manifest.json alongside the target-registry version that carries the injected spec's provenance metadata, so release artifacts show the substitution explicitly. Refs #386, #334, #368. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cover the four register behaviours against faithful CMS medicaid_enrollment state specs: - pass-with-substitution: a registry missing RI fails the take-up gate closed on FIPS 44 (the Build J failure); applying the register injects RI's nearest-prior-month count (November 2024 = 273,400), RI gains a target, and the same assignment passes. - fail-without: a genuinely-missing state with no register entry still fails the gate, so the register cannot become a silent blanket bypass. - cannot-rot: when a backfilled RI December-2024 fact appears, the register marks the entry stale, injects nothing, and the gate fails so the substitution cannot outlive its justification (#286 doctrine). - metadata-reaches-compiled-spec: the injected spec carries the full substitution provenance, which survives compilation into the calibration TargetSet the solver and diagnostics target surface consume. Also pins the register's single reviewed entry (RI 2024-11 = 273,400, populace#386) against silent drift. Refs #386, #334, #368. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…caid family (#386) The end-to-end main() ordering test drives the release tool with a synthetic registry that carries no medicaid_enrollment state specs; the register raised "no ... spec to derive the substituted target's calibration shape from" and broke main() for any target surface without the CMS family (the CI failure on all four jobs). A registry with no medicaid_enrollment state family has nothing for the register to stand in: record every entry applied=False, stale=False and inject nothing, instead of raising. Medicaid-absence is owned by the take-up stage's own empty-target guard, not the register. Covered by test_registry_without_medicaid_family_is_a_no_op, and the main() ordering test's _with_medicaid_take_up_outputs stub now mirrors the substitutions kwarg. Refs #386, #334, #368. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For the Medicaid stage owner's review
This implements the conductor recommendation on #386: a reviewed-substitution register at the Medicaid take-up gate, rather than relaxing the gate. It is deliberately staged for the Medicaid stage owner's review before merge — the substitution touches what the release calibrates state Medicaid enrollment to.
The problem (#386)
The #334 Medicaid take-up gate fails closed on the first full-release run (#368 Build J): Rhode Island's December 2024 CMS snapshot is unreported at source. Both RI 202412 rows in the pinned
pi-dataset-april-2026-release.csvcarryTotal Medicaid Enrollment = 0under the footnote "Unable to Provide Data due to System Limitations", so the point-in-time snapshot compiles to no FIPS-44medicaid_enrollmentspec and the count-calibration gate fails onstates without CMS enrollment targets: ['44'].The fix
A reviewed-substitution register maps RI to its nearest-prior reported month. No Ledger fact is synthesized — the substitution lives at the registry-compile/gate layer, where it is auditable per release:
cms_medicaid.month2024_11.state_enrollment.ri.total_medicaid_enrollmentVerified against the pinned ledger source: the RI 202411 final (U/Y) row reports
Total Medicaid Enrollment = 273400with no footnote (and 273,400 Medicaid + 32,761 CHIP = 306,161 total, internally consistent), while RI 202412 is the footnoted 0.The register injects a
medicaid_enrollmentTargetSpecfor RI, cloning an existing state spec's calibration/materialization shape and re-pointing its identity and value, stamping the substitution provenance (the CMS footnote, the substituted-for month, the tracking issue) into the spec metadata.Wiring
target_specsinto the materialized calibration registry that both the dense (calibrate) and sparse (calibrate_l0_refit) arms consume, and into the take-up target table.with_us_medicaid_take_upinto the take-up diagnostics and theus_medicaid_take_up.jsonrelease artifact.build_manifest.jsonalongside the target-registry version that carries the injected spec's provenance metadata, and the metadata survives compilation into the calibrationTargetSet(the diagnostics target surface). So release artifacts show exactly which point-in-time counts were substituted, and why.Cannot-rot semantics
The register cannot outlive its justification. A substitution applies only while its substituted-for month is genuinely absent. The moment CMS backfills a real RI December-2024 count, the natural FIPS-44 spec reappears, the register marks the entry stale and injects nothing, and the take-up gate fails — forcing the entry's removal (the #286 stale-exclusion doctrine, applied to a substitution register).
Alternatives considered
Tests
packages/populace-build/tests/test_us_medicaid_take_up.py::TestReviewedSubstitutionRegister:test_pass_with_substitution— the gate fails closed on FIPS 44 without the register and passes once RI gains its substituted target.test_fails_without_substitution_on_a_genuinely_missing_state— a genuinely-missing state with no register entry still fails, so the register is not a blanket bypass.test_cannot_rot_fails_when_the_backfilled_ri_fact_appears— a backfilled RI December fact marks the entry stale, injects nothing, and fails the gate.test_substitution_metadata_reaches_the_compiled_spec— the provenance survives into the compiledTargetSetthe solver and diagnostics consume.test_register_pins_rhode_island_to_the_ledger_value— pins the single reviewed entry against silent drift.test_registry_without_medicaid_family_is_a_no_op— a registry with nomedicaid_enrollmentfamily at all records every entry inapplicable and injects nothing (the register is not the owner of Medicaid-absence; the take-up stage's empty-target guard is).uv run pytestis clean — 1,622 passed / 54 skipped in a CI-identical venv (nousextra), and 37 passed intest_us_medicaid_take_up.pywithpolicyengine-usinstalled — andruff check/ruff format --checkare clean on the changed files.Refs #386, #334, #368.
🤖 Generated with Claude Code