koala73/worldmonitor · error · TypeError

Physical divergence composite repeats a metal: ${reading.met

Error message

Physical divergence composite repeats a metal: ${reading.metal}

What it means

Assertion error raised by shared/physical-divergence-contract.js when the physical divergence composite calculation receives a readings list containing the same metal more than once. Each metal must appear exactly once so the composite divergence score is unambiguous; duplicates make the per-metal weighting invalid, so the contract fails fast with the offending metal named in the message. Fix upstream by deduplicating readings (e.g., keep the latest reading per metal) before calling the composite builder.

Source

Thrown at shared/physical-divergence-contract.js:137

  return STORED_READING_REASONS_BY_STATE[state]?.includes(reason) === true;
}

export function isPhysicalDivergenceStoredCompositeReason(state, reason) {
  if (state === 'ok') return reason === '';
  return NON_OK_STATE_PRIORITY.includes(state)
    && COMPOSITE_MEMBER_REASONS.includes(reason)
    && reason.endsWith(`:${state}`);
}

export function buildPhysicalStressComposite(readings) {
  const values = Array.isArray(readings) ? readings : [];
  const byMetal = new Map();
  for (const reading of values) {
    if (!METAL_ORDER.includes(reading?.metal)) {
      throw new TypeError(`Unsupported physical divergence metal: ${String(reading?.metal)}`);
    }
    if (byMetal.has(reading.metal)) {
      throw new TypeError(`Physical divergence composite repeats a metal: ${reading.metal}`);
    }
    if (!STATES.includes(reading.state)) {
      throw new TypeError(`Unknown physical divergence state: ${String(reading.state)}`);
    }
    byMetal.set(reading.metal, reading);
  }
  const weights = METAL_ORDER.map((metal) => ({
    metal,
    weight: PHYSICAL_DIVERGENCE_CONTRACT.metals[metal].weight,
    methodologyVersion: METHODOLOGY_VERSION,
  }));
  for (const metal of METAL_ORDER) {
    if (!byMetal.has(metal)) {
      return {
        state: 'missing_input',
        reason: `member_not_ok:${metal}:missing_input`,
        index: null,
        weights,

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Dedupe readings by metal (last-wins or newest-timestamp) before the call
  2. Build readings as a Map keyed by metal so duplicates are impossible
  3. Assert uniqueness before calling: new Set(readings.map(r => r.metal)).size === readings.length
  4. Fix the producer to emit exactly one reading per contract metal

Example fix

// before
buildPhysicalStressComposite([...snapshotReadings, ...liveReadings])
// after
const byMetal = new Map([...snapshotReadings, ...liveReadings].map((r) => [r.metal, r]));
buildPhysicalStressComposite([...byMetal.values()])
Defensive patterns

Strategy: validation

Validate before calling

function dedupeByMetal(readings) {
  const byMetal = new Map();
  for (const r of Array.isArray(readings) ? readings : []) {
    if (r?.metal) byMetal.set(r.metal, r); // last wins
  }
  return [...byMetal.values()];
}

Type guard

function hasUniqueMetals(readings) {
  const metals = (Array.isArray(readings) ? readings : []).map((r) => r?.metal);
  return metals.every((m) => typeof m === 'string') && new Set(metals).size === metals.length;
}

Try / catch

try {
  composite = buildPhysicalStressComposite(readings);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('repeats a metal')) {
    composite = buildPhysicalStressComposite(dedupeByMetal(readings));
  } else throw e;
}

Prevention

When it happens

Trigger: Passing duplicate readings for 'gold' or 'silver' — e.g. concatenating two snapshot arrays, appending a live update to an already-complete reading list, or a producer emitting both a stored and a corrected reading.

Common situations: Merging historical + realtime data without dedupe; retry logic re-inserting a reading; upstream publishing the same metal under slightly different keys that normalize to the same metal.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/e5eaaa2d201620d3. Report an issue: GitHub.