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.
- 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.
- 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).
- 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.
- 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.
- Dates out of range. The forecast covers from 92 days back to 14 days ahead. Outside that, the card shows No weather data.
- 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
WeatherCacheRefreshTemporal workflow (one per project and day; concurrent viewers share it). The workflow fetches with retries and stores the result. - The
WeatherPrewarmTemporal workflow runs hourly (scheduleweather-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_snapshotsevent onproject_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
| File | Purpose |
|---|---|
backend/api/internal/weather/service.go | Reads the stored forecast; starts the refresh |
backend/api/internal/weather/handler.go | Weather HTTP handler |
backend/pkg/openmeteo/client.go, window.go | Open-Meteo fetch; the per-day snapshot key |
backend/workers/workflows/weather_cache_refresh.go | On-demand refresh workflow |
backend/workers/workflows/weather_prewarm.go | Hourly prewarm workflow |
backend/workers/activities/weather_cache_refresh.go | Fetch + store activities (refresh, prewarm) |
backend/workers/cmd/worker/weather_prewarm_wiring.go | Hourly schedule (not on previews) |
backend/api/internal/prestart/publish_weather.go | Saves the stored forecast on the prestart at publish |
backend/db/migrations/20260926124459_rc03_weather_snapshot_notify.sql | Live update when a stored forecast changes |
apps/web/src/app/_components/weather-widget.tsx | The forecast card |
apps/web/src/app/admin/_components/project-detail.tsx | Project lat/lng/timezone fields |