/** * ============================================================================= * QWHPI — Quebec Weekly Housing Price Index * Author : Simon-Pierre Boucher * Contact : contact@spboucher.ai * File : web/lib/api.ts * Purpose : Typed API client for the QHPI monthly FastAPI service. * ============================================================================= */ export const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8080"; export interface Observation { period: string; index: number | null; index_smoothed: number; representative_value: number | null; transactions: number; effective_sample_size: number | null; monthly_pct: number | null; three_month_pct: number | null; six_month_pct: number | null; yoy_pct: number | null; lower_95: number; upper_95: number; reliability_grade: string; shrinkage_weight: number | null; is_partial_month: boolean; } export interface SeriesResponse { geography: string; geography_name: string; geography_level: string; property_type: string; model_version: string; data_vintage: string; total_observations: number; observations: Observation[]; } export interface LatestResponse { geography: string; geography_name: string; property_type: string; period: string; latest_index: number; representative_value: number | null; monthly_change: number | null; three_month_change: number | null; yoy_change: number | null; transactions: number; reliability: string; lower_95: number; upper_95: number; is_partial_month: boolean; data_vintage: string; } export interface GeographyNode { geography_id: string; geography_name: string; geography_level: string; property_types: string[]; } export interface MapCell { geography_id: string; geography_name: string; value: number | null; reliability_grade: string; transactions: number; } async function get(path: string): Promise { const res = await fetch(`${API_BASE}${path}`, { headers: { Accept: "application/json" }, }); if (!res.ok) throw new Error(`${res.status} ${res.statusText} — ${path}`); return res.json() as Promise; } export const fetchSeries = (g: string, t: string) => get(`/v1/index?geography=${g}&type=${t}`); export const fetchLatest = (g: string, t: string) => get(`/v1/index/latest?geography=${g}&type=${t}`); export const fetchGeographies = () => get<{ published_series: GeographyNode[]; data_vintage: string }>( "/v1/geographies", ); export const fetchMap = (metric: string, type: string, period?: string) => get<{ metric: string; period: string; cells: MapCell[] }>( `/v1/map?metric=${metric}&level=region&type=${type}` + (period ? `&period=${period}` : ""), ); export const csvUrl = (g: string, t: string) => `${API_BASE}/v1/index?geography=${g}&type=${t}&format=csv`; export interface SeriesStats { geography: string; property_type: string; period: string; index: number; representative_value: number | null; lower_95: number; upper_95: number; reliability_grade: string; monthly_pct: number | null; three_month_pct: number | null; six_month_pct: number | null; yoy_pct: number | null; since_2021_pct: number; cagr_pct: number; peak_index: number; peak_period: string; drawdown_pct: number; months_since_peak: number; at_record_high: boolean; volatility_12m_pct: number; momentum_3m_ann_pct: number; yoy_rank: number | null; yoy_rank_of: number; volume_12m: number; volume_yoy_pct: number | null; dollar_volume_12m: number; assessment_gap: number | null; effective_sample_size: number | null; spark: number[]; } export interface OverviewRow { geography_id: string; geography_name: string; period: string; index: number; monthly_pct: number | null; three_month_pct: number | null; six_month_pct: number | null; yoy_pct: number | null; since_2021_pct: number; drawdown_pct: number; at_record_high: boolean; volume_12m: number; representative_value: number | null; reliability_grade: string; spark: number[]; } export const fetchStats = (g: string, t: string) => get(`/v1/stats?geography=${g}&type=${t}`); export const fetchOverview = (level: string, t: string) => get<{ period: string; rows: OverviewRow[] }>( `/v1/stats/overview?level=${level}&type=${t}`, ); export const reportUrl = (specs: string[]) => `${API_BASE}/v1/report?series=${specs.join(",")}`; export const marketReportUrl = (t: string) => `${API_BASE}/v1/report/market?type=${t}`;