koala73/worldmonitor · error · Error

Custom scorecard blocs require 2-30 members.

Error message

Custom scorecard blocs require 2-30 members.

What it means

When a custom member list (no preset) is supplied, resolveBlocSelection requires between 2 and 30 country codes inclusive. Lists of length 0 hit the XOR error first; lists of exactly 1 or longer than 30 hit this error. The bounds keep scorecard comparisons statistically meaningful and the request payload/cache key bounded.

Source

Thrown at server/worldmonitor/scorecard/v1/_bloc-presets.ts:36

  ASEAN: { id: 'ASEAN', label: 'ASEAN', members: ['BN', 'KH', 'ID', 'LA', 'MY', 'MM', 'PH', 'SG', 'TH', 'TL', 'VN'] },
  NATO: {
    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

  1. Provide 2–30 members; if you only need one country, call the single-country resilience score endpoint instead.
  2. For larger comparisons, split into multiple bloc requests of ≤30 and merge client-side.
  3. Cap the country picker UI at 30 selections before allowing submit.
  4. Deduplicate first — duplicates count toward array length but fail later on the uniqueness check.

Example fix

// before
resolveBlocSelection({ members: ['US'] }); // throws: only 1 member
// after
if (members.length < 2) return getResilienceScore({ countryCode: members[0] });
resolveBlocSelection({ members: members.slice(0, 30) });
Defensive patterns

Strategy: validation

Validate before calling

const members = (sel.members ?? []).filter(Boolean);
if (members.length < 2 || members.length > 30) throw new Error('Custom scorecard blocs require 2-30 members.');

Try / catch

try {
  const bloc = await getBlocScorecard({ members });
} catch (err) {
  if (err.message.includes('require 2-30 members')) {
    if (members.length === 1) return getResilienceScore({ countryCode: members[0] });
    return badRequest('select between 2 and 30 countries');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling getBlocScorecardWithReader with { members: ['US'] } (only 1 member) or with more than 30 members (e.g., dumping all ~200 ISO codes into a custom bloc). Zero members instead raises 'Select exactly one scorecard bloc preset or custom member list.'

Common situations: 1) A UI lets users pick a single country for comparison — must use the single-country scorecard API instead of a bloc; 2) 'select all' in a country picker sends the full universe; 3) scripted requests that join multiple blocs into one 40-member list instead of separate calls.

Related errors


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