{"record":{"id":"ba6defc37e3779a7","repo":"koala73/worldmonitor","slug":"unknown-scorecard-bloc-preset-preset","errorCode":null,"errorMessage":"Unknown scorecard bloc preset: ${preset}","messagePattern":"Unknown scorecard bloc preset: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/scorecard/v1/_bloc-presets.ts","lineNumber":33,"sourceCode":"  },\n  BRICS: { id: 'BRICS', label: 'BRICS', members: ['BR', 'RU', 'IN', 'CN', 'ZA', 'SA', 'EG', 'AE', 'ET', 'ID', 'IR'] },\n  GCC: { id: 'GCC', label: 'Gulf Cooperation Council', members: ['AE', 'BH', 'KW', 'OM', 'QA', 'SA'] },\n  ASEAN: { id: 'ASEAN', label: 'ASEAN', members: ['BN', 'KH', 'ID', 'LA', 'MY', 'MM', 'PH', 'SG', 'TH', 'TL', 'VN'] },\n  NATO: {\n    id: 'NATO',\n    label: 'NATO',\n    members: ['AL', 'BE', 'BG', 'CA', 'HR', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IT', 'LV', 'LT', 'LU', 'ME', 'NL', 'MK', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'TR', 'GB', 'US'],\n  },\n};\n\nexport function resolveBlocSelection(input: { preset?: string; members?: string[] }): ScorecardBlocSelection {\n  const preset = String(input.preset || '').trim();\n  const members = Array.isArray(input.members) ? input.members : [];\n  if (Boolean(preset) === (members.length > 0)) {\n    throw new Error('Select exactly one scorecard bloc preset or custom member list.');\n  }\n  if (preset) {\n    if (!(preset in SCORECARD_BLOC_PRESETS)) throw new Error(`Unknown scorecard bloc preset: ${preset}`);\n    return SCORECARD_BLOC_PRESETS[preset as ScorecardBlocPreset];\n  }\n  if (members.length < 2 || members.length > 30) throw new Error('Custom scorecard blocs require 2-30 members.');\n  if (members.some((member) => !/^[A-Z]{2}$/.test(member))) {\n    throw new Error('Custom scorecard bloc members must be uppercase ISO-2 codes.');\n  }\n  if (new Set(members).size !== members.length) throw new Error('Custom scorecard bloc members must be unique.');\n  if (members.some((member) => !isInRankableUniverse(member))) {\n    throw new Error('Custom scorecard bloc members must belong to the public rankable country universe.');\n  }\n  const sorted = [...members].sort();\n  return { id: `custom:${sorted.join('-')}`, label: sorted.join(' + '), members: sorted };\n}\n","sourceCodeStart":15,"sourceCodeEnd":47,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/scorecard/v1/_bloc-presets.ts#L15-L47","documentation":"resolveBlocSelection validates the preset name against the SCORECARD_BLOC_PRESETS record (USMCA, EU27, ASEAN, BRICS, etc.) and throws this error for any preset string not present as a key. Preset names are case-sensitive and exact; typo'd, lowercased, or deprecated preset ids are rejected so callers always get an official, curated member list.","triggerScenarios":"Calling getBlocScorecardWithReader with preset set to a string not in SCORECARD_BLOC_PRESETS: 'eu27' (lowercase), 'E.U.', 'EU' (not the exact 'EU27'), 'NAFTA' (renamed to USMCA), an empty-after-trim string when members is also empty (that hits the XOR error first), or a preset id removed in a newer API version.","commonSituations":"1) Client hardcodes an outdated preset name after a rename (NAFTA→USMCA); 2) case mismatch from user-typed input; 3) version drift where server added/removed presets and the client uses an old list; 4) localization code translating preset labels back into the request field instead of sending the id.","solutions":["Use an exact preset id from SCORECARD_BLOC_PRESETS (e.g., 'USMCA', 'EU27'), case-sensitive.","Discover valid ids at runtime from the API's preset listing instead of hardcoding strings.","For user-typed input, normalize and map to the closest valid id before sending, or switch to a custom members list instead.","If a preset was renamed or removed, migrate to the new id (e.g., NAFTA → USMCA)."],"exampleFix":"// before\nresolveBlocSelection({ preset: 'eu27' }); // throws: not a key\n// after\nconst id = presetName.trim().toUpperCase() === 'EU' ? 'EU27' : presetName;\nif (!(id in SCORECARD_BLOC_PRESETS)) throw new Error(`Unknown preset ${id}`);\nresolveBlocSelection({ preset: id });","handlingStrategy":"validation","validationCode":"const VALID_PRESETS = ['USMCA','EU27','ASEAN','BRICS']; // fetch dynamically when possible\nif (!VALID_PRESETS.includes(preset)) throw new Error(`Unknown preset: ${preset}`);","typeGuard":"function isBlocPreset(p: string): p is keyof typeof SCORECARD_BLOC_PRESETS {\n  return p in SCORECARD_BLOC_PRESETS;\n}","tryCatchPattern":"try {\n  const bloc = await getBlocScorecard({ preset });\n} catch (err) {\n  if (err.message.startsWith('Unknown scorecard bloc preset')) {\n    return badRequest(`preset must be one of: ${Object.keys(SCORECARD_BLOC_PRESETS).join(', ')}`);\n  }\n  throw err;\n}","preventionTips":["Discover preset ids from the API rather than hardcoding","Keep preset ids case-sensitive and exact in clients","Track renames (e.g., NAFTA → USMCA) with a compatibility map","Only send labels for display; always send ids in requests"],"tags":["validation","scorecard","preset","bad-request"],"backgroundTag":"invalid-bloc-selection","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"}