open-navigator / web_app /src /api /grandkidOutlook.ts
jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
Raw
History Blame Contribute Delete
2.78 kB
// API client for the "Grandkid outlook" intergenerational-mobility slopegraph —
// real Opportunity Atlas data (Chetty, Hendren, Jones & Porter, 2018). Filtered
// by parent income, child race, and child gender, mirroring the Opportunity
// Insights "Race and Class Trends" tool. NO fabricated numbers (CLAUDE.md):
// `local` is null when no commuting zone matched, and `local.available === false`
// when a CZ matched but the race×gender cell has too little data there.
import api from '../lib/api'
/** A single mobility outcome cell (parent → child income rank/percentile). */
export interface GrandkidOutcome {
/** True when the warehouse has enough data for this scope×group cell. */
available: boolean
/** Mean child income rank (0..1) for kids of parents at parent_percentile. */
child_income_rank?: number | null
/** Same as a 0..100 percentile (child_income_rank * 100). */
child_percentile?: number | null
/** Sample size for the local (commuting-zone) cell. */
n?: number | null
/** Sample size for the national cell. */
total_n?: number | null
}
export interface GrandkidOutlook {
race: string
gender: string
parent_income_level: string
/** Parent income percentile (e.g. 25 for "low"). */
parent_percentile: number
/** True when the place resolved to a commuting zone. */
resolved: boolean
/** Commuting-zone name, or null when nothing matched. */
cz_name: string | null
/** Human scope label, e.g. "the Tuscaloosa commuting zone". */
scope_label: string
/** Local commuting-zone outcome, or null when no CZ matched. */
local: GrandkidOutcome | null
/** National outcome — essentially always present. */
national: GrandkidOutcome | null
/** Honest one-line caveat to display verbatim near the chart. */
note: string
/** Attribution string. */
source: string
/** Attribution link. */
source_url: string
}
export interface GrandkidOutlookParams {
/** 2-letter state code. */
state?: string
city?: string
/** Child race: pooled | white | black | hisp | asian | natam | other. */
race?: string
/** Child gender: pooled | male | female. */
gender?: string
/** Parent income level: low | middle | high. */
parent_income?: string
}
export async function fetchGrandkidOutlook(
params?: GrandkidOutlookParams,
): Promise<GrandkidOutlook> {
const q = new URLSearchParams()
if (params?.state) q.set('state', params.state)
if (params?.city) q.set('city', params.city)
if (params?.race) q.set('race', params.race)
if (params?.gender) q.set('gender', params.gender)
if (params?.parent_income) q.set('parent_income', params.parent_income)
const qs = q.toString()
const res = await api.get<GrandkidOutlook>(`/grandkid-outlook${qs ? `?${qs}` : ''}`)
return res.data
}