You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browser (HTML/JS/Chart.js/Leaflet)
↕ HTTP / JSON
FastAPI (Python 3.x)
↕ ↕ ↕ ↕
SQLite UCDP GED API ACAPS/INFORM API GeoNames API
(evacuation_risk.db) (ucdp.uu.se/api) (api.acaps.org) (api.geonames.org)
All computation runs server-side in Python. The browser receives JSON and renders results — no client-side calculation of cost or mortality figures except for the real-time UI feedback path in app.js (which replicates the server-side formulas for instant response without a round-trip).
Tech Stack
Layer
Technology
Backend framework
FastAPI 0.111.0
ASGI server
Uvicorn 0.29.0
Data validation
Pydantic 2.7.1
Database
SQLite (via stdlib sqlite3)
AI context
Anthropic SDK ≥0.107.0
HTTP client
requests ≥2.31.0
Config
python-dotenv ≥1.0.0
Frontend framework
Bootstrap 5.3.3
Charts
Chart.js 4.4.3
Maps
Leaflet 1.9.4
Topology
TopoJSON client 3.1.0
Markdown render
marked.js 9.1.6
Calibration
scipy 1.13.1, statsmodels 0.14.6, numpy
File Structure
evacuation-risk-tool/
├── main.py # FastAPI app, all API endpoints, startup hook
├── calculators.py # Core computation: risk, resources, mortality, remaining costs
├── database.py # SQLite schema, migrations, CRUD for saved scenarios
├── historical_data.py # 31 documented conflict cases (1991–2024) with full metadata
├── demographic_data.py # 18-country vulnerable population dataset (UNICEF/UN DESA 2023)
├── acaps_data.py # ACAPS/INFORM API client with in-memory caching
├── world_risk.py # World risk index: ACAPS data → country-level risk levels
├── context_ai.py # Claude API integration for country context narratives
├── air_evac.py # Air evacuation cost model (UNHAS rate, sorties, fleet)
├── walking_evac.py # Walking evacuation model (speed, attrition, days)
├── weather_data.py # Seasonal terrain factor calculation by lat/month
├── ucdp_data.py # UCDP GED v26.1 API client and CSV fallback
├── requirements.txt # Python dependencies
├── evacuation_risk.db # SQLite database (auto-created on startup)
├── data/
│ └── ged261.csv # UCDP GED v26.1 local CSV (fallback when API unavailable)
├── static/
│ ├── index.html # Single-page application (all tabs, modals, UI)
│ ├── app.js # All frontend logic (~4,900 lines)
│ └── countries-110m.json # TopoJSON for world map choropleth
├── calibration/
│ ├── calibrate.py # Run v7 model against 16 in-scope cases; R², LOOCV, within-2×
│ ├── full_calibration.py # Optimise base rates + α via differential_evolution (scipy)
│ └── validate_v7.py # Full statistical validation: Shapiro-Wilk, BP, Cook's D, etc.
├── CONCEPT.md # Concept document: problem, ethics, philosophy, limitations
├── ARCHITECTURE.md # This file
├── BACKLOG.md # Product backlog and known technical debt
└── README.md # Project overview, calibration metrics, installation
Key Modules
calculators.py
Function
Description
calculate_risk(scores)
7-dimension weighted score → risk level (0–4) + NATO equivalent
infra_denial_mult(flag, d1, d4)
Infrastructure-denial multiplier (α=0.4251) for 4 calibration cases
Calculate air evacuation (sorties, cost) — fixed-wing/helicopter transport mode
POST
/api/walking-evacuation
Calculate walking evacuation (days, attrition) — walking transport mode
POST
/api/climate
Get climate/seasonal context for a location
GET
/api/historical-cases
List all 31 historical cases with enriched data
GET
/api/historical-cases/{id}
Get a single historical case
GET
/api/city-population/{name}
GeoNames city population lookup
GET
/api/demographics/{country}
Vulnerable population % suggestion
GET
/api/commodity-prices/{iso3}
EIA (Brent crude) + FRED (food basket) live prices for Market Price Adjustment
GET
/api/ucdp
UCDP GED event query (date/bbox filter)
GET
/api/world-risk
All country risk levels (ACAPS/INFORM)
GET
/api/iso-lookup
ISO3 → country name lookup table
POST
/api/country-context
Claude API country narrative (AI-generated)
GET
/api/acaps/{iso3}
Raw ACAPS data for a country
GET
/api/country-context-acaps/{iso3}
ACAPS-enriched country context
Note: risk/resources/staying-cost/remaining-cost calculations and the scenario-update (PUT)
route were removed from the API — the frontend computes all of these client-side
(calcRisk/calcResources/calcStay/calcRemaining in static/app.js) and no UI flow
ever called the backend versions or the PUT route. calculate_risk/calculate_resources/
calculate_staying_costs/calculate_remaining_costs remain in calculators.py as the
reference Python implementations (kept in numeric parity with their JS counterparts) but are
no longer exposed as standalone endpoints. /api/ucdp/status and /api/world-risk/{iso3}
were removed for the same reason (confirmed zero frontend callers).
Data Flow
User input (sliders, fields)
↓
updateAll() [app.js]
↓
calcRisk() / calcResources() / calcStay() / calcRemaining() [client-side replicas]
↓
DOM update (cards, charts, decision analysis)
[On save] POST /api/scenarios → SQLite
[On map pin] haversine(conflict, safezone) → state.distanceKm
[On city select] GET /api/city-population → GeoNames API
[On UCDP] GET /api/ucdp → UCDP GED API or ged261.csv fallback
[On world map] GET /api/world-risk → ACAPS/INFORM API (cached per session)
[On country ctx] POST /api/country-context → Anthropic Claude API
Database Schema
SQLite, single table scenarios with versioned migrations (_migrate_db):
CREATETABLEscenarios (
id INTEGERPRIMARY KEY AUTOINCREMENT,
name TEXTNOT NULL,
description TEXT DEFAULT '',
population INTEGERNOT NULL,
vulnerable_pct REAL DEFAULT 20.0,
distance_km REAL DEFAULT 50.0,
d1_kinetic REALNOT NULL,
d2_vulnerability REALNOT NULL,
d3_political REALNOT NULL,
d4_logistics REALNOT NULL,
d5_destination REALNOT NULL,
d6_urgency REALNOT NULL,
d7_information REALNOT NULL,
risk_score REAL,
risk_level INTEGER,
risk_label TEXT,
nato_equivalent TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
-- Migration v1 (geolocation + operational columns):
conflict_lat REAL DEFAULT NULL,
conflict_lng REAL DEFAULT NULL,
safe_zone_lat REAL DEFAULT NULL,
safe_zone_lng REAL DEFAULT NULL,
safe_zone_name TEXT DEFAULT NULL,
distance_source TEXT DEFAULT 'manual',
road_factor_applied INTEGER DEFAULT 0,
haversine_km REAL DEFAULT NULL,
terrain INTEGER DEFAULT 3,
conflict_pattern INTEGER DEFAULT 5
);