From 8af3d02cbbba20f721b5ce4f909e26f2af22d8be Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Tue, 28 Apr 2026 21:42:38 +0200 Subject: [PATCH] fix(weather): use nearest openmeteo hourly data (#4123) Open-Meteo updates `current_weather` every 15 minutes, but the hourly array only has entries at full hours. The old code did an exact timestamp match - so at 14:15, 14:30 or 14:45 it never found a match and silently fell back to index 0, showing midnight values for humidity, feels-like temp, precipitation, etc. Fix: `findLastIndex((hour) => hour.time <= currentMs)` - the last hourly entry at or before the current time. While fixing the bug I found several dead branches left over from the #4032 refactor: a path for pre-transposed hourly data that `#parseWeatherApiResponse` makes unreachable, an `Array.isArray` guard that's always true, and a `Log.debug` inside the dead branch. Removing those accounts for most of the ~40 deleted lines - the actual fix is one line. Fixes #4122. --- defaultmodules/weather/providers/openmeteo.js | 52 +++++-------------- .../weather/providers/openmeteo_spec.js | 5 +- 2 files changed, 16 insertions(+), 41 deletions(-) diff --git a/defaultmodules/weather/providers/openmeteo.js b/defaultmodules/weather/providers/openmeteo.js index 7cc5f439..ff02cc61 100644 --- a/defaultmodules/weather/providers/openmeteo.js +++ b/defaultmodules/weather/providers/openmeteo.js @@ -418,46 +418,18 @@ class OpenMeteoProvider { // Add hourly data if available if (parsedData.hourly) { - let h; - const currentTime = parsedData.current_weather.time; - - // Handle both data shapes: object with arrays or array of objects (after transpose) - if (Array.isArray(parsedData.hourly)) { - // Array of objects (after transpose) - const hourlyIndex = parsedData.hourly.findIndex((hour) => hour.time.getTime() === currentTime.getTime()); - h = hourlyIndex !== -1 ? hourlyIndex : 0; - - if (hourlyIndex === -1) { - Log.debug("[openmeteo] Could not find current time in hourly data, using index 0"); - } - - const hourData = parsedData.hourly[h]; - if (hourData) { - current.humidity = hourData.relativehumidity_2m; - current.feelsLikeTemp = hourData.apparent_temperature; - current.rain = hourData.rain; - current.snow = hourData.snowfall ? hourData.snowfall * 10 : undefined; - current.precipitationAmount = hourData.precipitation; - current.precipitationProbability = hourData.precipitation_probability; - current.uvIndex = hourData.uv_index; - } - } else if (parsedData.hourly.time) { - // Object with arrays (before transpose - shouldn't happen in normal flow) - const hourlyIndex = parsedData.hourly.time.findIndex((time) => time === currentTime); - h = hourlyIndex !== -1 ? hourlyIndex : 0; - - if (hourlyIndex === -1) { - Log.debug("[openmeteo] Could not find current time in hourly data, using index 0"); - } - - current.humidity = parsedData.hourly.relativehumidity_2m?.[h]; - current.feelsLikeTemp = parsedData.hourly.apparent_temperature?.[h]; - current.rain = parsedData.hourly.rain?.[h]; - current.snow = parsedData.hourly.snowfall?.[h] ? parsedData.hourly.snowfall[h] * 10 : undefined; - current.precipitationAmount = parsedData.hourly.precipitation?.[h]; - current.precipitationProbability = parsedData.hourly.precipitation_probability?.[h]; - current.uvIndex = parsedData.hourly.uv_index?.[h]; - } + // Open-Meteo updates current_weather every 15 min, but hourly entries only + // exist at full hours — find the last entry at or before the current time. + const currentMs = parsedData.current_weather.time.getTime(); + const hourlyIndex = parsedData.hourly.findLastIndex((hour) => hour.time.getTime() <= currentMs); + const hourData = parsedData.hourly[Math.max(0, hourlyIndex)]; + current.humidity = hourData.relativehumidity_2m; + current.feelsLikeTemp = hourData.apparent_temperature; + current.rain = hourData.rain; + current.snow = hourData.snowfall != null ? hourData.snowfall * 10 : null; + current.precipitationAmount = hourData.precipitation; + current.precipitationProbability = hourData.precipitation_probability; + current.uvIndex = hourData.uv_index; } // Add daily data if available (after transpose, daily is array of objects) diff --git a/tests/unit/modules/default/weather/providers/openmeteo_spec.js b/tests/unit/modules/default/weather/providers/openmeteo_spec.js index 0377a990..225a970c 100644 --- a/tests/unit/modules/default/weather/providers/openmeteo_spec.js +++ b/tests/unit/modules/default/weather/providers/openmeteo_spec.js @@ -144,12 +144,15 @@ describe("OpenMeteoProvider", () => { provider.start(); const result = await dataPromise; + const currentHourUnix = Math.floor(currentData.current_weather.time / 3600) * 3600; + const currentHourIndex = currentData.hourly.time.findIndex((time) => time === currentHourUnix); expect(result).toBeDefined(); expect(result.temperature).toBe(8.5); expect(result.windSpeed).toBeCloseTo(4.7, 1); expect(result.windFromDirection).toBe(9); - expect(result.humidity).toBe(83); + expect(result.humidity).toBe(currentData.hourly.relativehumidity_2m[currentHourIndex]); + expect(result.humidity).not.toBe(currentData.hourly.relativehumidity_2m[0]); }); it("should include sunrise and sunset from daily data", async () => {