SPB Git

spb/auto-ka Public

Python 82.8% TypeScript 11.9% CSS 5.1%
4.7 KB · 177 lines typescript
Raw Blame History
1// -----------------------------------------------------------------------------2// Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// api.ts : client typé de l'API FastAPI5// -----------------------------------------------------------------------------67export interface Vehicle {8  uid: string;9  kind: string;10  source: string;11  external_id: string;12  url: string;13  title: string;14  make: string;15  model: string;16  trim: string;17  year: number | null;18  price: number | null;19  price_label: string;20  mileage_km: number | null;21  mileage_label: string;22  transmission: string;23  fuel: string;24  drivetrain: string;25  body_type: string;26  exterior_color: string;27  interior_color: string;28  engine: string;29  doors: number | null;30  seats: number | null;31  vin: string;32  stock_number: string;33  dealer_name: string;34  city: string;35  region: string;36  description: string;37  features: string[];38  details: Record<string, unknown>;39  images: string[];40  carfax_url: string;41  first_seen: number;42  updated_at: number;43  active: number;44}4546export interface VehicleDetail extends Vehicle {47  price_history: { ts: number; price: number | null }[];48  similar: Vehicle[];49}5051export interface Facets {52  makes: { make: string; n: number }[];53  models: { model: string; n: number }[];54  body_types: string[];55  fuels: string[];56  regions: { region: string; n: number }[];57  sources: { source: string; dealer_name: string; n: number }[];58  years: { y_min: number | null; y_max: number | null }[];59}6061export interface Source {62  id: string;63  name: string;64  url: string;65  listing_url?: string;66  city?: string;67  region?: string;68  platform?: string;69  status?: string;70  active_listings: number;71  last_sync: number | null;72}7374export interface Stats {75  total: number;76  sources: number;77  regions: number;78  avg_price: number | null;79  avg_km: number | null;80  avg_year: number | null;81  by_region: { region: string; n: number; avg_price: number | null }[];82  by_make: { make: string; n: number; avg_price: number | null }[];83  by_body: { body_type: string; n: number }[];84  price_drops: {85    uid: string; title: string; year: number | null; price: number;86    prev_price: number; images: string[]; dealer_name: string; city: string;87  }[];88  recent_syncs: {89    source: string; ts: number; found: number; added: number;90    updated: number; removed: number; ok: number; message: string;91  }[];92}9394export interface VehicleQuery {95  kind?: string;96  make?: string;97  model?: string;98  body_type?: string;99  fuel?: string;100  transmission?: string;101  drivetrain?: string;102  region?: string;103  source?: string;104  year_min?: number;105  year_max?: number;106  price_min?: number;107  price_max?: number;108  km_max?: number;109  q?: string;110  sort?: string;111  limit?: number;112  offset?: number;113}114115async function get<T>(path: string): Promise<T> {116  const res = await fetch(path);117  if (!res.ok) throw new Error(`API ${res.status}`);118  return res.json();119}120121export function fetchVehicles(query: VehicleQuery) {122  const params = new URLSearchParams();123  for (const [k, v] of Object.entries(query)) {124    if (v !== undefined && v !== null && v !== "") params.set(k, String(v));125  }126  return get<{ total: number; count: number; vehicles: Vehicle[] }>(127    `/api/vehicles?${params.toString()}`128  );129}130131export function fetchVehicle(uid: string) {132  return get<VehicleDetail>(`/api/vehicles/${encodeURIComponent(uid)}`);133}134135export function fetchFacets(make?: string, kind?: string) {136  const params = new URLSearchParams();137  if (make) params.set("make", make);138  if (kind) params.set("kind", kind);139  const qs = params.toString();140  return get<Facets>(`/api/facets${qs ? `?${qs}` : ""}`);141}142143export function fetchSources() {144  return get<{ sources: Source[] }>("/api/sources");145}146147export function fetchStats() {148  return get<Stats>("/api/stats");149}150151// -- noms d'affichage des sources (peuplés depuis /api/sources) ---------------152const names: Record<string, string> = {};153154export function registerSourceNames(sources: Source[]) {155  for (const s of sources) names[s.id] = s.name;156}157158export function sourceName(id: string): string {159  return names[id] ?? id;160}161162// -- formatteurs ---------------------------------------------------------------163export const fmtPrice = (p: number | null | undefined) =>164  p == null ? "Prix sur demande" : `${Math.round(p).toLocaleString("fr-CA")} $`;165166export const fmtKm = (km: number | null | undefined) =>167  km == null ? "— km" : `${Math.round(km).toLocaleString("fr-CA")} km`;168169export const fmtDate = (ts: number | null | undefined) =>170  ts == null171    ? "—"172    : new Date(ts * 1000).toLocaleDateString("fr-CA", {173        day: "numeric",174        month: "short",175        year: "numeric",176      });177