01
Data sources
Historical weather comes from NASA's POWER daily point API (community AG), requested for each ward centroid with a two-day latency allowance. Solar radiation arrives in MJ/m²/day and is converted to a mean W/m² by multiplying by 1,000,000 and dividing by 86,400.
| Parameter | Meaning | Used for |
|---|---|---|
| T2M, T2M_MAX, T2M_MIN | Air temperature at 2 m (°C) | HTSI, forecast features |
| RH2M | Relative humidity at 2 m (%) | HTSI, forecast features |
| WS2M | Wind speed at 2 m (m/s) | HTSI, forecast features |
| ALLSKY_SFC_SW_DWN | All-sky surface shortwave irradiance | HTSI, forecast features |
| PS, PRECTOTCORR | Surface pressure, precipitation | Stored only |
A forward leg from the OpenWeather 5-day / 3-hour forecast is merged into the history when an API key is configured. Its daily solar value is estimated from cloud cover as 850 × (1 − cloud/100) × 0.55 W/m². Without a key the forecast reads history only.
The dataset loaded today is the project's seeded demonstration history: 3 states, 5 districts and 21 wards, with hospitals, cooling centers and 46 days of synthetic weather per ward generated by a seasonal model (base 25 °C, 12 °C amplitude peaking in late May) with random summer heat bursts. Interfaces label it as such.
02
Inputs and feature engineering
For the forecast, the last 30 observations of a ward are sorted by date and turned into 13 tabular features. The LSTM reads 7-day sequences of the four raw signals.
| Feature | Definition |
|---|---|
| temperature_c, temperature_max_c | Daily mean and maximum (maximum falls back to mean + 3 °C) |
| humidity_pct, wind_speed_ms, solar_radiation_wm2 | Raw daily values |
| temp_3day_mean, temp_3day_max, temp_7day_mean | Rolling windows with minimum one period |
| temp_anomaly | Temperature minus its expanding mean |
| humidity_7day_mean | Rolling 7-day humidity |
| heat_index_est | Temperature + 0.05 × humidity (model feature only) |
| day_of_year_sin, day_of_year_cos | sin and cos of 2π × day of year / 365.25 |
03
Human Thermal Stress Index
Three indices are computed from temperature T (°C), relative humidity RH (%), wind W (m/s) and solar radiation S (W/m²).
Heat Index (NOAA/NWS Rothfusz regression)
T_F = T × 9/5 + 32; simple = 0.5 × (T_F + 61 + (T_F − 68) × 1.2 + RH × 0.094)
If (simple + T_F)/2 ≥ 80: HI = −42.379 + 2.04901523 T_F + 10.14333127 RH − 0.22475541 T_F RH − 0.00683783 T_F² − 0.05481717 RH² + 0.00122874 T_F² RH + 0.00085282 T_F RH² − 0.00000199 T_F² RH²
Low-humidity and high-humidity adjustments per NWS; result converted back to °C.
WBGT (simplified outdoor approximation)
T_wet = T × (0.45 + 0.55 × RH/100)
T_globe = max(T, T + S/1000 × 8 − min(W, 6) × 0.5)
WBGT = 0.7 T_wet + 0.2 T_globe + 0.1 T
UTCI (reduced first-order approximation)
T_mrt = T + S/1000 × 12; W10 = max(W, 0.5)
UTCI = T + 0.607 (T_mrt − T) − 2.265 (W10 − 0.5) + 0.0091 (RH − 50) − 0.0004 (RH − 50) T
This is not the full 6th-order Bröde et al. (2012) polynomial; the original code keeps only the dominant terms.
Composite score
scale(x, lo, hi) = clamp((x − lo)/(hi − lo) × 100, 0, 100)
base = 0.40 scale(HI, 27, 54) + 0.35 scale(WBGT, 23, 35) + 0.25 scale(UTCI, 26, 46)
HTSI = min(100, base × vulnerability)
| Vulnerability term | Adds to the multiplier (starts at 1.0) |
|---|---|
| Age 65 or older, or 5 or younger | +0.15 |
| Chronic condition | +0.15 |
| Outdoor worker | +0.10 |
| Gender female (coarse proxy for pregnancy risk) | +0.03 |
| Daily sun exposure above 2 hours | +0.03 per hour, up to +0.25 |
| Class | HTSI range | Configured by |
|---|---|---|
| Safe | 0 to 30 | HTSI_SAFE_MAX |
| Caution | above 30 to 55 | HTSI_CAUTION_MAX |
| Danger | above 55 to 80 | HTSI_DANGER_MAX |
| Extreme Danger | above 80 |
The driver breakdown normalises five raw weights to 100%: max(T − 20, 0) for temperature, max(RH − 30, 0) × 0.5 for humidity, max(4 − W, 0) × 3 for still air, S/20 for solar load and (multiplier − 1) × 100 for vulnerability. The Citizen Heat Risk Engine applies the same HTSI with gender and exposure, treats 4 or more outdoor hours as outdoor work, and multiplies by 1.08 for high-exposure occupations such as construction, farming, delivery, vending, police and traffic duty.
04
Heatwave forecast
Training labels mark a day as part of a heatwave when temperature is at or above the 90th percentile for at least three consecutive days. The training script generates a synthetic history from 2015 to 2025 (seed 42), splits it 80/20 with stratification and trains three models.
| Model | Configuration | Ensemble weight |
|---|---|---|
| XGBoost | 400 estimators, depth 5, learning rate 0.05, subsample 0.85, colsample 0.85, scale_pos_weight = negatives / positives | 45% |
| Random Forest | 300 trees, depth 10, min 3 samples per leaf, balanced class weights | 30% |
| LSTM | 2 layers, 32 hidden units, dropout 0.2, head 32→16→1 with sigmoid, 15 epochs, Adam 1e-3, batch 64, BCE loss | 25% |
With trained models, the blended probability is multiplied by max(0.6, 1 − 0.04 × (horizon − 1)).
In this deployment the trained model files are not present, because they are produced by a Python job that does not run here. The original code's fallback therefore produces every forecast: P = 1 / (1 + e^(−(Δ − 3)/2)), with Δ the mean of the last three days minus the mean of the 30-day window, clipped to 1% to 99%. The same probability is reported for all five days and in all three model fields.
| Severity | Probability | Expected duration (days) |
|---|---|---|
| Low | below 30% | 1 + 2P |
| Moderate | 30% to 55% | 2 + 3P |
| High | 55% to 80% | 3 + 4P |
| Extreme | 80% and above | 4 + 5P |
Model evaluation: the training script prints a test accuracy for the Random Forest and XGBoost models, but no results are recorded in the project. THERMORA therefore publishes no accuracy, precision or recall figures.
05
Explainability
Forecast drivers compare the latest day to a mild baseline (28 °C mean, 32 °C maximum, 45% humidity, 3.5 m/s wind, 350 W/m² solar). Each deviation is grouped into Temperature, Humidity, Wind (sign inverted, since wind is protective) and Solar Radiation. A health-vulnerability term of 60 × elderly share + 20 × impervious share is added, and all terms are expressed as signed shares of their absolute total. With trained models, the deviations are additionally weighted by the averaged XGBoost and Random Forest feature importances.
06
Hospital surge model
cases = population/100,000 × 8 × (HTSI/100)^1.8 × (1 + elderly share + 0.3 × impervious share)
admissions = 0.35 × cases; ICU = 0.12 × admissions; outpatient = 4.5 × admissions
capacity use = min(100, admissions / available beds × 100)
strain = 0.45 × projected occupancy + 0.35 × ICU strain + 0.20 × outpatient strain
Strain below 40 is Normal, below 70 Elevated, below 90 High, otherwise Critical. Constants are epidemiologically inspired and have not been fitted to admissions data.
07
Alerting
Whenever a ward's HTSI is recomputed and reaches ALERT_RISK_THRESHOLD (55), an alert is created with the class, the score and the recommended action, and it expires after 12 hours. A refresh job recomputes every ward from its latest observation; the original scheduled it every 30 minutes, and here it runs whenever the site's scheduled job calls it. Each alert is sent to active users registered in that ward by push, email, SMS and WhatsApp; a channel sends only when its provider credentials are configured and otherwise records a dry run.
08
Architecture and API
The original FastAPI service has been rebuilt as this site's own server with the same paths, parameters and response shapes, backed by a SQLite database with the original tables. Repeated reads are cached in memory for 15 seconds (KPIs) to one hour (states). Passwords are hashed with bcrypt and sessions use HS256 JWT tokens.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/geo/states · /districts · /wards | State, district and ward hierarchy |
| GET | /api/dashboard/kpis?ward_id= | Latest HTSI, Heat Index, WBGT, UTCI for a ward |
| GET | /api/dashboard/active-alert · /top-risk-wards | Active alert and ward ranking |
| POST | /api/thermal/compute | Ad hoc HTSI from weather and profile inputs |
| GET / POST | /api/thermal/ward/{id}/latest · /refresh · /breakdown | Ward records, recompute, driver breakdown |
| GET | /api/forecast/ward/{id}/5-day · /latest-ensemble | Five-day forecast and stored prediction |
| GET / POST | /api/alerts · /{id} · /{id}/dismiss | Alert log and acknowledgement |
| GET / POST | /api/gis/wards/heatmap · /hotspots · /wards/{id}/boundary | Ward GeoJSON, UHI points, boundary upload or OSM fetch |
| POST / GET | /api/uhi/ward/{id}/analyze · /history | Urban heat island pipeline |
| GET | /api/hospitals · /surge-predictions | Facilities and surge estimates |
| GET | /api/cooling-centers · /recommend | Distance-sorted centers and best-center recommendation |
| GET / POST | /api/weather/ward/{id} · /ingest | Observation history and NASA POWER ingestion |
| GET / POST | /api/health-guidance/general-tips · /personalized | Safety tips and personalised HTSI |
| POST | /api/citizen/risk-assessment | Citizen Heat Risk Engine |
| GET | /api/explainability/ward/{id}/heatwave · /htsi | Signed forecast drivers, HTSI drivers |
| POST / GET | /api/auth/register · /login · /me | Accounts and JWT access tokens |
| GET | /api/government/district/{id}/overview · /state/{id}/overview | Role-gated officer overviews |
| GET | /api/settings/thresholds · /system-info | Configuration and status |
09
Limitations
- Weather is a synthetic demonstration history until NASA POWER data is ingested for a ward; seasonal values for the current date are generally mild.
- Forecasts use the fallback curve rather than trained models, and no model accuracy is documented.
- Ward boundaries are squares around each centroid until official boundaries are uploaded or fetched from OpenStreetMap.
- The WBGT and UTCI formulas are simplified approximations, not full physical models.
- Hospital surge numbers are directional estimates, not calibrated on admissions data.
- The urban heat island pipeline runs on synthetic bands until real Sentinel-2 or Landsat-8 scenes are connected.
- Officer account creation is open in this version, as in the original API, so the console should not hold sensitive data until access is restricted.
- HTSI is decision support. It is not a medical diagnosis or an official meteorological warning.