SPB Git

spb/countryatlas

Public
15.7 KB · 578 lines typescript
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';43export type SearchHitType = 'country' | 'indicator' | 'topic' | 'region' | 'source' | 'country_topic' | 'country_indicator';44export type TopicId =45  | 'economy'46  | 'government'47  | 'population'48  | 'labor'49  | 'income'50  | 'housing'51  | 'health'52  | 'education'53  | 'trade'54  | 'energy'55  | 'climate'56  | 'environment'57  | 'infrastructure'58  | 'digital'59  | 'innovation'60  | 'agriculture'61  | 'tourism'62  | 'security'63  | 'quality-of-life';64export type DnaDimension = 'income' | 'demographics' | 'urbanization' | 'trade' | 'energy' | 'emissions' | 'innovation' | 'education' | 'public_spending';6566// ---------------------------------------------------------------------------------------------- envelope & errors6768export interface Meta {69  built_at: string | null;70  run_id: string | null;71  generated_at: string;72}7374/** RFC 7807 problem+json (errors.py `problem_body`). */75export interface Problem {76  type?: string;77  title: string;78  status: number;79  detail?: string;80  instance?: string;81  resource?: string;82  id?: string;83}8485// ---------------------------------------------------------------------------------------------- provenance & values8687export interface Provenance {88  source: string | null; // worldbank | imf | oecd | eurostat | who | fred | owid | bis | ilo89  source_name: string | null;90  dataset: string | null;91  series_code: string | null;92  retrieved_at: string | null;93  source_updated_at: string | null;94  url: string | null;95  transform: string | null;96  licence: string | null;97  /** Series endpoint `sources[]` only. */98  n_values?: number;99}100101export interface ChangeValue {102  abs: number | null;103  pct: number | null;104  formatted: string | null;105  /** `change_10y` only. */106  value_10y_ago?: number | null;107}108109export interface Prev {110  period: string | null;111  value: number | null;112}113114/** `[year, value]` — API sparklines (last ≤ 30 annual, non-forecast points). */115export type SparkPoint = [number, number | null];116117/** One indicator's latest value for a country (schemas.MetricValue) — headline & topic pages. */118export interface MetricValue {119  indicator: string; // slug120  indicator_name: string | null;121  has_data: boolean;122  value: number | null;123  /** Server-formatted display string (same rules as lib/format.ts) — prefer it when present. */124  formatted: string | null;125  period: string | null;126  year: number | null;127  frequency: Frequency | string | null;128  unit: string | null;129  unit_short: string | null;130  format: IndicatorFormat | string | null;131  is_estimate: boolean;132  is_forecast: boolean;133  status: ObservationStatus | string | null;134  prev: Prev | null;135  change: ChangeValue | null;136  change_10y: ChangeValue | null;137  rank_world: number | null;138  n_world: number | null;139  rank_region: number | null;140  n_region: number | null;141  rank_income: number | null;142  n_income: number | null;143  rank_year: number | null;144  rank_is_stale: boolean;145  higher_is_better: boolean | null;146  sparkline: SparkPoint[];147  provenance: Provenance | null;148}149150// ---------------------------------------------------------------------------------------------- cards151152export interface CountryCard {153  id: string; // ISO3154  iso2: string | null;155  slug: string | null;156  name: string | null;157  flag: string | null;158  region: string | null; // WB region id (ECS, NAC …)159  region_name: string | null;160  income: IncomeGroup | string | null;161  income_name: string | null;162  kind: 'country' | 'aggregate' | string | null;163}164165export interface IndicatorCard {166  id: string;167  slug: string;168  name: string | null;169  short_name: string | null;170  topic: TopicId | string | null;171  subtopic: string | null;172  unit: string | null;173  unit_short: string | null;174  format: IndicatorFormat | string | null;175  precision: number | null;176  frequency: Frequency | string | null;177  aggregation: string | null;178  higher_is_better: boolean | null;179  ranking_eligible: boolean | null;180  featured: boolean | null;181}182183export interface IndicatorSummary extends IndicatorCard {184  description: string | null;185  n_countries: number | null;186  n_observations: number | null;187  first_year: number | null;188  last_year: number | null;189  latest_source_updated_at: string | null;190  primary_source_id: string | null;191  coverage_pct: number | null;192  tags?: string[];193}194195export interface GroupCard {196  id: string;197  slug: string | null;198  name: string | null;199  kind: 'world' | 'region' | 'continent' | 'income' | 'org' | 'custom' | string | null;200  wb_code: string | null;201  n_members: number | null;202}203204// ---------------------------------------------------------------------------------------------- countries205206/** `/countries` item. */207export interface CountrySummary extends CountryCard {208  capital: string | null;209  continent: string | null;210  subregion: string | null;211  population_latest: number | null;212  population_year: number | null;213  gdp_latest: number | null;214  gdp_year: number | null;215  gdp_per_capita_latest: number | null;216  gdp_per_capita_year: number | null;217  coverage_pct: number | null;218  n_indicators: number | null;219}220221export interface CountriesResponse {222  meta: Meta;223  n: number;224  filters: { region: string | null; income: string | null; q: string | null; sort: string };225  items: CountrySummary[];226}227228export interface Country extends CountryCard {229  official_name: string | null;230  iso3: string | null;231  iso_numeric: string | null;232  capital: string | null;233  continent: string | null;234  subregion: string | null;235  currency_code: string | null;236  currency_name: string | null;237  area_km2: number | null;238  latitude: number | null;239  longitude: number | null;240  un_member: boolean | null;241  independent: boolean | null;242  landlocked: boolean | null;243  borders: string[] | null;244  languages: string[] | null;245  demonym: string | null;246  status: CountryStatus | string | null;247}248249export interface Coverage {250  n_indicators: number | null;251  n_observations: number | null;252  latest_year: number | null;253  coverage_pct: number | null;254  updated_at: string | null;255}256257export interface Freshness {258  source_updated_at: string | null;259  retrieved_at: string | null;260  built_at: string | null;261}262263export interface TopicSummary {264  id: TopicId | string;265  name: string;266  short: string | null;267  order: number | null;268  blurb: string | null;269  n_indicators: number;270  n_with_data: number;271}272273export interface CountryResponse {274  meta: Meta;275  country: Country;276  groups: GroupCard[];277  coverage: Coverage | null;278  freshness: Freshness;279  headline: MetricValue[];280  topics: TopicSummary[];281  neighbours: CountryCard[];282}283284export interface SubtopicBlock {285  subtopic: string;286  indicators: MetricValue[];287}288289export interface CountryTopicResponse {290  meta: Meta;291  country: CountryCard;292  topic: { id: TopicId | string; name: string; short: string | null; order: number | null; blurb: string | null };293  n_with_data: number;294  n_indicators: number;295  subtopics: SubtopicBlock[];296}297298// ---------------------------------------------------------------------------------------------- series299300export interface SeriesValue {301  period: string | null;302  year: number | null;303  frequency: Frequency | string | null;304  value: number | null;305  is_forecast: boolean;306  is_estimate: boolean;307  status: ObservationStatus | string | null;308  source_id: string | null;309  provenance: Provenance | null;310}311312export interface SeriesStats {313  min: { year: number; value: number } | null;314  max: { year: number; value: number } | null;315  first: { year: number; value: number } | null;316  last: { year: number; value: number } | null;317  cagr: number | null; // % per year318  n: number;319}320321export interface Series {322  indicator: IndicatorCard;323  country: CountryCard;324  unit: string | null;325  frequency: Frequency | string | null;326  values: SeriesValue[];327  alternatives: SeriesValue[] | null;328  provenance: Provenance | null;329  sources: Provenance[];330  stats: SeriesStats;331}332333export interface SeriesResponse extends Series {334  meta: Meta;335}336337export interface MultiSeriesResponse {338  meta: Meta;339  n: number;340  series: Series[];341}342343// ---------------------------------------------------------------------------------------------- changes / events344345export interface ChangeItem {346  id: string | null;347  country: CountryCard | null;348  indicator: IndicatorCard | { id: string; slug: string };349  kind: ChangeKind | string | null;350  period: string | null;351  year: number | null;352  value: number | null;353  ref_value: number | null;354  delta: number | null;355  delta_pct: number | null;356  window_years: number | null;357  severity: number | null; // 0–1358  headline: string | null;359  detail: unknown;360  detected_at: string | null;361  formatted: string | null;362  provenance: Provenance | null;363}364365export interface ChangesResponse {366  meta: Meta;367  n: number;368  items: ChangeItem[];369}370371// ---------------------------------------------------------------------------------------------- similar / insights / dna372373/** `contributions` JSON: {indicator: {z_a, z_b, weight, contribution}} (ARCHITECTURE §2.1). */374export 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 }>;375376export interface SimilarPeer {377  country: CountryCard;378  score: number | null; // 0–100379  rank: number | null;380  contributions: Contributions | string | null;381}382383export interface SimilarResponse {384  meta: Meta;385  country: CountryCard;386  mode: SimilarityMode | string;387  modes: string[];388  peers: SimilarPeer[];389}390391export interface Insight {392  id: string | null;393  template_id: string | null;394  text: string;395  values: unknown;396  indicators: string[];397  computed_at: string | null;398  provenance: Provenance[];399}400401export interface InsightsResponse {402  meta: Meta;403  country: CountryCard;404  items: Insight[];405}406407export interface DnaDimensionRow {408  id: DnaDimension | string;409  label: string;410  indicator: string | null;411  value: number | null;412}413414export interface DNAResponse {415  meta: Meta;416  country: CountryCard;417  dims: Partial<Record<DnaDimension, number | null>>;418  year_ref: number | null;419  dimensions: DnaDimensionRow[];420}421422// ---------------------------------------------------------------------------------------------- maps & rankings423424export interface MapLegend {425  min: number | null;426  max: number | null;427  /** k − 1 interior quantile breaks (3–7 classes). */428  breaks: number[];429  n_classes: number;430}431432export interface MapResponse {433  meta: Meta;434  indicator: IndicatorCard;435  year: number | null;436  year_used: number | null;437  nearest: boolean;438  values: Record<string, number | null>; // ISO3 → value439  years: Record<string, number> | null;440  formatted: Record<string, string> | null;441  legend: MapLegend;442  n: number;443  provenance: Provenance | null;444  sources: Provenance[];445}446447export interface RankingRow {448  rank: number;449  rank_world: number | null;450  n_world: number | null;451  pct_rank: number | null;452  country: CountryCard;453  value: number | null;454  formatted: string | null;455  year: number | null;456  change_1y: ChangeValue | null;457  change_10y: ChangeValue | null;458  sparkline: SparkPoint[];459  provenance: Provenance | null;460}461462export interface RankingResponse {463  meta: Meta;464  indicator: IndicatorCard;465  group: GroupCard;466  year: number | null;467  year_used: number | null;468  years_available: number[];469  sort: 'asc' | 'desc' | string;470  n: number;471  limit: number;472  offset: number;473  rows: RankingRow[];474}475476// ---------------------------------------------------------------------------------------------- home / search / health477478export interface GlobalSnapshot {479  world_population: number | null;480  world_population_formatted: string | null;481  world_population_year: number | null;482  world_gdp: number | null;483  world_gdp_formatted: string | null;484  world_gdp_year: number | null;485  median_life_expectancy: number | null;486  median_life_expectancy_year: number | null;487  n_countries: number;488  n_territories: number;489  n_indicators: number;490  n_indicators_with_data: number;491  n_observations: number;492  n_sources: number;493  built_at: string | null;494  run_id: string | null;495  note: string | null;496}497498export interface HomeListRow {499  rank: number;500  country: CountryCard;501  value: number | null;502  formatted: string | null;503  year: number | null;504  change_pct: number | null;505  change_abs: number | null;506  rank_world: number | null;507  n_world: number | null;508  provenance: Provenance | null;509}510511export type HomeListKey = 'largest_economies' | 'fastest_population_growth' | 'fastest_gdp_growth' | 'highest_life_expectancy' | 'energy_transition_leaders' | 'highest_gdp_per_capita_ppp' | 'lowest_unemployment';512513export interface HomeList {514  title: string;515  description: string | null;516  /** e.g. "Countries above 1M inhabitants" — optional, rendered under the list title. */517  filter_note?: string | null;518  indicator: IndicatorCard;519  sort: 'asc' | 'desc';520  rows: HomeListRow[];521}522523export interface HomeResponse {524  meta: Meta;525  snapshot: GlobalSnapshot;526  lists: Partial<Record<HomeListKey, HomeList>> & Record<string, HomeList>;527  recent_changes: ChangeItem[];528  recently_updated: IndicatorSummary[];529  featured_indicators: IndicatorSummary[];530  trending: IndicatorSummary[];531}532533export interface SearchHit {534  type: SearchHitType | string;535  id: string;536  slug: string | null;537  name: string;538  hint: string | null;539  score: number;540  /** Site-relative URL chosen by the API (`/countries/canada`, `/countries/canada/economy` …). */541  url: string | null;542  country: CountryCard | null;543  topic: string | null;544  indicator: string | null;545}546547export interface SearchResponse {548  meta: Meta;549  q: string;550  n: number;551  hits: SearchHit[];552}553554export interface HealthResponse {555  status: 'ok' | 'empty' | 'degraded' | string;556  run_id: string | null;557  built_at: string | null;558  observations: number | null;559  countries: number | null;560  indicators: number | null;561  db_path: string | null;562  version: string | null;563  cache: Record<string, number> | null;564}565566// ---------------------------------------------------------------------------------------------- helpers shared by components567568/** Minimal display spec derived from a MetricValue or an IndicatorCard (what lib/format.ts needs). */569export interface FormatSpec {570  format: IndicatorFormat | string | null | undefined;571  unit?: string | null;572  unit_short?: string | null;573  precision?: number | null;574  frequency?: Frequency | string | null;575  name?: string | null;576  higher_is_better?: boolean | null;577}578
Files · d588bee37a233436d1bef2f0970b4b557e87e5f7
Menu

Theme