⚠️ URL live hors fonction pour le moment. L'application a été retirée du cluster MacLustr le 2026-09-04 (processus arrêtés, copie du nœud supprimée). Ce dépôt spbgit est désormais la seule source de vérité du projet. Ancienne URL : https://www.toit-ka.com
Toit-Ka — louer ou acheter un toit au Québec, au même endroit
Toit-Ka (www.toit-ka.com) est la fusion de deux agrégateurs immobiliers québécois en une seule plateforme : on y cherche un logement à louer OU une propriété à vendre, au même endroit, avec la même recherche, les mêmes cartes et le même vocabulaire :
- Lou-Ka — locatif : ~69 000 annonces de logements agrégées depuis 400+ gestionnaires immobiliers, courtiers et plateformes ;
- Immo-Ka — vente : ~157 000 propriétés agrégées depuis les grands réseaux (RE/MAX, Via Capitale, Sutton, Proprio Direct, flux Centris…).
Point capital : Toit-Ka ne scrape rien lui-même. Il consomme des réplicas en lecture seule des deux bases de données de production et les unifie via un ETL de normalisation. Les deux sites d'origine continuent de tourner sans être touchés, et chaque fiche renvoie vers l'annonce originale chez la source.
Chiffres au 2026-08-28 (
/api/stats) : 226 094 annonces publiées (68 925 à louer · 157 169 à vendre), ~500 sources héritées, loyer moyen 1 834 $/mois, prix affiché moyen ≈ 904 000 $.
Identité : le design bi-accent
Toit-Ka réunit deux univers qui avaient chacun leur couleur. Plutôt que d'en
choisir une, le site adopte un système bi-accent piloté par un unique
attribut html[data-mode] :
| Accent | Couleur | Rôle |
|---|---|---|
| Lime électrique | héritée de Lou-Ka | mode Louer : boutons, pastilles de carte, chips, stats |
| Rouge | hérité d'Immo-Ka | mode Acheter : mêmes composants, autre univers |
| Terracotta | propre à Toit-Ka | la marque elle-même (logo, badges, éléments neutres) |
Le toggle central Louer / Acheter de la page d'accueil est le cœur de l'expérience : il bascule instantanément les filtres, les paliers de prix, les chips rapides, les statistiques et l'accent visuel de tout le site. Le socle graphique est le style « éditorial tranché » du Groupe Ka : Space Grotesk / Inter / JetBrains Mono, bordures encre, ombres décalées, mobile-first, français (Québec).
Visite guidée (10 captures)
Captures du 2026-08-28 sur www.toit-ka.com (desktop 1440×900 · mobile 390×844).
1. Accueil — un toit, deux univers

La page d'accueil avec le toggle central Louer / Acheter, la barre de recherche par ville et les compteurs live des deux univers. L'accent de la page suit le mode sélectionné.
2. Fiche d'annonce — propriété à vendre

Une fiche complète en mode Acheter (5334, rue Waverly, Montréal) : galerie
photo, prix, caractéristiques, description, carte — et le lien vers
l'annonce originale chez la source. L'URL canonique
/annonce/{uid}/{slug} est rendue côté serveur pour le SEO.
3. Accueil en mode Louer — l'accent lime

La même page d'accueil après bascule du toggle (?tx=louer) : filtres,
paliers de prix et chips s'adaptent à l'univers locatif et tout le site passe
à l'accent lime hérité de Lou-Ka.
4. Statistiques de marché

La page /stats : volumes par univers, loyers et prix moyens, top villes et
top typologies, calculés en direct sur la base unifiée toitka.db.
5. Profil — KA ID

Le profil membre via KA ID, l'identité unique du Groupe Ka (groupe-ka.com) : connexion partagée et favoris centralisés dans « Mon univers Ka », communs aux 12 sites de l'écosystème.
6. Conditions d'utilisation

La page /conditions : cadre d'utilisation, statut d'agrégateur et renvoi
systématique aux annonces originales.
7. Politique de confidentialité

La page /confidentialite : données collectées, cookies, droits des membres
KA ID.
8. Accueil — sections thématiques (1/2)

En défilant l'accueil : sélections d'annonces par univers et raccourcis vers les grandes villes, chaque bloc portant l'accent de son univers.
9. Accueil — sections thématiques (2/2)

Suite de l'accueil : typologies populaires (3½, 4½, condo, maison, plex…), statistiques et maillage interne vers les pages SEO par ville et par type.
10. Accueil mobile

L'accueil sur téléphone (390×844) : toggle, recherche et compteurs au-dessus du pli, navigation plein écran (KA Nav v2), conforme au socle mobile du Groupe Ka (zones tactiles, safe-area, anti-zoom).
Galerie complémentaire (2026-08-25)
![]() Accueil mobile |
![]() Résultats à Montréal |
![]() Fiche annonce |
![]() Statistiques |
![]() Menu plein écran |
![]() Accueil desktop |
![]() Résultats — design bi-accent |
![]() Stats desktop |
||
Fonctionnalités
- Deux univers, une plateforme : toggle central Louer / Acheter ; filtres, paliers de prix, chips rapides et stats adaptés à l'univers actif.
- Recherche unifiée : par ville (canonique), prix, type (3½…6½+, Studio, Chambre, Condo, Maison, Plex, Terrain…), chambres/salles de bain, superficie, animaux, meublé, texte libre.
- Vue liste + carte 3D (Ka Maps / Mapbox GL) : marqueurs par univers (pastilles lime pour le locatif, encre pour la vente), « chercher dans cette zone », aperçus React au clic.
- Fiche complète : galerie + lightbox, caractéristiques, description, mini-carte avec anneaux de marchabilité, lien vers l'annonce originale.
- SEO programmatique rendu côté serveur :
/louer/{ville},/acheter/{ville},/{tx}/{ville}/{type},/{tx}/type/{type}avec loyers et prix moyens/médians, fils d'Ariane, JSON-LD, sitemaps, slugs canoniques 301, 410 pour les annonces retirées. - KA ID (SSO Groupe Ka) + favoris centralisés « Mon univers Ka ».
- Page /stats live sur la base unifiée.
- Mobile-first, 100 % français (Québec).
Architecture — un agrégateur d'agrégateurs, sans scraping
M3U96b (prod) M4M64a (prod)
lou-ka louka.db immo-ka immoka.db
│ sqlite3 .backup + rsync (LAN 192.168.2.x) │ ← lecture seule, toutes les 30 min
▼ ▼
M3U96a ~/apps/toit-ka
data/replicas/{louka,immoka}.db (toitka/replicate.py)
│ ETL (toitka/etl.py)
│ · règles de visibilité héritées (dédup Centris, prix plausibles)
│ · villes canoniques (toitka/villes.py — ~3 300 variantes brutes)
│ · typologie unifiée + réparation des entités HTML
▼
data/toitka.db — une seule table `listings`, `transaction_type ∈ {louer, acheter}`
│
FastAPI (toitka/web.py) ── API JSON + rendu SEO côté serveur (toitka/seo.py)
│
SPA React 18 (frontend/) ── toggle Louer/Acheter → accent du site
│
ngrok ── https://www.toit-ka.com (port local 8097)Pourquoi zéro scraping ?
Lou-Ka et Immo-Ka font déjà le travail dur (des centaines de connecteurs,
files de quarantaine, audits d'images, dédup). Toit-Ka hérite de ce
travail : toitka/replicate.py tire, toutes les 30 minutes, un snapshot
cohérent de chaque BD de prod (sqlite3 .backup côté source puis rsync via
le LAN du cluster), en lecture seule — aucune écriture, aucun verrou,
aucune charge sur les sites de prod. Le pipeline est idempotent : chaque passe
reconstruit l'état publié et gère le cycle de vie (nouvelles annonces,
retraits → 410).
Le moteur de normalisation des villes (toitka/villes.py)
Les deux corpus totalisaient ~3 300 chaînes de villes brutes distinctes
pour environ 1 000 municipalités réelles. Des règles déterministes plus une
passe d'élection par regroupement de slugs corrigent, entre autres :
parenthèses orphelines (Brossard )), parenthèses jamais fermées
(Gatineau (Hôpital), noms dupliqués (Gatineau (Gatineau)), arrondissements
collés dans les deux sens (Montréal (Ville-Marie) vs Ville-Marie (…)),
municipalités fusionnées (Saint-Hubert (Longueuil)), variantes anglaises
(Québec City, Montreal Downtown, City Of Montréal), accents/traits
d'union perdus (Trois Rivieres, Mont Tremblant), mojibake (Montrã©al) et
libellés marketing (Longueuil / South Shore). Le mapping complet
brut → canonique est persisté dans la table city_map pour audit
(python3 run.py villes).
La typologie unifiée (toitka/typologie.py)
Le locatif parle en « pièces et demie » (3½, 4½…), la vente en catégories
(condo, maison, plex, terrain…). typologie.py fond les deux vocabulaires en
un seul référentiel de types, utilisé par les filtres, les stats et les pages
SEO par type — pour que « 4½ à Montréal » et « condo à Québec » vivent dans le
même moteur de recherche.
API & routes
API JSON (FastAPI — toitka/web.py)
| Route | Description |
|---|---|
GET /api/listings |
recherche paginée (tx, ville, prix, type, chambres, sdb, superficie, animaux, meublé, texte…) |
GET /api/listings/{uid} |
détail d'une annonce (uid préfixé lou: / immo: selon l'origine) |
GET /api/listings.geojson |
flux GeoJSON pour la carte (bbox, filtres) |
GET /api/facets |
facettes live (villes, types, paliers de prix) selon l'univers |
GET /api/stats |
statistiques agrégées par univers + journal des passes ETL |
GET /api/health |
santé (ok, nb d'annonces, timestamp du dernier ETL) |
GET /api/seo/resolve |
résolution slug → entité pour le routage SPA |
GET /api/auth/* |
SSO KA ID (JWT HS256, stdlib uniquement) |
GET/POST/DELETE /api/favorites/* |
favoris centralisés « Mon univers Ka » |
Routes SEO rendues côté serveur (toitka/seo.py)
| Route | Contenu |
|---|---|
/louer/{ville} · /acheter/{ville} |
page ville par univers (moyennes, médianes, annonces, JSON-LD) |
/{tx}/{ville}/{type} |
page ville × type |
/{tx}/type/{type} |
page type provinciale |
/annonce/{uid}/{slug} |
fiche annonce (canonique 301 si slug obsolète, 410 si retirée) |
/robots.txt · /sitemap.xml · /sitemaps/{name} |
indexation programmatique |
Les autres chemins servent la SPA React (catch-all /{full_path:path}).
Structure du dépôt
run.py CLI unique : serve | etl | watch | replicate | villes
requirements.txt FastAPI + uvicorn (volontairement minimal)
toitka/
db.py schéma unifié (toitka.db) + connexions sources lecture seule
villes.py moteur de canonicalisation des villes (~3 300 variantes → city_map)
typologie.py vocabulaire de types unifié (locatif + vente)
etl.py répliques -> toitka.db (règles de visibilité, cycle de vie)
replicate.py tirage LAN des snapshots de prod (sqlite .backup + rsync)
web.py API JSON FastAPI + service de la SPA
seo.py rendu SEO côté serveur, robots.txt, sitemaps
auth.py SSO KA ID (JWT HS256, stdlib uniquement)
hubprofile.py profil membre lu depuis le hub Groupe KA
hubfav.py favorites.py favoris centralisés (« Mon univers Ka »)
frontend/ SPA React 18 + Vite + TypeScript (thème bi-accent)
src/pages/ Home, Category, Listing, Stats, Profil, Legal
src/components/ ListingCard, MapView, PropertyMap, Icons
src/kamaps/ adaptateur Ka Maps (Mapbox GL 3D)
docs/screenshots/ visite/ (10 JPG 2026-08-28) + mobile/ + desktop/ (webp 2026-08-25)
data/ (ignoré par git) réplicas + toitka.db — plusieurs GoLe frontend dépend de
@groupe-ka/ka-mapsenfile:../../ka-maps: le framework cartographique partagé du Groupe Ka doit être présent à côté du repo (~/apps/ka-mapssur le nœud) pournpm install.
Lancer en local
# 1. Config : .env pointe l'ETL vers des copies locales des BD sources
# (jamais commité — voir .gitignore)
# 2. Construire la base unifiée
python3 run.py etl # répliques -> data/toitka.db
# ou en continu :
python3 run.py watch 30 # replicate (si configuré) + etl toutes les 30 min
# 3. Backend + SPA
python3 run.py serve 8097 # http://localhost:8097
cd frontend && npm install && npm run build # la SPA est servie par le backendProduction (cluster MacLustr)
| Élément | Valeur |
|---|---|
| Nœud | M3U96a — ~/apps/toit-ka |
| Port | 8097 |
| PM2 | toit-ka-web (uvicorn :8097) · toit-ka-etl (run.py watch — replicate + ETL toutes les 30 min) · toit-ka-ngrok |
| Tunnel | ngrok http --url=www.toit-ka.com 8097 |
| Santé | GET /api/health → {"ok": true, "listings": …, "last_etl": …} |
| Sources | lou-ka (M3U96b) et immo-ka (M4M64a), snapshots lecture seule via LAN |
| Dépôt | spbgit (git perso du cluster) — ~/srv/git/toit-ka.git sur M3U96a |
Développement remote-first comme toutes les apps Ka : la source de vérité est le repo git sur le nœud, pas une copie laptop.
Écosystème Groupe Ka
Toit-Ka est une application Groupe-Ka, aux côtés de Lou-Ka (location), Immo-Ka (vente), Vrai-Prix, Auto-Ka, Food-Ka, Resto-Ka, Sorti-Ka, Job-Ka, Trouve-Ka, Créa-Ka, Fabri-Ka et des déclinaisons canadiennes (House-Ka, Rent-Ka). Identité partagée KA ID, favoris centralisés, design system ka-ui commun.
Auteur
Simon-Pierre Boucher — contact@spboucher.ai
Les données d'annonces appartiennent à leurs sources respectives ; chaque fiche renvoie vers l'annonce originale.







