Health-impact simulator for data-center siting in Atlanta. Drop a pin on the map and see a forward projection of that neighborhood's health burden through 2030 under three scenarios: no new facility, a facility at the pin, and a facility at an auto-selected lower-burden alternative site within 10 miles.
client/ React + Vite + Tailwind + Mapbox GL + Recharts (UI)
server/ Flask API + Prophet forecasting (Python venv) (simulation + data layer)
tools/ Node: docx briefing generator + Playwright filer (briefing + PSC submission)
For the full end-to-end system pipeline (data ingestion, Prophet ML, React UI, briefing, PSC automation), see PIPELINE.md.
| Tool | Version used | Notes |
|---|---|---|
| Python | 3.13 | Prophet installs from a prebuilt wheel — no compiler needed |
| Node.js | 20+ (tested on 24) | for the client and the tools/ scripts |
| Mapbox token | — | a free public token (pk.*) — only needed for the base map tiles |
The app runs fully offline. Live data sources (EJScreen, Census) fall back to bundled local data automatically, so missing network or keys never breaks the demo.
Run each block once, from the repo root.
cd server
python -m venv venv
venv\Scripts\python.exe -m pip install -r requirements.txtcd ..\client
npm installcd ..\tools
npm install
npx playwright install chromiumThis generates the synthetic Atlanta fixtures and the merged GeoJSON the map + simulator read from. (The server also auto-builds the merged file on first boot if it's missing.)
cd ..\server
venv\Scripts\python.exe generate_data.py # -> data/*.csv + epd_permits.json
venv\Scripts\python.exe -m app.services.ingest # -> data/merged_data.jsonVITE_MAPBOX_TOKEN=pk.your_token_here # base map tiles (app data works without it)
VITE_API_BASE=http://127.0.0.1:5000 # Flask API base (default is fine)
| Variable | Purpose |
|---|---|
CENSUS_API_KEY |
Enables live Census ACS data. Get one free at https://api.census.gov/data/key_signup.html. Without it, Census falls back to local. |
FORESIGHT_ALLOW_LIVE_FILING=1 |
Required (with confirm=true) before /file will file into the real PSC docket. Leave unset for the safe dry-run default. |
Set one for a session in PowerShell with e.g. $env:CENSUS_API_KEY="..." before run.py.
Use two terminals.
# Terminal 1 — API -> http://127.0.0.1:5000
cd server
venv\Scripts\python.exe run.py# Terminal 2 — UI -> http://127.0.0.1:5173
cd client
npm run devOpen http://127.0.0.1:5173. The app preloads South Fulton so the demo works instantly. Drag the pin, search a neighborhood, download a briefing, or submit to the (dry-run) PSC record.
# API health
Invoke-WebRequest http://127.0.0.1:5000/health -UseBasicParsing
# A full simulation
Invoke-WebRequest http://127.0.0.1:5000/simulate -Method Post `
-ContentType 'application/json' -Body '{"lat":33.631,"lng":-84.530}' -UseBasicParsing
# Inspect raw vs parsed live data sources (for tuning field mappings)
Invoke-WebRequest "http://127.0.0.1:5000/debug/sources?lat=33.631&lng=-84.530" -UseBasicParsing| Endpoint | Method | Purpose |
|---|---|---|
/health |
GET | Liveness check |
/tracts |
GET | Merged GeoJSON for the heat layer |
/simulate |
POST {lat,lng} |
Nearest tract → Prophet 3-scenario projection + score-80 crossings + demographics + sources provenance |
/debug/sources |
GET ?lat=&lng= |
Raw + parsed live EJScreen/Census payloads (for tuning field mappings) |
/briefing |
POST (sim payload) | DOCX briefing via the Node docx tool |
/file |
POST {docx_path?, confirm?} |
Playwright PSC submission (dry-run by default) |
/simulate orchestrates three sources, each with automatic local fallback:
| Source | Mode | Detail |
|---|---|---|
| EJScreen | live → fallback | EPA REST broker queried by lat/lng for the health-burden score + demographics. |
| Census ACS | live → fallback | Census Bureau API at tract level (tract resolved via the FCC Census Block geocoder). Needs CENSUS_API_KEY. |
| GA EPD permits | local static | No public API — served from server/data/epd_permits.json. |
- Every
/simulateresponse includes asourcesblock reportinglivevslocal-fallback, surfaced in the right-panel footer. - The burden time series is synthesized per tract by weighting the effective baseline score against cumulative permit-density growth year over year, then projected with Prophet.
- Bundled fixtures in
server/data/are realistic synthetic data fromgenerate_data.py. Swap in real files with the same schema for production. - Search is an offline local search over tract names (not Mapbox Geocoding).
/file writes into a real public record. It defaults to a dry run that drives the
full Playwright fill flow against a local mock docket and returns confirmation text
without touching the live PSC site. Live filing requires both confirm=true and
FORESIGHT_ALLOW_LIVE_FILING=1, and the live selector mapping in
tools/file_psc.js must be completed first. Do not enable live filing without authorization.
| Symptom | Fix |
|---|---|
| Map shows "needs a Mapbox token" | Add VITE_MAPBOX_TOKEN to client/.env.local and restart Vite. All data/simulation still works without it. |
| Panel shows "Could not reach the server" | Make sure the Flask server is running on port 5000 (Terminal 1). |
EJScreen/Census show local-fallback |
Expected offline or without CENSUS_API_KEY. Use /debug/sources to see the underlying error. |
prophet import errors |
Reinstall into the venv: venv\Scripts\python.exe -m pip install -r requirements.txt. |