How to Build a Hyper-Local Heat Stress Index Using WeatherAPI’s Hourly Forecast

Why a Feels-Like Number Isn’t Enough for Heat Stress

WeatherAPI gives you feelslike_c out of the box, and for most consumer use cases that’s fine. But if you’re building for construction site safety, outdoor event management, sports conditioning, or agricultural worker welfare, you need something more defensible than a single blended number. The Wet Bulb Globe Temperature (WBGT) index — the standard referenced by OSHA, NIOSH, and most occupational health frameworks — is what actually maps to physiological heat strain. You can approximate it well enough to be useful from data the API already gives you.

This isn’t a perfect reconstruction of a field-measured WBGT, which requires a black globe thermometer. The simplified indoor/outdoor WBGT approximations from Liljegren et al. and the ISO 7933 standard get close enough for advisory-level applications. The key inputs are dry-bulb temperature, humidity, solar radiation, and wind speed — all available in WeatherAPI’s hourly forecast.

The Data You Actually Need

Call the /forecast.json endpoint with hourly=1 and at least days=1. Per hour, you want:

  • temp_c — dry-bulb temperature
  • humidity — relative humidity, which you’ll convert to vapour pressure
  • wind_kph — needed for the natural wet-bulb component
  • uv_index — a proxy for solar load when nothing better is available
  • cloud — used to modulate the solar contribution

The solarradiation field is also in the response. If your plan includes it, use it directly — it’s a meaningfully better input than UV index for this calculation. Otherwise UV index plus cloud cover gets you a workable solar load proxy.

The Approximation: Outdoor WBGT Without a Globe Thermometer

The simplified outdoor WBGT formula used in most advisory tools:

WBGT ≈ 0.7 × T_nwb + 0.2 × T_g + 0.1 × T_db

T_nwb is the natural wet-bulb temperature, T_g is the globe temperature, T_db is dry-bulb. Without a physical globe thermometer, you estimate T_g from solar radiation and wind speed, and T_nwb from the psychrometric relationship between dry-bulb temperature and dew point.

Natural wet-bulb temperature can be approximated using the Stull (2011) formula — accurate to within about 1°C for most temperate conditions — from temperature and relative humidity alone:

T_nwb ≈ T * atan(0.151977 * (RH + 8.313659)^0.5)
        + atan(T + RH)
        - atan(RH - 1.676331)
        + 0.00391838 * RH^1.5 * atan(0.023101 * RH)
        - 4.686035

Messy-looking, but it’s a single closed-form expression — no iteration. For globe temperature, a reasonable outdoor estimate under direct solar load is T_db + (solar_radiation / wind_speed_adjustment) × 0.012, clamped to reasonable bounds. Above roughly 80% cloud cover, treat the scene as near-overcast and collapse T_g toward T_db.

Working Python Implementation

Below is a minimal but functional implementation. It pulls hourly data, computes WBGT per hour, and maps the result to OSHA’s four-tier heat stress categories.

import requests
import math

API_KEY = "your_key_here"
LOCATION = "Glasgow"

def stull_wet_bulb(temp_c, rh):
    """Stull (2011) natural wet-bulb approximation."""
    T, H = temp_c, rh
    return (
        T * math.atan(0.151977 * (H + 8.313659) ** 0.5)
        + math.atan(T + H)
        - math.atan(H - 1.676331)
        + 0.00391838 * H ** 1.5 * math.atan(0.023101 * H)
        - 4.686035
    )

def estimate_globe_temp(temp_c, solar_w_m2, wind_kph, cloud_pct):
    """Rough globe temperature estimate for outdoor conditions."""
    wind_ms = max(wind_kph / 3.6, 0.5)  # avoid divide-by-zero
    if cloud_pct >= 80:
        return temp_c  # overcast — globe collapses to dry-bulb
    solar_factor = solar_w_m2 * 0.012 / wind_ms
    return temp_c + min(solar_factor, 12)  # cap at +12C above ambient

def wbgt_outdoor(temp_c, rh, solar_w_m2, wind_kph, cloud_pct):
    t_nwb = stull_wet_bulb(temp_c, rh)
    t_g = estimate_globe_temp(temp_c, solar_w_m2, wind_kph, cloud_pct)
    return 0.7 * t_nwb + 0.2 * t_g + 0.1 * temp_c

def heat_stress_category(wbgt):
    if wbgt < 27.8:
        return "Low"
    elif wbgt < 31.1:
        return "Moderate"
    elif wbgt < 34.4:
        return "High"
    else:
        return "Very High / Dangerous"

url = "http://api.weatherapi.com/v1/forecast.json"
params = {
    "key": API_KEY,
    "q": LOCATION,
    "days": 1,
    "hourly": 1,
    "aqi": "no",
    "alerts": "no"
}

resp = requests.get(url, params=params)
data = resp.json()

for day in data["forecast"]["forecastday"]:
    for hour in day["hour"]:
        temp = hour["temp_c"]
        rh = hour["humidity"]
        wind = hour["wind_kph"]
        cloud = hour["cloud"]
        # Use solarradiation if available, otherwise approximate from UV + cloud
        solar = hour.get("solarradiation", hour["uv_index"] * (1 - cloud / 100) * 40)
        wbgt = wbgt_outdoor(temp, rh, solar, wind, cloud)
        category = heat_stress_category(wbgt)
        print(f"{hour['time']}  WBGT: {wbgt:.1f}°C  [{category}]")

The solarradiation fallback is deliberately rough — UV index times a cloud transmission fraction times a scalar approximation of W/m². If your plan includes the real field, drop the fallback entirely. The difference is most pronounced at midday under partial cloud, and least significant in morning and evening hours.

WBGT Thresholds That Match Published Guidance

The OSHA/NIOSH categories for acclimatized workers doing moderate work:

  • < 27.8°C WBGT — Low risk, normal work pace
  • 27.8–31.1°C — Moderate, increase rest-to-work ratio
  • 31.1–34.4°C — High, heavy work restrictions kick in
  • > 34.4°C — Very High to Dangerous, limit to brief exposures only

These shift downward for unacclimatized workers — knock roughly 2–3°C off each threshold. The American College of Sports Medicine uses similar but not identical thresholds for athletic events. The WBGT number you compute doesn't change depending on which framework you use — only the interpretation table does.

Where This Approximation Breaks Down

The Stull formula drifts noticeably above about 40°C and 80%+ relative humidity simultaneously — which is exactly the regime you care most about in a severe heat event. At those extremes, an iterative psychrometric solver using dew point is more reliable. WeatherAPI provides dewpoint_c in the hourly response, so you can swap in that approach for the hours where it matters most.

Globe temperature is where the most uncertainty sits. A real black globe thermometer in direct midday sun can read 15–20°C above ambient; our formula will underestimate that in calm wind, high-solar scenarios. For indoor shade conditions — workers in a metal building rather than open sun — drop the T_g term entirely and use the simpler indoor WBGT formula: 0.7 × T_nwb + 0.3 × T_db.

WBGT is also a population-level index, not an individual risk predictor. Age, fitness, medication, and clothing shift individual tolerance well outside what any meteorological index captures.

One Pattern Worth Building In

Rather than surfacing peak hourly WBGT, compute it across all 24 hourly slots and find the rolling 2-hour window with the highest sustained value. A single hot hour at 1pm is a different operational problem than 34°C WBGT held from noon to 3pm. The hourly granularity in the forecast response makes this a small addition, and it gives a safety manager something they can actually act on rather than a daily max figure.

If you're already pulling hourly data for another purpose, the marginal cost of adding WBGT is a handful of arithmetic lines. Whether that's worth it depends on your domain — but for any occupational or sporting context where heat exposure has liability attached, a defensible index beats a feels-like number every time someone asks how you arrived at the threshold.

Scroll to Top