spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1/**2 * TypeScript mirror of the FastAPI response models in `src/countryatlas/api/schemas.py` (ARCHITECTURE §8).3 * Aligned on 2026-09-11 against that file — when it changes, change this one. The next agent extends it for4 * compare / rankings / indicators / regions / sources pages (the corresponding pydantic models already exist:5 * CompareResponse, RankingResponse, IndicatorResponse, RegionResponse, SourcesResponse … add them here).6 *7 * Conventions: every top-level response carries `meta`. Optional fields are `| null` (the API sends explicit8 * nulls, never omits) except where pydantic gives a default (`is_forecast: false`, lists `[]`). Dates are ISO9 * strings (`period` = first day of the period, `YYYY-MM-DD`). Models are `extra="allow"` server-side, so unknown10 * keys may appear — never rely on them without adding them here.11 */1213export type Frequency = 'A' | 'Q' | 'M';14export type ObservationStatus = 'verified' | 'imported' | 'warning' | 'stale' | 'quarantined';15export type IndicatorFormat =16 | 'number'17 | 'percent'18 | 'currency'19 | 'index'20 | 'years'21 | 'per_1000'22 | 'per_100k'23 | 'per_million'24 | 'ratio'25 | 'celsius'26 | 'tonnes'27 | 'kwh'28 | 'ha'29 | 'km';30export type IncomeGroup = 'HIC' | 'UMC' | 'LMC' | 'LIC';31export type CountryStatus = 'country' | 'territory' | 'historical';32export type SimilarityMode = 'overall' | 'economic' | 'demographic' | 'energy' | 'social';33export type ChangeKind =34 | 'yoy_drop'35 | 'yoy_jump'36 | 'record_high'37 | 'record_low'38 | 'n_year_high'39 | 'n_year_low'40 | 'sign_flip'41 | 'accelerating'42 | 'decelerating'43 | 'structural_break'44 | 'trend_reversal'45 | 'volatility_spike';46export type SearchHitType = 'country' | 'indicator' | 'topic' | 'region' | 'source' | 'country_topic' | 'country_indicator' | 'action';47export type TopicId =48 | 'economy'49 | 'government'50 | 'population'51 | 'labor'52 | 'income'53 | 'housing'54 | 'health'55 | 'education'56 | 'trade'57 | 'energy'58 | 'climate'59 | 'environment'60 | 'infrastructure'61 | 'digital'62 | 'innovation'63 | 'agriculture'64 | 'tourism'65 | 'security'66 | 'quality-of-life';67export type DnaDimension = 'income' | 'demographics' | 'urbanization' | 'trade' | 'energy' | 'emissions' | 'innovation' | 'education' | 'public_spending';6869// ---------------------------------------------------------------------------------------------- envelope & errors7071export interface Meta {72 built_at: string | null;73 run_id: string | null;74 generated_at: string;75}7677/** RFC 7807 problem+json (errors.py `problem_body`). */78export interface Problem {79 type?: string;80 title: string;81 status: number;82 detail?: string;83 instance?: string;84 resource?: string;85 id?: string;86}8788// ---------------------------------------------------------------------------------------------- provenance & values8990export interface Provenance {91 source: string | null; // worldbank | imf | oecd | eurostat | who | fred | owid | bis | ilo92 source_name: string | null;93 dataset: string | null;94 series_code: string | null;95 retrieved_at: string | null;96 source_updated_at: string | null;97 url: string | null;98 transform: string | null;99 licence: string | null;100 /** Series endpoint `sources[]` only. */101 n_values?: number;102}103104export interface ChangeValue {105 abs: number | null;106 pct: number | null;107 formatted: string | null;108 /** `change_10y` only. */109 value_10y_ago?: number | null;110}111112export interface Prev {113 period: string | null;114 value: number | null;115}116117/** `[year, value]` — API sparklines (last ≤ 30 annual, non-forecast points). */118export type SparkPoint = [number, number | null];119120/** One indicator's latest value for a country (schemas.MetricValue) — headline & topic pages. */121export interface MetricValue {122 indicator: string; // slug123 indicator_name: string | null;124 has_data: boolean;125 value: number | null;126 /** Server-formatted display string (same rules as lib/format.ts) — prefer it when present. */127 formatted: string | null;128 period: string | null;129 year: number | null;130 frequency: Frequency | string | null;131 unit: string | null;132 unit_short: string | null;133 format: IndicatorFormat | string | null;134 is_estimate: boolean;135 is_forecast: boolean;136 status: ObservationStatus | string | null;137 prev: Prev | null;138 change: ChangeValue | null;139 change_10y: ChangeValue | null;140 rank_world: number | null;141 n_world: number | null;142 rank_region: number | null;143 n_region: number | null;144 rank_income: number | null;145 n_income: number | null;146 rank_year: number | null;147 rank_is_stale: boolean;148 higher_is_better: boolean | null;149 sparkline: SparkPoint[];150 provenance: Provenance | null;151}152153// ---------------------------------------------------------------------------------------------- cards154155export interface CountryCard {156 id: string; // ISO3157 iso2: string | null;158 slug: string | null;159 name: string | null;160 flag: string | null;161 region: string | null; // WB region id (ECS, NAC …)162 region_name: string | null;163 income: IncomeGroup | string | null;164 income_name: string | null;165 kind: 'country' | 'aggregate' | string | null;166}167168export interface IndicatorCard {169 id: string;170 slug: string;171 name: string | null;172 short_name: string | null;173 topic: TopicId | string | null;174 subtopic: string | null;175 unit: string | null;176 unit_short: string | null;177 format: IndicatorFormat | string | null;178 precision: number | null;179 frequency: Frequency | string | null;180 aggregation: string | null;181 higher_is_better: boolean | null;182 ranking_eligible: boolean | null;183 featured: boolean | null;184}185186export interface IndicatorSummary extends IndicatorCard {187 description: string | null;188 n_countries: number | null;189 n_observations: number | null;190 first_year: number | null;191 last_year: number | null;192 latest_source_updated_at: string | null;193 primary_source_id: string | null;194 coverage_pct: number | null;195 tags?: string[];196}197198export interface GroupCard {199 id: string;200 slug: string | null;201 name: string | null;202 kind: 'world' | 'region' | 'continent' | 'income' | 'org' | 'custom' | string | null;203 wb_code: string | null;204 n_members: number | null;205}206207// ---------------------------------------------------------------------------------------------- countries208209/** `/countries` item. */210export interface CountrySummary extends CountryCard {211 capital: string | null;212 continent: string | null;213 subregion: string | null;214 population_latest: number | null;215 population_year: number | null;216 gdp_latest: number | null;217 gdp_year: number | null;218 gdp_per_capita_latest: number | null;219 gdp_per_capita_year: number | null;220 coverage_pct: number | null;221 n_indicators: number | null;222}223224export interface CountriesResponse {225 meta: Meta;226 n: number;227 filters: { region: string | null; income: string | null; q: string | null; sort: string };228 items: CountrySummary[];229}230231export interface Country extends CountryCard {232 official_name: string | null;233 iso3: string | null;234 iso_numeric: string | null;235 capital: string | null;236 continent: string | null;237 subregion: string | null;238 currency_code: string | null;239 currency_name: string | null;240 area_km2: number | null;241 latitude: number | null;242 longitude: number | null;243 un_member: boolean | null;244 independent: boolean | null;245 landlocked: boolean | null;246 borders: string[] | null;247 languages: string[] | null;248 demonym: string | null;249 status: CountryStatus | string | null;250}251252export interface Coverage {253 n_indicators: number | null;254 n_observations: number | null;255 latest_year: number | null;256 coverage_pct: number | null;257 updated_at: string | null;258}259260export interface Freshness {261 source_updated_at: string | null;262 retrieved_at: string | null;263 built_at: string | null;264}265266export interface TopicSummary {267 id: TopicId | string;268 name: string;269 short: string | null;270 order: number | null;271 blurb: string | null;272 n_indicators: number;273 n_with_data: number;274}275276export interface CountryResponse {277 meta: Meta;278 country: Country;279 groups: GroupCard[];280 coverage: Coverage | null;281 freshness: Freshness;282 headline: MetricValue[];283 topics: TopicSummary[];284 neighbours: CountryCard[];285}286287export interface SubtopicBlock {288 subtopic: string;289 indicators: MetricValue[];290}291292export interface CountryTopicResponse {293 meta: Meta;294 country: CountryCard;295 topic: { id: TopicId | string; name: string; short: string | null; order: number | null; blurb: string | null };296 n_with_data: number;297 n_indicators: number;298 subtopics: SubtopicBlock[];299}300301// ---------------------------------------------------------------------------------------------- series302303export interface SeriesValue {304 period: string | null;305 year: number | null;306 frequency: Frequency | string | null;307 value: number | null;308 is_forecast: boolean;309 is_estimate: boolean;310 status: ObservationStatus | string | null;311 source_id: string | null;312 provenance: Provenance | null;313}314315export interface SeriesStats {316 min: { year: number; value: number } | null;317 max: { year: number; value: number } | null;318 first: { year: number; value: number } | null;319 last: { year: number; value: number } | null;320 cagr: number | null; // % per year321 n: number;322}323324export interface Series {325 indicator: IndicatorCard;326 country: CountryCard;327 unit: string | null;328 frequency: Frequency | string | null;329 values: SeriesValue[];330 alternatives: SeriesValue[] | null;331 provenance: Provenance | null;332 sources: Provenance[];333 stats: SeriesStats;334}335336export interface SeriesResponse extends Series {337 meta: Meta;338}339340export interface MultiSeriesResponse {341 meta: Meta;342 n: number;343 series: Series[];344}345346// ---------------------------------------------------------------------------------------------- changes / events347348export interface ChangeItem {349 id: string | null;350 country: CountryCard | null;351 indicator: IndicatorCard | { id: string; slug: string };352 kind: ChangeKind | string | null;353 period: string | null;354 year: number | null;355 value: number | null;356 ref_value: number | null;357 delta: number | null;358 delta_pct: number | null;359 window_years: number | null;360 severity: number | null; // 0–1361 headline: string | null;362 detail: unknown;363 detected_at: string | null;364 formatted: string | null;365 provenance: Provenance | null;366}367368export interface ChangesResponse {369 meta: Meta;370 n: number;371 items: ChangeItem[];372}373374// ---------------------------------------------------------------------------------------------- similar / insights / dna375376/** `contributions` JSON: {indicator: {z_a, z_b, weight, contribution}} (ARCHITECTURE §2.1). */377export type Contributions = Record<string, { z_a?: number | null; z_b?: number | null; weight?: number | null; contribution?: number | null; value_a?: number | null; value_b?: number | null }>;378379export interface SimilarPeer {380 country: CountryCard;381 score: number | null; // 0–100382 rank: number | null;383 contributions: Contributions | string | null;384}385386export interface SimilarResponse {387 meta: Meta;388 country: CountryCard;389 mode: SimilarityMode | string;390 modes: string[];391 peers: SimilarPeer[];392}393394export interface Insight {395 id: string | null;396 template_id: string | null;397 text: string;398 values: unknown;399 indicators: string[];400 computed_at: string | null;401 provenance: Provenance[];402}403404export interface InsightsResponse {405 meta: Meta;406 country: CountryCard;407 items: Insight[];408}409410export interface DnaDimensionRow {411 id: DnaDimension | string;412 label: string;413 indicator: string | null;414 value: number | null;415}416417export interface DnaReference {418 kind: 'world' | 'region' | 'income' | 'country' | string;419 id: string | null;420 label: string;421 dims: Record<string, number | null>;422}423424export interface DNAResponse {425 meta: Meta;426 country: CountryCard;427 dims: Partial<Record<DnaDimension, number | null>>;428 year_ref: number | null;429 dimensions: DnaDimensionRow[];430 /** Present when `?reference=` was requested (API 1.1). */431 reference?: DnaReference | null;432}433434// ---------------------------------------------------------------------------------------------- maps & rankings435436export interface MapLegend {437 min: number | null;438 max: number | null;439 /** k − 1 interior quantile breaks (3–7 classes). */440 breaks: number[];441 n_classes: number;442}443444export interface MapResponse {445 meta: Meta;446 indicator: IndicatorCard;447 year: number | null;448 year_used: number | null;449 nearest: boolean;450 values: Record<string, number | null>; // ISO3 → value451 years: Record<string, number> | null;452 formatted: Record<string, string> | null;453 legend: MapLegend;454 n: number;455 provenance: Provenance | null;456 sources: Provenance[];457}458459export interface RankingRow {460 rank: number;461 rank_world: number | null;462 n_world: number | null;463 pct_rank: number | null;464 country: CountryCard;465 value: number | null;466 formatted: string | null;467 year: number | null;468 change_1y: ChangeValue | null;469 change_10y: ChangeValue | null;470 sparkline: SparkPoint[];471 provenance: Provenance | null;472}473474export interface RankingResponse {475 meta: Meta;476 indicator: IndicatorCard;477 group: GroupCard;478 year: number | null;479 year_used: number | null;480 years_available: number[];481 sort: 'asc' | 'desc' | string;482 n: number;483 limit: number;484 offset: number;485 rows: RankingRow[];486}487488// ---------------------------------------------------------------------------------------------- home / search / health489490export interface GlobalSnapshot {491 world_population: number | null;492 world_population_formatted: string | null;493 world_population_year: number | null;494 world_gdp: number | null;495 world_gdp_formatted: string | null;496 world_gdp_year: number | null;497 median_life_expectancy: number | null;498 median_life_expectancy_year: number | null;499 n_countries: number;500 n_territories: number;501 n_indicators: number;502 n_indicators_with_data: number;503 n_observations: number;504 n_sources: number;505 built_at: string | null;506 run_id: string | null;507 note: string | null;508}509510export interface HomeListRow {511 rank: number;512 country: CountryCard;513 value: number | null;514 formatted: string | null;515 year: number | null;516 change_pct: number | null;517 change_abs: number | null;518 rank_world: number | null;519 n_world: number | null;520 provenance: Provenance | null;521}522523export type HomeListKey = 'largest_economies' | 'fastest_population_growth' | 'fastest_gdp_growth' | 'highest_life_expectancy' | 'energy_transition_leaders' | 'highest_gdp_per_capita_ppp' | 'lowest_unemployment';524525export interface HomeList {526 title: string;527 description: string | null;528 /** e.g. "Countries above 1M inhabitants" — optional, rendered under the list title. */529 filter_note?: string | null;530 indicator: IndicatorCard;531 sort: 'asc' | 'desc';532 rows: HomeListRow[];533}534535export interface HomeResponse {536 meta: Meta;537 snapshot: GlobalSnapshot;538 lists: Partial<Record<HomeListKey, HomeList>> & Record<string, HomeList>;539 recent_changes: ChangeItem[];540 recently_updated: IndicatorSummary[];541 featured_indicators: IndicatorSummary[];542 trending: IndicatorSummary[];543}544545export interface SearchHit {546 type: SearchHitType | string;547 id: string;548 slug: string | null;549 name: string;550 hint: string | null;551 score: number;552 /** Site-relative URL chosen by the API (`/countries/canada`, `/countries/canada/economy` …). */553 url: string | null;554 country: CountryCard | null;555 topic: string | null;556 indicator: string | null;557 /** `type: "action"` intent hits (API 1.1): compare | ranking | group_ranking | explore. */558 action?: string | null;559}560561export interface SearchResponse {562 meta: Meta;563 q: string;564 n: number;565 hits: SearchHit[];566}567568export interface HealthResponse {569 status: 'ok' | 'empty' | 'degraded' | string;570 run_id: string | null;571 built_at: string | null;572 observations: number | null;573 countries: number | null;574 indicators: number | null;575 db_path: string | null;576 version: string | null;577 cache: Record<string, number> | null;578}579580// ---------------------------------------------------------------------------------------------- helpers shared by components581582/** Minimal display spec derived from a MetricValue or an IndicatorCard (what lib/format.ts needs). */583export interface FormatSpec {584 format: IndicatorFormat | string | null | undefined;585 unit?: string | null;586 unit_short?: string | null;587 precision?: number | null;588 frequency?: Frequency | string | null;589 name?: string | null;590 higher_is_better?: boolean | null;591}592