spb/toit-ka Public
Toit-Ka — louer ou acheter un toit au Québec, un seul endroit (fusion Lou-Ka × Immo-Ka) — www.toit-ka.com
Python 40.2%
TypeScript 39%
CSS 20.2%
HTML 0.7%
1<!--2 Author: Simon-Pierre Boucher3 Contact: contact@spboucher.ai4 Project: Toit-Ka5-->67# Toit-Ka — rent or buy a roof in Québec, one place891011121314151617-4264fb?style=flat-square)18-blue?style=flat-square)1920**Toit-Ka** ([www.toit-ka.com](https://www.toit-ka.com)) is the merger of two21Québec real-estate aggregators into a single platform where users search **for22rent OR for sale** in one place:2324- **[Lou-Ka](https://www.lou-ka.com)** — ~23,000 rental listings aggregated from25 200+ property managers, brokers and platforms;26- **[Immo-Ka](https://www.immo-ka.com)** — ~63,000 for-sale properties aggregated27 from 29 networks (RE/MAX, Via Capitale, Sutton, Proprio Direct, Centris feeds…).2829Toit-Ka **does not scrape anything itself**: it consumes read-only replicas of30both production databases and unifies them through a normalization ETL. The two31original sites keep running untouched. Every listing card links back to the32original ad at the source.3334## Architecture3536```37M3U96b (prod) M4M64a (prod)38 lou-ka louka.db immo-ka immoka.db39 │ sqlite3 .backup + rsync (LAN) │ ← read-only, every 30 min40 ▼ ▼41 M3U96a ~/apps/toit-ka42 data/replicas/{louka,immoka}.db43 │ ETL (toitka/etl.py)44 │ · inherited visibility rules (Centris dedup, plausible prices)45 │ · canonical city mapping (toitka/villes.py — 3,300+ raw variants)46 │ · unified type vocabulary + HTML-entity repair47 ▼48 data/toitka.db — one `listings` table, `transaction_type ∈ {louer, acheter}`49 │50 FastAPI (toitka/web.py) ── JSON API + server-side SEO rendering (seo.py)51 │52 React 18 SPA (frontend/) ── Louer/Acheter toggle drives the site accent53 │54 ngrok ── https://www.toit-ka.com55```5657### The city-normalization engine (`toitka/villes.py`)5859The two corpora carried **~3,300 distinct raw city strings** for roughly 1,00060real municipalities. Deterministic rules + a slug-grouping election pass fix,61among others: orphan parentheses (`Brossard )`), never-closed parentheses62(`Gatineau (Hôpital`), duplicated names (`Gatineau (Gatineau)`), boroughs63glued in both orders (`Montréal (Ville-Marie)` vs `Ville-Marie (…)`), merged64municipalities (`Saint-Hubert (Longueuil)`), English variants (`Québec City`,65`Montreal Downtown`, `City Of Montréal`), lost accents/hyphens66(`Trois Rivieres`, `Mont Tremblant`), mojibake (`Montrã©al`) and marketing67labels (`Longueuil / South Shore`). The full raw→canonical mapping is persisted68in the `city_map` table for auditing (`python3 run.py villes`).6970### Dual-accent design system7172Single "sharp editorial" design system (Space Grotesk / Inter / JetBrains Mono,73ink borders, offset shadows) with an accent that **follows the universe**:74Lou-Ka electric lime in *Louer* mode, Immo-Ka red in *Acheter* mode, terracotta75for the Toit-Ka brand itself. The switch is one `html[data-mode]` attribute.7677## Features7879- Home page with a central **Louer / Acheter toggle** — filters, price steps,80 quick chips and stats all adapt to the selected universe.81- Search by city (canonical), price, type (3½…6½+, Studio, Condo, Maison,82 Plex, Terrain…), bedrooms/bathrooms, area, pets, furnished, free text.83- List + **3D map view** (Ka Maps / Mapbox GL) with per-universe markers84 (lime rent pills, ink sale pills), "search this area", React previews.85- Full listing page: gallery + lightbox, specs, description, walkability rings86 mini-map, link to the original ad.87- **Programmatic SEO** rendered server-side: `/louer/{ville}`,88 `/acheter/{ville}`, `/{tx}/{ville}/{type}`, `/{tx}/type/{type}` with average89 and median rent/price, breadcrumbs, JSON-LD, sitemaps, 301 canonical slugs,90 410 for withdrawn listings.91- **KA ID single sign-on** (groupe-ka.com identity hub) + favorites stored92 centrally in "Mon univers Ka".93- Mobile-first, French (Québec).9495## Repository layout9697```98run.py CLI: serve | etl | watch | replicate | villes99toitka/100 db.py unified schema (toitka.db) + read-only source connections101 villes.py canonical city mapping engine102 typologie.py unified type vocabulary (rent + sale)103 etl.py replicas -> toitka.db (visibility rules, lifecycle)104 replicate.py LAN pull of production snapshots (sqlite .backup + rsync)105 web.py FastAPI JSON API + SPA serving106 seo.py server-side SEO rendering, robots.txt, sitemaps107 auth.py KA ID SSO (JWT HS256, stdlib only)108 hubprofile.py member profile read from the Groupe KA hub109 hubfav.py favorites.py central favorites ("Mon univers Ka")110frontend/ React 18 + Vite + TypeScript SPA (dual-accent theme)111```112113## Running locally114115```bash116# point the ETL at local copies of the source DBs (.env)117python3 run.py etl # build data/toitka.db118python3 run.py serve 8097 # http://localhost:8097119cd frontend && npm install && npm run build # SPA served by the backend120```121122## Production (MacLustr cluster)123124- Node **M3U96a**, `~/apps/toit-ka`, PM2: `toit-ka-web` (port 8097),125 `toit-ka-etl` (replicate + ETL every 30 min), `toit-ka-ngrok`126 (`ngrok http --url=www.toit-ka.com 8097`).127- Health: `GET /api/health`.128129## Author130131**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai)132133Toit-Ka is a **Groupe-Ka** application ([groupe-ka.com](https://www.groupe-ka.com)).134Listing data belongs to its respective sources; every card links to the135original ad.136