# ----------------------------------------------------------------------------- # Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec) # Auteur : Simon-Pierre Boucher — contact@spboucher.ai # connectors/dupontford.py : connecteur Dupont & Dupont Ford # (dupontdupontford.com), concessionnaire Ford de Gatineau. # # Plateforme : WordPress + thème Leadbox « lbx-egypt » (SPA Vue pour # l'inventaire). Le SPA charge un export JSON statique complet : # /wp-content/uploads/data/inventory.json -> {"vehicles": [...]} où chaque # véhicule est COMPLET : condition (New/Used), année, marque, modèle, # version, prix, km, VIN, stock, transmission, rouage, carburant, # carrosserie, moteur, couleur, photo, description. UNE seule requête pour # tout l'inventaire — on filtre condition == Used (voitures seulement). # # URL détail reconstruite comme dans le sitemap inventory.xml : # /vue/used----/. # ----------------------------------------------------------------------------- from __future__ import annotations import html as htmllib import re from ..schema import Vehicle, strip_accents from .base import BaseConnector _EXCLUDE_RE = re.compile( r"moto(?:cyclette|neige|marine)?\b|\bvtt\b|scooter|spyder|ryker|" r"can-?am|ski-?doo|sea-?doo|roulotte|remorque|\bvr\b|campeur|trailer", re.I) def _slug(text: str) -> str: t = strip_accents(str(text or "")).lower() t = re.sub(r"[^a-z0-9]+", "-", t) return t.strip("-") def _clean(text) -> str: if not text: return "" out = htmllib.unescape(str(text)) out = re.sub(r"<[^>]+>", " ", out) return re.sub(r"\s{2,}", " ", out).strip() def _num(value, cast=float): try: v = cast(value) except (TypeError, ValueError): return None return v if v else None class DupontDupontFord(BaseConnector): """Dupont & Dupont Ford — export JSON Leadbox (inventory.json).""" source_id = "dupontdupontford" base_url = "https://dupontdupontford.com" dealer_name = "Dupont & Dupont Ford" city = "Gatineau" request_delay = 1.0 use_detail_cache = False # une seule requête, pas de pages détail def fetch(self) -> list[Vehicle]: data = self.get( f"{self.base_url}/wp-content/uploads/data/inventory.json").json() vehicles: list[Vehicle] = [] for d in data.get("vehicles") or []: veh = self._to_vehicle(d) if veh is not None: vehicles.append(veh) return vehicles def _to_vehicle(self, d: dict) -> Vehicle | None: if str(d.get("condition") or "").lower() != "used": return None # occasion seulement if d.get("nonvehicle") or d.get("onweb") is False: return None if d.get("salepending") or d.get("sold"): return None if str(d.get("kind") or "car").lower() != "car": return None ext_id = str(d.get("id") or "") make = _clean(d.get("make")) model = _clean(d.get("model")) if not ext_id or not (make or model): return None body = _clean(d.get("bodystyle") or d.get("type")) if _EXCLUDE_RE.search(f"{make} {model} {body}"): return None year = _num(d.get("year"), int) trim = _clean(d.get("trim")) title = " ".join(str(x) for x in (make, model, trim, year or "") if x) url = (f"{self.base_url}/vue/used-{year or ''}-" f"{_slug(make)}-{_slug(model)}-{ext_id}/") price = _num(d.get("specialprice")) or _num(d.get("price")) \ or _num(d.get("saleprice")) km = _num(d.get("mileage")) images = [] if d.get("picture"): images = [str(d["picture"])] media = d.get("media") if isinstance(media, list): for u in media: if isinstance(u, str) and u not in images: images.append(u) elif isinstance(u, dict): link = u.get("url") or u.get("src") if link and link not in images: images.append(str(link)) details: dict = {} for key, out in (("certified", "certified"), ("demo", "demo"), ("asis", "as_is")): if d.get(key): details[out] = True if d.get("carprooflink"): carfax = str(d["carprooflink"]) else: carfax = "" if d.get("rebatesamount"): details["rebates"] = d["rebatesamount"] return Vehicle( source=self.source_id, external_id=ext_id, url=url, title=title, make=make, model=model, trim=trim, year=year, price=price, price_label=f"{price:,.0f} $".replace(",", " ") if price else "", mileage_km=km, mileage_label=f"{km:,.0f} km".replace(",", " ") if km else "", transmission=_clean(d.get("transmission")), fuel=_clean(d.get("fueltype")), drivetrain=_clean(d.get("drivetrain")), body_type=body, exterior_color=_clean(d.get("exteriorcolor")), engine=_clean(d.get("engine")), seats=_num(d.get("seats"), int), vin=_clean(d.get("vin")), stock_number=_clean(d.get("stocknumber")), dealer_name=self.dealer_name, city=self.city, description=_clean(d.get("description"))[:4000], details=details, images=images[:20], carfax_url=carfax, )