SPB Git

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%
6.4 KB · 136 lines markdown
Rendered Raw Blame History
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 place89![Listings](https://img.shields.io/badge/listings-85%20503-e07a3f?style=flat-square)10![For rent](https://img.shields.io/badge/for%20rent-22%20881-d9f26b?style=flat-square&labelColor=141814)11![For sale](https://img.shields.io/badge/for%20sale-62%20622-e23744?style=flat-square&labelColor=141814)12![Sources](https://img.shields.io/badge/sources-229%2B-181410?style=flat-square)13![Avg rent](https://img.shields.io/badge/avg%20rent-%241%20732%2Fmo-1c5c41?style=flat-square)14![Avg price](https://img.shields.io/badge/avg%20price-%24683%20945-b3202b?style=flat-square)15![Backend](https://img.shields.io/badge/backend-FastAPI%20%2B%20SQLite-009688?style=flat-square)16![Frontend](https://img.shields.io/badge/frontend-React%2018%20%2B%20Vite%20%2B%20TS-61dafb?style=flat-square&labelColor=20232a)17![Maps](https://img.shields.io/badge/maps-Ka%20Maps%20(Mapbox%20GL%203D)-4264fb?style=flat-square)18![Lang](https://img.shields.io/badge/lang-fran%C3%A7ais%20(QC)-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 repair4748  data/toitka.db  — one `listings` table, `transaction_type ∈ {louer, acheter}`4950  FastAPI (toitka/web.py)  ── JSON API + server-side SEO rendering (seo.py)5152  React 18 SPA (frontend/) ── Louer/Acheter toggle drives the site accent5354  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