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 temperaturehumidity— relative humidity, which you’ll convert to vapour pressurewind_kph— needed for the natural wet-bulb componentuv_index— a proxy for solar load when nothing better is availablecloud— 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.
