From c86c1a4ccedb0468bc622c00ee151c1224bab71a Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 11 Oct 2026 09:35:40 -0400 Subject: [PATCH] fix(weather): keep absent observations unknown instead of reporting clear skies --- CHANGELOG.md | 2 ++ docs/CURRENT-STATE.md | 5 ++++ src/data/regionalModel.js | 23 +++++++++++++++++-- .../weather-missing-observations.test.mjs | 20 ++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/data/weather-missing-observations.test.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7acdedf6b..d3a8f9027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ the five borough cores load and `CCTV_NYC_MAX_SOURCES` changes the cap. The catalog-wide ceiling is unchanged; extra cameras share it round-robin with the other packs. +- Weather records keep missing conditions unknown and refuse summaries without a measured temperature. + - Treat explicitly current, complete empty vessel coverage as a successful update, clearing obsolete contacts while retaining stale or incomplete snapshots. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index c77d1d8e3..c066ab182 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5310,3 +5310,8 @@ datacenters, dams and the Nepal locator retain that shared credit while displaye Military fragments stitch by source identity. Different mapped parcels merge only when their footprints touch or contain one another, never across a seam tolerance gap. + + +### fix(weather): keep absent observations unknown instead of reporting clear skies + +Refuse absent or boolean primary temperatures and keep absent/boolean weather codes explicitly CONDITIONS UNKNOWN while retaining valid numeric strings and real zero observations. diff --git a/src/data/regionalModel.js b/src/data/regionalModel.js index 08b4e9336..90eeac1fd 100644 --- a/src/data/regionalModel.js +++ b/src/data/regionalModel.js @@ -90,9 +90,22 @@ export function normalizeRegionalArticles(payload, limit = MAX_ARTICLES) { /** Normalize Open-Meteo current conditions into a small source-stamped record. */ export function normalizeRegionalWeather(payload) { const current = payload?.current; - if (!current || !Number.isFinite(Number(current.temperature_2m))) return null; + if ( + !current || + current.temperature_2m == null || + typeof current.temperature_2m === 'boolean' || + (typeof current.temperature_2m === 'string' && + !current.temperature_2m.trim()) || + !Number.isFinite(Number(current.temperature_2m)) + ) + return null; const numberOrNull = (value) => { - if (value === null || value === undefined || value === '') return null; + if ( + value == null || + typeof value === 'boolean' || + (typeof value === 'string' && !value.trim()) + ) + return null; return Number.isFinite(Number(value)) ? Number(value) : null; }; // Open-Meteo reports zone-naive timestamps ("2026-08-17T00:15") that are UTC, @@ -119,6 +132,12 @@ export function normalizeRegionalWeather(payload) { /** Translate the WMO weather code used by Open-Meteo into concise cockpit copy. */ export function weatherCodeLabel(code) { + if ( + code == null || + typeof code === 'boolean' || + (typeof code === 'string' && !code.trim()) + ) + return 'CONDITIONS UNKNOWN'; const value = Number(code); if (!Number.isFinite(value)) return 'CONDITIONS UNKNOWN'; if (value === 0) return 'CLEAR'; diff --git a/src/data/weather-missing-observations.test.mjs b/src/data/weather-missing-observations.test.mjs new file mode 100644 index 000000000..4e89dcedd --- /dev/null +++ b/src/data/weather-missing-observations.test.mjs @@ -0,0 +1,20 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { normalizeRegionalWeather, weatherCodeLabel } from './regionalModel.js'; +test('missing weather observations never assert clear conditions', () => { + for (const value of [null, undefined, '', ' ', false, true]) { + assert.equal(weatherCodeLabel(value), 'CONDITIONS UNKNOWN'); + assert.equal( + normalizeRegionalWeather({ current: { temperature_2m: value } }), + null, + ); + } + assert.equal(weatherCodeLabel(0), 'CLEAR'); + assert.equal(weatherCodeLabel('0'), 'CLEAR'); + const record = normalizeRegionalWeather({ + current: { temperature_2m: 0, weather_code: null, wind_speed_10m: false }, + }); + assert.equal(record.temperatureC, 0); + assert.equal(record.weatherCode, null); + assert.equal(record.windKph, null); +});