SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
16.2 KB · 592 lines typescript
Raw Blame History
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