When we built the WBGT heat stress calculator for @smartqhse/hse-calculators, the first thing we learned is that most heat stress calculators on the internet are wrong. Not cosmetically wrong. Structurally wrong.
Here's what they typically do:
// Wrong: uses temperature instead of WBGT
if (temp > 38) return 'danger';
Here's what ACGIH's TLV actually requires: a Wet Bulb Globe Temperature (WBGT) value, compared against a four-axis lookup table with dimensions of:
- Metabolic rate — sedentary, light, moderate, heavy, very heavy
- Acclimatisation status — acclimatised vs unacclimatised
- WBGT value — the output of the three-sensor reading
- Work/rest ratio — continuous work, 75/25, 50/50, 25/75
The output isn't a binary safe/danger. It's a work-rest ratio recommendation that determines how much of each hour a worker can perform the given metabolic task.
How WBGT is calculated
WBGT requires three measurements:
- Twb — natural wet bulb temperature (humidity)
- Tg — black globe temperature (radiant heat)
- Tdb — dry bulb temperature (air temperature)
Outdoor formula: WBGT = 0.7 × Twb + 0.2 × Tg + 0.1 × Tdb
Indoor formula: WBGT = 0.7 × Twb + 0.3 × Tg
The weighting of 0.7 on the wet bulb isn't arbitrary — it reflects that evaporative cooling (sweat) accounts for ~70% of the human body's heat dissipation capacity. When humidity is high, evaporative cooling fails, and WBGT rises even without temperature change.
The metabolic rate lookup
export const METABOLIC_RATES = {
sedentary: { value: 65, description: 'Sitting, light arm work' },
light: { value: 130, description: 'Standing, moderate arm/hand work' },
moderate: { value: 200, description: 'Walking, carrying moderate loads' },
heavy: { value: 260, description: 'Digging, climbing, carrying heavy loads' },
veryHeavy: { value: 400, description: 'Shoveling wet sand, running with load' },
} as const;
The ACGIH TLV table maps (metabolic rate × acclimatisation status → WBGT ceiling per work/rest pattern). When the field WBGT exceeds the ceiling for the worker's task, you step down to the next work/rest ratio.
The full calculator function
import { wbgtHeatStress } from '@smartqhse/hse-calculators';
const result = wbgtHeatStress({
tdb: 42, // dry bulb °C
tw: 28, // wet bulb °C
tg: 55, // globe temperature °C (sun-exposed steel surface)
workload: 'heavy',
acclimatised: true,
outdoor: true,
});
// result:
// {
// wbgt: 33.6,
// tlvCeiling: 28.0, // ACGIH TLV for acclimatised/heavy
// exceedance: 5.6, // °C above TLV
// recommendedWorkRest: '0/60', // complete cessation for this hour
// risk: 'extreme',
// acgihReference: 'ACGIH TLV-TWA 2024 Table 1',
// }
The tg: 55 input is the realistic black globe temperature for steel decking in Dubai in July — not 42°C ambient. That 13°C difference is why site-measured WBGT consistently runs higher than "feels like" temperature apps.
The edge cases that tripped us up
1. Indoor vs outdoor formula. Most implementations use the outdoor formula for everything. Indoor environments (e.g., a partially enclosed industrial building with a metal roof) don't have solar load, so the formula drops the 0.1 × Tdb term and redistributes to globe temperature.
2. Acclimatisation flag. The ACGIH table has separate columns for acclimatised and unacclimatised workers. For a new hire in their first week, the TLV ceiling is 3-5°C lower. Failing to account for this correctly overstates safe exposure time for the highest-risk population.
3. Very heavy metabolic rate. Most tooling stops at "heavy." ACGIH's TLV table includes "very heavy" (shovelling wet sand, running with a load). For that category, ceiling values are lower still and complete cessation is required above ~26°C WBGT.
Install
npm install @smartqhse/hse-calculators
# or
pip install smartqhse-hse-calculators
Source: https://github.com/SmartQHSE/hse-calculators
Embeddable widget: https://tools.smartqhse.com/wbgt-calculator
Any corrections on the TLV table mapping — especially if you're an industrial hygienist who uses this daily — open an issue. The calculation is only as good as the lookup table.
Top comments (0)