Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1162.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add CHIP enrollment calibration targets and CPS CHIP take-up anchoring.
7 changes: 7 additions & 0 deletions policyengine_us_data/build_outputs/us_augmentations.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,13 @@ def _build_reported_takeup_anchors(
reported_anchors["takes_up_medicaid_if_eligible"] = data[
"has_medicaid_health_coverage_at_interview"
][time_period].astype(bool)
if (
"reported_has_chip_health_coverage_at_interview" in data
and time_period in data["reported_has_chip_health_coverage_at_interview"]
):
reported_anchors["takes_up_chip_if_eligible"] = data[
"reported_has_chip_health_coverage_at_interview"
][time_period].astype(bool)
if (
"receives_housing_assistance" in data
and time_period in data["receives_housing_assistance"]
Expand Down
7 changes: 7 additions & 0 deletions policyengine_us_data/calibration/entity_clone.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ def _build_reported_takeup_anchors(data: dict, time_period: int) -> dict:
reported_anchors["takes_up_medicaid_if_eligible"] = data[
"has_medicaid_health_coverage_at_interview"
][time_period].astype(bool)
if (
"reported_has_chip_health_coverage_at_interview" in data
and time_period in data["reported_has_chip_health_coverage_at_interview"]
):
reported_anchors["takes_up_chip_if_eligible"] = data[
"reported_has_chip_health_coverage_at_interview"
][time_period].astype(bool)
if (
"receives_housing_assistance" in data
and time_period in data["receives_housing_assistance"]
Expand Down
6 changes: 6 additions & 0 deletions policyengine_us_data/calibration/target_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ include:
- variable: person_count
geo_level: national
domain_variable: medicaid_enrolled
- variable: person_count
geo_level: state
domain_variable: chip_enrolled
- variable: person_count
geo_level: national
domain_variable: chip_enrolled
# REMOVED: is_pregnant — 100% unachievable across all 51 state geos
- variable: snap
geo_level: state
Expand Down
8 changes: 8 additions & 0 deletions policyengine_us_data/calibration/unified_matrix_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2800,6 +2800,14 @@ def build_matrix(
reported_takeup_anchors["takes_up_medicaid_if_eligible"] = f[
"has_medicaid_health_coverage_at_interview"
][period_key][...].astype(bool)
if (
"reported_has_chip_health_coverage_at_interview" in f
and period_key
in f["reported_has_chip_health_coverage_at_interview"]
):
reported_takeup_anchors["takes_up_chip_if_eligible"] = f[
"reported_has_chip_health_coverage_at_interview"
][period_key][...].astype(bool)
if (
"receives_housing_assistance" in f
and period_key in f["receives_housing_assistance"]
Expand Down
9 changes: 9 additions & 0 deletions policyengine_us_data/datasets/cps/cps.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ def add_takeup(self):
snap_rate = load_take_up_rate("snap", self.time_period)
aca_rate = load_take_up_rate("aca", self.time_period)
medicaid_rates_by_state = load_take_up_rate("medicaid", self.time_period)
chip_rate = load_take_up_rate("chip", self.time_period)
head_start_rate = load_take_up_rate("head_start", self.time_period)
early_head_start_rate = load_take_up_rate("early_head_start", self.time_period)
ssi_rate = load_take_up_rate("ssi", self.time_period)
Expand Down Expand Up @@ -637,6 +638,14 @@ def add_takeup(self):
group_keys=person_states,
)

# CHIP: preserve current full-takeup default while anchoring CPS reporters.
rng = seeded_rng("takes_up_chip_if_eligible")
data["takes_up_chip_if_eligible"] = assign_takeup_with_reported_anchors(
rng.random(n_persons),
chip_rate,
reported_mask=data["reported_has_chip_health_coverage_at_interview"],
)

# Head Start
rng = seeded_rng("takes_up_head_start_if_eligible")
data["takes_up_head_start_if_eligible"] = rng.random(n_persons) < head_start_rate
Expand Down
123 changes: 121 additions & 2 deletions policyengine_us_data/db/etl_medicaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,61 @@ def transform_administrative_medicaid_data(state_admin_df, year):
return state_df[["ucgid_str", "medicaid_enrollment"]]


def transform_administrative_chip_data(state_admin_df, year):
reporting_period = year * 100 + 12
print(f"Reporting period is {reporting_period}")
state_df = state_admin_df.loc[
(state_admin_df["Reporting Period"] == reporting_period)
& (state_admin_df["Final Report"] == "Y"),
[
"State Abbreviation",
"Reporting Period",
"Total CHIP Enrollment",
],
].copy()

state_df["FIPS"] = state_df["State Abbreviation"].map(STATE_ABBREV_TO_FIPS)

state_df = state_df.rename(columns={"Total CHIP Enrollment": "chip_enrollment"})

problem_states = state_df[state_df["chip_enrollment"].isna()][
"State Abbreviation"
].tolist()

if problem_states:
print(
f"Warning: States with missing CHIP enrollment in {reporting_period}: "
f"{problem_states}"
)
print("Attempting to use most recent non-zero values...")

for state_abbrev in problem_states:
state_history = state_admin_df[
(state_admin_df["State Abbreviation"] == state_abbrev)
& (state_admin_df["Final Report"] == "Y")
& (state_admin_df["Total CHIP Enrollment"] > 0)
& (state_admin_df["Reporting Period"] < reporting_period)
].sort_values("Reporting Period", ascending=False)

if not state_history.empty:
fallback_value = state_history.iloc[0]["Total CHIP Enrollment"]
fallback_period = state_history.iloc[0]["Reporting Period"]
print(
f" {state_abbrev}: Using {fallback_value:,.0f} "
f"from period {fallback_period}"
)
state_df.loc[
state_df["State Abbreviation"] == state_abbrev,
"chip_enrollment",
] = fallback_value
else:
print(f" {state_abbrev}: No historical data found, keeping 0")

state_df["ucgid_str"] = "0400000US" + state_df["FIPS"].astype(str)

return state_df[["ucgid_str", "chip_enrollment"]]


def transform_survey_medicaid_data(cd_survey_df):
cd_df = cd_survey_df[
["GEO_ID", "state", "congressional district", "S2704_C02_006E"]
Expand All @@ -145,7 +200,7 @@ def transform_survey_medicaid_data(cd_survey_df):
return cd_df[["ucgid_str", "medicaid_enrollment"]]


def load_medicaid_data(long_state, long_cd, year):
def load_medicaid_data(long_state, long_cd, year, long_chip_state=None):
DATABASE_URL = f"sqlite:///{STORAGE_FOLDER / 'calibration' / 'policy_data.db'}"
engine = create_engine(DATABASE_URL)

Expand Down Expand Up @@ -175,6 +230,30 @@ def load_medicaid_data(long_state, long_cd, year):
"state": {},
}

if long_chip_state is not None:
nat_chip_stratum = Stratum(
parent_stratum_id=geo_strata["national"],
notes="National CHIP Enrolled",
)
nat_chip_stratum.constraints_rel = [
StratumConstraint(
constraint_variable="chip_enrolled",
operation="==",
value="True",
),
]
nat_chip_stratum.targets_rel.append(
Target(
variable="person_count",
period=year,
value=long_chip_state["chip_enrollment"].sum(),
active=True,
source="CMS CHIP",
)
)
session.add(nat_chip_stratum)
session.flush()

# State -------------------
for _, row in long_state.iterrows():
# Parse the UCGID to get state_fips
Expand Down Expand Up @@ -215,6 +294,40 @@ def load_medicaid_data(long_state, long_cd, year):
session.flush()
medicaid_stratum_lookup["state"][state_fips] = new_stratum.stratum_id

if long_chip_state is not None:
for _, row in long_chip_state.iterrows():
geo_info = parse_ucgid(row["ucgid_str"])
state_fips = geo_info["state_fips"]
parent_stratum_id = geo_strata["state"][state_fips]

new_stratum = Stratum(
parent_stratum_id=parent_stratum_id,
notes=f"State FIPS {state_fips} CHIP Enrolled",
)
new_stratum.constraints_rel = [
StratumConstraint(
constraint_variable="state_fips",
operation="==",
value=str(state_fips),
),
StratumConstraint(
constraint_variable="chip_enrolled",
operation="==",
value="True",
),
]
new_stratum.targets_rel.append(
Target(
variable="person_count",
period=year,
value=row["chip_enrollment"],
active=True,
source="CMS CHIP",
)
)
session.add(new_stratum)
session.flush()

# District -------------------
if long_cd is None:
session.commit()
Expand Down Expand Up @@ -291,9 +404,15 @@ def main():

# Transform -------------------
long_state = transform_administrative_medicaid_data(state_admin_df, year)
long_chip_state = transform_administrative_chip_data(state_admin_df, year)

# Load (state admin only, no CD survey) ---
load_medicaid_data(long_state, long_cd=None, year=year)
load_medicaid_data(
long_state,
long_cd=None,
year=year,
long_chip_state=long_chip_state,
)


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions policyengine_us_data/db/validate_hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,13 @@ def validate_demographic_strata(session):
# because CD-level survey data is disabled pending 119th Congress
# district code remapping (see etl_medicaid.py TODO).
# the national medicaid target actually uses the `medicaid` (expense) variable
# chip_enrolled has national and state targets but no district targets.
expected_counts = {
"age": 18 * 488,
"adjusted_gross_income": 9 * 488,
"snap": 1 * 488,
"medicaid_enrolled": 1 * 51,
"chip_enrolled": 1 * 52,
"eitc_child_count": 4 * 488,
}

Expand Down
12 changes: 12 additions & 0 deletions policyengine_us_data/parameters/take_up/chip.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
description: Percentage of people who enroll in CHIP, if eligible.
metadata:
label: CHIP takeup rate
unit: /1
period: year
reference:
- title: CMS Medicaid and CHIP enrollment data
href: https://data.medicaid.gov/dataset/6165f45b-ca93-5bb5-9d06-db29c692a360
# Keep the current PolicyEngine-US default behavior until state-specific CHIP
# rates are derived from the new CMS enrollment targets and modeled eligibility.
values:
2018-01-01: 1.0
6 changes: 6 additions & 0 deletions policyengine_us_data/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
• Location: https://data.medicaid.gov/dataset/6165f45b-ca93-5bb5-9d06-db29c692a360?conditions%5B0%5D%5Boperator%5D=%3D&conditions%5B0%5D%5Bproperty%5D=reporting_period&conditions%5B0%5D%5Bvalue%5D=202512&conditions%5B1%5D%5Boperator%5D=%3D&conditions%5B1%5D%5Bproperty%5D=preliminary_or_updated&conditions%5B1%5D%5Bvalue%5D=U
• Notes: Uses `total_medicaid_enrollment`, not combined Medicaid and CHIP enrollment.

- **chip_enrollment_2024.csv, chip_enrollment_2025.csv, chip_enrollment_2026.csv**
• Source: Medicaid.gov performance indicator dataset, Applications, Eligibility, and Enrollment Data
• Date: December 2024 final reports, December 2025 final reports, and January 2026 preliminary reports
• Location: https://data.medicaid.gov/dataset/6165f45b-ca93-5bb5-9d06-db29c692a360
• Notes: Uses `total_chip_enrollment`, not combined Medicaid and CHIP enrollment. The 2026 file mirrors the reporting period and preliminary status used by `medicaid_enrollment_2026.csv`.

- **district_mapping.csv**
• Source: created by the script `policyengine_us/storage/calibration_targets/make_district_mapping.py`
• Notes: this script is not part of `make data` because of the length of time it takes to run and the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
state,enrollment
AK,14814
AL,183595
AR,82091
AZ,120881
CA,1232909
CO,135402
CT,23444
DC,17485
DE,12528
FL,172021
GA,234732
HI,22067
IA,85253
ID,20533
IL,291267
IN,143162
KS,77160
KY,133062
LA,147957
MA,197970
MD,197870
ME,28061
MI,173347
MN,5012
MO,131476
MS,84484
MT,19812
NC,347586
ND,5221
NE,37691
NH,19337
NJ,253484
NM,47202
NV,52007
NY,675709
OH,241124
OK,83925
OR,188738
PA,289619
RI,0
SC,104711
SD,16387
TN,171595
TX,356109
UT,36723
VA,200323
VT,5196
WA,60356
WI,80976
WV,37499
WY,5961
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
state,enrollment
AK,12976
AL,194447
AR,83783
AZ,112634
CA,1233169
CO,140354
CT,25353
DC,16392
DE,13989
FL,160955
GA,190017
HI,24762
IA,85504
ID,19848
IL,322451
IN,126144
KS,72305
KY,133266
LA,137824
MA,191701
MD,198218
ME,27038
MI,169340
MN,4361
MO,133259
MS,83921
MT,20272
NC,346463
ND,5542
NE,37597
NH,19186
NJ,259759
NM,43713
NV,47076
NY,662825
OH,242337
OK,79005
OR,184481
PA,281068
RI,33661
SC,114661
SD,14418
TN,183297
TX,346787
UT,34982
VA,195480
VT,5432
WA,65056
WI,100494
WV,39681
WY,5945
Loading
Loading