factcircuit
FactCircuit: an auditable claim-and-evidence verification loop with immutable provenance, staged checks, bounded retrieval, and reproducible evaluation.
131,000 pedestrian fatality records. 2001–2023.
Translation pending
DataJavaScript · PythonGitHub @eddielathamjonesOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
An interactive map of every recorded pedestrian fatality in the United States from 2001 to 2023 — over 123,000 incidents — drawn from the NHTSA Fatality Analysis Reporting System (FARS).
Each dot on the map is a person who died. The map makes two arguments visually:
1. Darkness kills. Fatalities cluster at dusk, night, and dawn — the solar overlay makes this causally visible, not just decorative.
2. The same roads keep killing. The road heat layer traces the arterial pathology of every city. The same stretches appear year after year.Read the full README ↗An interactive map of every recorded pedestrian fatality in the United States from 2001 to 2023 — over 123,000 incidents — drawn from the NHTSA Fatality Analysis Reporting System (FARS).
Each dot on the map is a person who died. The map makes two arguments visually:
Static mode (default)
Animate mode
Incident popup
Road heat layer
Year range selector
| Layer | Technology | Notes |
|---|---|---|
| Frontend | MapLibre GL JS | Open-source Mapbox fork — no API key |
| Map tiles | OpenFreeMap | Free, no rate limits |
| Solar math | SunCalc.js | Per-slot centroid solar altitude curves |
| Backend | Python / Flask | REST API serving GeoJSON |
| Database | PostgreSQL + PostGIS | Spatial indexing for fast bbox queries |
| ETL | Python (stdlib only) | Processes raw FARS zips → PostGIS |
| Dev environment | Docker Compose | One command to start |
| Deployment target | Render + Neon | Zero ongoing cost |
data/raw/# 1. Clone (sparse — skips the large data zips)
git clone --filter=blob:none https://github.com/eddielathamjones/pedestrian-safety-mapper.git
cd pedestrian-safety-mapper
# 2. Configure environment
cp .env.example .env
# 3. Start PostGIS + Flask
docker compose up -d
# 4. Load data
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Single year
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/pedestrian_safety \
python -m src.data_processing.etl --years 2023 --data-dir data/raw
# All years (requires full data/raw/ checkout)
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/pedestrian_safety \
python -m src.data_processing.etl --years 2001-2023 --data-dir data/raw
# 5. Open http://localhost:5001
python scripts/data_download.py
Downloads all years to data/raw/ as FARS{year}NationalCSV.zip. Decimal lat/lon coordinates are available from 2001 onwards; the ETL filters out records with missing or sentinel coordinates.
NHTSA publishes a new FARS year annually, typically August–October for the prior year. The app checks for this automatically — no manual monitoring needed to know when new data lands:
GET /api/data-status compares the live DB's max year against what's published on NHTSA and is polled once per page load (15-minute cache); if a newer year is available, the frontend shows a "Data available through YYYY" badge.scripts/check_fars_update.py does the same check standalone (python scripts/check_fars_update.py), useful for a cron/CI check outside the running app. It exits non-zero when an update is available.When a new year is available, ingest it:
# 1. Download just the new year (not the full 1975-2024 range)
python scripts/data_download.py --years 2025
# 2. Run the ETL for that year only
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/pedestrian_safety \
python -m src.data_processing.etl --years 2025 --data-dir data/raw
# 3. Update DB_MAX_YEAR in scripts/check_fars_update.py to the new max
The ETL does not deduplicate — it's a plain INSERT, not INSERT ... ON CONFLICT. Only run it for years not already in the DB; re-running it for an already-loaded year will duplicate every row for that year. If a year needs to be reloaded, delete it first: DELETE FROM incidents WHERE year = 2025;.
pedestrian-safety-mapper/
├── src/
│ ├── backend/
│ │ ├── app.py # Flask API
│ │ └── schema.sql # incidents table + GiST spatial index
│ ├── frontend/
│ │ ├── index.html
│ │ ├── css/app.css
│ │ └── js/app.js # map, animation, filter, solar overlay, popup
│ └── data_processing/
│ └── etl.py # FARS zips → PostGIS
├── data/
│ └── raw/ # FARS zip files (not tracked in git)
├── docs/
│ ├── roadmap.md # Design direction and priorities
│ ├── solar-correction.md # Solar position methodology — full technical writeup
│ ├── future-ml-direction.md
│ └── shaping/ # Product design decisions (R, shapes, slices)
├── scripts/
│ └── data_download.py
├── docker-compose.yml
├── requirements.txt
└── .env.example
GET /api/incidentsReturns a GeoJSON FeatureCollection of pedestrian fatalities.
| Parameter | Type | Required | Description |
|---|---|---|---|
year | integer | yes | Year to query (2001–2023) |
bbox | string | no | minLon,minLat,maxLon,maxLat |
Example response:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": { "type": "Point", "coordinates": [-86.69, 33.54] },
"properties": {
"year": 2022, "month": 1, "day": 2,
"hour": 18, "minute": 48,
"lgt_cond": 2, "weather": 1, "route": 5, "rur_urb": 2,
"state": 1, "county": 73,
"age": 27, "sex": 1, "inj_sev": 4
}
}]
}
GET /api/summaryReturns total incident counts by year.
{ "2001": 4389, "2022": 7522 }
| Field | Source | Description |
|---|---|---|
geom | ACCIDENT | Point geometry (EPSG:4326) |
year, month, day | ACCIDENT | Crash date |
hour, minute | ACCIDENT | Local wall-clock time (99 = unknown) |
lgt_cond | ACCIDENT | 1=Daylight, 2=Dark–not lit, 3=Dark–lit, 4=Dawn, 5=Dusk |
weather | ACCIDENT | 1=Clear, 2=Rain, 3=Sleet, 4=Snow, 5=Fog |
route | ACCIDENT | 1=Interstate, 2=US Hwy, 3=State, 4=County, 5=Local |
rur_urb | ACCIDENT | 1=Rural, 2=Urban (available 2013+) |
state, county | ACCIDENT | FIPS codes |
age, sex | PERSON | Victim demographics (FARS sentinel values suppressed in display) |
inj_sev | PERSON | Injury severity (all records = 4, fatal) |
The map applies a solar position overlay rather than using the FARS lgt_cond field directly. Clock time is a social construct that varies by time zone and daylight saving. Solar altitude is a physical measurement.
For each hour slot, the app computes a centroid from all incidents at that hour, then uses SunCalc.js to calculate the sun's altitude at the equinox for that location. This produces a 24-element solar curve that drives the night overlay opacity and Sun HUD display.
Full technical writeup — altitude formula, UTC offset approximation, smoothstep opacity mapping, per-slot centroid method, and known limitations — is in docs/solar-correction.md.
| Slice | Status | Description |
|---|---|---|
| V1 — Data on the map | ✅ Done | Fatalities as interactive points |
| V2 — Year selector | ✅ Done | Multi-year filter (2001–2023) |
| V3 — Incident popup | ✅ Done | Date, time, lighting, weather, demographics, Street View |
| V4 — Viewport loading | ✅ Done | Fetch only visible incidents on pan/zoom |
| V5 — Extended history | ✅ Done | Full 2001–2023 dataset (123k+ incidents) |
| V6 — Solar overlay + animation | ✅ Done | 24h and week animation, solar-corrected night overlay, Sun HUD |
| V7 — Static filter mode | ✅ Done | Time window, day-of-week chips, presets, solar condition label |
| V8 — Road heat layer | ✅ Done | Kernel-density heat lines on roads |
| Phase 2 — Solar terminator | 💡 Planned | Real terminator line as a GeoJSON layer |
| Phase 2 — Sound layer | 💡 Planned | Ambient tone per dot on first appearance |
| Phase 2 — Streetlight overlay | 💡 Planned | OSM streetlight data correlated with fatality clusters |
| Phase 3 — Predictive risk model | 💡 Planned | ML risk scoring from FARS feature data |
| Phase 3 — Street View forensics | 💡 Planned | Embedded panel, thumbnails, vehicle direction |
See docs/roadmap.md for design direction and priorities.
MIT — see LICENSE for details.
Research and educational use. Consult official sources for safety-critical decisions.