From d094e78885fe07b443066d51eff6cfa1453c6a71 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Mon, 31 Aug 2026 11:23:14 +0000 Subject: [PATCH] Fix daily-tier ordering: filter by cutoff date, not exact-match skip list Met Office's daily series can include a stray leftover entry for today/yesterday that the old exact-match skip set (today, +1, +2) didn't catch. It sorted to the front of the 4-day window and showed up as an extra day ahead of the real day+3..+6 (live symptom: kiosk bottom row showing Sun, Thu, Fri, Sat instead of Thu, Fri, Sat, Sun). Now filters on "date key > cutoff" (day+2) so any stray day before the window is excluded regardless of why it appeared, and explicitly sorts before slicing so the window is chronological even if the provider's array isn't. Applied to both the Met Office and Open-Meteo daily parsers for consistency. Co-Authored-By: Claude Sonnet 5 --- backend/src/lib/weather-fetch.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/backend/src/lib/weather-fetch.js b/backend/src/lib/weather-fetch.js index 94cab47..349e522 100644 --- a/backend/src/lib/weather-fetch.js +++ b/backend/src/lib/weather-fetch.js @@ -122,9 +122,20 @@ async function fetchMetOfficeThreeHourly(lat, lon, apiKey, dayKeys) { // response is the only one with sunrise/sunset fields, so the two travel together; // if this tier falls back to Open-Meteo, astro falls back with it (see // openMeteoAstroPoints below). -async function fetchMetOfficeDaily(lat, lon, apiKey, skipKeys, astroKeys) { +// +// Filters by "date key > cutoffKey" (day+2) rather than excluding an exact set of +// keys — Met Office's daily series has been observed to include a stray leftover +// entry for today/yesterday that an exact-match skip list doesn't catch, which +// then sorts to the front of the window and shows up as an extra day ahead of the +// real day+3..+6 (seen live 2026-08-31: a stray Sunday entry displayed before +// Thu/Fri/Sat). Explicitly sorting before slicing guards the same window against +// the provider not returning entries in chronological order at all. +async function fetchMetOfficeDaily(lat, lon, apiKey, cutoffKey, astroKeys) { const series = await fetchMetOfficeTimestep('daily', lat, lon, apiKey) - const dayRows = series.filter(p => !skipKeys.has(londonDateKey(p.time))).slice(0, 4) + const dayRows = series + .filter(p => londonDateKey(p.time) > cutoffKey) + .sort((a, b) => a.time.localeCompare(b.time)) + .slice(0, 4) const dailyPoints = assertParsed(dayRows.length, dayRows .map(p => ({ resolution: 'daily', @@ -211,9 +222,10 @@ function openMeteoThreeHourlyPoints(hourlyRows, dayKeys) { source: 'open_meteo', })) } -function openMeteoDailyPoints(dailyRows, skipKeys) { +function openMeteoDailyPoints(dailyRows, cutoffKey) { return dailyRows - .filter(r => !skipKeys.has(r.dateKey)) + .filter(r => r.dateKey > cutoffKey) + .sort((a, b) => a.dateKey.localeCompare(b.dateKey)) .slice(0, 4) .map(r => ({ resolution: 'daily', @@ -321,7 +333,10 @@ export async function refreshForecast(log = console) { const today = londonDateKey(new Date().toISOString()) const dayKeys = [londonDateKeyPlusDays(1), londonDateKeyPlusDays(2)] - const skipKeys = new Set([today, ...dayKeys]) + // The 'daily' tier (days 4-7) keeps anything strictly after this date key — + // see fetchMetOfficeDaily's comment for why an exact-match skip list isn't + // used here. + const cutoffKey = dayKeys[dayKeys.length - 1] const astroKeys = [today, ...dayKeys] const metOffice = await getIntegration('met_office') @@ -337,7 +352,7 @@ export async function refreshForecast(log = console) { const [hourlyR, threeHourlyR, dailyR] = await Promise.allSettled([ fetchWithKeys(key => fetchMetOfficeHourly(lat, lon, key), keys), fetchWithKeys(key => fetchMetOfficeThreeHourly(lat, lon, key, dayKeys), keys), - fetchWithKeys(key => fetchMetOfficeDaily(lat, lon, key, skipKeys, astroKeys), keys), + fetchWithKeys(key => fetchMetOfficeDaily(lat, lon, key, cutoffKey, astroKeys), keys), ]) if (hourlyR.status === 'fulfilled') { @@ -376,7 +391,7 @@ export async function refreshForecast(log = console) { tierSource.threeHourly = 'open_meteo' } if (!tierSource.daily) { - dailyPoints = openMeteoDailyPoints(dailyRows, skipKeys) + dailyPoints = openMeteoDailyPoints(dailyRows, cutoffKey) astroPoints = openMeteoAstroPoints(dailyRows, astroKeys) tierSource.daily = 'open_meteo' }