fix(weather): treat a missing code as unavailable in cloud effects too

Number(null) is 0, so the cockpit cloud profile treated a missing weather code
as clear but available. The briefing's three checks now share one helper, and
the current-state note is its own bullet instead of folding into the route one.
This commit is contained in:
Sameh Khamis committed 2026-10-14 15:06:52 -07:00
1 parent a7e5f341ab
commit 5be2185dc7
4 files changed
+32 -29

No files matched your search

+15 -26
View File
@@ -87,27 +87,22 @@ export function normalizeRegionalArticles(payload, limit = MAX_ARTICLES) {
return articles;
}
/** A reported number, or null when the observation is missing or not numeric. */
function observedNumber(value) {
if (
value == null ||
typeof value === 'boolean' ||
(typeof value === 'string' && !value.trim())
)
return null;
return Number.isFinite(Number(value)) ? Number(value) : null;
}
/** Normalize Open-Meteo current conditions into a small source-stamped record. */
export function normalizeRegionalWeather(payload) {
const current = payload?.current;
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 ||
typeof value === 'boolean' ||
(typeof value === 'string' && !value.trim())
)
return null;
return Number.isFinite(Number(value)) ? Number(value) : null;
};
if (!current || observedNumber(current.temperature_2m) == null) return null;
const numberOrNull = observedNumber;
// Open-Meteo reports zone-naive timestamps ("2026-08-17T00:15") that are UTC,
// but JS parses zoneless date-times as LOCAL — pin them to UTC explicitly.
const observedRaw =
@@ -132,14 +127,8 @@ 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';
const value = observedNumber(code);
if (value == null) return 'CONDITIONS UNKNOWN';
if (value === 0) return 'CLEAR';
if ([1, 2].includes(value)) return 'PARTLY CLOUDY';
if (value === 3) return 'OVERCAST';
+7 -1
View File
@@ -13,7 +13,13 @@ function range01(value, min, max) {
* Missing weather fails clear instead of inventing conditions.
*/
export function deriveWeatherEffectProfile(weather) {
if (!weather || !Number.isFinite(Number(weather.weatherCode))) {
const reported = weather?.weatherCode;
// Number(null) and Number('') are 0 (clear), so a missing code is checked first.
if (
reported == null ||
reported === '' ||
!Number.isFinite(Number(reported))
) {
return {
available: false,
cloud: 0,
+9
View File
@@ -10,6 +10,15 @@ test('missing weather fails clear instead of inventing effects', () => {
assert.equal(profile.storm, 0);
});
test('a missing weather code is unavailable, not clear', () => {
for (const weatherCode of [null, undefined, '']) {
const profile = deriveWeatherEffectProfile({ weatherCode, cloudCoverPct: 90 });
assert.equal(profile.available, false, `weatherCode ${weatherCode}`);
assert.equal(profile.cloud, 0);
}
assert.equal(deriveWeatherEffectProfile({ weatherCode: 0 }).available, true);
});
test('clear weather follows observed cloud cover without precipitation', () => {
const profile = deriveWeatherEffectProfile({
weatherCode: 1,