import type { Metadata } from 'next'; import Link from 'next/link'; import type { ReactNode } from 'react'; import { t, tOpt } from '@/i18n'; import { isNotBuilt } from '@/lib/api'; import { apiExplore } from '@/lib/api-explore'; import { formatDate, grouped } from '@/lib/format'; import { routes } from '@/lib/site'; import type { MethodologyResponse } from '@/lib/types-explore'; import { StatusBadge } from '@/components/data/freshness-badge'; import { PageHeader } from '@/components/explore/page-header'; import { Toc } from '@/components/methodology/toc'; export const revalidate = 3600; export const metadata: Metadata = { title: t('method.title'), description: t('method.sub'), alternates: { canonical: routes.methodology() }, }; const SECTIONS = ['principles', 'countries', 'indicators', 'sources', 'validation', 'freshness', 'rankings', 'changes', 'similarity', 'insights', 'dna', 'limitations', 'versioning', 'licence'] as const; function Prose({ id, title, children }: { id: string; title: string; children: ReactNode }) { return (

{title}

{children}
); } function Kv({ rows }: { rows: Array<[string, ReactNode]> }) { return (
{rows.map(([k, v]) => (
{k}
{v}
))}
); } export default async function MethodologyPage() { let m: MethodologyResponse | null = null; try { m = await apiExplore.methodology(); } catch (e) { if (!isNotBuilt(e)) throw e; } const toc = SECTIONS.map((id) => ({ id, label: t(`method.${id}` as 'method.principles') })); const stale = m?.validation.stale_after_days ?? { A: 800, Q: 200, M: 75 }; return ( <>
    {(m?.principles ?? []).map((p) => (
  1. {p}
  2. ))}

{t('method.countries.text')}

{t('regions.title')} →

{t('method.indicators.text', { n: grouped(m?.indicators.n ?? 260) })}

{m ? (

{t('method.indicators.formats')}

[f.format, grouped(f.n)] as [string, ReactNode])} />

{t('method.indicators.units')}

[u.unit, grouped(u.n)] as [string, ReactNode])} />

{t('method.indicators.frequencies')}

[tOpt(`indicator.frequency.${k}`, k), grouped(v)] as [string, ReactNode])} />

{t('method.indicators.aggregations')}

[k, grouped(v)] as [string, ReactNode])} />
) : null}

{t('indicators.title')} →

{m?.sources.priority_rule}

{t('method.sources.text2')}

{m ? (

{t('method.sources.mapped')}

b[1] - a[1]) .map(([k, v]) => [k, grouped(v)] as [string, ReactNode])} />

{t('method.sources.urls')}

    {Object.entries(m.sources.urls).map(([k, v]) => (
  • {k} {v}
  • ))}
) : null}

{t('sources.title')} →

{t('method.validation.text')}

{m ? ( {m.validation.rules.map((r) => ( ))}
{t('method.rule.code')} {t('method.rule.severity')} {t('method.rule.effect')}
{r.code} {r.severity} {r.text}
) : null}

{t('method.validation.statuses')}

    {(['verified', 'imported', 'warning', 'stale', 'quarantined'] as const).map((s) => (
  • {t(`method.validation.status.${s}` as 'method.validation.status.verified')}
  • ))}

{t('method.validation.stale')}

{t('method.validation.staleText', { a: stale.A ?? 800, q: stale.Q ?? 200, m: stale.M ?? 75 })}

{t('method.freshness.text')}

{m ? ( <>

{t('method.periods')}

[k, {v}] as [string, ReactNode])} /> ) : null}

{t('method.rankings.text')}

{m ?

{m.derived.rankings}

: null}

{t('nav.rankings')} →

{t('method.changes.text')}

{m ?

{m.derived.changes}

: null}

{t('changes.title')} →

{t('method.similarity.text')}

{m ? ( <>

{t('method.similarity.modes')}

{Object.entries(m.derived.similarity.modes).map(([k, v]) => (
{tOpt(`country.similar.mode.${k}`, k)}
{v}
))}

{m.derived.similarity.method}

) : null}

{t('method.insights.text')}

{m ?

{m.derived.insights}

: null}

{t('method.dna.text')}

{m ? (
    {m.derived.country_dna.dimensions.map((d) => (
  • {d.label} {d.indicators.map((s, i) => ( {i > 0 ? ', ' : ''} {s} ))}
  • ))}
) : null} {m ?

{m.derived.world_aggregates}

: null}

{t('method.limitations.text')}

{t('method.versioning.text')}

{t('method.licence.text')}

{m ? ( <>

{t('method.formatting')}

[k, v] as [string, ReactNode])} /> ) : null}

{t('data.title')} →

); }