koala73/worldmonitor · error · Error

A valid country code is required for a resilience score…

Error message

A valid country code is required for a resilience score generation

What it means

Thrown by ensureResilienceScoreGenerationCached in server/worldmonitor/resilience/v1/_shared.ts when the country-code argument cannot be normalized into a 2-letter ISO 3166-1 alpha-2 code. normalizeCountryCode() trims, uppercases, and requires /^[A-Z]{2}$/; anything else (empty, null-ish, 'usa', 'us ', 'U1') yields '' and triggers this error. The generation path is strict because the cache key and trace sidecar are keyed off the normalized code.

Solutions

  1. Validate and normalize the input first: trim, uppercase, and check /^[A-Z]{2}$/ before calling.
  2. If you have a country name or ISO-3 code, map it to ISO-2 (lookup table) before invoking.
  3. Ensure callers don't pass undefined when the field is optional — default to a valid code or skip the call.
  4. For API entry points, reject invalid countryCode with a ValidationError at the request boundary, mirroring get-resilience-indicators.ts.

Example fix

// before
await ensureResilienceScoreGenerationCached(userInput.country); // may be 'Brazil' or ''
// after
const iso2 = String(userInput.country ?? '').trim().toUpperCase();
if (!/^[A-Z]{2}$/.test(iso2)) throw new ValidationError([{ field: 'countryCode', description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code' }]);
await ensureResilienceScoreGenerationCached(iso2);
Defensive patterns

Strategy: validation

Validate before calling

function toIso2(v) { const s = String(v ?? '').trim().toUpperCase(); return /^[A-Z]{2}$/.test(s) ? s : null; }
const cc = toIso2(input.country);
if (!cc) throw new Error('countryCode must be a 2-letter ISO 3166-1 alpha-2 code');

Type guard

function isIso2(v: unknown): v is string {
  return typeof v === 'string' && /^[A-Z]{2}$/.test(v.trim().toUpperCase());
}

Try / catch

try {
  await ensureResilienceScoreGenerationCached(cc);
} catch (err) {
  if (err.message.includes('A valid country code is required')) {
    return badRequest({ field: 'countryCode', description: 'must be ISO-2' });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling ensureResilienceScoreGenerationCached('') , with undefined/null, with a 3-letter code like 'USA', with lowercase-only garbage that fails normalization, or with codes containing whitespace/numbers/dashes. Unlike ensureResilienceScoreCached (which returns a blank score), the *generation* wrapper throws.

Common situations: 1) A caller passes an unvalidated user-supplied country string (e.g., from a URL query param or country name like 'Brazil'); 2) an upstream seed/manifest produces an empty code; 3) code refactors pass full country names or ISO-3 codes where ISO-2 is required.

Related errors


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

Appendix: source

Thrown at server/worldmonitor/resilience/v1/_shared.ts:1290

        level: 'unknown',
        domains: [],
        trend: 'stable',
        change30d: 0,
        lowConfidence: true,
        imputationShare: 0,
        dataVersion: '',
        pillars: [],
        schemaVersion: '1.0',
        // Plan §U3: missing-cache fallback → not headline-eligible. A
        // country without a successful score build can't make the
        // PR 6 coverage gate either, so the conservative default is
        // false even during the PR-2 "true-by-default" window.
        headlineEligible: false,
      };
}

export async function ensureResilienceScoreGenerationCached(
  countryCode: string,
  reader?: ResilienceSeedReader,
): Promise<{ score: GetResilienceScoreResponse; trace: ResilienceScoreTraceSidecar }> {
  const normalizedCountryCode = normalizeCountryCode(countryCode);
  if (!normalizedCountryCode) {
    throw new Error('A valid country code is required for a resilience score generation');
  }

  await ensureResilienceScoreCached(normalizedCountryCode, reader);
  const cached = await getCachedJson(scoreCacheKey(normalizedCountryCode)) as CachedScorePayload | null;
  if (cached?._traceGenerationId && cached._traceCacheIdentity) {
    const trace = await getCachedJson(
      scoreTraceCacheKey(normalizedCountryCode, cached._traceGenerationId),
    );
    if (isMatchingTraceSidecar(trace, normalizedCountryCode, cached)) {
      return {
        score: await toPublicCachedScore(normalizedCountryCode, cached),
        trace,
      };

View on GitHub (pinned to 7d06c8633d)