ModulesPrestartsFeaturesWeather Forecasts

Weather Forecasts

Prestarts and diaries show the site’s weather forecast to help supervisors plan for outdoor work conditions. The forecast comes from Open-Meteo, looked up at the project’s latitude, longitude and timezone.

Prerequisites

For weather to work, the project must have coordinates configured:

  • Latitude and Longitude — set in the project detail Overview tab
  • Timezone — defaults to Australia/Sydney

How It Works

Opening a prestart or a diary never waits on the weather provider. The forecast is fetched in the background and stored; the screen reads the stored copy.

  1. Stored per project and day. One forecast is kept per project, day and timezone. It covers that day and the next, so a day shift and a night shift that starts the same evening read the same forecast.
  2. Kept warm every hour. A background job refreshes today’s and tomorrow’s forecast for every active project (live, with coordinates, and a prestart or diary in the last 30 days).
  3. Refreshed when someone looks. If the stored forecast is more than 30 minutes old, it is shown straight away and a refresh starts in the background. If it is more than 6 hours old, the card shows Last known until the refresh lands.
  4. First view of a day. If nothing is stored yet, the card says Getting the forecast and fills in on its own when the forecast arrives — no reload.
  5. Dates out of range. The forecast covers from 92 days back to 14 days ahead. Outside that, the card shows No weather data.
  6. Saved at publish. When a prestart is published, the stored forecast for its start time is saved on the prestart (unless it already has one). Published prestarts always show that saved forecast.

Data Structure

{
  "date": "2026-02-27",
  "timezone": "Australia/Brisbane",
  "provider": "Open-Meteo",
  "location": "Pelican Rd, Brisbane QLD",
  "stale": false,
  "fetched_at": "2026-02-27T05:07:12Z",
  "hourly": {
    "time": ["2026-02-27T06:00", "2026-02-27T07:00", ...],
    "temperature_2m": [22.1, 23.4, ...],
    "apparent_temperature": [21.5, 22.8, ...],
    "wet_bulb_temperature_2m": [18.2, 18.9, ...],
    "precipitation": [0, 0, ...],
    "relative_humidity_2m": [78, 72, ...],
    "wind_speed_10m": [8, 10, ...]
  }
}

While the first forecast for a day is being fetched, fetched_at is absent and every hourly array is empty.

Architecture

  • GET /v1/orgs/{orgId}/projects/{projectId}/weather?datetime=… reads the stored snapshot (weather_snapshots) — one indexed read. It never calls Open-Meteo and never writes.
  • When the snapshot is missing or older than 30 minutes, the API starts the WeatherCacheRefresh Temporal workflow (one per project and day; concurrent viewers share it). The workflow fetches with retries and stores the result.
  • The WeatherPrewarm Temporal workflow runs hourly (schedule weather-prewarm-hourly, not on preview stacks): one fetch per active project for today and tomorrow, stored as two day snapshots.
  • A stored forecast that changed sends a weather_snapshots event on project_changes (Postgres LISTEN/NOTIFY → WebSocket), so open screens refresh the card. A refresh that brings back an identical forecast sends nothing.
  • Prestart publish reads the stored forecast and saves it on the prestart in the same request.

Security

  • Any project member can read the forecast (same-org users and cross-org project members)
  • The lat/lng comes from the project record (DB), not from request parameters
  • Only the worker calls Open-Meteo; outbound calls are traced with the query string removed from the recorded URL

Key Files

FilePurpose
backend/api/internal/weather/service.goReads the stored forecast; starts the refresh
backend/api/internal/weather/handler.goWeather HTTP handler
backend/pkg/openmeteo/client.go, window.goOpen-Meteo fetch; the per-day snapshot key
backend/workers/workflows/weather_cache_refresh.goOn-demand refresh workflow
backend/workers/workflows/weather_prewarm.goHourly prewarm workflow
backend/workers/activities/weather_cache_refresh.goFetch + store activities (refresh, prewarm)
backend/workers/cmd/worker/weather_prewarm_wiring.goHourly schedule (not on previews)
backend/api/internal/prestart/publish_weather.goSaves the stored forecast on the prestart at publish
backend/db/migrations/20260926124459_rc03_weather_snapshot_notify.sqlLive update when a stored forecast changes
apps/web/src/app/_components/weather-widget.tsxThe forecast card
apps/web/src/app/admin/_components/project-detail.tsxProject lat/lng/timezone fields