{"record":{"id":"29bfde2a00221110","repo":"koala73/worldmonitor","slug":"jodi-gas-catalog-must-contain-one-supported-csv-archive","errorCode":null,"errorMessage":"JODI Gas catalog must contain one supported CSV archive","messagePattern":"JODI Gas catalog must contain one supported CSV archive","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/seed-jodi-gas.mjs","lineNumber":271,"sourceCode":"      return { dataOffset, compSize, compression, entryName };\n    }\n    // If bit 3 is set, compSize in local header is 0 — fall back to byte scan\n    if ((flags & 0x08) && compSize === 0) { offset++; continue; }\n    offset = dataOffset + compSize;\n  }\n  return null;\n}\n\n// Publication IDs identify specific releases. Follow the same catalog as the\n// official download page; a successful fetch of an old ID never finds new data.\nfunction resolveGasArchiveUrl(catalog) {\n  if (!Number.isSafeInteger(catalog?.publicationId) || catalog.publicationId <= 0\n    || !Array.isArray(catalog.files)) {\n    throw new Error('JODI Gas catalog has no valid publication');\n  }\n  const csvFiles = catalog.files.filter(file => file?.format === 'CSV' && file.ignore === false);\n  if (csvFiles.length !== 1 || csvFiles[0].filename !== 'GAS_world_NewFormat.zip') {\n    throw new Error('JODI Gas catalog must contain one supported CSV archive');\n  }\n  return `${ARCHIVE_ROOT}/${catalog.publicationId}/${csvFiles[0].filename}`;\n}\n\nasync function fetchAndParseCsv() {\n  // Discovery and archive download share the existing total request budget.\n  const signal = AbortSignal.timeout(120_000);\n  const catalogResponse = await fetch(CATALOG_URL, {\n    headers: { 'User-Agent': CHROME_UA },\n    signal,\n    redirect: 'error',\n  });\n  if (!catalogResponse.ok) throw new Error(`JODI Gas catalog fetch failed: HTTP ${catalogResponse.status}`);\n  const zipUrl = resolveGasArchiveUrl(await catalogResponse.json());\n  console.log(`  Fetching JODI Gas ZIP from ${zipUrl}`);\n  const resp = await fetch(zipUrl, {\n    headers: { 'User-Agent': CHROME_UA, 'Accept-Encoding': 'identity' },\n    signal,","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/scripts/seed-jodi-gas.mjs#L253-L289","documentation":"resolveGasArchiveUrl() expects the catalog's files array to contain exactly one usable CSV entry (format === 'CSV' and ignore === false) whose filename is 'GAS_world_NewFormat.zip'. If the count differs or the filename changed, it throws 'JODI Gas catalog must contain one supported CSV archive'. This pins the downloader to the known-supported archive so a silent upstream rename or new file doesn't seed wrong data.","triggerScenarios":"JODI renames the archive (e.g. GAS_world_v2.zip), adds a second CSV entry, marks the expected file ignore:true, or publishes a different format entry — any of which changes csvFiles to not be exactly one entry named GAS_world_NewFormat.zip.","commonSituations":"Upstream release format change at JODI, a new data product added to the catalog alongside the classic archive, or a temporary catalog publishing zero available files.","solutions":["Log/print the catalog.files entries to see what changed","Update the expected filename constant and validation to the new archive name after confirming it is the correct successor","Relax or extend the filter if multiple legitimate CSV archives now exist and pick the right one deterministically","Re-run the seed once the catalog again lists the supported archive"],"exampleFix":"// before\nif (csvFiles.length !== 1 || csvFiles[0].filename !== 'GAS_world_NewFormat.zip') {\n  throw new Error('JODI Gas catalog must contain one supported CSV archive');\n}\n// after\nconst EXPECTED = 'GAS_world_NewFormat.zip'; // update if JODI renames the archive\nif (csvFiles.length !== 1 || csvFiles[0].filename !== EXPECTED) {\n  throw new Error(`JODI Gas catalog must contain one supported CSV archive; got: ${csvFiles.map(f => f.filename).join(', ')}`);\n}","handlingStrategy":"type-guard","validationCode":"const csvFiles = (catalog?.files ?? []).filter(f => f?.format === 'CSV' && f.ignore === false);\nif (csvFiles.length !== 1 || csvFiles[0].filename !== 'GAS_world_NewFormat.zip') {\n  console.error('unexpected CSV archive set:', csvFiles.map(f => f?.filename));\n}","typeGuard":"function hasSupportedArchive(catalog) {\n  const csv = (catalog?.files ?? []).filter(f => f?.format === 'CSV' && f.ignore === false);\n  return csv.length === 1 && csv[0].filename === 'GAS_world_NewFormat.zip';\n}","tryCatchPattern":"try {\n  const zipUrl = resolveGasArchiveUrl(catalog);\n} catch (err) {\n  if (err.message.includes('one supported CSV archive')) console.error('JODI catalog archive name/set changed; review catalog.files');\n  throw err;\n}","preventionTips":["Log catalog.files when validation fails so upstream renames are visible","Keep the expected filename in a single named constant for easy updates","Periodically verify the catalog still lists the expected archive"],"tags":["validation","external-api","data-seeding","upstream-change"],"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"}