If you’ve ever cached a forecast response, served it to a user, and then noticed that the condition code for a specific future hour changed when you fetched fresh data — you’re not seeing a bug. You’re seeing how numerical weather prediction actually works, and why treating a forecast code as stable once it’s been issued creates real problems for any logic that branches on it.
What’s actually happening when a code changes
A forecast for, say, 14:00 local time on Thursday doesn’t come from a single authoritative computation that gets locked in Monday and never touched again. GFS runs four times per day at 00z, 06z, 12z, and 18z. HRRR runs hourly. Each new cycle ingests updated observations — surface stations, radiosondes, aircraft PIREPs, satellite retrievals — and produces a fresh analysis field that becomes the starting point for the next forward integration. The hour-14 forecast from the 06z Tuesday run is a different object than the hour-14 forecast from the 12z Wednesday run. The target clock time is the same; the starting conditions and the model state are completely different.
What that means for a condition code specifically: the HRRR 3km grid — which matters a lot for mesoscale convective events and coastal precip — might show 0mm of precipitation at a given grid point in the Tuesday run and 4mm in the Wednesday 12z run once an approaching system gets resolved more sharply. Our GRIB2 ingestion pipeline picks up those model outputs and re-derives the condition code from the updated precip, cloud, and convection fields. So the code changes. That’s the system working correctly.
The problem is entirely on the application side, when code is written as though condition codes are immutable once issued.
The practical failure modes
The one that comes up most often: an app fetches a 10-day forecast on Sunday, writes the condition codes to a database, and uses those stored codes to power scheduling logic or push notifications all week without re-fetching. By Wednesday, the forecast for Friday is often materially different from what was stored. A code for clear/sunny that was valid at storage time might have shifted to moderate rain as a front gets resolved better — but the app is still showing sunshine icons and hasn’t triggered the rain alert.
The second failure mode is subtler: re-fetching frequently but caching aggressively at the CDN or application layer. If your TTL is 6 hours and GFS just completed a 12z run that significantly revised Thursday’s conditions, users won’t see the update for up to 6 hours. For days 1-2 that’s a real gap. For day 5+ it matters less, because forecast skill is low enough that the revision might be noise anyway.
A third: using a condition code as the sole trigger for a business decision rather than checking the underlying threshold fields. A code for moderate rain tells you which bucket you’re in — but the precip amount behind it could be anywhere from just over the light-rain threshold to something approaching heavy. For outdoor operations decisions, the code alone doesn’t give you enough to act on confidently.
How model run timing structures the revision pattern
GFS is available with roughly 3.5-4 hours of latency after initialization. The 00z run (midnight UTC) typically reaches our ingestion pipeline around 03:30-04:00z; the 06z run arrives around 09:30-10:00z. So for a location in the UK, a Thursday-forecast query at 08:00 BST (07:00z) is pulling GFS data from the 00z run — about 7 hours old. The 06z run hasn’t landed yet.
HRRR fills some of that gap for US locations, running hourly out to 18 hours (48 hours on the extended 00z/06z/12z/18z runs). Past that horizon it’s not in the picture, and you’re back on GFS or NAM for the medium range.
ECMWF — which we also ingest — runs twice daily at 00z and 12z and is generally the most skillful global model for days 3-10 by most standard verification metrics. But it still revises. A 5-day ECMWF forecast is not the same object as a 3-day ECMWF forecast for the same target time.
The practical pattern that falls out of all this: hours 0-6 ahead are highly stable, dominated by HRRR for US locations and very recent observations. Hours 6-48 get revised meaningfully with each major model cycle. Days 3-5 can shift substantially between morning and evening runs. Beyond day 7, treat any specific condition code as a probability distribution, not a fact.
What to actually do about it in your app
A few patterns that hold up in practice:
Store condition codes with a freshness timestamp, not as settled facts. When you write forecast data to your DB, include the last_updated timestamp from the API response alongside every field you store. Your application logic should know how old the forecast is before acting on it. A Thursday condition code fetched on Monday should be treated as provisional; the same code fetched Wednesday evening is considerably more reliable.
Calibrate your re-fetch cadence to forecast horizon, not a single global TTL. For hours 0-6, re-fetching every 30-60 minutes is defensible — HRRR updates that often and near-term conditions can change quickly. For days 4-7, re-fetching more than twice a day burns quota without meaningful signal gain. A tiered approach works: aggressive polling for the near-term window, relaxed for the medium range.
Don’t branch solely on condition code for consequential decisions. Use the code for display. For logic that actually does something — triggers an alert, blocks a booking, adjusts a price — also check the underlying fields: precip_mm, chance_of_rain, wind_kph. The condition code is a derived label; the fields it was derived from give you better signal for threshold-based decisions.
Build in revision detection if you’re storing forecasts longitudinally. Keep a record of what the forecast said for a given target hour across multiple fetch times. Comparing Monday’s Thursday forecast against Wednesday’s Thursday forecast will quickly build intuition for how much revision your specific location and season actually produce. That’s useful ground truth for calibrating TTLs and alert thresholds.
One honest caveat
None of this touches the fundamental skill limit. Past day 5-6, no re-fetching strategy makes a condition code meaningfully reliable — not from our API, not from anyone else’s. The revision between a Monday 12z run and a Wednesday 00z run for a Saturday target can flip clear to stormy and back within 48 hours. The right product choice for day 6+ is probably to surface uncertainty explicitly rather than a crisp condition icon that implies precision the underlying model doesn’t have. That’s a UX decision, not an API limitation — but it’s worth making deliberately rather than discovering it through confused user reports.
If you’re building anything that makes consequential decisions from medium-range condition codes, pull forecast data for the same target hour across several consecutive model cycles for your key locations and see how much they actually move. For convective-season locations it’s usually more than you’d expect. For stable winter anticyclonic patterns, less than you’d fear.
