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%
4.1 KB · 61 lines typescript
Raw Blame History
1'use client';2import { finderQuery, type FinderFilterInput } from './finder-query';3import { ClientApiError } from './client-api';4import type {5  CountryQualityResponse,6  DistributionResponse,7  ExtremesResponse,8  FinderResponse,9  FramesResponse,10  IndicatorQualityResponse,11  MoversResponse,12  PeersResponse,13  PulseResponse,14  RaceResponse,15  RegionCompareResponse,16  RelatedResponse,17  ScatterResponse,18  StoryResponse,19  TrajectoryResponse,20  UpdatesResponse,21} from './types-analytics';2223/**24 * Browser-side fetches for the analytics API (same-origin `/api/v1/*`, rewritten to FastAPI). Every call takes an25 * optional AbortSignal so interactive views cancel superseded requests (slider scrubs, selector changes).26 */27async function get<T>(path: string, signal?: AbortSignal): Promise<T> {28  const res = await fetch(`/api/v1${path}`, { headers: { accept: 'application/json' }, signal });29  if (!res.ok) throw new ClientApiError(res.status, `API ${res.status}`);30  return (await res.json()) as T;31}3233function qs(query: Record<string, string | number | boolean | null | undefined>): string {34  const p = new URLSearchParams();35  for (const [k, v] of Object.entries(query)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v));36  const s = p.toString();37  return s ? `?${s}` : '';38}3940export const clientAnalytics = {41  pulse: (signal?: AbortSignal) => get<PulseResponse>('/pulse', signal),42  movers: (q: { window?: number; category?: string; kind?: string; limit?: number; min_population?: number | null }, signal?: AbortSignal) => get<MoversResponse>(`/movers${qs(q)}`, signal),43  extremes: (q: { window?: string; topic?: string | null; min_population?: number | null }, signal?: AbortSignal) => get<ExtremesResponse>(`/extremes${qs(q)}`, signal),44  scatter: (q: { x: string; y: string; size?: string | null; year?: number | null; group?: string | null; log_x?: string | null; log_y?: string | null }, signal?: AbortSignal) => get<ScatterResponse>(`/scatter${qs(q)}`, signal),45  trajectory: (q: { x?: string; y?: string; size?: string | null; from?: number | null; to?: number | null; group?: string | null }, signal?: AbortSignal) => get<TrajectoryResponse>(`/trajectory${qs(q)}`, signal),46  finder: (filters: FinderFilterInput[], q: { mode?: 'and' | 'or'; region?: string | null; income?: string | null; sort?: string | null; limit?: number } = {}, signal?: AbortSignal) =>47    get<FinderResponse>(`/finder${finderQuery(filters, q)}`, signal),48  peers: (q: { y?: string; x?: string; year?: number | null; method?: string; log_x?: string | null }, signal?: AbortSignal) => get<PeersResponse>(`/peers${qs(q)}`, signal),49  indicatorRelated: (slug: string, limit = 12, signal?: AbortSignal) => get<RelatedResponse>(`/indicators/${encodeURIComponent(slug)}/related${qs({ limit })}`, signal),50  indicatorDistribution: (slug: string, q: { year?: number | null; highlight?: string | null; bins?: number }, signal?: AbortSignal) => get<DistributionResponse>(`/indicators/${encodeURIComponent(slug)}/distribution${qs(q)}`, signal),51  indicatorFrames: (slug: string, q: { from?: number | null; to?: number | null; group?: string | null } = {}, signal?: AbortSignal) => get<FramesResponse>(`/indicators/${encodeURIComponent(slug)}/frames${qs(q)}`, signal),52  indicatorQuality: (slug: string, signal?: AbortSignal) => get<IndicatorQualityResponse>(`/indicators/${encodeURIComponent(slug)}/quality`, signal),53  race: (indicator: string, q: { from?: number | null; to?: number | null; top?: number; group?: string | null } = {}, signal?: AbortSignal) => get<RaceResponse>(`/rankings/${encodeURIComponent(indicator)}/race${qs(q)}`, signal),54  regionsCompare: (a: string, b: string, indicators?: string[], signal?: AbortSignal) => get<RegionCompareResponse>(`/regions/compare${qs({ a, b, indicators: indicators?.join(',') })}`, signal),55  countryStory: (id: string, signal?: AbortSignal) => get<StoryResponse>(`/countries/${encodeURIComponent(id)}/story`, signal),56  countryQuality: (id: string, signal?: AbortSignal) => get<CountryQualityResponse>(`/countries/${encodeURIComponent(id)}/quality`, signal),57  updates: (signal?: AbortSignal) => get<UpdatesResponse>('/updates', signal),58};5960export type { FinderFilterInput };61