/** * TypeScript mirror of the FastAPI response models in `src/countryatlas/api/schemas.py` (ARCHITECTURE §8). * Aligned on 2026-09-11 against that file — when it changes, change this one. The next agent extends it for * compare / rankings / indicators / regions / sources pages (the corresponding pydantic models already exist: * CompareResponse, RankingResponse, IndicatorResponse, RegionResponse, SourcesResponse … add them here). * * Conventions: every top-level response carries `meta`. Optional fields are `| null` (the API sends explicit * nulls, never omits) except where pydantic gives a default (`is_forecast: false`, lists `[]`). Dates are ISO * strings (`period` = first day of the period, `YYYY-MM-DD`). Models are `extra="allow"` server-side, so unknown * keys may appear — never rely on them without adding them here. */ export type Frequency = 'A' | 'Q' | 'M'; export type ObservationStatus = 'verified' | 'imported' | 'warning' | 'stale' | 'quarantined'; export type IndicatorFormat = | 'number' | 'percent' | 'currency' | 'index' | 'years' | 'per_1000' | 'per_100k' | 'per_million' | 'ratio' | 'celsius' | 'tonnes' | 'kwh' | 'ha' | 'km'; export type IncomeGroup = 'HIC' | 'UMC' | 'LMC' | 'LIC'; export type CountryStatus = 'country' | 'territory' | 'historical'; export type SimilarityMode = 'overall' | 'economic' | 'demographic' | 'energy' | 'social'; export type ChangeKind = | 'yoy_drop' | 'yoy_jump' | 'record_high' | 'record_low' | 'n_year_high' | 'n_year_low' | 'sign_flip' | 'accelerating' | 'decelerating' | 'structural_break' | 'trend_reversal' | 'volatility_spike'; export type SearchHitType = 'country' | 'indicator' | 'topic' | 'region' | 'source' | 'country_topic' | 'country_indicator' | 'action'; export type TopicId = | 'economy' | 'government' | 'population' | 'labor' | 'income' | 'housing' | 'health' | 'education' | 'trade' | 'energy' | 'climate' | 'environment' | 'infrastructure' | 'digital' | 'innovation' | 'agriculture' | 'tourism' | 'security' | 'quality-of-life'; export type DnaDimension = 'income' | 'demographics' | 'urbanization' | 'trade' | 'energy' | 'emissions' | 'innovation' | 'education' | 'public_spending'; // ---------------------------------------------------------------------------------------------- envelope & errors export interface Meta { built_at: string | null; run_id: string | null; generated_at: string; } /** RFC 7807 problem+json (errors.py `problem_body`). */ export interface Problem { type?: string; title: string; status: number; detail?: string; instance?: string; resource?: string; id?: string; } // ---------------------------------------------------------------------------------------------- provenance & values export interface Provenance { source: string | null; // worldbank | imf | oecd | eurostat | who | fred | owid | bis | ilo source_name: string | null; dataset: string | null; series_code: string | null; retrieved_at: string | null; source_updated_at: string | null; url: string | null; transform: string | null; licence: string | null; /** Series endpoint `sources[]` only. */ n_values?: number; } export interface ChangeValue { abs: number | null; pct: number | null; formatted: string | null; /** `change_10y` only. */ value_10y_ago?: number | null; } export interface Prev { period: string | null; value: number | null; } /** `[year, value]` — API sparklines (last ≤ 30 annual, non-forecast points). */ export type SparkPoint = [number, number | null]; /** One indicator's latest value for a country (schemas.MetricValue) — headline & topic pages. */ export interface MetricValue { indicator: string; // slug indicator_name: string | null; has_data: boolean; value: number | null; /** Server-formatted display string (same rules as lib/format.ts) — prefer it when present. */ formatted: string | null; period: string | null; year: number | null; frequency: Frequency | string | null; unit: string | null; unit_short: string | null; format: IndicatorFormat | string | null; is_estimate: boolean; is_forecast: boolean; status: ObservationStatus | string | null; prev: Prev | null; change: ChangeValue | null; change_10y: ChangeValue | null; rank_world: number | null; n_world: number | null; rank_region: number | null; n_region: number | null; rank_income: number | null; n_income: number | null; rank_year: number | null; rank_is_stale: boolean; higher_is_better: boolean | null; sparkline: SparkPoint[]; provenance: Provenance | null; } // ---------------------------------------------------------------------------------------------- cards export interface CountryCard { id: string; // ISO3 iso2: string | null; slug: string | null; name: string | null; flag: string | null; region: string | null; // WB region id (ECS, NAC …) region_name: string | null; income: IncomeGroup | string | null; income_name: string | null; kind: 'country' | 'aggregate' | string | null; } export interface IndicatorCard { id: string; slug: string; name: string | null; short_name: string | null; topic: TopicId | string | null; subtopic: string | null; unit: string | null; unit_short: string | null; format: IndicatorFormat | string | null; precision: number | null; frequency: Frequency | string | null; aggregation: string | null; higher_is_better: boolean | null; ranking_eligible: boolean | null; featured: boolean | null; } export interface IndicatorSummary extends IndicatorCard { description: string | null; n_countries: number | null; n_observations: number | null; first_year: number | null; last_year: number | null; latest_source_updated_at: string | null; primary_source_id: string | null; coverage_pct: number | null; tags?: string[]; } export interface GroupCard { id: string; slug: string | null; name: string | null; kind: 'world' | 'region' | 'continent' | 'income' | 'org' | 'custom' | string | null; wb_code: string | null; n_members: number | null; } // ---------------------------------------------------------------------------------------------- countries /** `/countries` item. */ export interface CountrySummary extends CountryCard { capital: string | null; continent: string | null; subregion: string | null; population_latest: number | null; population_year: number | null; gdp_latest: number | null; gdp_year: number | null; gdp_per_capita_latest: number | null; gdp_per_capita_year: number | null; coverage_pct: number | null; n_indicators: number | null; } export interface CountriesResponse { meta: Meta; n: number; filters: { region: string | null; income: string | null; q: string | null; sort: string }; items: CountrySummary[]; } export interface Country extends CountryCard { official_name: string | null; iso3: string | null; iso_numeric: string | null; capital: string | null; continent: string | null; subregion: string | null; currency_code: string | null; currency_name: string | null; area_km2: number | null; latitude: number | null; longitude: number | null; un_member: boolean | null; independent: boolean | null; landlocked: boolean | null; borders: string[] | null; languages: string[] | null; demonym: string | null; status: CountryStatus | string | null; } export interface Coverage { n_indicators: number | null; n_observations: number | null; latest_year: number | null; coverage_pct: number | null; updated_at: string | null; } export interface Freshness { source_updated_at: string | null; retrieved_at: string | null; built_at: string | null; } export interface TopicSummary { id: TopicId | string; name: string; short: string | null; order: number | null; blurb: string | null; n_indicators: number; n_with_data: number; } export interface CountryResponse { meta: Meta; country: Country; groups: GroupCard[]; coverage: Coverage | null; freshness: Freshness; headline: MetricValue[]; topics: TopicSummary[]; neighbours: CountryCard[]; } export interface SubtopicBlock { subtopic: string; indicators: MetricValue[]; } export interface CountryTopicResponse { meta: Meta; country: CountryCard; topic: { id: TopicId | string; name: string; short: string | null; order: number | null; blurb: string | null }; n_with_data: number; n_indicators: number; subtopics: SubtopicBlock[]; } // ---------------------------------------------------------------------------------------------- series export interface SeriesValue { period: string | null; year: number | null; frequency: Frequency | string | null; value: number | null; is_forecast: boolean; is_estimate: boolean; status: ObservationStatus | string | null; source_id: string | null; provenance: Provenance | null; } export interface SeriesStats { min: { year: number; value: number } | null; max: { year: number; value: number } | null; first: { year: number; value: number } | null; last: { year: number; value: number } | null; cagr: number | null; // % per year n: number; } export interface Series { indicator: IndicatorCard; country: CountryCard; unit: string | null; frequency: Frequency | string | null; values: SeriesValue[]; alternatives: SeriesValue[] | null; provenance: Provenance | null; sources: Provenance[]; stats: SeriesStats; } export interface SeriesResponse extends Series { meta: Meta; } export interface MultiSeriesResponse { meta: Meta; n: number; series: Series[]; } // ---------------------------------------------------------------------------------------------- changes / events export interface ChangeItem { id: string | null; country: CountryCard | null; indicator: IndicatorCard | { id: string; slug: string }; kind: ChangeKind | string | null; period: string | null; year: number | null; value: number | null; ref_value: number | null; delta: number | null; delta_pct: number | null; window_years: number | null; severity: number | null; // 0–1 headline: string | null; detail: unknown; detected_at: string | null; formatted: string | null; provenance: Provenance | null; } export interface ChangesResponse { meta: Meta; n: number; items: ChangeItem[]; } // ---------------------------------------------------------------------------------------------- similar / insights / dna /** `contributions` JSON: {indicator: {z_a, z_b, weight, contribution}} (ARCHITECTURE §2.1). */ export type Contributions = Record; export interface SimilarPeer { country: CountryCard; score: number | null; // 0–100 rank: number | null; contributions: Contributions | string | null; } export interface SimilarResponse { meta: Meta; country: CountryCard; mode: SimilarityMode | string; modes: string[]; peers: SimilarPeer[]; } export interface Insight { id: string | null; template_id: string | null; text: string; values: unknown; indicators: string[]; computed_at: string | null; provenance: Provenance[]; } export interface InsightsResponse { meta: Meta; country: CountryCard; items: Insight[]; } export interface DnaDimensionRow { id: DnaDimension | string; label: string; indicator: string | null; value: number | null; } export interface DnaReference { kind: 'world' | 'region' | 'income' | 'country' | string; id: string | null; label: string; dims: Record; } export interface DNAResponse { meta: Meta; country: CountryCard; dims: Partial>; year_ref: number | null; dimensions: DnaDimensionRow[]; /** Present when `?reference=` was requested (API 1.1). */ reference?: DnaReference | null; } // ---------------------------------------------------------------------------------------------- maps & rankings export interface MapLegend { min: number | null; max: number | null; /** k − 1 interior quantile breaks (3–7 classes). */ breaks: number[]; n_classes: number; } export interface MapResponse { meta: Meta; indicator: IndicatorCard; year: number | null; year_used: number | null; nearest: boolean; values: Record; // ISO3 → value years: Record | null; formatted: Record | null; legend: MapLegend; n: number; provenance: Provenance | null; sources: Provenance[]; } export interface RankingRow { rank: number; rank_world: number | null; n_world: number | null; pct_rank: number | null; country: CountryCard; value: number | null; formatted: string | null; year: number | null; change_1y: ChangeValue | null; change_10y: ChangeValue | null; sparkline: SparkPoint[]; provenance: Provenance | null; } export interface RankingResponse { meta: Meta; indicator: IndicatorCard; group: GroupCard; year: number | null; year_used: number | null; years_available: number[]; sort: 'asc' | 'desc' | string; n: number; limit: number; offset: number; rows: RankingRow[]; } // ---------------------------------------------------------------------------------------------- home / search / health export interface GlobalSnapshot { world_population: number | null; world_population_formatted: string | null; world_population_year: number | null; world_gdp: number | null; world_gdp_formatted: string | null; world_gdp_year: number | null; median_life_expectancy: number | null; median_life_expectancy_year: number | null; n_countries: number; n_territories: number; n_indicators: number; n_indicators_with_data: number; n_observations: number; n_sources: number; built_at: string | null; run_id: string | null; note: string | null; } export interface HomeListRow { rank: number; country: CountryCard; value: number | null; formatted: string | null; year: number | null; change_pct: number | null; change_abs: number | null; rank_world: number | null; n_world: number | null; provenance: Provenance | null; } export type HomeListKey = 'largest_economies' | 'fastest_population_growth' | 'fastest_gdp_growth' | 'highest_life_expectancy' | 'energy_transition_leaders' | 'highest_gdp_per_capita_ppp' | 'lowest_unemployment'; export interface HomeList { title: string; description: string | null; /** e.g. "Countries above 1M inhabitants" — optional, rendered under the list title. */ filter_note?: string | null; indicator: IndicatorCard; sort: 'asc' | 'desc'; rows: HomeListRow[]; } export interface HomeResponse { meta: Meta; snapshot: GlobalSnapshot; lists: Partial> & Record; recent_changes: ChangeItem[]; recently_updated: IndicatorSummary[]; featured_indicators: IndicatorSummary[]; trending: IndicatorSummary[]; } export interface SearchHit { type: SearchHitType | string; id: string; slug: string | null; name: string; hint: string | null; score: number; /** Site-relative URL chosen by the API (`/countries/canada`, `/countries/canada/economy` …). */ url: string | null; country: CountryCard | null; topic: string | null; indicator: string | null; /** `type: "action"` intent hits (API 1.1): compare | ranking | group_ranking | explore. */ action?: string | null; } export interface SearchResponse { meta: Meta; q: string; n: number; hits: SearchHit[]; } export interface HealthResponse { status: 'ok' | 'empty' | 'degraded' | string; run_id: string | null; built_at: string | null; observations: number | null; countries: number | null; indicators: number | null; db_path: string | null; version: string | null; cache: Record | null; } // ---------------------------------------------------------------------------------------------- helpers shared by components /** Minimal display spec derived from a MetricValue or an IndicatorCard (what lib/format.ts needs). */ export interface FormatSpec { format: IndicatorFormat | string | null | undefined; unit?: string | null; unit_short?: string | null; precision?: number | null; frequency?: Frequency | string | null; name?: string | null; higher_is_better?: boolean | null; }