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
2 changes: 2 additions & 0 deletions api/_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
PROGRAM_DEFINITIONS,
PUBLIC_ASSISTANCE_PROGRAM_OPTIONS,
STATE_INFO,
STATE_PROGRAM_OVERRIDES,
)


Expand All @@ -47,6 +48,7 @@ def metadata_response() -> dict[str, Any]:
"states": STATE_INFO,
"household_types": HOUSEHOLD_TYPES,
"programs": PROGRAM_DEFINITIONS,
"state_program_overrides": STATE_PROGRAM_OVERRIDES,
"household_costs": HOUSEHOLD_COST_DEFINITIONS,
"public_assistance_programs": PUBLIC_ASSISTANCE_PROGRAM_OPTIONS,
"ccdf_modeled_states": sorted(CCDF_MODELED_STATES),
Expand Down
39 changes: 30 additions & 9 deletions cliff_watch/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
PUBLIC_ASSISTANCE_PROGRAM_OPTIONS,
STATE_INFO,
STATE_NAME_BY_CODE,
STATE_PROGRAM_OVERRIDES,
STATE_TANF_VARIABLES,
)

Expand Down Expand Up @@ -87,13 +88,26 @@ class HouseholdInput:


PROGRAM_LABEL_BY_KEY = {item["key"]: item["label"] for item in PROGRAM_DEFINITIONS}
PROGRAM_DEFINITION_BY_KEY = {item["key"]: item for item in PROGRAM_DEFINITIONS}
HOUSEHOLD_COST_LABEL_BY_KEY = {
item["key"]: item["label"] for item in HOUSEHOLD_COST_DEFINITIONS
}
PUBLIC_ASSISTANCE_PROGRAM_KEYS = {
item["key"] for item in PUBLIC_ASSISTANCE_PROGRAM_OPTIONS
}
FILING_STATUS_CODES = {item["code"] for item in FILING_STATUS_OPTIONS}


def _program_definition_for_state(key: str, state_code: str | None = None) -> dict[str, Any]:
definition = PROGRAM_DEFINITION_BY_KEY[key]
override = STATE_PROGRAM_OVERRIDES.get(state_code or "", {}).get(key, {})
return {**definition, **override}


def _program_label_for_state(key: str, state_code: str | None = None) -> str:
return _program_definition_for_state(key, state_code)["label"]


REFUNDABLE_CREDIT_COMPONENTS = (
{"key": "eitc", "variable": "eitc", "map_to": "tax_unit"},
{
Expand Down Expand Up @@ -1208,19 +1222,22 @@ def _attach_cliff_metrics(
return base_result


def _format_program_breakdown(programs: dict[str, float]) -> list[dict[str, Any]]:
labels = {item["key"]: item for item in PROGRAM_DEFINITIONS}
def _format_program_breakdown(
programs: dict[str, float],
state_code: str | None = None,
) -> list[dict[str, Any]]:
ordered = []
for key in [item["key"] for item in PROGRAM_DEFINITIONS]:
annual = round(programs.get(key, 0.0), 2)
if annual <= 0:
continue
definition = _program_definition_for_state(key, state_code)
ordered.append(
{
"key": key,
"label": labels[key]["label"],
"short_label": labels[key]["short_label"],
"description": labels[key]["description"],
"label": definition["label"],
"short_label": definition["short_label"],
"description": definition["description"],
"annual": annual,
"monthly": round(annual / 12, 2),
}
Expand Down Expand Up @@ -1271,10 +1288,11 @@ def _resolve_series_step(
def _build_cliff_drivers(
previous_result: dict[str, Any],
result: dict[str, Any],
state_code: str | None = None,
) -> list[dict[str, Any]]:
drivers = []

for key, label in PROGRAM_LABEL_BY_KEY.items():
for key in PROGRAM_LABEL_BY_KEY:
annual_change = round(
result["programs"].get(key, 0.0)
- previous_result["programs"].get(key, 0.0),
Expand All @@ -1284,7 +1302,7 @@ def _build_cliff_drivers(
drivers.append(
{
"key": key,
"label": label,
"label": _program_label_for_state(key, state_code),
"kind": "benefit_loss",
"raw_change_annual": annual_change,
"raw_change_monthly": _monthly_amount(annual_change),
Expand Down Expand Up @@ -1343,7 +1361,10 @@ def calculate_household(
result = _simulate_core(payload)
result = _attach_cliff_metrics(payload, result, delta=delta)
result["state_name"] = STATE_NAME_BY_CODE[payload.state]
result["program_breakdown"] = _format_program_breakdown(result["programs"])
result["program_breakdown"] = _format_program_breakdown(
result["programs"],
payload.state,
)
result["eligible"] = result["totals"]["core_support"] > 0
result["monthly"] = {
key: round(value / 12, 2) for key, value in result["totals"].items()
Expand Down Expand Up @@ -1710,7 +1731,7 @@ def calculate_income_series(
net_change = net_resources - previous_net_resources
cliff_drop = max(0.0, previous_net_resources - net_resources)
cliff_drivers = (
_build_cliff_drivers(previous_result, result)
_build_cliff_drivers(previous_result, result, payload.state)
if previous_result is not None and cliff_drop > 0
else []
)
Expand Down
98 changes: 98 additions & 0 deletions cliff_watch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,60 @@
"WY": "wy_power",
}

STATE_TANF_LABELS = {
"AK": "Alaska ATAP benefit",
"AL": "Alabama TANF",
"AR": "Arkansas Transitional Employment Assistance",
"AZ": "Arizona TANF",
"CA": "California CalWORKs Cash Benefit",
"CO": "Colorado TANF",
"CT": "Connecticut Temporary Family Assistance (TFA) benefit amount",
"DC": "DC Temporary Assistance for Needy Families (TANF)",
"DE": "Delaware TANF",
"FL": "Florida Temporary Cash Assistance",
"GA": "Georgia Temporary Assistance for Needy Families (TANF)",
"HI": "Hawaii TANF benefit amount",
"IA": "Iowa Family Investment Program (FIP)",
"ID": "Idaho Temporary Assistance for Families in Idaho (TAFI)",
"IL": "Illinois Temporary Assistance for Needy Families (TANF)",
"IN": "Indiana Temporary Assistance for Needy Families (TANF)",
"KS": "Kansas Temporary Assistance for Needy Families (TANF)",
"KY": "Kentucky K-TAP benefit",
"LA": "Louisiana FITAP",
"MA": "Massachusetts Temporary Assistance for Families with Dependent Children (TAFDC)",
"MD": "Maryland Temporary Cash Assistance",
"ME": "Maine TANF",
"MI": "Michigan Family Independence Program",
"MN": "Minnesota MFIP",
"MO": "Missouri Temporary Assistance for Needy Families (TANF)",
"MS": "Mississippi TANF",
"MT": "Montana Temporary Assistance for Needy Families (TANF)",
"NC": "North Carolina TANF",
"ND": "North Dakota Temporary Assistance for Needy Families",
"NE": "Nebraska Aid to Dependent Children (ADC)",
"NH": "New Hampshire Financial Assistance to Needy Families",
"NJ": "New Jersey WFNJ benefit",
"NM": "New Mexico Works",
"NV": "Nevada Temporary Assistance for Needy Families (TANF)",
"NY": "New York TANF",
"OH": "Ohio OWF",
"OK": "Oklahoma TANF",
"OR": "Oregon Temporary Assistance for Needy Families (TANF)",
"PA": "Pennsylvania TANF",
"RI": "Rhode Island Works benefit",
"SC": "South Carolina TANF",
"SD": "South Dakota Temporary Assistance for Needy Families (TANF)",
"TN": "Tennessee Families First",
"TX": "Texas Temporary Assistance for Needy Families (TANF)",
"UT": "Utah Family Employment Program benefit",
"VA": "VA TANF",
"VT": "Vermont Reach Up (TANF)",
"WA": "Washington Temporary Assistance for Needy Families (TANF)",
"WI": "Wisconsin Works",
"WV": "West Virginia WV Works benefit",
"WY": "Wyoming POWER benefit",
}

HOUSEHOLD_TYPES = [
{
"id": "single_adult",
Expand Down Expand Up @@ -432,3 +486,47 @@
"label": "Social Security Disability Insurance (SSDI)",
},
]


def build_state_program_overrides() -> dict[str, dict[str, dict[str, str]]]:
overrides = {}
for state in STATE_INFO:
state_code = state["code"]
state_name = state["name"]
state_overrides = {
"tanf": {
"label": STATE_TANF_LABELS.get(
state_code,
f"{state_name} Temporary Assistance for Needy Families (TANF)",
),
"short_label": "TANF",
"description": "State cash assistance for families with children.",
"variable": STATE_TANF_VARIABLES.get(state_code, "tanf"),
},
"medicaid": {
"label": f"{state_name} Medicaid",
"short_label": "Medicaid",
"description": f"Public health coverage through {state_name}'s Medicaid program.",
},
"chip": {
"label": f"{state_name} Children's Health Insurance Program (CHIP)",
"short_label": "CHIP",
"description": f"Children's public health coverage through {state_name}'s CHIP program.",
},
"state_refundable_credits": {
"label": f"{state_name} refundable state tax credits",
"short_label": "State credits",
"description": f"Refundable state income tax credits modeled for {state_name}.",
},
}
if state_code in CCDF_MODELED_STATES:
state_overrides["child_care_subsidies"] = {
"label": f"{state_name} child care subsidy (CCDF)",
"short_label": "Child care",
"description": f"State CCDF child care subsidy modeled for {state_name}.",
}
overrides[state_code] = state_overrides
return overrides


STATE_PROGRAM_OVERRIDES = build_state_program_overrides()
7 changes: 6 additions & 1 deletion frontend/src/components/InputPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useMemo } from 'react'
import { applyStateProgramLabels } from '../utils/programLabels.js'

const PROGRAM_MODES = [
{ key: 'all', label: 'All' },
Expand Down Expand Up @@ -123,7 +124,11 @@ function InputPanel({ metadata, inputs, loading, onCalculate, onChange, onReset
const dependentCount = people.filter((person) => person.kind === 'child').length
const maxAdults = Math.max(1, Number(metadata?.defaults?.max_adults) || 6)
const maxDependents = Math.max(0, Number(metadata?.defaults?.max_dependents) || 6)
const programOptions = metadata?.public_assistance_programs || metadata?.programs || []
const baseProgramOptions = metadata?.public_assistance_programs || metadata?.programs || []
const programOptions = useMemo(
() => applyStateProgramLabels(baseProgramOptions, metadata, inputs?.state),
[baseProgramOptions, metadata, inputs?.state],
)
const selectedPrograms = new Set(inputs?.selected_programs || programOptions.map((program) => program.key))

const rowMeta = useMemo(() => {
Expand Down
28 changes: 15 additions & 13 deletions frontend/src/policyengineApi.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { applyStateProgramLabels } from './utils/programLabels.js'

const PE_API_URL = 'https://api.policyengine.org'

const STATE_TANF_VARIABLES = {
Expand Down Expand Up @@ -871,16 +873,16 @@ function getStateName(metadata, stateCode) {
return metadata?.states?.find((item) => item.code === stateCode)?.name || stateCode
}

function getProgramDefinitions(metadata) {
function getProgramDefinitions(metadata, stateCode) {
const definitions = metadata?.programs
if (Array.isArray(definitions) && definitions.length) {
return definitions
return applyStateProgramLabels(definitions, metadata, stateCode)
}
return DEFAULT_PUBLIC_ASSISTANCE_PROGRAM_OPTIONS.map((program) => ({
return applyStateProgramLabels(DEFAULT_PUBLIC_ASSISTANCE_PROGRAM_OPTIONS.map((program) => ({
...program,
short_label: program.label,
description: '',
}))
})), metadata, stateCode)
}

function getHouseholdCostDefinitions(metadata) {
Expand All @@ -891,9 +893,9 @@ function getHouseholdCostDefinitions(metadata) {
return DEFAULT_HOUSEHOLD_COST_DEFINITIONS
}

function getProgramLabelMap(metadata) {
function getProgramLabelMap(metadata, stateCode) {
return Object.fromEntries(
getProgramDefinitions(metadata).map((program) => [program.key, program.label]),
getProgramDefinitions(metadata, stateCode).map((program) => [program.key, program.label]),
)
}

Expand All @@ -914,11 +916,11 @@ function bestAccessProgram({ acaEligible, medicaidEligible, chipEligible }) {
return 'none'
}

function formatProgramBreakdown(programs, metadata) {
function formatProgramBreakdown(programs, metadata, stateCode) {
const byKey = Object.fromEntries(
getProgramDefinitions(metadata).map((program) => [program.key, program]),
getProgramDefinitions(metadata, stateCode).map((program) => [program.key, program]),
)
return getProgramDefinitions(metadata)
return getProgramDefinitions(metadata, stateCode)
.map((program) => {
const annual = roundCurrency(programs[program.key])
if (annual <= 0) {
Expand Down Expand Up @@ -1144,7 +1146,7 @@ export function buildHouseholdResultFromResponse(payload, metadata, apiResponse,
},
people,
state_name: getStateName(metadata, payload.state),
program_breakdown: formatProgramBreakdown(programs, metadata),
program_breakdown: formatProgramBreakdown(programs, metadata, payload.state),
eligible: coreSupport > 0,
monthly: {
market_income: monthlyAmount(marketIncome),
Expand All @@ -1156,8 +1158,8 @@ export function buildHouseholdResultFromResponse(payload, metadata, apiResponse,
}
}

export function buildCliffDrivers(previousPoint, currentPoint, metadata) {
const labelByKey = getProgramLabelMap(metadata)
export function buildCliffDrivers(previousPoint, currentPoint, metadata, stateCode) {
const labelByKey = getProgramLabelMap(metadata, stateCode)
const householdCostLabels = getHouseholdCostLabelMap(metadata)
const drivers = Object.keys(labelByKey).flatMap((key) => {
const changeAnnual = roundCurrency(
Expand Down Expand Up @@ -1421,7 +1423,7 @@ export function buildSeriesDataFromResponse(payload, metadata, apiResponse, desc
? roundCurrency(point.totals.net_resources - previousPoint.totals.net_resources)
: 0
const cliffDrivers = previousPoint
? buildCliffDrivers(previousPoint, point, metadata)
? buildCliffDrivers(previousPoint, point, metadata, payload.state)
: []
return {
earned_income: point.earned_income,
Expand Down
28 changes: 27 additions & 1 deletion frontend/src/policyengineApi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ import {

const metadata = {
states: [{ code: 'GA', name: 'Georgia' }],
programs: [],
programs: [
{ key: 'tanf', label: 'TANF', short_label: 'TANF', description: '' },
{ key: 'chip', label: 'CHIP', short_label: 'CHIP', description: '' },
],
state_program_overrides: {
GA: {
tanf: { label: 'Georgia Temporary Assistance for Needy Families (TANF)' },
chip: { label: "Georgia Children's Health Insurance Program (CHIP)" },
},
},
household_costs: [{ key: 'chip_premium', label: 'CHIP premium' }],
}

Expand Down Expand Up @@ -179,6 +188,23 @@ test('buildCliffDrivers reports household cost increases as cliff drivers', () =
)
})

test('buildCliffDrivers uses state-specific benefit labels', () => {
const previousPoint = {
programs: { tanf: 1200 },
household_costs: {},
totals: { taxes: 0 },
}
const currentPoint = {
programs: { tanf: 0 },
household_costs: {},
totals: { taxes: 0 },
}

const drivers = buildCliffDrivers(previousPoint, currentPoint, metadata, 'GA')

assert.equal(drivers[0].label, 'Georgia Temporary Assistance for Needy Families (TANF)')
})

test('buildSeriesDataFromResponse carries CHIP premiums into series net resources and cliff drivers', () => {
const payload = {
state: 'GA',
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/utils/programLabels.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function getStateProgramOverrides(metadata, stateCode) {
return metadata?.state_program_overrides?.[stateCode] || {}
}

export function applyStateProgramLabels(programs = [], metadata, stateCode) {
const overrides = getStateProgramOverrides(metadata, stateCode)
return (programs || []).map((program) => {
const override = overrides[program.key] || {}
return {
...program,
...override,
label: override.label || program.label,
short_label: override.short_label || program.short_label,
description: override.description || program.description,
}
})
}
Loading