Throw on wrong-field-name parse failures instead of silent zero-point success

Spot is now confirmed subscribed to hourly/three-hourly too (settings'
met_office test matrix), so those parsers are genuinely exercised for the
first time -- previously a wrong field name would silently produce an
empty points array while still reporting the tier as a 'met_office'
success. assertParsed() throws when raw entries came back but none parsed
a valid temp_c, so a field-name mismatch now correctly falls back to
Open-Meteo (and logs an error) instead of quietly showing an empty today
strip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-25 14:06:31 +00:00
parent 15a801b1f4
commit 64afd2b5fd

View file

@ -62,12 +62,26 @@ async function fetchMetOfficeTimestep(timestep, lat, lon, apiKey) {
return series
}
// Throws if the raw response had entries but every one of them failed to parse a
// valid temp_c — that's not "no data for this range" (which is a legitimate empty
// array, not an error), it's "the field names are wrong". Confirmed live 2026-08-25
// that Spot is actually subscribed to all three endpoints (settings' met_office
// test route), so hourly/three-hourly parsing is now genuinely exercised for the
// first time — this turns a silent wrong-field-name bug (reported as "success"
// with zero points) into a real failure that falls back to Open-Meteo instead.
function assertParsed(rawCount, points, label) {
if (rawCount > 0 && points.length === 0) {
throw new Error(`Met Office ${label}: got ${rawCount} raw entries but parsed 0 valid points — check field names`)
}
return points
}
// Every hour the endpoint returns, not just today — the "tap a Coming up card"
// hourly-detail modal needs this for tomorrow/day-after (and, where available,
// days 4-7) too.
async function fetchMetOfficeHourly(lat, lon, apiKey) {
const series = await fetchMetOfficeTimestep('hourly', lat, lon, apiKey)
return series
const points = series
.map(p => ({
resolution: 'hourly',
ts: p.time,
@ -81,12 +95,13 @@ async function fetchMetOfficeHourly(lat, lon, apiKey) {
source: 'met_office',
}))
.filter(p => typeof p.temp_c === 'number')
return assertParsed(series.length, points, 'hourly')
}
async function fetchMetOfficeThreeHourly(lat, lon, apiKey, dayKeys) {
const series = await fetchMetOfficeTimestep('three-hourly', lat, lon, apiKey)
return series
.filter(p => dayKeys.includes(londonDateKey(p.time)))
const dayRows = series.filter(p => dayKeys.includes(londonDateKey(p.time)))
const points = dayRows
.map(p => ({
resolution: '3hourly',
ts: p.time,
@ -100,6 +115,7 @@ async function fetchMetOfficeThreeHourly(lat, lon, apiKey, dayKeys) {
source: 'met_office',
}))
.filter(p => typeof p.temp_c === 'number')
return assertParsed(dayRows.length, points, 'three-hourly')
}
// Returns both the daily tier's points AND astro (sunrise/sunset) — the daily
@ -108,9 +124,8 @@ async function fetchMetOfficeThreeHourly(lat, lon, apiKey, dayKeys) {
// openMeteoAstroPoints below).
async function fetchMetOfficeDaily(lat, lon, apiKey, skipKeys, astroKeys) {
const series = await fetchMetOfficeTimestep('daily', lat, lon, apiKey)
const dailyPoints = series
.filter(p => !skipKeys.has(londonDateKey(p.time)))
.slice(0, 4)
const dayRows = series.filter(p => !skipKeys.has(londonDateKey(p.time))).slice(0, 4)
const dailyPoints = assertParsed(dayRows.length, dayRows
.map(p => ({
resolution: 'daily',
ts: p.time,
@ -123,7 +138,7 @@ async function fetchMetOfficeDaily(lat, lon, apiKey, skipKeys, astroKeys) {
icon: iconFromMetOffice(p.daySignificantWeatherCode),
source: 'met_office',
}))
.filter(p => typeof p.temp_c === 'number')
.filter(p => typeof p.temp_c === 'number'), 'daily')
const astroPoints = series
.filter(p => astroKeys.includes(londonDateKey(p.time)))
.map(p => ({ date: londonDateKey(p.time), sunrise: p.sunrise ?? null, sunset: p.sunset ?? null }))