{"record":{"id":"f11f47eaccb04d73","repo":"koala73/worldmonitor","slug":"unsupported-physical-divergence-metal-string-re","errorCode":null,"errorMessage":"Unsupported physical divergence metal: ${String(reading?.metal)}","messagePattern":"Unsupported physical divergence metal: (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"shared/physical-divergence-contract.js","lineNumber":134,"sourceCode":"}\n\nexport function isPhysicalDivergenceStoredReadingReason(state, reason) {\n  return STORED_READING_REASONS_BY_STATE[state]?.includes(reason) === true;\n}\n\nexport function isPhysicalDivergenceStoredCompositeReason(state, reason) {\n  if (state === 'ok') return reason === '';\n  return NON_OK_STATE_PRIORITY.includes(state)\n    && COMPOSITE_MEMBER_REASONS.includes(reason)\n    && reason.endsWith(`:${state}`);\n}\n\nexport function buildPhysicalStressComposite(readings) {\n  const values = Array.isArray(readings) ? readings : [];\n  const byMetal = new Map();\n  for (const reading of values) {\n    if (!METAL_ORDER.includes(reading?.metal)) {\n      throw new TypeError(`Unsupported physical divergence metal: ${String(reading?.metal)}`);\n    }\n    if (byMetal.has(reading.metal)) {\n      throw new TypeError(`Physical divergence composite repeats a metal: ${reading.metal}`);\n    }\n    if (!STATES.includes(reading.state)) {\n      throw new TypeError(`Unknown physical divergence state: ${String(reading.state)}`);\n    }\n    byMetal.set(reading.metal, reading);\n  }\n  const weights = METAL_ORDER.map((metal) => ({\n    metal,\n    weight: PHYSICAL_DIVERGENCE_CONTRACT.metals[metal].weight,\n    methodologyVersion: METHODOLOGY_VERSION,\n  }));\n  for (const metal of METAL_ORDER) {\n    if (!byMetal.has(metal)) {\n      return {\n        state: 'missing_input',","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/shared/physical-divergence-contract.js#L116-L152","documentation":"buildPhysicalStressComposite validates each reading's metal against METAL_ORDER (the closed set ['gold','silver']); any other metal (or missing metal on null/undefined readings) throws this TypeError. The composite is defined only over the contract's metals.","triggerScenarios":"Passing readings with metals like 'copper', 'platinum', or a typo such as 'golf'; passing null/undefined readings (reading?.metal is undefined); feeding raw market data not normalized to the contract's metal set.","commonSituations":"Extending the dashboard to new metals without updating the contract; upstream feed using different metal symbols (XAU/XAG); map/filter pipelines that drop or mangle the metal field.","solutions":["Only pass readings whose metal is 'gold' or 'silver'","Filter or reject unsupported metals before building the composite","Normalize upstream symbols (XAU->gold, XAG->silver) first","If a new metal is genuinely needed, add it to METAL_ORDER and PHYSICAL_DIVERGENCE_CONTRACT.metals with a weight"],"exampleFix":"// before\nbuildPhysicalStressComposite([{ metal: 'copper', state: 'ok', index: 1 }])\n// after\nconst supported = readings.filter((r) => ['gold', 'silver'].includes(r?.metal));\nbuildPhysicalStressComposite(supported)","handlingStrategy":"type-guard","validationCode":"import { METAL_ORDER } from './physical-divergence-contract.js';\nfunction filterSupportedReadings(readings) {\n  return (Array.isArray(readings) ? readings : []).filter((r) => METAL_ORDER.includes(r?.metal));\n}","typeGuard":"function isSupportedReading(r) {\n  return r != null && METAL_ORDER.includes(r.metal) && typeof r.metal === 'string';\n}","tryCatchPattern":"try {\n  composite = buildPhysicalStressComposite(readings);\n} catch (e) {\n  if (e instanceof TypeError && e.message.startsWith('Unsupported physical divergence metal')) {\n    composite = null; // drop and report the unsupported metal\n  } else throw e;\n}","preventionTips":["Normalize upstream symbols (XAU->gold, XAG->silver) at ingestion","Filter readings against METAL_ORDER before composing","Update METAL_ORDER and contract weights deliberately when adding metals","Guard against null/undefined readings upstream"],"tags":["contract","typeerror","data-contract"],"backgroundTag":"invalid-enum-value","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}