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

# Toit-Ka — rent or buy a roof in Québec, one place

Listings For rent For sale Sources Avg rent Avg price Backend Frontend Maps Lang

Toit-Ka (www.toit-ka.com) is the merger of two Québec real-estate aggregators into a single platform where users search for rent OR for sale in one place:

  • Lou-Ka — ~23,000 rental listings aggregated from 200+ property managers, brokers and platforms;
  • Immo-Ka — ~63,000 for-sale properties aggregated from 29 networks (RE/MAX, Via Capitale, Sutton, Proprio Direct, Centris feeds…).

Toit-Ka does not scrape anything itself: it consumes read-only replicas of both production databases and unifies them through a normalization ETL. The two original sites keep running untouched. Every listing card links back to the original ad at the source.

# Architecture

text
M3U96b (prod)                M4M64a (prod)
  lou-ka  louka.db             immo-ka  immoka.db
      │ sqlite3 .backup + rsync (LAN) │        ← read-only, every 30 min
      ▼                               ▼
              M3U96a  ~/apps/toit-ka
  data/replicas/{louka,immoka}.db
      │  ETL (toitka/etl.py)
      │    · inherited visibility rules (Centris dedup, plausible prices)
      │    · canonical city mapping (toitka/villes.py — 3,300+ raw variants)
      │    · unified type vocabulary + HTML-entity repair

  data/toitka.db  — one `listings` table, `transaction_type ∈ {louer, acheter}`

  FastAPI (toitka/web.py)  ── JSON API + server-side SEO rendering (seo.py)

  React 18 SPA (frontend/) ── Louer/Acheter toggle drives the site accent

  ngrok ── https://www.toit-ka.com

# The city-normalization engine (toitka/villes.py)

The two corpora carried ~3,300 distinct raw city strings for roughly 1,000 real municipalities. Deterministic rules + a slug-grouping election pass fix, among others: orphan parentheses (Brossard )), never-closed parentheses (Gatineau (Hôpital), duplicated names (Gatineau (Gatineau)), boroughs glued in both orders (Montréal (Ville-Marie) vs Ville-Marie (…)), merged municipalities (Saint-Hubert (Longueuil)), English variants (Québec City, Montreal Downtown, City Of Montréal), lost accents/hyphens (Trois Rivieres, Mont Tremblant), mojibake (Montrã©al) and marketing labels (Longueuil / South Shore). The full raw→canonical mapping is persisted in the city_map table for auditing (python3 run.py villes).

# Dual-accent design system

Single "sharp editorial" design system (Space Grotesk / Inter / JetBrains Mono, ink borders, offset shadows) with an accent that follows the universe: Lou-Ka electric lime in Louer mode, Immo-Ka red in Acheter mode, terracotta for the Toit-Ka brand itself. The switch is one html[data-mode] attribute.

# Features

  • Home page with a central Louer / Acheter toggle — filters, price steps, quick chips and stats all adapt to the selected universe.
  • Search by city (canonical), price, type (3½…6½+, Studio, Condo, Maison, Plex, Terrain…), bedrooms/bathrooms, area, pets, furnished, free text.
  • List + 3D map view (Ka Maps / Mapbox GL) with per-universe markers (lime rent pills, ink sale pills), "search this area", React previews.
  • Full listing page: gallery + lightbox, specs, description, walkability rings mini-map, link to the original ad.
  • Programmatic SEO rendered server-side: /louer/{ville}, /acheter/{ville}, /{tx}/{ville}/{type}, /{tx}/type/{type} with average and median rent/price, breadcrumbs, JSON-LD, sitemaps, 301 canonical slugs, 410 for withdrawn listings.
  • KA ID single sign-on (groupe-ka.com identity hub) + favorites stored centrally in "Mon univers Ka".
  • Mobile-first, French (Québec).

# Repository layout

text
run.py                  CLI: serve | etl | watch | replicate | villes
toitka/
  db.py                 unified schema (toitka.db) + read-only source connections
  villes.py             canonical city mapping engine
  typologie.py          unified type vocabulary (rent + sale)
  etl.py                replicas -> toitka.db (visibility rules, lifecycle)
  replicate.py          LAN pull of production snapshots (sqlite .backup + rsync)
  web.py                FastAPI JSON API + SPA serving
  seo.py                server-side SEO rendering, robots.txt, sitemaps
  auth.py               KA ID SSO (JWT HS256, stdlib only)
  hubprofile.py         member profile read from the Groupe KA hub
  hubfav.py favorites.py  central favorites ("Mon univers Ka")
frontend/               React 18 + Vite + TypeScript SPA (dual-accent theme)

# Running locally

bash
# point the ETL at local copies of the source DBs (.env)
python3 run.py etl        # build data/toitka.db
python3 run.py serve 8097 # http://localhost:8097
cd frontend && npm install && npm run build   # SPA served by the backend

# Production (MacLustr cluster)

  • Node M3U96a, ~/apps/toit-ka, PM2: toit-ka-web (port 8097), toit-ka-etl (replicate + ETL every 30 min), toit-ka-ngrok (ngrok http --url=www.toit-ka.com 8097).
  • Health: GET /api/health.

# Author

Simon-Pierre Bouchercontact@spboucher.ai

Toit-Ka is a Groupe-Ka application (groupe-ka.com). Listing data belongs to its respective sources; every card links to the original ad.