diff --git a/backend/src/lib/weather.js b/backend/src/lib/weather.js index f9aba68..0472939 100644 --- a/backend/src/lib/weather.js +++ b/backend/src/lib/weather.js @@ -7,10 +7,18 @@ // // Provider selection: Met Office DataHub if an api_key is configured AND the // integration is enabled in settings (the existing enable/disable toggle IS the -// "use Met Office" switch); otherwise Open-Meteo, which needs no key at all. Either -// way this only ever returns TODAY's forecast high — not live/current temperature — -// so a tier, once picked at an occupied-state transition, doesn't need to fight -// hour-to-hour swings (see scheduler.js's applyStateTransition()). +// "use Met Office" switch); otherwise, OR if the Met Office call fails for any reason +// (see the catch below), Open-Meteo — which needs no key at all. Either way this only +// ever returns TODAY's forecast high — not live/current temperature — so a tier, once +// picked at an occupied-state transition, doesn't need to fight hour-to-hour swings +// (see scheduler.js's applyStateTransition()). +// +// IMPORTANT: fetchFromMetOffice() below calls the "Global Spot" deterministic product +// (`sitespecific/v0/point/daily`) — a DIFFERENT Met Office DataHub subscription from +// the "Site-Specific Blended Probabilistic Forecast" product (percentile/probability +// bands, no plain max-temp field). Subscribing to the wrong one on the DataHub side +// gives a 403 here, which the catch below quietly degrades to Open-Meteo — confirmed +// live 2026-08-25. const CACHE_TTL_MS = 60 * 60 * 1000 // 1h — forecasts don't move fast enough to poll more often const STALE_FALLBACK_MS = 6 * 60 * 60 * 1000 // still serve a cached value up to 6h old if a refetch fails @@ -73,9 +81,22 @@ export async function getPeakTempC() { } const metOffice = await getIntegration('met_office') - const peakC = metOffice?.api_key - ? await fetchFromMetOffice(lat, lon, metOffice.api_key) - : await fetchFromOpenMeteo(lat, lon) + let peakC + if (metOffice?.api_key) { + try { + peakC = await fetchFromMetOffice(lat, lon, metOffice.api_key) + } catch (err) { + // Met Office configured but the call failed (wrong product subscription, key not + // yet propagated, outage, ...) — degrade to Open-Meteo rather than going dark. + // Common cause: the key is only subscribed to the Site-Specific Blended + // Probabilistic Forecast product, not the deterministic Global Spot one this + // fetches from — they're separate subscriptions on Met Office's side. + console.error('[weather] Met Office fetch failed, falling back to Open-Meteo:', err.message) + peakC = await fetchFromOpenMeteo(lat, lon) + } + } else { + peakC = await fetchFromOpenMeteo(lat, lon) + } cache = { peakC, fetchedAt: Date.now() } return peakC