Weather Caching Behavior

Scope

This document defines the runtime lookup policy for getWeatherPointCached(...) in src/services/weatherService.ts. Daily work-area pre-warming uses the same weather service module but follows its own dedicated 24-hour horizon warming path through precacheWeatherForLocations(...).

Canonical Flow

  1. Validate lat and lon before any cache or network work.
  2. Normalize targetTime with toHourKey(...) and derive the cache cell with calcWeatherCacheKey(...).
  3. Try an exact cache lookup for (cell_id, forecast_hour).
  4. Return the cached point only when the exact row exists and Date.now() < valid_to.
  5. When an exact row exists but is stale, delete it before continuing. Delete failures are logged and do not block refresh.
  6. When the requested hour is the current wall-clock hour and the exact row is unavailable, allow a fresh same-cell row for the next forecast hour. This covers upstream NDFD responses that start one hour after the requested current hour.
  7. On miss or exact-read failure, fetch one NDFD series with fetchNdfdWeatherSeries(lat, lon, 24, 12000, forecast_hour).
  8. Persist fetched points with putForecastPoints(...) and enforce bounded size with evictLRU(12000).
  9. Return the fetched exact-hour point when it exists, even if cache persistence or eviction fails.
  10. When the requested hour is the current wall-clock hour and the fetched series omits that hour, return the fetched next-hour point when it exists.
  11. When refresh fails or the fetched series does not contain the requested hour or allowed current-hour substitute, query nearby cached rows for the same normalized hour.
  12. Nearby fallback accepts only fresh rows whose cell center is within 5 km of the requested coordinates, and returns the nearest candidate.

Daily Pre-Cache Flow

  1. Validate and dedupe requested work-area locations by weather cache cell.
  2. For each unique cell, fetch one fresh NDFD 24-hour series anchored to the requested hour, with bounded fanout of 8 in-flight requests instead of launching the full work-area batch at once.
  3. Persist every returned hourly point with one shared valid_to, then enforce bounded size with one post-batch evictLRU(12000) call after the warm set completes.
  4. Count the cell as successfully warmed when the fetched series covers either:
    • the requested hour through the next 23 hours, or
    • the next 24 hours starting one hour later when the current hour is unavailable upstream.
  5. Pre-cache failures are summarized per cell and do not change foreground runtime lookup semantics.

Failure Semantics

  • Exact-cache read failures are treated as warnings and the service continues to refresh or fallback.
  • Exact-hit last_accessed updates happen inside getForecastPoint(...); if that update fails, the exact read is treated as failed.
  • Nearby-fallback touchForecastPointAccess(...) is best effort only; touch failures do not block returning fallback weather.
  • When all paths fail, getWeatherPointCached(...) throws WeatherCacheMissError.
  • Miss-error cause priority is: refresh failure, stale-delete failure, nearby-fallback read failure, exact-cache read failure, then generic miss.

Supporting References

// TODO LUCAS CAN’T BE BOTHERED TO FIX THIS RN

Invariants

  • Runtime orchestration lives in src/services/weatherService.ts; persistence helpers stay in src/db/weatherCacheDb.ts.
  • Cache storage is normalized to UTC hour keys via toHourKey(...).
  • The fallback path never returns stale rows.