spb/auto-ka Public
Python 82.8%
TypeScript 11.9%
CSS 5.1%
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