koala73/worldmonitor · error · Error
Custom scorecard bloc members must be uppercase ISO-2 codes.
Error message
Custom scorecard bloc members must be uppercase ISO-2 codes.
What it means
Each custom member in resolveBlocSelection must match /^[A-Z]{2}$/ — exactly two uppercase ASCII letters (ISO 3166-1 alpha-2). Lowercase codes, 3-letter ISO-3 codes, names, whitespace, or mixed-case entries cause this error. The strict format guarantees deterministic, safe cache keys (custom:<sorted codes joined>) and stable sorting.
Source
Thrown at server/worldmonitor/scorecard/v1/_bloc-presets.ts:38
id: 'NATO',
label: 'NATO',
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'],
},
};
export function resolveBlocSelection(input: { preset?: string; members?: string[] }): ScorecardBlocSelection {
const preset = String(input.preset || '').trim();
const members = Array.isArray(input.members) ? input.members : [];
if (Boolean(preset) === (members.length > 0)) {
throw new Error('Select exactly one scorecard bloc preset or custom member list.');
}
if (preset) {
if (!(preset in SCORECARD_BLOC_PRESETS)) throw new Error(`Unknown scorecard bloc preset: ${preset}`);
return SCORECARD_BLOC_PRESETS[preset as ScorecardBlocPreset];
}
if (members.length < 2 || members.length > 30) throw new Error('Custom scorecard blocs require 2-30 members.');
if (members.some((member) => !/^[A-Z]{2}$/.test(member))) {
throw new Error('Custom scorecard bloc members must be uppercase ISO-2 codes.');
}
if (new Set(members).size !== members.length) throw new Error('Custom scorecard bloc members must be unique.');
if (members.some((member) => !isInRankableUniverse(member))) {
throw new Error('Custom scorecard bloc members must belong to the public rankable country universe.');
}
const sorted = [...members].sort();
return { id: `custom:${sorted.join('-')}`, label: sorted.join(' + '), members: sorted };
}
View on GitHub (pinned to 9361220cc0)
Solutions
- Uppercase and trim each member before sending: m.trim().toUpperCase().
- Validate with /^[A-Z]{2}$/ client-side and reject or map non-conforming entries (names/ISO-3 → ISO-2 lookup).
- Use a dropdown of valid ISO-2 codes instead of free-text input.
- If a code can't be mapped, drop it (if ≥2 remain) rather than sending an invalid entry.
Example fix
// before
resolveBlocSelection({ members: ['de', 'fr'] }); // throws
// after
const members = raw.map((m) => m.trim().toUpperCase()).filter((m) => /^[A-Z]{2}$/.test(m));
if (members.length >= 2) resolveBlocSelection({ members }); Defensive patterns
Strategy: type-guard
Validate before calling
const members = raw.map((m) => String(m).trim().toUpperCase());
if (!members.every((m) => /^[A-Z]{2}$/.test(m))) throw new Error('members must be uppercase ISO-2 codes'); Type guard
function isUppercaseIso2(v: unknown): v is string {
return typeof v === 'string' && /^[A-Z]{2}$/.test(v);
} Try / catch
try {
const bloc = await getBlocScorecard({ members });
} catch (err) {
if (err.message.includes('uppercase ISO-2 codes')) {
return badRequest({ invalid: members.filter((m) => !/^[A-Z]{2}$/.test(m)) });
}
throw err;
} Prevention
- Normalize (trim/uppercase) at input collection time
- Use an ISO-2 dropdown instead of free text
- Provide a name/ISO-3 → ISO-2 lookup for pasted data
- Type member arrays as UppercaseIso2 in TypeScript
When it happens
Trigger: Calling getBlocScorecardWithReader with members like ['de','FR'] (lowercase), ['USA','FR'] (ISO-3), ['Germany','FR'] (names), ['D E','FR'], or ['US ','FR'] (trailing space — trim yourself first).
Common situations: 1) Client collects country names from free-text input instead of an ISO-2 picker; 2) codes arrive lowercase from a locale/URL slug; 3) server previously accepted case-insensitive input and an old client still sends lowercase; 4) CSV/clipboard paste introduces stray whitespace.
Related errors
- A valid country code is required for a resilience score gene
- Select exactly one scorecard bloc preset or custom member li
- Unknown scorecard bloc preset: ${preset}
- Custom scorecard blocs require 2-30 members.
- Custom scorecard bloc members must be unique.
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/37f5fb1a15df0244.
Report an issue: GitHub.