/** * TypeScript mirror of the analytics endpoints (docs/API.md ยง "Analytics endpoints (API 1.1)"). The Python side * implements exactly these shapes; keep the two in sync. Base cards live in `./types`. */ import type { CountryCard, GroupCard, IndicatorCard, IndicatorSummary, Meta, Provenance } from './types'; export type Direction = 'up' | 'down'; export type Interpretation = 'improvement' | 'deterioration' | null; export type DirectionSemantics = 'higher_is_better' | 'lower_is_better' | 'neutral'; export type MoverWindow = 1 | 5 | 10; export type MoverCategory = 'all' | 'economic' | 'demographic' | 'health' | 'energy' | 'climate' | 'digital' | 'housing' | 'labor'; export type MoverKindFilter = 'all' | 'improvement' | 'deterioration' | 'increase' | 'decrease' | 'record' | 'reversal' | 'acceleration' | 'structural'; export type ExtremesWindow = '1' | '5' | '10' | '25' | 'since1990'; export type QualityBadge = 'fresh' | 'historical' | 'sparse' | 'limited-coverage' | 'stale' | 'flagged' | 'forecast'; export interface PointCountry { id: string; slug: string | null; name: string | null; flag: string | null; region: string | null; income: string | null; } // ---------------------------------------------------------------------------------------------- pulse export interface MoverLite { country: CountryCard; value: number | null; ref_value: number | null; delta: number | null; delta_pct: number | null; formatted: string | null; year: number | null; } export interface Convergence { direction: 'converging' | 'diverging' | 'stable'; cv_start: number; cv_end: number; n: number; from_year: number; } export interface PulseItem { indicator: IndicatorCard; year: number; n: number; n_up: number; n_down: number; n_flat: number; share_up: number; share_down: number; median_change_abs: number | null; median_change_pct: number | null; direction_semantics: DirectionSemantics; record_highs: number; record_lows: number; headline: string; top_up: MoverLite | null; top_down: MoverLite | null; convergence: Convergence | null; provenance: Provenance | null; } export interface PulseResponse { meta: Meta; year_reference: number; summary: { n_indicators: number; n_countries_reporting: number; n_record_highs: number; n_record_lows: number; n_changes: number }; items: PulseItem[]; } // ---------------------------------------------------------------------------------------------- movers / extremes export interface MoverItem { country: CountryCard; indicator: IndicatorCard; kind: string; year: number | null; ref_year: number | null; value: number | null; ref_value: number | null; delta: number | null; delta_pct: number | null; formatted: string | null; formatted_ref: string | null; severity: number | null; direction: Direction; interpretation: Interpretation; headline: string | null; provenance: Provenance | null; } export interface MoversResponse { meta: Meta; window: MoverWindow; category: MoverCategory; kind: MoverKindFilter; min_population: number | null; filter_note: string | null; categories: MoverCategory[]; kinds: MoverKindFilter[]; n: number; items: MoverItem[]; } export interface ExtremeRow { country: CountryCard; value_start: number | null; value_end: number | null; year_start: number | null; year_end: number | null; delta: number | null; delta_pct: number | null; formatted_start: string | null; formatted_end: string | null; } export interface ExtremeFacet { id: string; title: string; indicator: IndicatorCard; direction: Direction; metric: 'abs' | 'pct' | 'points'; rows: ExtremeRow[]; n: number; provenance: Provenance | null; } export interface ExtremesResponse { meta: Meta; window: ExtremesWindow | string; from_year: number | null; to_year: number | null; min_population: number | null; filter_note: string | null; facets: ExtremeFacet[]; } // ---------------------------------------------------------------------------------------------- scatter / trajectory / peers export interface ScatterPointRow extends PointCountry { x: number | null; y: number | null; size: number | null; year_x: number | null; year_y: number | null; } export interface ScatterStats { n: number; pearson: number | null; spearman: number | null; ols: { slope: number; intercept: number; r2: number | null } | null; log_x: boolean; log_y: boolean; } export interface ScatterResponse { meta: Meta; x: IndicatorCard; y: IndicatorCard; size: IndicatorCard | null; year: number | null; year_used: number | null; nearest_years: number; group: GroupCard; n: number; points: ScatterPointRow[]; stats: ScatterStats; note: string; provenance?: Provenance[]; } export interface TrajectoryResponse { meta: Meta; x: IndicatorCard; y: IndicatorCard; size: IndicatorCard | null; group: GroupCard; years: number[]; countries: PointCountry[]; series: Record; y: Array; size: Array }>; domains: { x: [number, number]; y: [number, number]; size: [number, number] | null }; log_x: boolean; log_y: boolean; provenance: Provenance[]; } export interface PeerPoint extends PointCountry { x: number | null; y: number | null; expected: number | null; residual: number | null; residual_z: number | null; } export interface PeersResponse { meta: Meta; x: IndicatorCard; y: IndicatorCard; year_used: number | null; n: number; method: 'theil-sen' | 'ols' | string; fit: { slope: number; intercept: number; r2: number | null; log_x: boolean; residual_scale: number | null } | null; points: PeerPoint[]; above: PeerPoint[]; below: PeerPoint[]; pairs: Array<{ x: string; y: string; label: string }>; note: string; methodology: string; } // ---------------------------------------------------------------------------------------------- finder export type FinderOp = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'between'; export interface FinderFilter { indicator: IndicatorCard; op: FinderOp | string; value: number; value2: number | null; year_used: number | null; } export interface FinderItem { country: CountryCard; matched: string[]; values: Record; } export interface FinderResponse { meta: Meta; mode: 'and' | 'or'; filters: FinderFilter[]; n_matching: number; n_evaluated: number; items: FinderItem[]; } // ---------------------------------------------------------------------------------------------- indicator analytics export interface RelatedItem { indicator: IndicatorCard; pearson: number | null; spearman: number | null; n: number; year: number | null; log_x: boolean; log_y: boolean; direction: 'positive' | 'negative'; } export interface RelatedResponse { meta: Meta; indicator: IndicatorCard; year_used: number | null; n_candidates: number; items: RelatedItem[]; note: string; } export interface Histogram { edges: number[]; counts: number[]; log: boolean; } export interface DistributionResponse { meta: Meta; indicator: IndicatorCard; year: number | null; year_used: number | null; n: number; log: boolean; histogram: Histogram; stats: { min: number | null; p10: number | null; p25: number | null; median: number | null; mean: number | null; p75: number | null; p90: number | null; max: number | null }; highlight: { country: CountryCard; value: number | null; percentile: number | null; rank: number | null; n: number | null; region: GroupCard | null; region_median: number | null; income: GroupCard | null; income_median: number | null } | null; by_region: Array<{ group: GroupCard; median: number | null; n: number }>; by_income: Array<{ group: GroupCard; median: number | null; n: number }>; provenance: Provenance | null; } export interface FramesResponse { meta: Meta; indicator: IndicatorCard; group: GroupCard; years: number[]; values: Record>; legend: { min: number | null; max: number | null; breaks: number[]; n_classes: number }; n_by_year: number[]; provenance: Provenance | null; } export interface IndicatorQualityResponse { meta: Meta; indicator: IndicatorCard; n_countries: number; n_countries_total: number; coverage_pct: number; first_year: number | null; last_year: number | null; latest_common_year: number | null; n_years: number; years_with_50plus: number; median_points_per_country: number | null; sparse_countries: number; stale_countries: number; flagged_values: number; sources: Provenance[]; badges: QualityBadge[]; } // ---------------------------------------------------------------------------------------------- race / regions compare export interface RaceResponse { meta: Meta; indicator: IndicatorCard; group: GroupCard; top: number; years: number[]; frames: Array<{ year: number; rows: Array<{ id: string; value: number; rank: number }> }>; countries: Record; max_value: number | null; provenance: Provenance | null; } export interface RegionCompareResponse { meta: Meta; groups: [GroupCard, GroupCard]; rows: Array<{ indicator: IndicatorCard; kind: 'sum' | 'median' | 'weighted_mean' | string; label: string; values: Record }>; shares: Record; history: Record | number[]>>; } // ---------------------------------------------------------------------------------------------- country analytics export interface StoryItem { indicator: IndicatorCard; first: { year: number; value: number; formatted: string | null }; last: { year: number; value: number; formatted: string | null }; change_abs: number | null; change_pct: number | null; cagr: number | null; peak: { year: number; value: number } | null; trough: { year: number; value: number } | null; rank_first: { rank: number; n: number; year: number } | null; rank_last: { rank: number; n: number; year: number } | null; series: Array<[number, number | null]>; text: string; provenance: Provenance | null; } export interface StoryResponse { meta: Meta; country: CountryCard; since: number | null; items: StoryItem[]; } export interface DnaReference { kind: 'world' | 'region' | 'income' | 'country' | string; id: string | null; label: string; dims: Record; } export interface CountryQualityItem { indicator: IndicatorCard; latest_year: number | null; first_year: number | null; n_points: number; expected_points: number; missing_years: number; continuity_pct: number | null; status: string | null; source: string | null; source_updated_at: string | null; retrieved_at: string | null; badges: QualityBadge[]; } export interface CountryQualityResponse { meta: Meta; country: CountryCard; summary: { n_indicators: number; n_with_data: number; coverage_pct: number | null; latest_year: number | null; n_fresh: number; n_stale: number; n_sparse: number; n_flagged: number }; items: CountryQualityItem[]; } // ---------------------------------------------------------------------------------------------- updates export interface UpdatesSource { source: { id: string; name: string | null; organization: string | null; url: string | null; licence: string | null }; status: 'ok' | 'partial' | 'failed' | 'stale' | 'unknown' | string; last_success_at: string | null; last_retrieved_at: string | null; source_updated_at: string | null; n_datasets: number; n_indicators: number; n_observations: number; latest_year: number | null; values_changed: number; countries_affected: number; } export interface UpdatesResponse { meta: Meta; snapshot: { run_id: string | null; built_at: string | null; observations: number; indicators: number; countries: number; values_changed: number; values_changed_by_source: Record }; sources: UpdatesSource[]; recent_runs: Array<{ run_id: string | null; connector: string; dataset: string | null; started_at: string | null; finished_at: string | null; status: string | null; rows_valid: number | null; warnings: number | null; errors: number | null; message: string | null }>; indicators_recently_updated: IndicatorSummary[]; } /** Search hit for a parsed intent ("compare canada usa", "rank gdp"). */ export interface ActionHit { type: 'action'; action: 'compare' | 'ranking' | 'group_ranking' | 'explore' | string; id: string; name: string; hint: string | null; url: string; score: number; }