{"record":{"id":"a953da50486d28ec","repo":"koala73/worldmonitor","slug":"eonet-malformed-event-category","errorCode":null,"errorMessage":"EONET malformed event category","messagePattern":"EONET malformed event category","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/seed-natural-events.mjs","lineNumber":253,"sourceCode":"        retryAfterMs: cause.retryAfterMs,\n      });\n      if (deadline - performance.now() <= Math.max(500, error.retryAfterMs || 0)) error.nonRetryable = true;\n      throw error;\n    } finally {\n      await dispatcher?.destroy();\n    }\n  }, 1, 500);\n}\n\nasync function fetchEonet(days, fetchFn = globalThis.fetch, now = Date.now()) {\n  const url = `${EONET_API_URL}?status=open&days=${days}`;\n  const data = await fetchEventSourceJson('eonet', url, fetchFn);\n  if (!Array.isArray(data?.events)) throw new Error('EONET malformed response');\n  const events = [];\n\n  for (const event of data.events || []) {\n    const category = event.categories?.[0];\n    if (!category) throw new Error('EONET malformed event category');\n    const normalizedCategory = normalizeCategory(category.id);\n    if (normalizedCategory === 'earthquakes') continue;\n\n    const latestGeo = event.geometry?.[event.geometry.length - 1];\n    if (!latestGeo || !['Point', 'Polygon'].includes(latestGeo.type) || !Array.isArray(latestGeo.coordinates)) {\n      throw new Error('EONET malformed event geometry');\n    }\n    if (latestGeo.type !== 'Point') continue;\n\n    const eventDate = new Date(latestGeo.date);\n    const [lon, lat] = latestGeo.coordinates;\n\n    if (normalizedCategory === 'wildfires' && now - eventDate.getTime() > WILDFIRE_MAX_AGE_MS) continue;\n\n    const source = event.sources?.[0];\n    events.push({\n      id: event.id || '',\n      title: event.title || '',","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/scripts/seed-natural-events.mjs#L235-L271","documentation":"Thrown by fetchEonet while iterating NASA EONET events: an event has no categories[0], so its category id cannot be normalized. The seeder requires every event to carry a category to classify it (and skip earthquakes), and treats its absence as a malformed payload rather than skipping the event.","triggerScenarios":"Any event object in data.events whose categories array is missing, null, or empty (event.categories?.[0] is undefined).","commonSituations":"EONET API schema change or partial outage returning events without categories; caching/proxy layers serving truncated GeoJSON; a new EONET event type that does not populate categories yet.","solutions":["Check the raw EONET API response for the offending event (curl the same URL) to confirm categories is missing vs a transient issue.","Retry the seed — if the API returned a truncated/partial response, a fresh request often fixes it.","Consider skipping events without a category instead of failing the whole seed, preserving the rest of the batch.","Verify no intermediate cache/proxy is truncating the JSON payload."],"exampleFix":"// before\nconst category = event.categories?.[0];\nif (!category) throw new Error('EONET malformed event category');\n// after\nconst category = event.categories?.[0];\nif (!category) { console.warn('Skipping EONET event without category', event.id); continue; }","handlingStrategy":"type-guard","validationCode":"const badEvents = (data?.events || []).filter(e => !e?.categories?.[0]); if (badEvents.length) console.warn('EONET events missing category:', badEvents.map(e => e.id));","typeGuard":"function hasCategory(event) { return Array.isArray(event?.categories) && event.categories.length > 0 && event.categories[0] != null; }","tryCatchPattern":"try { const events = await fetchEonet(fetchFn); } catch (e) { if (e.message === 'EONET malformed event category') { console.error('EONET payload missing categories; inspect raw response before retrying'); } throw e; }","preventionTips":["Validate the EONET envelope (events array shape) before the per-event loop","Skip-and-warn on individual malformed events instead of failing the whole seed","Compare responses against a saved good snapshot when the API changes","Watch NASA EONET API changelogs for schema updates"],"tags":["api","schema-validation","nasa-eonet","seed-script"],"backgroundTag":"unexpected-response-shape","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}