Terris exists to make environmental context inspectable.
- Combine scattered federal site datasets into one normalized geospatial index.
- Provide consistent proximity and density signals with explicit scoring logic.
- Surface evidence and drivers so users can verify what influenced each result.
Terris is a screening tool. It does not measure contaminants, diagnose exposure, or replace official records and local testing.
- Deterministic scoring (
v2) with no AI dependency in/analyze. - Transparent breakdown by source signal:
landfill_proximity,military_proximity,industrial_density,superfund_proximity. - Evidence lists per category (nearest records with distances).
- Deterministic
/reportendpoint with structured rationale and cached responses. - Fast startup indexing with
BallTree(haversine) and no per-request CSV reads. - Frontend map workflow with click-to-analyze, geocoding, and heat layers.
- Data preparation (
scripts/) normalizes raw datasets into:data/processed/landfill.csvdata/processed/military_base.csvdata/processed/industrial_frs.csvdata/processed/superfund_npl.csvdata/processed/all_sites.csv- Industrial rows are derived from the FRS National Facility file.
- Backend startup loads
all_sites.csvonce and builds category-specific spatial indices. POST /analyzecomputes nearest distances and density counts around a point.- Score engine applies fixed thresholds and clamps total to
0-10. - Frontend renders score, band, drivers, and evidence for inspection.
Data + startup guarantees
- Dataset is loaded once at app startup.
- BallTrees are built once per category.
- No per-request CSV reads.
- Output is deterministic for the same input coordinates and dataset.
Reference:
Low:0.00 - 3.30Moderate:3.31 - 6.60High:6.61 - 10.00
Inspectable thresholds (scoring v2)
< 1.0 mi→+3.0< 3.0 mi→+2.0< 10.0 mi→+1.0
< 1.0 mi→+3.0< 5.0 mi→+2.0< 15.0 mi→+1.0
- Within
1 mi:>= 25sites →+2.0>= 10sites →+1.5>= 1site →+1.0
- Within
3 mi:>= 200sites →+2.0>= 75sites →+1.5>= 10sites →+1.0
- Within
10 mi:>= 1500sites →+1.5>= 600sites →+1.0>= 200sites →+0.5
- Nearest industrial proximity:
< 0.5 mi→+1.0< 1.5 mi→+0.5
< 1.0 mi→+4.0< 3.0 mi→+3.0< 10.0 mi→+2.0< 25.0 mi→+1.0
Implementation source:
flowchart LR
A["Raw Source Files<br/>data/raw"] --> B["Pipeline Scripts<br/>scripts/"]
B --> C["Processed Dataset<br/>data/processed/all_sites.csv"]
C --> D["FastAPI Backend<br/>startup load + BallTree index"]
D --> E["Analyze + Report Endpoints<br/>deterministic responses"]
E --> F["Next.js Frontend<br/>map, score, evidence, report UI"]
- Frontend (
frontend/)- Next.js 14 + TypeScript + Tailwind + Leaflet
- Map interaction, geocoding, heat overlays, response rendering
- Backend (
backend/)- FastAPI + Pydantic + pandas + scikit-learn BallTree
- Deterministic scoring + report generation
- Data Pipeline (
scripts/)- Normalization and export to processed CSV artifacts
Repository layout
.
├── backend/
│ └── app/
├── frontend/
│ ├── app/
│ ├── components/
│ └── lib/
├── data/
│ ├── raw/
│ └── processed/
├── scripts/
└── README.md
Base URL (local): http://localhost:8000
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness + dataset/version status |
GET |
/stats |
Dataset counts + startup/load metrics |
POST |
/analyze |
Deterministic scoring + evidence for { lat, lon } |
POST |
/report |
Deterministic structured explanation for { lat, lon } |
{
"lat": 40.7128,
"lon": -74.0060
}signals: nearest distances + density countsscore.total: clamped0-10score.breakdown: category pointsscore.band:Low | Moderate | Highscore.top_drivers: ranked text driversevidence: nearest records by category
cURL quick checks
curl -s http://localhost:8000/health
curl -s http://localhost:8000/stats
curl -s -X POST http://localhost:8000/analyze \
-H "Content-Type: application/json" \
-d '{"lat":40.7128,"lon":-74.0060}'- Python 3
- Node.js 18+
python3 -m venv .venv
source .venv/bin/activate
pip install -r backend/requirements.txt
python -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8000cd frontend
npm install
cp .env.example .env.local
npm run devSet frontend/.env.local:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000python scripts/smoke_test.py