import type { ResilienceDimensionId } from './_dimension-scorers.ts';

// Phase 2 T2.2a signal tiering. See docs/internal/country-resilience-upgrade-plan.md
// section "Signal tiering (Core / Enrichment / Experimental)".
export type IndicatorTier = 'core' | 'enrichment' | 'experimental';

// Phase 2 T2.2a license audit field. The values come from the parent plan's
// licensing audit workstream (Phase 2 A9). New entries that have not yet been
// audited use 'unknown' as a placeholder; the linter test reports the count
// without failing so the audit can chase them.
export type IndicatorLicense =
  | 'public-domain' // WGI, WHO GHO, FRED, UN Comtrade (openly usable)
  | 'open-data' // ND-GAIN, World Bank Open Data, IMF Open Data (CC-BY or similar)
  | 'open-attribution' // GDELT, RSF Index, OWID (attribution required)
  | 'research-only' // UCDP (Uppsala), ACLED (academic/press), INFORM-Global (CC-BY-NC)
  | 'non-commercial' // FSI, BIS EER, GPI/IEP (free with carve-out)
  | 'proprietary' // Bloomberg, S&P Global Platts (not used in Core)
  | 'unknown'; // placeholder for any indicator still awaiting license audit

export type IndicatorSpec = {
  id: string;
  dimension: ResilienceDimensionId;
  description: string;
  direction: 'higherBetter' | 'lowerBetter';
  goalposts: { worst: number; best: number };
  weight: number;
  sourceKey: string;
  scope: 'global' | 'curated';
  cadence: 'realtime' | 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annual';
  imputation?: { type: 'absenceSignal' | 'conservative'; score: number; certainty: number };
  // Phase 2 T2.2a additions (REQUIRED on every entry):
  tier: IndicatorTier; // Core = moves the public overall score, Enrichment = drill-down only, Experimental = internal
  coverage: number; // expected country count; the tier linter enforces Core >= 180
  license: IndicatorLicense; // source license category for the audit trail
};

export const INDICATOR_REGISTRY: IndicatorSpec[] = [
  // ── macroFiscal (4 sub-metrics) ───────────────────────────────────────────
  {
    id: 'govRevenuePct',
    dimension: 'macroFiscal',
    description: 'Government revenue as % of GDP (IMF GGR_G01_GDP_PT); fiscal capacity proxy',
    direction: 'higherBetter',
    goalposts: { worst: 5, best: 45 },
    weight: 0.4,
    sourceKey: 'economic:imf:macro:v2',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 212,
    license: 'open-data',
  },
  {
    id: 'debtGrowthRate',
    dimension: 'macroFiscal',
    description: 'Annual debt growth rate; rapid accumulation signals fiscal stress',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.2,
    sourceKey: 'economic:national-debt:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'open-data',
  },
  {
    id: 'currentAccountPct',
    dimension: 'macroFiscal',
    description: 'Current account balance as % of GDP (IMF); external position vulnerability',
    direction: 'higherBetter',
    goalposts: { worst: -20, best: 20 },
    weight: 0.2,
    sourceKey: 'economic:imf:macro:v2',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'open-data',
  },
  {
    id: 'unemploymentPct',
    dimension: 'macroFiscal',
    description: 'Unemployment rate (IMF WEO LUR); higher = labor-market slack & lower fiscal absorption capacity',
    direction: 'lowerBetter',
    goalposts: { worst: 25, best: 3 },
    weight: 0.15,
    sourceKey: 'economic:imf:labor:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'enrichment',
    coverage: 150,
    license: 'open-data',
  },
  {
    id: 'householdDebtService',
    dimension: 'macroFiscal',
    description: 'BIS household debt service ratio (% income, quarterly). DSR > 10% precedes banking crises (Drehmann 2011). Lower is safer; goalposts anchor 20% → 0, 0% → 100.',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.05,
    sourceKey: 'economic:bis:dsr:v1',
    scope: 'curated',
    cadence: 'quarterly',
    imputation: { type: 'conservative', score: 60, certainty: 0.3 },
    tier: 'enrichment',
    coverage: 40,
    license: 'non-commercial',
  },

  // ── currencyExternal (3 sub-metrics, plus IMF inflation fallback for non-BIS) ─
  {
    id: 'fxVolatility',
    dimension: 'currencyExternal',
    description: 'Annualized BIS real effective exchange rate volatility (std-dev of monthly changes * sqrt(12)). Fallback chain when BIS absent: (1) IMF inflation + WB reserves proxy, (2) IMF inflation alone, (3) reserves alone, (4) conservative imputation.',
    direction: 'lowerBetter',
    goalposts: { worst: 50, best: 0 },
    weight: 0.6,
    sourceKey: 'economic:bis:eer:v1',
    scope: 'curated',
    cadence: 'monthly',
    imputation: { type: 'conservative', score: 50, certainty: 0.3 },
    // BIS REER is curated (~60 countries). Demoted to Enrichment by the
    // Phase 2 A4 coverage gate; the IMF inflation + WB reserves fallback
    // chain still feeds the Core fxReservesAdequacy signal globally.
    tier: 'enrichment',
    coverage: 60,
    license: 'non-commercial',
  },
  {
    id: 'fxDeviation',
    dimension: 'currencyExternal',
    description: 'Absolute deviation of latest BIS real EER from 100 (equilibrium index). Fallback chain when BIS absent: (1) IMF inflation + WB reserves proxy, (2) IMF inflation alone, (3) reserves alone, (4) conservative imputation.',
    direction: 'lowerBetter',
    goalposts: { worst: 35, best: 0 },
    weight: 0.25,
    sourceKey: 'economic:bis:eer:v1',
    scope: 'curated',
    cadence: 'monthly',
    imputation: { type: 'conservative', score: 50, certainty: 0.3 },
    // BIS REER curated source, same coverage limitation as fxVolatility.
    tier: 'enrichment',
    coverage: 60,
    license: 'non-commercial',
  },
  {
    id: 'fxReservesAdequacy',
    dimension: 'currencyExternal',
    description: 'Total reserves in months of imports (World Bank FI.RES.TOTL.MO). Supplementary metric for BIS countries (weight 0.15), primary metric alongside IMF inflation for non-BIS countries (~160 countries).',
    direction: 'higherBetter',
    goalposts: { worst: 1, best: 12 },
    weight: 0.15,
    sourceKey: 'resilience:static:*',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },

  // ── tradeSanctions (4 sub-metrics) ────────────────────────────────────────
  {
    id: 'sanctionCount',
    dimension: 'tradeSanctions',
    description: 'OFAC sanctions entity count per country; piecewise normalization (0=100, 200+=near 0)',
    direction: 'lowerBetter',
    goalposts: { worst: 200, best: 0 },
    weight: 0.45,
    sourceKey: 'sanctions:country-counts:v1',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 200,
    license: 'public-domain',
  },
  {
    id: 'tradeRestrictions',
    dimension: 'tradeSanctions',
    description: 'WTO trade restrictions count (IN_FORCE weighted 3x); curated reporter set',
    direction: 'lowerBetter',
    goalposts: { worst: 30, best: 0 },
    weight: 0.15,
    sourceKey: 'trade:restrictions:v1:tariff-overview:50',
    scope: 'curated',
    cadence: 'weekly',
    imputation: { type: 'conservative', score: 60, certainty: 0.4 },
    // WTO tariff-overview reporter set is the curated top-50 reporters; below
    // the Core 180 gate so the signal sits in Enrichment until a wider seeder
    // ships in a future PR.
    tier: 'enrichment',
    coverage: 50,
    license: 'open-data',
  },
  {
    id: 'tradeBarriers',
    dimension: 'tradeSanctions',
    description: 'WTO trade barrier notifications count; curated reporter set',
    direction: 'lowerBetter',
    goalposts: { worst: 40, best: 0 },
    weight: 0.15,
    sourceKey: 'trade:barriers:v1:tariff-gap:50',
    scope: 'curated',
    cadence: 'weekly',
    imputation: { type: 'conservative', score: 60, certainty: 0.4 },
    tier: 'enrichment',
    coverage: 50,
    license: 'open-data',
  },
  {
    id: 'appliedTariffRate',
    dimension: 'tradeSanctions',
    description: 'World Bank applied tariff rate, weighted mean, all products (TM.TAX.MRCH.WM.AR.ZS); 0%=free trade, 20%+=heavily restricted',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.25,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },

  // ── cyberDigital (3 sub-metrics) ──────────────────────────────────────────
  {
    id: 'cyberThreats',
    dimension: 'cyberDigital',
    description: 'Severity-weighted cyber threat count (critical=3x, high=2x, medium=1x, low=0.5x)',
    direction: 'lowerBetter',
    goalposts: { worst: 25, best: 0 },
    weight: 0.45,
    sourceKey: 'cyber:threats:v2',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'internetOutages',
    dimension: 'cyberDigital',
    description: 'Internet outage penalty (total=4x, major=2x, partial=1x)',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.35,
    sourceKey: 'infra:outages:v1',
    scope: 'global',
    cadence: 'realtime',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'gpsJamming',
    dimension: 'cyberDigital',
    description: 'GPS jamming hex penalty (high=3x, medium=1x)',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.2,
    sourceKey: 'intelligence:gpsjam:v2',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },

  // ── logisticsSupply (3 sub-metrics) ───────────────────────────────────────
  {
    id: 'roadsPavedLogistics',
    dimension: 'logisticsSupply',
    description: 'Paved roads as % of total road network (World Bank IS.ROD.PAVE.ZS)',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 100 },
    weight: 0.5,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },
  {
    id: 'shippingStress',
    dimension: 'logisticsSupply',
    description: 'Global shipping stress score from supply-chain monitor',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.25,
    sourceKey: 'supply_chain:shipping_stress:v1',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'transitDisruption',
    dimension: 'logisticsSupply',
    description: 'Mean transit corridor disruption (disruptionPct + incidentCount7d * 0.5)',
    direction: 'lowerBetter',
    goalposts: { worst: 30, best: 0 },
    weight: 0.25,
    sourceKey: 'supply_chain:transit-summaries:v1',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },

  // ── infrastructure (3 sub-metrics) ────────────────────────────────────────
  {
    id: 'electricityAccess',
    dimension: 'infrastructure',
    description: 'Access to electricity as % of population (World Bank EG.ELC.ACCS.ZS)',
    direction: 'higherBetter',
    goalposts: { worst: 40, best: 100 },
    weight: 0.4,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 217,
    license: 'open-data',
  },
  {
    id: 'roadsPavedInfra',
    dimension: 'infrastructure',
    description: 'Paved roads as % of total road network (World Bank IS.ROD.PAVE.ZS)',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 100 },
    weight: 0.35,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },
  {
    id: 'infraOutages',
    dimension: 'infrastructure',
    description: 'Internet outage penalty (total=4x, major=2x, partial=1x); shared source with cyberDigital',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.25,
    sourceKey: 'infra:outages:v1',
    scope: 'global',
    cadence: 'realtime',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },

  // ── energy (7 sub-metrics) ────────────────────────────────────────────────
  {
    id: 'energyImportDependency',
    dimension: 'energy',
    description: 'IEA energy import dependency (% of total energy supply from imports)',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.25,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },
  {
    id: 'gasShare',
    dimension: 'energy',
    description: 'Natural gas share of energy mix (%); high share = single-source vulnerability',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.12,
    sourceKey: 'energy:mix:v1:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'coalShare',
    dimension: 'energy',
    description: 'Coal share of energy mix (%); high share = transition risk and pollution',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.08,
    sourceKey: 'energy:mix:v1:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'renewShare',
    dimension: 'energy',
    description: 'Renewable energy share of energy mix (%); diversification and resilience',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 100 },
    weight: 0.05,
    sourceKey: 'energy:mix:v1:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'gasStorageStress',
    dimension: 'energy',
    description: 'Gas storage fill stress: (80 - fillPct) / 80 clamped to [0,1], scaled to 0-100',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.1,
    sourceKey: 'energy:gas-storage:v1:{ISO2}',
    scope: 'global',
    cadence: 'daily',
    // GIE AGSI+ covers EU + a few neighbours; below the Core 180 gate so the
    // signal lives in Enrichment until a wider gas-storage feed lands.
    tier: 'enrichment',
    coverage: 38,
    license: 'open-attribution',
  },
  {
    id: 'energyPriceStress',
    dimension: 'energy',
    description: 'Mean absolute energy price change across commodities',
    direction: 'lowerBetter',
    goalposts: { worst: 25, best: 0 },
    weight: 0.1,
    sourceKey: 'economic:energy:v1:all',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'public-domain',
  },
  {
    id: 'electricityConsumption',
    dimension: 'energy',
    description: 'Per-capita electricity consumption (kWh/year, World Bank EG.USE.ELEC.KH.PC); low = grid collapse',
    direction: 'higherBetter',
    goalposts: { worst: 200, best: 8000 },
    weight: 0.3,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 217,
    license: 'open-data',
  },

  // ── governanceInstitutional (6 sub-metrics, equal weight) ─────────────────
  {
    id: 'wgiVoiceAccountability',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Voice and Accountability (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'wgiPoliticalStability',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Political Stability and Absence of Violence (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'wgiGovernmentEffectiveness',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Government Effectiveness (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'wgiRegulatoryQuality',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Regulatory Quality (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'wgiRuleOfLaw',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Rule of Law (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'wgiControlOfCorruption',
    dimension: 'governanceInstitutional',
    description: 'World Bank WGI: Control of Corruption (-2.5 to +2.5)',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 1 / 6,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },

  // ── socialCohesion (3 sub-metrics) ────────────────────────────────────────
  {
    id: 'gpiScore',
    dimension: 'socialCohesion',
    description: 'Global Peace Index score; empirical range 1.1 (Iceland) to 3.4 (Yemen 2024)',
    direction: 'lowerBetter',
    goalposts: { worst: 3.6, best: 1.0 },
    weight: 0.55,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    // GPI/IEP covers 163 economies, below the Phase 2 A4 Core gate of 180.
    // Demoted to Enrichment so the overall public score is not driven by a
    // signal that misses ~30 countries; PR 4 (T2.3) aggregation will respect
    // this. The license is also non-commercial (IEP carve-out), which would
    // independently disqualify Core. See parent plan, "Signal tiering" section.
    tier: 'enrichment',
    coverage: 163,
    license: 'non-commercial',
  },
  {
    id: 'displacementTotal',
    dimension: 'socialCohesion',
    description: 'UNHCR total displaced persons (log10 scale); absent = null (excluded from blend, no imputation)',
    direction: 'lowerBetter',
    goalposts: { worst: 7, best: 0 },
    weight: 0.25,
    sourceKey: 'displacement:summary:v1:{year}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 200,
    license: 'open-data',
  },
  {
    id: 'unrestEvents',
    dimension: 'socialCohesion',
    description: 'Unrest event count (severity-weighted) + sqrt(fatalities)',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.2,
    sourceKey: 'unrest:events:v1',
    scope: 'global',
    cadence: 'realtime',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },

  // ── borderSecurity (2 sub-metrics) ────────────────────────────────────────
  {
    id: 'ucdpConflict',
    dimension: 'borderSecurity',
    description: 'UCDP armed conflict metric: eventCount*2 + typeWeight + sqrt(deaths)',
    direction: 'lowerBetter',
    goalposts: { worst: 30, best: 0 },
    weight: 0.65,
    sourceKey: 'conflict:ucdp-events:v1',
    scope: 'global',
    cadence: 'realtime',
    // UCDP is global (193 countries) but the license is research-only
    // (Uppsala). The parent plan keeps UCDP Core; the linter allowlist
    // KNOWN_EXCEPTIONS in tests/resilience-indicator-tiering.test.mts holds
    // the carve-out until Phase 2 A9 licensing review resolves it.
    tier: 'core',
    coverage: 193,
    license: 'research-only',
  },
  {
    id: 'displacementHosted',
    dimension: 'borderSecurity',
    description: 'UNHCR hosted/total displaced persons (log10 scale); refugee pressure proxy',
    direction: 'lowerBetter',
    goalposts: { worst: 7, best: 0 },
    weight: 0.35,
    sourceKey: 'displacement:summary:v1:{year}',
    scope: 'global',
    cadence: 'annual',
    imputation: { type: 'absenceSignal', score: 85, certainty: 0.6 },
    tier: 'core',
    coverage: 200,
    license: 'open-data',
  },

  // ── informationCognitive (3 sub-metrics) ──────────────────────────────────
  // Promoted back to Core in T2.9 after language / source-density
  // normalization landed (getLanguageCoverageFactor in _language-coverage.ts).
  // Social velocity and news threat scores are now adjusted by the
  // English-language coverage factor before normalization.
  {
    id: 'rsfPressFreedom',
    dimension: 'informationCognitive',
    description: 'Reporters Sans Frontieres press freedom score (0-100)',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 100 },
    weight: 0.55,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 180,
    license: 'open-attribution',
  },
  {
    id: 'socialVelocity',
    dimension: 'informationCognitive',
    description: 'Reddit social velocity score (log10(velocity+1)); language-normalized viral narrative stress',
    direction: 'lowerBetter',
    goalposts: { worst: 3, best: 0 },
    weight: 0.15,
    sourceKey: 'intelligence:social:reddit:v1',
    scope: 'global',
    cadence: 'realtime',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },
  {
    id: 'newsThreatScore',
    dimension: 'informationCognitive',
    description: 'AI news threat summary (critical=4x, high=2x, medium=1x, low=0.5x); language-normalized',
    direction: 'lowerBetter',
    goalposts: { worst: 20, best: 0 },
    weight: 0.3,
    sourceKey: 'news:threat:summary:v1',
    scope: 'global',
    cadence: 'daily',
    tier: 'core',
    coverage: 195,
    license: 'open-attribution',
  },

  // ── healthPublicService (3 sub-metrics) ───────────────────────────────────
  {
    id: 'uhcIndex',
    dimension: 'healthPublicService',
    description: 'WHO Universal Health Coverage service coverage index (0-100)',
    direction: 'higherBetter',
    goalposts: { worst: 40, best: 90 },
    weight: 0.45,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 194,
    license: 'public-domain',
  },
  {
    id: 'measlesCoverage',
    dimension: 'healthPublicService',
    description: 'WHO measles immunization coverage among 1-year-olds (%)',
    direction: 'higherBetter',
    goalposts: { worst: 50, best: 99 },
    weight: 0.35,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 194,
    license: 'public-domain',
  },
  {
    id: 'hospitalBeds',
    dimension: 'healthPublicService',
    description: 'WHO hospital beds per 1,000 people',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 8 },
    weight: 0.2,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 194,
    license: 'public-domain',
  },

  // ── foodWater (3 sub-metrics) ─────────────────────────────────────────────
  {
    id: 'ipcPeopleInCrisis',
    dimension: 'foodWater',
    description: 'IPC/FAO people in food crisis (log10 scale)',
    direction: 'lowerBetter',
    goalposts: { worst: 7, best: 0 },
    weight: 0.45,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    imputation: { type: 'absenceSignal', score: 88, certainty: 0.7 },
    // IPC measured coverage is ~52 crisis-tracked countries; absence is a
    // strong positive signal (stable-absence imputation, score 88), so the
    // effective country coverage is global. Stays Core per the parent plan.
    tier: 'core',
    coverage: 195,
    license: 'open-data',
  },
  {
    id: 'ipcPhase',
    dimension: 'foodWater',
    description: 'IPC food crisis phase (1-5 scale)',
    direction: 'lowerBetter',
    goalposts: { worst: 5, best: 1 },
    weight: 0.15,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    imputation: { type: 'absenceSignal', score: 88, certainty: 0.7 },
    tier: 'core',
    coverage: 195,
    license: 'open-data',
  },
  {
    id: 'aquastatWaterStress',
    dimension: 'foodWater',
    description: 'FAO AQUASTAT stress/withdrawal/dependency indicators (% scale 0-100)',
    direction: 'lowerBetter',
    goalposts: { worst: 100, best: 0 },
    weight: 0.25,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },
  {
    id: 'aquastatWaterAvailability',
    dimension: 'foodWater',
    description: 'FAO AQUASTAT availability/renewable/access indicators (0-100 % or 0-5000 m3/capita)',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 5000 },
    weight: 0.15,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },

  // ── fiscalSpace (3 sub-metrics) ──────────────────────────────────────────
  {
    id: 'recoveryGovRevenue',
    dimension: 'fiscalSpace',
    description: 'Government revenue as % of GDP (IMF GGR_G01_GDP_PT); fiscal mobilization capacity for recovery',
    direction: 'higherBetter',
    goalposts: { worst: 5, best: 45 },
    weight: 0.4,
    sourceKey: 'resilience:recovery:fiscal-space:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'open-data',
  },
  {
    id: 'recoveryFiscalBalance',
    dimension: 'fiscalSpace',
    description: 'General government net lending/borrowing as % of GDP (IMF GGXCNL_G01_GDP_PT); deficit signals reduced recovery firepower',
    direction: 'higherBetter',
    goalposts: { worst: -15, best: 5 },
    weight: 0.3,
    sourceKey: 'resilience:recovery:fiscal-space:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'open-data',
  },
  {
    id: 'recoveryDebtToGdp',
    dimension: 'fiscalSpace',
    description: 'General government gross debt as % of GDP (IMF GGXWDG_NGDP_PT); high debt limits recovery borrowing capacity',
    direction: 'lowerBetter',
    goalposts: { worst: 150, best: 0 },
    weight: 0.3,
    sourceKey: 'resilience:recovery:fiscal-space:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'open-data',
  },

  // ── reserveAdequacy (1 sub-metric) ───────────────────────────────────────
  {
    id: 'recoveryReserveMonths',
    dimension: 'reserveAdequacy',
    description: 'Total reserves in months of imports (World Bank FI.RES.TOTL.MO); recovery buffer against external shocks',
    direction: 'higherBetter',
    goalposts: { worst: 1, best: 18 },
    weight: 1.0,
    sourceKey: 'resilience:recovery:reserve-adequacy:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 188,
    license: 'open-data',
  },

  // ── externalDebtCoverage (1 sub-metric) ──────────────────────────────────
  {
    id: 'recoveryDebtToReserves',
    dimension: 'externalDebtCoverage',
    description: 'Short-term external debt to reserves ratio (World Bank DT.DOD.DSTC.CD / FI.RES.TOTL.CD); values above 1 signal reserve inadequacy for debt service',
    direction: 'lowerBetter',
    goalposts: { worst: 5, best: 0 },
    weight: 1.0,
    sourceKey: 'resilience:recovery:external-debt:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 185,
    license: 'open-data',
  },

  // ── importConcentration (1 sub-metric) ───────────────────────────────────
  {
    id: 'recoveryImportHhi',
    dimension: 'importConcentration',
    description: 'Herfindahl-Hirschman Index of import partner concentration (UN Comtrade HS2 bilateral); higher HHI = more dependent on fewer partners = slower recovery if a key partner is disrupted',
    direction: 'lowerBetter',
    goalposts: { worst: 5000, best: 0 },
    weight: 1.0,
    sourceKey: 'resilience:recovery:import-hhi:v1',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 190,
    license: 'public-domain',
  },

  // ── stateContinuity (3 sub-metrics, derived from existing keys) ──────────
  {
    id: 'recoveryWgiContinuity',
    dimension: 'stateContinuity',
    description: 'Mean WGI score as institutional durability proxy; higher governance = better state continuity under shock',
    direction: 'higherBetter',
    goalposts: { worst: -2.5, best: 2.5 },
    weight: 0.5,
    sourceKey: 'resilience:static:{ISO2}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 214,
    license: 'public-domain',
  },
  {
    id: 'recoveryConflictPressure',
    dimension: 'stateContinuity',
    description: 'UCDP conflict metric inverted to state continuity; active conflict directly undermines state continuity',
    direction: 'lowerBetter',
    goalposts: { worst: 30, best: 0 },
    weight: 0.3,
    sourceKey: 'conflict:ucdp-events:v1',
    scope: 'global',
    cadence: 'realtime',
    tier: 'core',
    coverage: 193,
    license: 'research-only',
  },
  {
    id: 'recoveryDisplacementVelocity',
    dimension: 'stateContinuity',
    description: 'UNHCR displacement as state continuity signal; mass displacement signals state function breakdown',
    direction: 'lowerBetter',
    goalposts: { worst: 7, best: 0 },
    weight: 0.2,
    sourceKey: 'displacement:summary:v1:{year}',
    scope: 'global',
    cadence: 'annual',
    tier: 'core',
    coverage: 200,
    license: 'open-data',
  },

  // ── fuelStockDays (1 sub-metric) ─────────────────────────────────────────
  {
    id: 'recoveryFuelStockDays',
    dimension: 'fuelStockDays',
    description: 'Days of fuel stock cover (IEA Oil Stocks / EIA Weekly Petroleum Status); strategic buffer for energy-dependent recovery',
    direction: 'higherBetter',
    goalposts: { worst: 0, best: 120 },
    weight: 1.0,
    sourceKey: 'resilience:recovery:fuel-stocks:v1',
    scope: 'global',
    cadence: 'monthly',
    tier: 'enrichment',
    coverage: 45,
    license: 'open-data',
  },
];
