Scientific methodology

Every number, traced to its formula.

This page documents the platform as implemented: the data it reads, the equations it applies, the weights and thresholds it uses, and what it does not yet do. Where something is not documented in the project, it says so.

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.

ParameterMeaningUsed for
T2M, T2M_MAX, T2M_MINAir temperature at 2 m (°C)HTSI, forecast features
RH2MRelative humidity at 2 m (%)HTSI, forecast features
WS2MWind speed at 2 m (m/s)HTSI, forecast features
ALLSKY_SFC_SW_DWNAll-sky surface shortwave irradianceHTSI, forecast features
PS, PRECTOTCORRSurface pressure, precipitationStored 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.

FeatureDefinition
temperature_c, temperature_max_cDaily mean and maximum (maximum falls back to mean + 3 °C)
humidity_pct, wind_speed_ms, solar_radiation_wm2Raw daily values
temp_3day_mean, temp_3day_max, temp_7day_meanRolling windows with minimum one period
temp_anomalyTemperature minus its expanding mean
humidity_7day_meanRolling 7-day humidity
heat_index_estTemperature + 0.05 × humidity (model feature only)
day_of_year_sin, day_of_year_cossin 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 termAdds 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
ClassHTSI rangeConfigured by
Safe0 to 30HTSI_SAFE_MAX
Cautionabove 30 to 55HTSI_CAUTION_MAX
Dangerabove 55 to 80HTSI_DANGER_MAX
Extreme Dangerabove 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.

ModelConfigurationEnsemble weight
XGBoost400 estimators, depth 5, learning rate 0.05, subsample 0.85, colsample 0.85, scale_pos_weight = negatives / positives45%
Random Forest300 trees, depth 10, min 3 samples per leaf, balanced class weights30%
LSTM2 layers, 32 hidden units, dropout 0.2, head 32→16→1 with sigmoid, 15 epochs, Adam 1e-3, batch 64, BCE loss25%

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.

SeverityProbabilityExpected duration (days)
Lowbelow 30%1 + 2P
Moderate30% to 55%2 + 3P
High55% to 80%3 + 4P
Extreme80% and above4 + 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.

MethodEndpointPurpose
GET/api/geo/states · /districts · /wardsState, 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-wardsActive alert and ward ranking
POST/api/thermal/computeAd hoc HTSI from weather and profile inputs
GET / POST/api/thermal/ward/{id}/latest · /refresh · /breakdownWard records, recompute, driver breakdown
GET/api/forecast/ward/{id}/5-day · /latest-ensembleFive-day forecast and stored prediction
GET / POST/api/alerts · /{id} · /{id}/dismissAlert log and acknowledgement
GET / POST/api/gis/wards/heatmap · /hotspots · /wards/{id}/boundaryWard GeoJSON, UHI points, boundary upload or OSM fetch
POST / GET/api/uhi/ward/{id}/analyze · /historyUrban heat island pipeline
GET/api/hospitals · /surge-predictionsFacilities and surge estimates
GET/api/cooling-centers · /recommendDistance-sorted centers and best-center recommendation
GET / POST/api/weather/ward/{id} · /ingestObservation history and NASA POWER ingestion
GET / POST/api/health-guidance/general-tips · /personalizedSafety tips and personalised HTSI
POST/api/citizen/risk-assessmentCitizen Heat Risk Engine
GET/api/explainability/ward/{id}/heatwave · /htsiSigned forecast drivers, HTSI drivers
POST / GET/api/auth/register · /login · /meAccounts and JWT access tokens
GET/api/government/district/{id}/overview · /state/{id}/overviewRole-gated officer overviews
GET/api/settings/thresholds · /system-infoConfiguration 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.
See it in the command dashboard
Made with Modulify