Skip to content
Merged
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
107 changes: 103 additions & 4 deletions scripts/compute_impacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from policyengine_us import Microsimulation
from policyengine_core.reforms import Reform
from policyengine_core.periods import instant
from microdf import MicroSeries
import numpy as np
HAS_POLICYENGINE = True
except ImportError:
Expand All @@ -46,6 +47,15 @@
3: "Congressional District 3",
4: "Congressional District 4",
},
"SC": {
1: "Congressional District 1",
2: "Congressional District 2",
3: "Congressional District 3",
4: "Congressional District 4",
5: "Congressional District 5",
6: "Congressional District 6",
7: "Congressional District 7",
},
"OK": {
1: "Congressional District 1",
2: "Congressional District 2",
Expand All @@ -67,6 +77,59 @@
}
}
},
{
"id": "ut-hb210-marriage-penalty-removal",
"state": "ut",
"label": "Utah HB210 Marriage Penalty Removal",
"reform": {
"gov.contrib.states.ut.hb210.in_effect": {
"2026-01-01.2100-12-31": True
},
"gov.contrib.states.ut.hb210.taxpayer_credit_add_on.amount.JOINT": {
"2026-01-01.2100-12-31": 66.0
},
"gov.contrib.states.ut.hb210.taxpayer_credit_add_on.amount.SEPARATE": {
"2026-01-01.2100-12-31": 33.0
},
"gov.contrib.states.ut.hb210.taxpayer_credit_add_on.amount.SURVIVING_SPOUSE": {
"2026-01-01.2100-12-31": 66.0
},
"gov.states.ut.tax.income.credits.ctc.reduction.start.HEAD_OF_HOUSEHOLD": {
"2026-01-01.2100-12-31": 27000.0
},
"gov.states.ut.tax.income.credits.ctc.reduction.start.SINGLE": {
"2026-01-01.2100-12-31": 27000.0
},
"gov.states.ut.tax.income.credits.earned_income.rate": {
"2026-01-01.2100-12-31": 0.0
},
"gov.states.ut.tax.income.credits.retirement.phase_out.threshold.HEAD_OF_HOUSEHOLD": {
"2026-01-01.2100-12-31": 16000.0
},
"gov.states.ut.tax.income.credits.retirement.phase_out.threshold.SINGLE": {
"2026-01-01.2100-12-31": 16000.0
},
"gov.states.ut.tax.income.credits.taxpayer.phase_out.threshold.HEAD_OF_HOUSEHOLD": {
"2026-01-01.2100-12-31": 18625.8
},
"gov.states.ut.tax.income.credits.ss_benefits.phase_out.threshold.HEAD_OF_HOUSEHOLD": {
"2026-01-01.2100-12-31": 45000.0
},
"gov.states.ut.tax.income.credits.ss_benefits.phase_out.threshold.SINGLE": {
"2026-01-01.2100-12-31": 45000.0
}
}
},
{
"id": "sc-h3492-refundable-eitc",
"state": "sc",
"label": "SC H.3492 Partially Refundable EITC",
"reform": {
"gov.contrib.states.sc.h3492.in_effect": {
"2026-01-01.2100-12-31": True
}
}
},
{
"id": "ok-hb2229-eitc",
"state": "ok",
Expand Down Expand Up @@ -244,7 +307,7 @@ def compute_district_impacts(state: str, reform_dict: dict, year: int = 2026) ->

# Get state FIPS code for filtering
STATE_FIPS = {
"UT": 49, "CA": 6, "NY": 36, "TX": 48, "FL": 12, "OK": 40,
"UT": 49, "CA": 6, "NY": 36, "TX": 48, "FL": 12, "SC": 45, "OK": 40,
# Add more as needed
}

Expand Down Expand Up @@ -304,6 +367,8 @@ def apply(self):
income_change = reform_income - baseline_income

household_weight = baseline.calculate("household_weight", year).values
household_count_people = baseline.calculate("household_count_people", year).values
household_income_decile = baseline.calculate("household_income_decile", year).values
state_code = baseline.calculate("state_code_str", year).values
cd_geoid = baseline.calculate("congressional_district_geoid", year).values

Expand Down Expand Up @@ -344,9 +409,43 @@ def apply(self):
total_benefit = float(np.sum(district_income_change * district_weights))
avg_benefit = total_benefit / total_households if total_households > 0 else 0

# Winners share (households with positive income change)
winners = district_income_change > 1 # More than $1 gain
winners_share = float(np.sum(district_weights[winners]) / total_households) if total_households > 0 else 0
# Winners share - match API's intra_decile_impact calculation exactly
# API methodology:
# 1. Calculate relative income change using capped values
# 2. Use MicroSeries with weights for proper weighted sums
# 3. Calculate proportion of winners per decile
# 4. Average across 10 deciles
district_baseline = baseline_income[in_district]
district_reform = reform_income[in_district]
absolute_change = district_reform - district_baseline
capped_baseline = np.maximum(district_baseline, 1)
capped_reform = np.maximum(district_reform, 1) + absolute_change
relative_change = (capped_reform - capped_baseline) / capped_baseline

# Create MicroSeries with weights (matching API pattern)
district_people = MicroSeries(
household_count_people[in_district],
weights=district_weights
)
district_decile = household_income_decile[in_district]

# API threshold: > 0.001 (0.1%) = winner
is_winner = relative_change > 0.001

# Calculate proportion of winners per decile, then average
decile_proportions = []
for decile in range(1, 11):
in_decile = district_decile == decile
if not np.any(in_decile):
decile_proportions.append(0.0)
continue
people_in_decile = float(district_people[in_decile].sum())
winners_in_decile = float(district_people[in_decile & is_winner].sum())
proportion = winners_in_decile / people_in_decile if people_in_decile > 0 else 0.0
decile_proportions.append(proportion)

# Average across deciles (matching API's sum / 10)
winners_share = sum(decile_proportions) / 10

district_impacts[f"{state_upper}-{district_num}"] = {
"districtName": district_name,
Expand Down
9 changes: 9 additions & 0 deletions src/components/reform/DistrictMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ const STATE_DISTRICTS = {
{ id: "3", name: "District 3", region: "Central & Eastern Utah" },
{ id: "4", name: "District 4", region: "Salt Lake Suburbs" },
],
SC: [
{ id: "1", name: "District 1", region: "Charleston & Coast" },
{ id: "2", name: "District 2", region: "Columbia & Midlands" },
{ id: "3", name: "District 3", region: "Upstate West" },
{ id: "4", name: "District 4", region: "Greenville-Spartanburg" },
{ id: "5", name: "District 5", region: "Rock Hill & North Central" },
{ id: "6", name: "District 6", region: "Pee Dee & Rural East" },
{ id: "7", name: "District 7", region: "Myrtle Beach & Northeast" },
],
OK: [
{ id: "1", name: "District 1", region: "Tulsa Area" },
{ id: "2", name: "District 2", region: "Eastern Oklahoma" },
Expand Down
Loading