/** * TypeScript mirror of the compare / rankings / regions / indicators response models in * `src/countryatlas/api/schemas.py` (CompareResponse, CompareSnapshotResponse, RankingsListResponse, * RankHistoryResponse, IndicatorsResponse, RegionsResponse). Kept in its own file so the compare / rankings * pages and the indicators / regions pages can be built concurrently without touching `types.ts`. */ import type { CountryCard, IndicatorCard, IndicatorSummary, Meta, MetricValue, Series, TopicId } from './types'; // ---------------------------------------------------------------------------------------------- compare /** `mode` query of `/compare` (docs/API.md § Compare). */ export type CompareMode = 'absolute' | 'per-capita' | 'index100' | 'pct'; export const COMPARE_MODES: readonly CompareMode[] = ['absolute', 'per-capita', 'index100', 'pct']; /** UI-only modes computed client-side: `percentile` (world percentile from rank/n) and `change` (value − value at the range start). */ export type CompareUiMode = CompareMode | 'percentile' | 'change'; export const COMPARE_UI_MODES: readonly CompareUiMode[] = ['absolute', 'per-capita', 'index100', 'pct', 'percentile', 'change']; /** Map a UI mode to the API mode (client-side modes fetch the absolute series). */ export function apiModeOf(mode: CompareUiMode): CompareMode { return mode === 'percentile' || mode === 'change' ? 'absolute' : mode; } /** Transformation echo added by the router on every series (`extra="allow"`). */ export interface CompareTransform { mode: CompareMode | string; base_year?: number | null; applied: boolean; unit?: string | null; } export interface CompareSeries extends Series { transform?: CompareTransform; } export interface CompareResponse { meta: Meta; mode: CompareMode | string; base_year: number | null; countries: CountryCard[]; indicators: IndicatorCard[]; series: CompareSeries[]; } /** One row of `/compare/snapshot`: the indicator, its latest value per country (ISO3 → MetricValue), the best ISO3. */ export interface CompareSnapshotRow { indicator: IndicatorCard; values: Record; /** ISO3 of the best value when `higher_is_better` is known, else null. */ best: string | null; } export interface CompareSnapshotResponse { meta: Meta; topic: { id: TopicId | string; name: string; short: string | null; blurb: string | null } | null; countries: CountryCard[]; rows: CompareSnapshotRow[]; } // ---------------------------------------------------------------------------------------------- rankings /** `/rankings` item — an IndicatorSummary enriched with the latest ranking year and its size. */ export interface RankableIndicator extends IndicatorSummary { ranking_year: number | null; ranking_n: number | null; } export interface RankingsListResponse { meta: Meta; n: number; items: RankableIndicator[]; } export interface RankHistoryPoint { year: number; rank: number | null; n: number | null; value: number | null; pct_rank: number | null; } export interface RankHistoryResponse { meta: Meta; indicator: IndicatorCard; countries: CountryCard[]; years: number[]; /** ISO3 → points (one per year with a rank). */ series: Record; } // ---------------------------------------------------------------------------------------------- indicators list / regions list export interface IndicatorsResponse { meta: Meta; n: number; filters: { topic: string | null; q: string | null; featured: boolean | null; source: string | null }; items: IndicatorSummary[]; } /** `/regions` item (schemas.RegionsResponse.items is `dict[str, Any]`; these are the keys the router emits). */ export interface RegionItem { id: string; slug: string; name: string; kind: 'world' | 'region' | 'continent' | 'income' | 'org' | string; wb_code: string | null; n_members: number | null; description: string | null; population_latest: number | null; gdp_latest: number | null; } export interface RegionsResponse { meta: Meta; n: number; items: RegionItem[]; } // ---------------------------------------------------------------------------------------------- UI-side helpers /** Minimal country reference passed to client components (kept small: the full CountrySummary list is ~60 kB). */ export interface CountryLite { id: string; slug: string; name: string; flag: string | null; region: string | null; income: string | null; /** Optional: latest population (rankings min-population filter). */ population?: number | null; } export function toCountryLite(c: CountryCard & { population_latest?: number | null }): CountryLite { return { id: c.id, slug: c.slug ?? c.id.toLowerCase(), name: c.name ?? c.id, flag: c.flag, region: c.region, income: c.income, population: c.population_latest ?? null }; }