Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c4692f3
Add Utah HB 15 Medicaid expansion repeal analysis
daphnehanse11 Jan 23, 2026
b656b8a
Simplify to parametric reform approach
daphnehanse11 Jan 23, 2026
68a99c8
Clarify this is a scenario analysis assuming trigger is met
daphnehanse11 Jan 23, 2026
ffe220f
Add ACA transition investigation script
daphnehanse11 Jan 23, 2026
426a01e
Switch to Utah-calibrated dataset for more accurate estimates
daphnehanse11 Jan 23, 2026
e210083
Use medicaid_enrolled instead of is_medicaid_eligible for realistic c…
daphnehanse11 Jan 23, 2026
e5888f1
Add example households notebook for Utah HB 15 analysis
daphnehanse11 Jan 26, 2026
efde215
Add visualization graphs to Utah HB 15 example households notebook
daphnehanse11 Jan 26, 2026
f14088a
Apply PolicyEngine v2 chart styling to Utah HB 15 notebook
daphnehanse11 Jan 26, 2026
8cad37a
Switch to Plotly charts matching PolicyEngine app v2 style
daphnehanse11 Jan 26, 2026
2f719ad
Remove coverage gap annotations from charts
daphnehanse11 Jan 26, 2026
2ab3ceb
Update charts to match PolicyEngine analysis-notebooks style
daphnehanse11 Jan 26, 2026
fad087d
Clean up Utah HB 15 example households notebook
daphnehanse11 Jan 27, 2026
24c2188
Add side-by-side charts for single adult and parent+child households
daphnehanse11 Jan 27, 2026
7c44517
Add chart generation script for Utah HB 15 blog post
daphnehanse11 Jan 27, 2026
fb58920
Update chart script to use axes feature for proper data generation
daphnehanse11 Jan 27, 2026
55870b4
Add table generation and CHIP to Utah HB 15 charts
daphnehanse11 Jan 27, 2026
a69837a
Extend x-axis to $120k and fix CHIP in charts
daphnehanse11 Jan 27, 2026
d89c939
Add parent Medicaid fallback scenario to tables
daphnehanse11 Jan 27, 2026
a06d82f
Update analysis scripts with revised model numbers and add new files
daphnehanse11 Feb 23, 2026
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
80 changes: 80 additions & 0 deletions us/aca_enrollment_spending.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from policyengine_us import Microsimulation
import pandas as pd

YEAR = 2025

STATES = [
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL",
"GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME",
"MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH",
"NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA", "RI",
"SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI",
]

results = []

for state in STATES:
print(f"Processing {state}...")
try:
sim = Microsimulation(
dataset=f"hf://policyengine/policyengine-us-data/states/{state}.h5"
)
aca_ptc = sim.calc("aca_ptc", period=YEAR, map_to="person")
enrolled = (aca_ptc > 0).sum()
spending = aca_ptc.sum()
results.append({
"state": state,
"aca_enrollment": enrolled,
"aca_spending": spending,
})
print(f" {state}: {enrolled:,.0f} enrolled, ${spending:,.0f} spending")
except Exception as e:
print(f" ERROR for {state}: {e}")
# CA workaround: set in_la=False to avoid LA county rating area bug
if state == "CA":
import numpy as np
print(f" Retrying {state} with in_la workaround...")
sim = Microsimulation(
dataset=f"hf://policyengine/policyengine-us-data/states/{state}.h5"
)
household = sim.populations['household']
sim.set_input('in_la', YEAR, np.zeros(household.count, dtype=bool))
aca_ptc = sim.calc("aca_ptc", period=YEAR, map_to="person")
enrolled = (aca_ptc > 0).sum()
spending = aca_ptc.sum()
results.append({
"state": state,
"aca_enrollment": enrolled,
"aca_spending": spending,
})
print(f" {state}: {enrolled:,.0f} enrolled, ${spending:,.0f} spending")
else:
results.append({
"state": state,
"aca_enrollment": None,
"aca_spending": None,
})

# National
print("\nProcessing national...")
sim_national = Microsimulation()
aca_ptc_national = sim_national.calc("aca_ptc", period=YEAR, map_to="person")
national_enrolled = (aca_ptc_national > 0).sum()
national_spending = aca_ptc_national.sum()
results.append({
"state": "US (National)",
"aca_enrollment": national_enrolled,
"aca_spending": national_spending,
})

df = pd.DataFrame(results)
df["aca_spending_billions"] = df["aca_spending"] / 1e9
df = df.sort_values("aca_enrollment", ascending=False)

print("\n" + "=" * 70)
print("ACA Enrollment and Spending by State")
print("=" * 70)
print(df.to_string(index=False, float_format=lambda x: f"{x:,.0f}"))

df.to_csv(f"us/aca_enrollment_spending_by_state_{YEAR}.csv", index=False)
print("\nSaved to us/aca_enrollment_spending_by_state.csv")
52 changes: 52 additions & 0 deletions us/aca_enrollment_spending_by_state.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
state,aca_enrollment,aca_spending,aca_spending_billions
US (National),17383484.732709058,179014488228.1197,179.0144882281197
TX,7045259.248884158,86460101552.99954,86.46010155299955
CA,6392819.0,61266319340.0,61.26631934
FL,4763130.907424998,61079815728.53207,61.07981572853207
GA,2457752.326743666,27439860750.8346,27.4398607508346
PA,2348139.8470139103,23420307290.99984,23.42030729099984
OH,2305060.443552648,22196402330.640697,22.196402330640694
IL,2211039.985772523,26555439405.93521,26.55543940593521
NC,2086741.8142803991,24947377457.642536,24.947377457642535
NY,1953039.1004624225,16089885227.700983,16.089885227700986
MI,1857987.0081065544,17767462914.75386,17.76746291475386
TN,1592600.8250531708,19973526197.83516,19.97352619783516
NJ,1539928.6046926808,13172055697.809889,13.172055697809888
VA,1474410.3597104885,11479779070.163813,11.479779070163811
AZ,1381595.3213190753,13732783973.741192,13.732783973741192
IN,1377786.4813249563,10268298367.75582,10.268298367755822
WI,1354486.004249754,14638132714.766865,14.638132714766863
WA,1347994.4825802264,13545412240.742018,13.545412240742015
MO,1312163.1666086228,14013492428.452984,14.013492428452984
SC,1239063.7865523277,12912301890.222267,12.912301890222269
AL,1229309.7415236607,14151670647.668756,14.151670647668755
CO,1027351.5600919002,9107220686.89117,9.107220686891171
MA,1022073.3352083332,6560450294.139649,6.560450294139649
KY,959811.3184823088,12092156264.34015,12.09215626434015
MD,929678.3681728876,5579924673.534227,5.579924673534228
OK,879446.0107236379,10898721075.430405,10.898721075430403
LA,876879.9132559479,10524063400.097616,10.524063400097615
UT,836792.5388366659,9348062271.00832,9.34806227100832
MS,747166.1928366562,9404532792.797682,9.40453279279768
KS,721101.5106305224,9157055464.51527,9.15705546451527
MN,699327.0917778963,5201652953.358235,5.201652953358235
AR,688070.3462367074,10445357164.006168,10.445357164006168
IA,664351.792916852,5089952648.955997,5.089952648955998
OR,656362.3123666104,5548745613.863974,5.548745613863973
NV,613462.5641283542,5409709120.288629,5.409709120288628
CT,581912.3486924558,9054273485.205309,9.054273485205307
NE,490228.6737917632,6991057381.380745,6.991057381380745
ID,440370.6405512402,4038585402.305117,4.0385854023051175
NM,392587.7489387901,4553466809.422207,4.553466809422207
WV,384198.72947609494,8510778710.398851,8.510778710398851
NH,292619.7376561556,2099425092.7739608,2.0994250927739606
ME,284222.17484532436,4529157327.2300625,4.529157327230062
MT,235317.3814670497,2952751928.720352,2.9527519287203523
HI,220836.27874800004,1703108104.3514469,1.7031081043514469
ND,201077.59784668495,2583745310.161701,2.583745310161701
SD,197912.42513002065,2461979782.0080175,2.461979782008018
RI,194962.17444041232,1679774483.108898,1.679774483108898
DE,189378.5689020855,2528841844.144252,2.5288418441442517
VT,123538.29485612042,2501834368.098485,2.501834368098485
AK,98996.27022879756,1652613679.3413544,1.6526136793413544
DC,55924.91141050868,426306591.2642381,0.4263065912642381
52 changes: 52 additions & 0 deletions us/aca_enrollment_spending_by_state_2024.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
state,aca_enrollment,aca_spending,aca_spending_billions
AL,0.0,0.0,0.0
PA,0.0,0.0,0.0
NV,0.0,0.0,0.0
NH,0.0,0.0,0.0
NJ,0.0,0.0,0.0
NM,0.0,0.0,0.0
NY,0.0,0.0,0.0
NC,0.0,0.0,0.0
ND,0.0,0.0,0.0
OH,0.0,0.0,0.0
OK,0.0,0.0,0.0
OR,0.0,0.0,0.0
RI,0.0,0.0,0.0
MT,0.0,0.0,0.0
SC,0.0,0.0,0.0
SD,0.0,0.0,0.0
TN,0.0,0.0,0.0
TX,0.0,0.0,0.0
UT,0.0,0.0,0.0
VT,0.0,0.0,0.0
VA,0.0,0.0,0.0
WA,0.0,0.0,0.0
WV,0.0,0.0,0.0
WI,0.0,0.0,0.0
NE,0.0,0.0,0.0
MO,0.0,0.0,0.0
AK,0.0,0.0,0.0
ID,0.0,0.0,0.0
AZ,0.0,0.0,0.0
AR,0.0,0.0,0.0
CA,0.0,0.0,0.0
CO,0.0,0.0,0.0
CT,0.0,0.0,0.0
DE,0.0,0.0,0.0
DC,0.0,0.0,0.0
FL,0.0,0.0,0.0
GA,0.0,0.0,0.0
HI,0.0,0.0,0.0
IL,0.0,0.0,0.0
MS,0.0,0.0,0.0
IN,0.0,0.0,0.0
IA,0.0,0.0,0.0
KS,0.0,0.0,0.0
KY,0.0,0.0,0.0
LA,0.0,0.0,0.0
ME,0.0,0.0,0.0
MD,0.0,0.0,0.0
MA,0.0,0.0,0.0
MI,0.0,0.0,0.0
MN,0.0,0.0,0.0
US (National),0.0,0.0,0.0
52 changes: 52 additions & 0 deletions us/aca_enrollment_spending_by_state_2025.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
state,aca_enrollment,aca_spending,aca_spending_billions
US (National),21572726.942273073,181223754299.774,181.223754299774
CA,8258359.718990697,75333815139.14964,75.33381513914965
TX,8154141.478774493,77021600904.5361,77.0216009045361
FL,5480503.306234306,55610399173.550514,55.61039917355051
NY,2920759.347690541,26876070556.55341,26.876070556553408
GA,2855316.4533272963,26911336485.505817,26.911336485505817
PA,2845628.6703047836,24197944419.820126,24.197944419820125
OH,2801191.123286927,25536017657.964554,25.536017657964553
IL,2716568.46740863,24011346931.944237,24.011346931944235
NC,2543226.848436172,25369382439.49144,25.36938243949144
MI,2206450.235455502,17810806397.0245,17.810806397024503
NJ,2030025.1104657375,17360635430.15796,17.36063543015796
TN,1877230.9010269186,17877734072.469265,17.877734072469266
VA,1822987.0508065247,12605701272.806177,12.605701272806177
IN,1644125.78897559,11031265485.617647,11.031265485617647
WI,1632773.8356423066,15222107733.353607,15.222107733353607
WA,1632212.747060252,11107151098.56737,11.10715109856737
AZ,1611555.536846539,12774439338.496689,12.77443933849669
MO,1589053.149089912,14907236215.289948,14.907236215289947
SC,1456141.7433837387,13911916925.125687,13.911916925125686
AL,1440763.2141796688,14659416638.629711,14.659416638629711
MA,1364252.0275837937,9265263267.48693,9.265263267486931
CO,1315091.3830277754,10486010855.399256,10.486010855399256
MD,1231546.1830028284,7439723643.565978,7.439723643565978
KY,1123867.6968579488,10866795607.628288,10.866795607628289
LA,1032368.7955663666,10887378833.949318,10.887378833949318
OK,1030973.3978550192,11047539448.776329,11.047539448776329
UT,1022815.1581275805,10815686650.487175,10.815686650487175
MN,1007975.4702420774,6160068826.019312,6.1600688260193115
OR,885710.2963893601,7807541204.385212,7.807541204385212
KS,851624.0196430138,8652615702.235592,8.652615702235591
MS,831854.577155319,8088174705.164504,8.088174705164503
IA,813172.183629359,6221756669.238905,6.221756669238905
CT,809628.3426085422,10478832726.64508,10.47883272664508
AR,769524.7271320827,6873299358.368113,6.873299358368112
NV,730709.7043699923,5934058476.354357,5.934058476354357
NE,600562.0920331676,7707765423.0975685,7.707765423097569
ID,529698.3963936316,4688614115.276829,4.688614115276828
NM,475964.13492398657,4911455805.543261,4.911455805543261
WV,468581.0576358518,9470285869.939915,9.470285869939914
ME,355824.7205406721,4292957621.2281113,4.292957621228111
NH,327098.38002613944,2010970739.2548814,2.0109707392548812
HI,318478.70823075064,2718272465.394373,2.718272465394373
MT,300547.43412913976,3311576180.466132,3.3115761804661323
RI,247608.49538935843,1919906280.4145892,1.9199062804145892
ND,247107.05076210294,3277744693.109666,3.277744693109666
DE,242876.5929896055,2533376397.566221,2.5333763975662213
SD,242241.82739121973,2931362862.695174,2.9313628626951744
VT,169877.2291643463,3535068119.476025,3.535068119476025
AK,161816.04310611496,3612140008.017561,3.612140008017561
DC,95431.3645341415,792773104.7096058,0.7927731047096058
137 changes: 137 additions & 0 deletions us/states/ut/analyze_hb15_transitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Analyze Utah HB 15 Medicaid transitions with microsimulation."""

from policyengine_us import Microsimulation
from policyengine_core.periods import instant
from policyengine_core.reforms import Reform
import numpy as np

YEAR = 2027
DATASET = "hf://policyengine/policyengine-us-data/enhanced_cps_2024.h5"

def create_ut_medicaid_expansion_repeal():
def modify_parameters(parameters):
parameters.gov.hhs.medicaid.eligibility.categories.adult.income_limit.UT.update(
start=instant(f'{YEAR}-01-01'),
stop=instant('2100-12-31'),
value=float('-inf'),
)
return parameters

class reform(Reform):
def apply(self):
self.modify_parameters(modify_parameters)

return reform


def main():
print("Loading baseline microsimulation...")
baseline = Microsimulation(dataset=DATASET)

print("Loading reform microsimulation...")
reform = Microsimulation(dataset=DATASET, reform=create_ut_medicaid_expansion_repeal())

# Filter to Utah residents - map household state to person level
# state_code is defined at household level, map to person
state_code = baseline.calculate("state_code", YEAR, map_to="person").values
in_utah = state_code == "UT"

# Get person weights
person_weight = baseline.calculate("person_weight", YEAR).values

# Get Medicaid categories (baseline vs reform) - these return string values
baseline_category = baseline.calculate("medicaid_category", YEAR).values
reform_category = reform.calculate("medicaid_category", YEAR).values

# Get Medicaid amounts (person level)
baseline_medicaid = baseline.calculate("medicaid", YEAR).values
reform_medicaid = reform.calculate("medicaid", YEAR).values

print(f"\nDebug: Total people in sample: {len(baseline_category):,}")
print(f"Debug: People in Utah: {in_utah.sum():,}")
print(f"Debug: Unique baseline categories: {np.unique(baseline_category)}")

# Categories are strings now
ADULT = "ADULT"
PARENT = "PARENT"
NONE = "NONE"

print("\n" + "="*70)
print("UTAH HB 15 MEDICAID TRANSITION ANALYSIS")
print("="*70)

# People on expansion Medicaid in baseline (in Utah)
on_expansion_baseline = (baseline_category == ADULT) & in_utah
expansion_count = (on_expansion_baseline * person_weight).sum()
print(f"\nPeople on expansion Medicaid (baseline): {expansion_count:,.0f}")

# What happens to expansion adults under reform?
# Transition to parent Medicaid
to_parent = on_expansion_baseline & (reform_category == PARENT)
to_parent_count = (to_parent * person_weight).sum()

# Transition to other Medicaid categories (not NONE, not ADULT, not PARENT)
to_other_medicaid = on_expansion_baseline & (reform_category != NONE) & (reform_category != ADULT) & (reform_category != PARENT)
to_other_count = (to_other_medicaid * person_weight).sum()

# Fall to no Medicaid
to_none = on_expansion_baseline & (reform_category == NONE)
to_none_count = (to_none * person_weight).sum()

print(f"\nTransitions from expansion Medicaid:")
print(f" → Parent Medicaid: {to_parent_count:,.0f}")
print(f" → Other Medicaid: {to_other_count:,.0f}")
print(f" → No Medicaid: {to_none_count:,.0f}")

# Of those who lose all Medicaid, how many gain ACA?
loses_medicaid = on_expansion_baseline & (reform_medicaid == 0)
loses_medicaid_count = (loses_medicaid * person_weight).sum()

# People above 100% FPL can get ACA
income_level = baseline.calculate("medicaid_income_level", YEAR).values
above_100_fpl = income_level >= 1.0

gains_aca = loses_medicaid & above_100_fpl
gains_aca_count = (gains_aca * person_weight).sum()

# Coverage gap = loses Medicaid and below 100% FPL (can't get ACA)
coverage_gap = loses_medicaid & ~above_100_fpl
coverage_gap_count = (coverage_gap * person_weight).sum()

if loses_medicaid_count > 0:
print(f"\nOf those losing Medicaid ({loses_medicaid_count:,.0f}):")
print(f" → Can get ACA (>=100% FPL): {gains_aca_count:,.0f} ({gains_aca_count/loses_medicaid_count*100:.1f}%)")
print(f" → Coverage gap (<100% FPL): {coverage_gap_count:,.0f} ({coverage_gap_count/loses_medicaid_count*100:.1f}%)")

# Fiscal impact (person-level Medicaid only for now)
utah_weight = person_weight * in_utah

baseline_medicaid_total = (baseline_medicaid * utah_weight).sum()
reform_medicaid_total = (reform_medicaid * utah_weight).sum()
medicaid_savings = baseline_medicaid_total - reform_medicaid_total

print(f"\n" + "="*70)
print("FISCAL IMPACT")
print("="*70)
print(f"\nMedicaid spending change: -${medicaid_savings/1e6:,.0f} million")
print(f" Federal share (90%): -${medicaid_savings*0.9/1e6:,.0f} million")
print(f" State share (10%): -${medicaid_savings*0.1/1e6:,.0f} million")

# Summary for blog post
print(f"\n" + "="*70)
print("SUMMARY FOR BLOG POST")
print("="*70)
print(f"\n- ~{expansion_count/1000:.0f},000 people currently on expansion Medicaid")
if to_parent_count > 0:
print(f"- ~{to_parent_count/1000:.0f},000 would transition to parent Medicaid")
if to_other_count > 0:
print(f"- ~{to_other_count/1000:.0f},000 would transition to other Medicaid categories")
print(f"- ~{loses_medicaid_count/1000:.0f},000 would lose Medicaid coverage entirely")
if loses_medicaid_count > 0:
print(f" - ~{gains_aca_count/1000:.0f},000 ({gains_aca_count/loses_medicaid_count*100:.0f}%) could transition to ACA")
print(f" - ~{coverage_gap_count/1000:.0f},000 ({coverage_gap_count/loses_medicaid_count*100:.0f}%) would fall into coverage gap")
print(f"- State savings: ~${medicaid_savings*0.1/1e6:.0f} million/year")


if __name__ == "__main__":
main()
Loading