# Lou·Ka ### Tous les **logements à louer du Québec**. Un seul endroit. **[www.lou-ka.com](https://www.lou-ka.com)** — un service **[Groupe Ka](https://www.groupe-ka.com)** ![Aperçu de Lou-Ka](docs/screenshot.png) ![Python](https://img.shields.io/badge/Python-3.14-141814?style=for-the-badge&logo=python&logoColor=d9f26b) ![FastAPI](https://img.shields.io/badge/FastAPI-API_+_SSR-141814?style=for-the-badge&logo=fastapi&logoColor=d9f26b) ![React](https://img.shields.io/badge/React_18-Vite_+_TS-141814?style=for-the-badge&logo=react&logoColor=d9f26b) ![SQLite](https://img.shields.io/badge/SQLite-storage-141814?style=for-the-badge&logo=sqlite&logoColor=d9f26b) ![Mapbox](https://img.shields.io/badge/Lou--Ka_Maps-Mapbox_GL_3D-141814?style=for-the-badge&logo=mapbox&logoColor=d9f26b) ![Swift](https://img.shields.io/badge/iOS-SwiftUI-141814?style=for-the-badge&logo=swift&logoColor=d9f26b) ![Annonces](https://img.shields.io/badge/annonces_actives-22_900%2B-1c5c41?style=flat-square) ![Sources](https://img.shields.io/badge/sources_recens%C3%A9es-265-1c5c41?style=flat-square) ![Connecteurs](https://img.shields.io/badge/connecteurs-212-1c5c41?style=flat-square) ![Villes](https://img.shields.io/badge/villes-820-1c5c41?style=flat-square) ![Régions](https://img.shields.io/badge/r%C3%A9gions-11-1c5c41?style=flat-square) ![Géocodées](https://img.shields.io/badge/g%C3%A9olocalis%C3%A9es-70%25-1c5c41?style=flat-square) --- ## Description Chercher un appartement au Québec, c'est ouvrir des dizaines de sites web différents — chacun avec sa navigation, ses filtres, son format. **Lou-Ka retourne le problème** : un **connecteur dédié par gestionnaire immobilier** visite chaque site, **normalise chaque annonce** vers un schéma unique, et **détecte les changements en continu**. Lou-Ka n'est pas une plateforme d'annonces : c'est un **index fidèle** et un **agrégateur indépendant** de logements locatifs. **Aucun prix inventé**, **aucune coordonnée devinée**, et chaque fiche renvoie vers **l'annonce originale du gestionnaire** via une passerelle de sortie transparente (`/passerelle/{uid}`). > Les sites d'agences n'offrent pas de webhooks. Lou-Ka reproduit l'équivalent : **synchronisation périodique + hash de contenu** → ajouts, mises à jour et retraits détectés automatiquement. Une annonce qui disparaît du site source disparaît de Lou-Ka (et répond **`410 Gone`** aux moteurs de recherche). **En chiffres** : **22 900+ annonces actives**, **275 sources recensées**, **212 connecteurs**, **820 villes**, **11 régions**, **~70 %** des annonces géolocalisées. ## Fonctionnalités - **Recherche filtrée** — ville, quartier, type (**3½**, 4½…), loyer min/max, animaux, meublé, superficie, texte libre. - **Vue carte 3D** — **Lou-Ka Maps** (framework **`@groupe-ka/ka-maps`**, moteur **Mapbox GL JS**) : clusters par prix moyen, « Rechercher dans cette zone », caméra partageable dans l'URL, bascule **3D/2D**, synchro liste ↔ carte. - **Fiches complètes** — **toutes les photos**, digest structuré de la description (**text mining regex FR, aucun LLM**), badge marché, **historique de prix** (`price_log`), bloc « **Le quartier** » (recensement 2021, INSPQ, SPVM, écoles MEQ, POI OSM), **fiche PDF** avec QR code. - **Pages villes SEO** — `/villes`, `/ville/{slug}`, `/ville/{slug}/{type}` avec **SSR léger**, sitemaps dynamiques (chunks de **10 000**), **JSON-LD** (`RealEstateListing` + `Offer` CAD), vrais `404`/`410`. - **Observatoire du marché** — KPI temps réel, loyers médians par région/ville/taille, **rapport de marché PDF** multi-pages (`/api/stats/rapport.pdf`). - **Registre des sources** — chaque gestionnaire, son statut, sa dernière synchro ; toute source **non connectable est documentée avec sa raison** dans `data/sources.json`. - **Comptes & SSO KA** — « **Se connecter avec KA** » via le hub **groupe-ka.com** (JWT HS256, audience `lou-ka`, **`ka_id`** émis par le hub) + **Google OAuth** direct ; favoris synchronisés vers « Mon univers Ka » ; pages gestionnaires réclamables (`/g/{source_id}`) ; profil public **opt-in** `/u/{ka_id}`. - **Widget KA Agent** — bulle de chat IA du Groupe Ka intégrée au site. - **PWA mobile-first** installable + **app iOS SwiftUI 100 % native** (iOS 17+, TestFlight, dépôt séparé `git.spboucher.ai/lou-ka-ios`) avec moteur de recommandation **on-device**. - **Robustesse** — garde-fou **anti-dérive** (`DRIFT_RATIO = 0.25`) : si une source retourne soudainement beaucoup moins d'annonces, les retraits sont **suspendus** au lieu de vider l'inventaire. ### Visite guidée | | | |:---:|:---:| | **Recherche filtrée** | **Vue carte Lou-Ka Maps** | | Grille d'annonces | Vue carte Lou-Ka Maps | | **Fiche complète** | **Pages villes (SEO)** | | Fiche d'un logement | Page ville Québec | | **Observatoire du marché** | **Registre des sources** | | Page stats | Registre des sources | ## Stack technique | Couche | Technologies | Fichiers | |---|---|---| | **Backend** | **Python 3.14**, **FastAPI**, uvicorn, **SQLite** (migrations auto, upsert par hash), reportlab/**fpdf2** (PDF) — **8 dépendances** | `louka/` | | **Connecteurs** | **205 modules auto-découverts** (`pkgutil.iter_modules`) : HTML, API JSON internes (Building Stack, RealVuu, Planpoint, Rentsync, source.immo, WordPress REST…), **Firecrawl** (Cloudflare), **Scrapfly** (anti-bot) | `louka/connectors/*.py` | | **Text mining** | digest déterministe des descriptions — regex FR, **< 5 ms/annonce**, **aucun LLM** | `louka/textmine.py` | | **Géocodage** | **Nominatim** (1 req/s) + **Adresses Québec** (MERN ArcGIS, mode **EN LOT** de 200) validé par bounding box provinciale | `louka/geocode.py` | | **Frontend** | **React 18 + Vite + TypeScript**, design « éditorial sharp » Groupe Ka (Space Grotesk, accent), **PWA** | `frontend/` | | **Cartographie** | **`@groupe-ka/ka-maps`** — framework partagé du Groupe Ka, **Mapbox GL JS** style Standard **3D**, monté en `React.lazy` | `frontend/src/kamaps/` | | **SEO** | SSR léger FastAPI, sitemaps dynamiques, JSON-LD, hreflang, GZip, `/assets` immuable 1 an | `louka/seo.py` | | **iOS** | **SwiftUI natif** (iOS 17+, zéro dépendance externe), TestFlight | dépôt séparé | | **Prod** | **PM2** (3 processus) + **ngrok** sur le nœud **M3U96b** | — | ## Structure du dépôt ``` lou-ka/ ├── run.py # point d'entrée CLI (sync / watch / serve / geocode / poi / quartier / record) ├── requirements.txt # 8 dépendances backend (FastAPI, uvicorn, bs4, reportlab, fpdf2…) ├── louka/ # paquet backend (Python 3.14) │ ├── web.py # app FastAPI : API JSON + service du build Vite │ ├── seo.py # SSR léger, pages programmatiques, sitemaps, robots, JSON-LD │ ├── db.py # SQLite : migrations auto, upsert par hash, anti-dérive │ ├── schema.py # dataclass Listing │ ├── normalize.py # normalisation (prix, dates ISO, types d'unités, adresses) │ ├── textmine.py # digest déterministe des descriptions (regex FR) │ ├── ingest.py # pipeline sync / watch │ ├── auth.py # SSO KA + Google OAuth, sessions HMAC-SHA256 (cookie httpOnly 30 j) │ ├── accounts.py # rôles, favoris, réclamation de page gestionnaire │ ├── profile.py / hubprofile.py / hubfav.py # profils & intégration hub Groupe Ka │ ├── geocode.py # Nominatim + Adresses Québec (mode EN LOT) │ ├── poi.py / quartier.py # commodités OSM, stats de quartier │ ├── marketstats.py # agrégats du marché (source unique stats/API/PDF) │ ├── pdfgen.py # fiches PDF + rapport de marché (QR code) │ ├── fixtures.py # enregistrement/rejeu HTTP pour tests hors-ligne │ └── connectors/ # 212 connecteurs + base.py (auto-découverte) ├── frontend/ # React 18 + Vite + TypeScript (pages, composants, kamaps/) ├── data/ │ ├── sources.json # registre des 265 sources (statut + raison si non connectable) │ ├── louka.db # base de production (gitignorée) │ └── quartier.db # base statique de quartier (gitignorée) ├── scripts/ # build_recensement / build_contexte / build_environnement / merge_quartier / screenshots.mjs ├── tests/ # pytest + fixtures HTTP rejouables hors-ligne ├── reports/ # audits : fiches connecteurs, expansion par région, quartier… └── docs/ # screenshot.png, screenshots/, ka-maps-lou-ka.md ``` ## Démarrage local ```bash git clone https://git.spboucher.ai/lou-ka.git && cd lou-ka # Backend python3 -m venv .venv && .venv/bin/pip install -r requirements.txt # Frontend (nécessite le framework ka-maps bâti à côté : ../ka-maps) cd frontend && npm install && npm run build && cd .. # (optionnel) sites JavaScript / anti-bot cat > .env <`** | (ré)enregistre les fixtures HTTP d'un connecteur | ### Variables d'environnement `run.py` charge **`.env`** à la racine (parsing maison, aucune dépendance). | Variable | Rôle | |---|---| | **`FIRECRAWL_API_KEY`** | rendu JavaScript / contournement Cloudflare (optionnel) | | **`SCRAPFLY_KEY`** | scraping anti-bot ASP (optionnel) | | **`SESSION_SECRET`** | signature HMAC des sessions | | **`GOOGLE_CLIENT_ID`** / **`GOOGLE_CLIENT_SECRET`** | connexion Google OAuth | | **`KA_SSO_SECRET`** | secret partagé du SSO KA (JWT HS256) | | **`KA_HUB_URL`** | hub Groupe Ka (défaut `https://www.groupe-ka.com`) | | **`LOUKA_BASE_URL`** | URL publique (derrière ngrok/proxy) | | `LOUKA_*_DETAIL_LIMIT` / `LOUKA_*_MAX_PAGES` | limites par portail (Kijiji, LesPAC, RE/MAX…) | | **`VITE_MAPBOX_TOKEN`** | jeton Mapbox du frontend (repli public codé en dur) | ### Tests ```bash .venv/bin/python -m pytest ``` Chaque connecteur est rejoué **hors-ligne** contre ses fixtures HTTP enregistrées (`tests/fixtures//` + `expected.json`) ; la normalisation et le text mining ont leurs suites dédiées. ## API (aperçu) | Endpoint | Description | |---|---| | **`GET /api/listings`** | recherche filtrée : `city, sector, unit_type, source, price_min/max, pets, furnished, available_by, area_min, q, limit, offset` | | **`GET /api/listings.geojson?bbox=O,S,E,N`** | FeatureCollection pour la carte (+ `totalGeocoded`, `totalMatching`) | | **`GET /api/listings/{uid}`** | fiche complète : images, POI, quartier, digest, historique de prix | | **`GET /api/listings/{uid}/pdf`** | fiche de propriété PDF (QR code) | | **`GET /api/facets`** | villes, quartiers, types, sources — pour construire les filtres | | **`GET /api/stats`** / **`/api/stats/detailed`** / **`/api/stats/rapport.pdf`** | agrégats du marché + rapport PDF | | **`GET /api/sources`** | registre des **265 sources** + compteurs + dernière synchro | | **`POST /api/sync`** | synchronisation en arrière-plan (`?source=`) | | **`GET /api/auth/ka/login`** / **`/api/auth/google/login`** | SSO KA & Google OAuth | | `GET /api/me` · `/api/favorites` · `/api/org/*` · `/api/users/{ka_id}` | session, favoris, pages gestionnaires, profils publics | Pages HTML servies par le SSR (`louka/seo.py`) : `/` · `/villes` · `/ville/{slug}[/{type}]` · `/logement/{uid}` (**410** si retirée) · `/g/{source}` · `/stats` · `/sources` · `/robots.txt` · `/sitemap*.xml` ## Déploiement En production sur le nœud **M3U96b** (Mac Studio, cluster MacLustr), dans **`~/apps/lou-ka`**, sous **PM2** (**3 processus**) derrière **ngrok** : | Processus PM2 | Commande | Rôle | |---|---|---| | **`lou-ka-web`** | `.venv/bin/python run.py serve 8095` | API + SSR + frontend sur le **port 8095** | | **`lou-ka-sync`** | `.venv/bin/python run.py watch 60` | resynchronisation **horaire** des sources | | **`lou-ka-ngrok`** | `ngrok http --url=www.lou-ka.com 8095` | tunnel → **https://www.lou-ka.com** | Philosophie d'exploitation : **on ne pousse que le code — le serveur maintient ses données lui-même** (`data/*.db`, `frontend/dist` et `.env` ne sont **jamais versionnés**). ## Développement remote-first (IMPORTANT) La **source de vérité est le repo git sur le nœud M3U96b** (`~/apps/lou-ka`), **pas** une copie locale sur le laptop. Toute modification se fait **sur le nœud via SSH** : édition, build frontend, `pm2 restart lou-ka-web`, puis commit/push **depuis le nœud**. - Remote **`origin` = spbgit** — le serveur git personnel **git.spboucher.ai** (bare repo `~/srv/git/lou-ka.git` sur M3U96a, alias SSH `gitsrv`). **Pas GitHub.** - Le push fonctionne grâce à l'**agent forwarding SSH** actif pendant une session depuis le laptop. ## Principes 1. **Politesse** — délai **≥ 0,5 s** entre requêtes, garde-fous de crawl, User-Agent identifié (**`LouKaBot`**, page de transparence `/bot`). 2. **Fidélité** — **aucun prix inventé** (`price = null` si absent), **aucune coordonnée devinée** (géocodage validé ou rien). 3. **Traçabilité** — chaque fiche renvoie vers l'annonce originale via **`/passerelle/{uid}`**. 4. **Robustesse** — un connecteur qui casse n'affecte jamais les autres ; le **garde-fou anti-dérive** suspend les retraits quand une source déraille. 5. **Vie privée** — **aucun traceur tiers**, consentement honnête, profil public strictement **opt-in**. ## Écosystème Groupe Ka Lou-Ka fait partie des plateformes du **[Groupe Ka](https://www.groupe-ka.com)**, aux côtés d'**Immo-Ka** (propriétés à vendre), **Vrai-Prix** et les autres univers ·Ka — avec en partage : le **SSO KA** (`ka_id` émis par le hub), le framework cartographique **Ka Maps**, le design system « **éditorial sharp** » et le géocodage **Adresses Québec**. ---
**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai) · [git.spboucher.ai](https://git.spboucher.ai) © 2026 — tous droits réservés. Un service **Groupe Ka**