When every summer storm is a high-severity alert
A safety-monitoring side project I run paged me at 09:00 with "dangerous weather conditions near you. Conditions: Thunderstorm." It was a routine summer pop-up storm on the Gulf Coast. Nothing dangerous, but if you live where afternoon thunderheads are weekly, every one of them ringing your phone makes you stop trusting the alerts.
What was happening
The weather cron pulled forecasts from Open-Meteo, took the WMO weather code, and mapped it to a severity. The SMS gate fires at HIGH or above. The mapping was:
- code 95 (thunderstorm) → SEVERITY_HIGH
- code 96 / 99 (thunderstorm with hail) → SEVERITY_EXTREME
So any plain thunderstorm anywhere in the forecast window pushed an SMS to emergency contacts. In a place where that happens four afternoons a week, contacts learn to ignore you.
What I found
Code 95 is a junk drawer. Open-Meteo emits it for everything from "some lightning forecast 30 km away" to a genuine cell on top of you. Severity has to come from co-occurring conditions, not the code alone.
The fix
Standalone code 95 is now SEVERITY_LOW (in-app feed entry, no SMS). It only escalates back to HIGH when a hazard factor co-occurs in the same evaluation:
$hazardFactors = [
'very_heavy_precipitation',
'extreme_wind',
'high_wind_gusts',
'freezing_rain',
'freezing_drizzle',
];
if ($code === 95 && array_intersect($hazardFactors, $activeFactors)) {
$severity = SEVERITY_HIGH;
$reason = 'thunderstorm_with_hazard';
} elseif ($code === 95) {
$severity = SEVERITY_LOW;
}
Codes 96 and 99 stay EXTREME — hail is its own thing.
Smoke-tested by invoking the cron Lambda directly and watching the next evaluation cycle classify a passing storm correctly.
What I'd do differently
The original mapping was a one-line guess made from a table on Open-Meteo's docs page. It worked great in dev because dev didn't get four storms a week. Climate of the test environment matters. Next time I touch a severity table, I want a 30-day replay against historical forecast pulls before I trust it.