{"record":{"id":"3182eb01a0f10fbf","repo":"koala73/worldmonitor","slug":"hs4-must-be-a-supported-four-digit-heading","errorCode":null,"errorMessage":"hs4 must be a supported four-digit heading","messagePattern":"hs4 must be a supported four-digit heading","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"server/worldmonitor/supply-chain/v1/get-country-products.ts","lineNumber":209,"sourceCode":"    ...product,\n    topExporters: product.topExporters.map(exporter => {\n      const scale = byCode.get(exporter.partnerCode);\n      return scale && scale.year === product.year ? { ...exporter, scale } : exporter;\n    }),\n  };\n}\n\nexport async function getCountryProducts(\n  ctx: ServerContext,\n  req: GetCountryProductsRequest,\n): Promise<GetCountryProductsResponse> {\n  const iso2 = (req.iso2 ?? '').trim().toUpperCase();\n  if (!/^[A-Z]{2}$/.test(iso2)) {\n    throw new ValidationError([{ field: 'iso2', description: 'iso2 must be a 2-letter uppercase ISO country code' }]);\n  }\n  const hs4 = req.hs4?.trim();\n  if (hs4 && !HS4_CODES.includes(hs4)) {\n    throw new ValidationError([{ field: 'hs4', description: 'hs4 must be a supported four-digit heading' }]);\n  }\n  const isPro = await isCallerPremium(ctx.request);\n  const empty: GetCountryProductsResponse = { iso2, products: [], fetchedAt: '' };\n  if (!isPro) return empty;\n\n  const key = `comtrade:bilateral-hs4:${iso2}:v1`;\n  // Status-aware reads for the two country keys so a read error stays\n  // distinguishable from a miss; the canonical one decides cache_unavailable,\n  // the sibling one only decides how deep the origins go.\n  // The sibling detail and the world-exports snapshot are served only for a\n  // requested heading, so a whole-catalogue caller (the deep-dive panel) does\n  // not read them: the snapshot is a few hundred kilobytes (36 headings x ~140\n  // reporters), past what the 1.5 s single-GET deadline is sized for, and the\n  // large-value reader waits on the pipeline deadline instead.\n  const [cached, siblingRead, worldExportsValue, meta] = await Promise.all([\n    readCachedJson(key, true),\n    hs4 ? readCachedJson(PARTNERS_KEY(iso2), true) : { status: 'miss' as const },\n    hs4 ? getLargeRawJson(WORLD_EXPORTS_KEY).catch(() => null) : null,","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/server/worldmonitor/supply-chain/v1/get-country-products.ts#L191-L227","documentation":"getCountryProducts accepts an optional `hs4` filter restricted to a fixed allowlist of supported four-digit HS headings (HS4_CODES). If hs4 is provided but is not in that list, a ValidationError with field 'hs4' is thrown; note the endpoint also returns an empty product list for non-premium callers.","triggerScenarios":"Calling get-country-products with hs4 = '12345' (5 digits), '85' (2-digit chapter), a lowercase or padded value, or a valid-looking HS4 code that the platform simply does not track.","commonSituations":"Confusing HS chapters (2-digit) or lines (6+ digit) with 4-digit headings; using a code valid in the full HS taxonomy but absent from the tracked subset; trailing spaces from spreadsheet data; non-premium callers probing the parameter expecting data.","solutions":["Check hs4 against the supported HS4_CODES list (fetch the unfiltered products response to see which headings are returned) and pick a supported one","Normalize input: trim and pass exactly four characters, digits only (e.g. '8501', not '8501.10' or ' 8501 ')","Omit hs4 entirely to get all tracked products for the country, then filter client-side","If you need an untracked heading, request it be added to HS4_CODES rather than sending it to the endpoint"],"exampleFix":"// before\nawait client.getCountryProducts({ iso2: 'DE', hs4: '8501.10' });\n// after\nconst hs4 = req.hs4?.trim();\nif (hs4 && !/^\\d{4}$/.test(hs4)) throw new Error('hs4 must be 4 digits');\nawait client.getCountryProducts({ iso2: 'DE', hs4 }); // or omit hs4 and filter locally","handlingStrategy":"validation","validationCode":"const SUPPORTED_HS4 = ['8501','8471','5201']; // mirror of HS4_CODES / from API response\nif (hs4 !== undefined && hs4 !== '' && !SUPPORTED_HS4.includes(hs4.trim())) {\n  throw new Error(`Unsupported hs4 heading: ${hs4}`);\n}","typeGuard":"function isSupportedHs4(v: string): boolean {\n  return /^\\d{4}$/.test(v) && HS4_CODES.includes(v);\n}","tryCatchPattern":"try {\n  return await client.getCountryProducts({ iso2, hs4 });\n} catch (e) {\n  if (e instanceof ValidationError && e.fields?.[0]?.field === 'hs4') {\n    return client.getCountryProducts({ iso2 }); // all tracked products, filter client-side\n  }\n  throw e;\n}","preventionTips":["Trim and normalize hs4 to exactly four digits before sending","Do not confuse HS chapters (2-digit) or 6-digit lines with 4-digit headings","Build hs4 pickers from the endpoint's own supported list, not raw tariff tables"],"tags":["validation","api","trade","invalid-parameter"],"backgroundTag":"invalid-enum-value","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}