// ----------------------------------------------------------------------------- // Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // api.ts : client typé de l'API FastAPI // ----------------------------------------------------------------------------- export interface Vehicle { uid: string; kind: string; source: string; external_id: string; url: string; title: string; make: string; model: string; trim: string; year: number | null; price: number | null; price_label: string; mileage_km: number | null; mileage_label: string; transmission: string; fuel: string; drivetrain: string; body_type: string; exterior_color: string; interior_color: string; engine: string; doors: number | null; seats: number | null; vin: string; stock_number: string; dealer_name: string; city: string; region: string; description: string; features: string[]; details: Record; images: string[]; carfax_url: string; first_seen: number; updated_at: number; active: number; } export interface VehicleDetail extends Vehicle { price_history: { ts: number; price: number | null }[]; similar: Vehicle[]; } export interface Facets { makes: { make: string; n: number }[]; models: { model: string; n: number }[]; body_types: string[]; fuels: string[]; regions: { region: string; n: number }[]; sources: { source: string; dealer_name: string; n: number }[]; years: { y_min: number | null; y_max: number | null }[]; } export interface Source { id: string; name: string; url: string; listing_url?: string; city?: string; region?: string; platform?: string; status?: string; active_listings: number; last_sync: number | null; } export interface Stats { total: number; sources: number; regions: number; avg_price: number | null; avg_km: number | null; avg_year: number | null; by_region: { region: string; n: number; avg_price: number | null }[]; by_make: { make: string; n: number; avg_price: number | null }[]; by_body: { body_type: string; n: number }[]; price_drops: { uid: string; title: string; year: number | null; price: number; prev_price: number; images: string[]; dealer_name: string; city: string; }[]; recent_syncs: { source: string; ts: number; found: number; added: number; updated: number; removed: number; ok: number; message: string; }[]; } export interface VehicleQuery { kind?: string; make?: string; model?: string; body_type?: string; fuel?: string; transmission?: string; drivetrain?: string; region?: string; source?: string; year_min?: number; year_max?: number; price_min?: number; price_max?: number; km_max?: number; q?: string; sort?: string; limit?: number; offset?: number; } async function get(path: string): Promise { const res = await fetch(path); if (!res.ok) throw new Error(`API ${res.status}`); return res.json(); } export function fetchVehicles(query: VehicleQuery) { const params = new URLSearchParams(); for (const [k, v] of Object.entries(query)) { if (v !== undefined && v !== null && v !== "") params.set(k, String(v)); } return get<{ total: number; count: number; vehicles: Vehicle[] }>( `/api/vehicles?${params.toString()}` ); } export function fetchVehicle(uid: string) { return get(`/api/vehicles/${encodeURIComponent(uid)}`); } export function fetchFacets(make?: string, kind?: string) { const params = new URLSearchParams(); if (make) params.set("make", make); if (kind) params.set("kind", kind); const qs = params.toString(); return get(`/api/facets${qs ? `?${qs}` : ""}`); } export function fetchSources() { return get<{ sources: Source[] }>("/api/sources"); } export function fetchStats() { return get("/api/stats"); } // -- noms d'affichage des sources (peuplés depuis /api/sources) --------------- const names: Record = {}; export function registerSourceNames(sources: Source[]) { for (const s of sources) names[s.id] = s.name; } export function sourceName(id: string): string { return names[id] ?? id; } // -- formatteurs --------------------------------------------------------------- export const fmtPrice = (p: number | null | undefined) => p == null ? "Prix sur demande" : `${Math.round(p).toLocaleString("fr-CA")} $`; export const fmtKm = (km: number | null | undefined) => km == null ? "— km" : `${Math.round(km).toLocaleString("fr-CA")} km`; export const fmtDate = (ts: number | null | undefined) => ts == null ? "—" : new Date(ts * 1000).toLocaleDateString("fr-CA", { day: "numeric", month: "short", year: "numeric", });