The Field You Shouldn’t Trust Alone
precip_type returns a string — rain, snow, sleet, freezing rain — and most integrations just pass it through to the UI or use it to trigger an alert. Near the freezing line, that’s a problem. The field is collapsing a genuinely ambiguous multi-layer atmospheric state into a single label, and the logic that picks that label can get it wrong in ways that matter for certain applications.
If you’re building anything that cares about road surfaces, aircraft de-icing windows, or agricultural frost damage, you need to know what’s actually happening here — and you need a fallback when the field gives you the wrong answer.
What the Field Is Actually Encoding
Precipitation phase near 0°C isn’t determined at the surface. It’s determined by what happens to a hydrometeor as it falls through the full atmospheric column above you. A snowflake can melt into rain at 700 meters AGL, refreeze into sleet through a shallow cold layer, then arrive as rain if the surface air is just warm enough. The surface temperature you get from temp_c tells you almost nothing about what’s happening at 850 hPa.
Model-derived precip_type values — which is what most API responses including ours are serving — are computed from that full column profile, from GFS, NAM, or ECMWF depending on location and forecast horizon. That’s the right approach. But models get it wrong more often near phase boundaries, and the label that survives into the API response doesn’t tell you how confident the model was. There’s no precip_type_confidence field. It’s a hard categorical output from a probabilistic process.
The Three Failure Modes Worth Building Around
1. Surface Temperature Is Near 0°C but Not Below It
If temp_c is sitting at 1.5°C and the response says rain, that’s plausible — but typical NWP surface temperature error at short timescales is wide enough to straddle the phase boundary. The model isn’t wrong about what it predicted; the uncertainty band around 1.5°C is just large enough that the label becomes unreliable. This is the most common failure mode we see.
The fix is treating any temp_c within roughly ±2°C of 0 as an ambiguous zone and layering in other signals rather than relying on the precip_type label alone.
2. Dew Point and Temperature Diverge
When temp_c is 3°C but dewpoint_c is -1°C, that spread tells you the boundary layer is dry. Evaporative cooling during precipitation can push the effective temperature below freezing even when air temperature says it shouldn’t. This is the classic scenario for unexpected road icing — the precip label says rain, the surface reads fine, but the dew point depression is your warning.
The dew point field isn’t always surfaced directly by every endpoint, but you can derive it from humidity and temp_c using the Magnus approximation:
// Magnus approximation for dew point
// temp in °C, humidity as % (0-100)
function dewPoint(tempC, humidity) {
const a = 17.625;
const b = 243.04;
const alpha = (a * tempC) / (b + tempC) + Math.log(humidity / 100.0);
return (b * alpha) / (a - alpha);
}
If dewPoint(temp_c, humidity) < -1.0 and precip_mm > 0, flag it regardless of what precip_type says.
3. Condition Code Contradicts precip_type
WeatherAPI maps raw observations and model output to our own numeric condition codes — that mapping is ours, not a third-party lookup. Those codes carry implicit phase information. Condition codes in the 1200–1282 range (freezing drizzle, freezing rain, sleet, blizzard) are traveling a different signal path than precip_type. If the condition code points at frozen precip but precip_type says rain, you have a genuine disagreement in the data — not necessarily an error, but not something to silently reconcile either.
The condition code might be derived from a METAR observation while precip_type is model-derived. Two sources disagreeing is actually useful signal, if your pipeline is listening for it.
A Practical Phase Detection Layer
Here’s the decision logic worth building, in order of confidence:
- If
temp_c > 3°Cand dew point > 0°C: Trustrain. You’re well clear of the ambiguous zone. - If
temp_c < -3°C: Trustsnoworsleet. Surface is cold enough that phase isn’t in dispute. - If
temp_cis between -3°C and 3°C: Enter cautious mode. Check dew point, check condition code, checkcloud_cover(thick low cloud often means warmer and wetter than the model surface suggests), and return a phase confidence level rather than a hard label. - If condition code contradicts
precip_type: Returnambiguouswith both candidates and let the consuming layer decide — or default to the more impactful phase (frozen over liquid) if your use case warrants conservative handling.
The key shift is replacing a hard categorical output with a confidence-weighted one:
{
"phase": "freezing_rain",
"confidence": "low",
"temp_c": 0.8,
"dewpoint_c": -0.5,
"condition_code": 1198,
"precip_type_raw": "rain",
"flags": ["temp_near_freezing", "dewpoint_below_zero", "condition_code_conflict"]
}
Your application can then decide whether “low confidence freezing rain” triggers a road alert, an email, or nothing — rather than having that decision silently baked into the API response.
Where This Actually Matters
Flat, warm-climate use cases rarely see this problem. If you’re serving weather data for Dallas in August, precip_type is fine. But if your application touches UK and Scottish winters (we see this constantly in support), mountainous terrain where freezing level shifts rapidly, high-latitude continental locations, or anything in the 800–2000 meter elevation band, this field will eventually surprise you.
The use case that pushed us to look closely at this was agriculture in Scotland. A user getting a rain label at 1°C at 300 meters elevation makes a very different operational decision than one who knows there’s meaningful probability that’s actually freezing rain or sleet. The hard label was making that call for them without surfacing any of the ambiguity behind it.
A Caveat Worth Stating Plainly
No surface-based API can fully resolve atmospheric column ambiguity from outside the column. Even stacking every available field — temp, dew point, condition code, cloud cover — doesn’t give you the 850 hPa wet-bulb temperature that actually determines phase. What the layer above does is reduce the probability of a silent wrong answer, not eliminate it. For life-safety applications, METAR PIREPs and actual soundings are irreplaceable; this approach is appropriate for risk-flagging and user-facing alerts, not aircraft dispatch decisions.
If you’re already handling the ambiguous temperature zone in your integration, check whether you’re also catching the dew point depression case. In our experience, that’s the one that sneaks through most often in otherwise well-built pipelines — worth auditing before the first freezing rain event of the season finds it for you.
