SPB Git forge

spb/immo-ka

Public

Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)

112commits 1branches 0releases
125.4 MBsize
maindefault branch
13 days agolast push
Python 47.5% HTML 27.9% TypeScript 15.5% CSS 7.2% JavaScript 2%
2.9 KB · 75 lines python
Raw Blame History
1#!/usr/bin/env python32# -----------------------------------------------------------------------------3# Immo-Ka — Annuaire des bannières & courtiers immobiliers du Québec4# bannieres.py : sert data/bannieres.json — les ~690 agences (bannières) du5# registre OACIQ et leurs 17 357 courtiers (recensement complet, 17 régions).6# Régénération du dataset : rescraper registre.oaciq.com (voir7# data/oaciq_census.json pour la méthode) puis reconstruire le JSON.8# -----------------------------------------------------------------------------9from __future__ import annotations1011import json12import threading13from pathlib import Path1415from fastapi import APIRouter, HTTPException1617ROOT = Path(__file__).resolve().parent.parent18DATA = ROOT / "data" / "bannieres.json"1920router = APIRouter()21_cache: dict = {"mtime": None, "doc": None, "index": None, "light": None}22_lock = threading.Lock()232425def load() -> dict:26    """Charge (avec cache par mtime) l'annuaire canonique des bannières."""27    try:28        mtime = DATA.stat().st_mtime29    except FileNotFoundError:30        return {"generated": None, "count": 0, "brokers_total": 0,31                "bannieres": [], "regions": []}32    with _lock:33        if _cache["doc"] is None or mtime != _cache["mtime"]:34            doc = json.loads(DATA.read_text(encoding="utf-8"))35            regs: dict[str, int] = {}36            for b in doc.get("bannieres", []):37                for r in b.get("regions", []):38                    regs[r] = regs.get(r, 0) + 139            doc["regions"] = [{"region": r, "n": n}40                              for r, n in sorted(regs.items(),41                                                 key=lambda x: (-x[1], x[0]))]42            _cache["doc"], _cache["mtime"] = doc, mtime43            _cache["index"] = {b["id"]: b for b in doc.get("bannieres", [])}44            _cache["light"] = [{k: v for k, v in b.items() if k != "brokers"}45                               for b in doc.get("bannieres", [])]46        return _cache["doc"]474849@router.get("/api/bannieres")50def api_bannieres(region: str | None = None, q: str | None = None):51    """Liste des bannières (sans le détail des courtiers) — filtres région/texte."""52    doc = load()53    items = _cache["light"] or []54    if region:55        items = [b for b in items if region in b.get("regions", [])]56    if q:57        needle = q.strip().lower()58        items = [b for b in items if needle in b["name"].lower()]59    return {"generated": doc.get("generated"),60            "total": doc.get("count", 0),61            "brokers_total": doc.get("brokers_total", 0),62            "count": len(items),63            "regions": doc.get("regions", []),64            "bannieres": items}656667@router.get("/api/bannieres/{banniere_id}")68def api_banniere(banniere_id: str):69    """Une bannière avec la liste complète de ses courtiers."""70    load()71    b = (_cache["index"] or {}).get(banniere_id)72    if b is None:73        raise HTTPException(status_code=404, detail="bannière inconnue")74    return b75