Why snow_cm and snowfall_cm Report Different Totals for the Same Hour — and How to Stop Them from Breaking Your Winter Display Logic

Two Snow Fields, Two Different Questions

If you’ve built a winter weather display and noticed snow_cm and snowfall_cm occasionally disagree — sometimes by a lot — you’re not looking at a bug. You’re looking at two fields that measure fundamentally different things, and conflating them will quietly produce wrong numbers for your users.

snow_cm in the daily response is a depth field. It reflects the total snow depth sitting on the ground as of a point in time — an accumulation of everything that hasn’t melted yet, not what fell in a given window. snowfall_cm, where it appears in hourly or forecast payloads, is a flux field: the volume of precipitation that fell as snow during a specific period, expressed as water-equivalent or snow-equivalent depth depending on the context. Same unit. Completely different physical quantities.

The confusion is understandable. Both are labelled in centimetres, both show up in snow-related contexts, and when it’s cold enough and you’re in the middle of a fresh storm, they can track each other reasonably well for a while. But as soon as temperatures climb above freezing, or the storm moves out, they diverge — sometimes sharply. A warm afternoon can erode snow_cm by several centimetres while snowfall_cm for those same hours sits near zero because nothing new fell. Display logic that sums or compares these two fields will show nonsense.

Where Each Field Gets Its Value

The cleaner way to think about it: snowfall_cm is derived from the precipitation modelling side of the pipeline. Model output — GFS, NAM, HRRR depending on the domain — gives us precipitation rates, and phase-partitioning logic allocates the snow fraction based on wet-bulb temperature at the surface. HRRR’s 3km horizontal grid makes this partitioning reasonably sharp in most terrain, though it still blurs in complex topography. The resulting value answers one question: how many centimetres of snow-equivalent fell in this window?

snow_cm comes from a different place entirely. For current conditions, it’s pulled from METAR reports or similar surface observations where available, and those come in irregularly, only from stations that actually have automated snow sensors. For forecast hours, it’s modelled — but the model is tracking a full snowpack, accounting for new deposition, compaction, and melt. These are not the same calculation.

Station coverage is the hidden variable. METAR stations with reliable snow depth sensors are sparse. Outside of well-instrumented areas like parts of the US, Canada, and northern Europe, snow_cm in real-time responses can go stale or fall back to model-estimated depth that lags reality by a full cycle. The number is there, it’s not null, but it isn’t necessarily what fell in the last six hours.

The Display Logic Failure Modes

Three patterns we see cause problems:

  • Summing hourly snowfall_cm and comparing to daily snow_cm. Summed hourly snowfall gives you storm total new snow — that’s a valid and useful number. Daily snow depth is a snapshot of what’s on the ground right now. These won’t match, and shouldn’t be expected to.
  • Using snow_cm as an “is it snowing” signal. A non-zero snow depth means there’s snow on the ground somewhere nearby. It tells you nothing about whether it’s actively snowing. For current activity, use snowfall_cm for the current hour, or check the condition code.
  • Using snowfall_cm as ground-truth accumulation for a multi-day storm. Summing this across hours works for new snow, but underestimates total snow-on-ground if older accumulation from prior days hasn’t fully melted. The fields don’t describe a consistent physical state across time.

A Sensible Way to Structure This

Separate your data model along the same lines the underlying physics does: flux vs. state.

Treat snowfall_cm as your rate and accumulation input — sum it across a window to get storm totals for a custom date range, compare it to thresholds for alert logic, use it to decide whether snowfall is actively occurring in a given hour. It’s the more reliable field for time-series operations because it measures a bounded period.

Treat snow_cm as a snapshot field — useful for a “current depth on ground” display, but not safe to subtract across hours or assume changes in it equal what fell. If you’re surfacing it, label it clearly as depth, not accumulation, and don’t do arithmetic across time steps with it.

A minimal decision tree in pseudocode:

if request.type == "current" or "hourly":
    is_snowing = snowfall_cm > 0.1  # or condition_code check
    storm_total = sum(snowfall_cm for each hour in window)
    display_depth = snow_cm  # snapshot only, not delta

if request.type == "daily":
    new_snow_today = day.snowfall_cm  # precipitation-based
    depth_on_ground = day.snow_cm    # state-based, don't subtract from yesterday's

The 0.1 cm threshold for snowfall_cm is worth calling out explicitly. Trace values below that show up regularly from model output and don’t represent meaningful snowfall — they’re rounding artefacts from precipitation phase partitioning. If your alert fires every time the model outputs 0.03 cm/hr, you’ll burn through notification quota fast and train your users to ignore it.

Forecast vs. Observed Makes This Harder

In the forecast endpoint, both fields are model-derived and internally consistent. In real-time responses, snow_cm may be pulled from an observation that was taken hours ago, while snowfall_cm for the current period is blended from more recent model output. So in a live storm, these can diverge simply because they’re reflecting different ages of information — not because either is wrong in isolation.

We flag observation age alongside depth in our METAR pipeline, but that metadata doesn’t surface directly in the API response right now, which means you can’t easily tell from the response alone whether the depth figure is fresh or several hours old. If you’re building something where staleness matters — live trail conditions, avalanche advisory inputs, airfield ops — log the obs_time field and treat snow_cm as suspect if it’s more than a couple of hours behind the current request time.

What This Means for Specific Use Cases

For ski resort or trail apps: use snowfall_cm summed over the last 24 or 72 hours for “new snow” displays. Use snow_cm only for base depth, anchored to a single point-in-time reading rather than tracking changes across it.

For logistics and routing apps: active snowfall rate matters more than depth for real-time decisions. Build your thresholds around per-hour snowfall_cm, not the daily depth field.

For historical backtesting: archived snow_cm values in the history endpoint reflect the observation as it was recorded, which may already incorporate melt. If you’re reconstructing storm totals from historical data, summing snowfall_cm is the more consistent approach than diffing depth snapshots.

Depth and flux are physically distinct quantities that happen to share a unit. If you’re not sure which one you’re actually pulling, check the full field path in the response — the nesting usually tells you which endpoint and aggregation level it came from, and that’s enough context to know which physical quantity you’re holding. That check takes thirty seconds and will save you from the kind of melt-day discrepancy that’s otherwise very hard to debug at 2am when someone’s ski report looks wrong.

Scroll to Top