# SPEC — Transformation lou-ka → Rent-Ka (LOCKED DECISIONS) Rent-Ka = English rental-listings aggregator for ALL of Canada EXCEPT Québec (Ontario first — 5,861 ON listings already seeded in the node DB; national expansion via platform client registries comes right after launch). Sister sites: Lou-Ka (QC rentals), House-Ka (ROC homes for sale). ## Identity - Site name: **Rent-Ka** · wordmark **Rent·Ka** · domain **www.rent-ka.com** - `BASE_URL = "https://www.rent-ka.com"`, `SITE_NAME = "Rent-Ka"` - FastAPI: title "Rent-Ka API", description "Rental listings aggregator — Canada (outside Québec)" - Tagline (frontend + SEO): **"Apartments and homes for rent across Canada."** - UA: `RentKaBot/1.0 (+https://www.rent-ka.com/bot; contact@spboucher.ai)` (replaces every LouKaBot) - Language: en-CA everywhere (backend SEO strings, frontend copy, ``) - Python package: rename `louka/` → `rentka/` (all imports, `uvicorn.run("rentka.web:app", ...)`) - DB path: `data/rentka.db` (db.py DB_PATH) - Env prefix: `LOUKA_*` → `RENTKA_*` everywhere. `LOUKA_ONTARIO` gate is DELETED (not renamed). `LOUKA_BASE_URL` → `RENTKA_BASE_URL`. - kaid site id: `kaid.init("rent-ka")`; hubfav URL `https://www.rent-ka.com/listing/{uid}`; KA_CLIENT_ID default "rent-ka". - ecosystem.json (frontend/src/ka/ecosystem.json): ADD entry (backend web.py:940/983 looks it up — required): `{"id":"rent-ka","wordmark":"Rent·Ka","domain":"www.rent-ka.com","accent":"#2456e6","accentSoft":"#e9efff","accentDeep":"#1738a8","onAccent":"#ffffff","tagline":"Rentals across Canada"}` ## Routes (English — backend seo.py/web.py AND frontend App.tsx/KAID_CONF must match) - Listing page: **`/listing/{uid}`** (was `/logement/{uid}`) - Cities index: **`/cities`** (was `/villes`); city page: **`/city/{slug}`** and **`/city/{slug}/{type_slug}`** - Keep: `/stats`, `/sources`, `/contact`, `/bot`, `/g/{sourceId}` - `/confidentialite` → **`/privacy`**, `/conditions` → **`/terms`** - `/profil` → `/profile`, `/favoris` → `/favorites`, `/bienvenue` → `/welcome`, `/gestion` → `/manage`, `/passerelle/:uid` → `/gateway/:uid`, `/juste-valeur` → `/fair-value`, `/ka-scores` stays `/ka-scores` - DELETED routes/pages: `/court-terme*` (short-term subsystem), `/demenageurs` (movers, QC dataset) - web.py `_CLIENT_ROUTES = {"profile","favorites","manage","welcome","bot","contact","fair-value","ka-scores"}`, `_CLIENT_PREFIXES = ("u/","gateway/")` - Sitemaps: `sitemap-pages.xml`, `sitemap-cities.xml`, `sitemap-listings-{n}.xml` (drop sitemap-ct-*) - index.html KAID_CONF: `{card:"a[href^='/listing/']",detail:"^/listing/(.+)$",entity:"listing"}` ## Canonical unit types (English enum — replaces the QC "n½" system) `Studio | 1 bedroom | 2 bedrooms | 3 bedrooms | 4 bedrooms | 5+ bedrooms | Loft | Penthouse | Condo | Townhouse | House | Room` - normalize.normalize_unit_type: map French AND English input INTO this enum (reverse of today: `3½` → "1 bedroom", `4½` → "2 bedrooms", `n½` → `(n-2) bedrooms`, `6½+` → "4 bedrooms"; "bachelor" → Studio; "maison de ville|townhouse|mdv" → Townhouse; "chambre|room" → Room) - bedrooms_from_unit_type: Studio/Loft→0, Room→1, "n bedroom(s)"→n, "5+ bedrooms"→5, Penthouse/Condo/Townhouse/House→None - Slugs (seo.slugify): "1-bedroom", "2-bedrooms", "5-plus-bedrooms", "studio", "room", … (kill the `½ → -1-2` special case) - quality.py rule 5: `unit != "Room"` (was "Chambre") ## Geography / scope - Scope = Canada minus QC. quality.evaluate: DELETE `pause-ontario` rule; ADD `if d.get("province") == "QC": reasons.append("outside-scope-quebec")`. - schema.Listing default province: `"ON"` (connectors should set explicitly; platform registries gain an optional per-client `"province"` key, default "ON"). - schema.finalize + geocode: per-province bbox dict for all 12 (BC,AB,SK,MB,ON,NB,NS,PE,NL,YT,NT,NU), fallback = Canada bbox (41.6–83.2 N, −141.0…−52.5 E). - geocode.py: REMOVE all Adresses Québec (MERN) code, `_BBOX_QUEBEC`, `_VILLES_QUEBEC`; keep Nominatim (1 r/s) as primary; `run_batch()` becomes a loop over Nominatim `resolve()` (keep the batch CLI signature working). state= full province name from a PROVINCE_NAMES dict. Cache keys: prefix `"{prov.lower()}|"` for every province. - web.py `/api/stats`: replace the `_VILLES_GM` Montréal buckets with per-province counts: response gains `"provinces": {"ON": n, "BC": n, ...}` (keep "total"/existing top-level keys the frontend Ticker needs — frontend Ticker will read `provinces`). - web.py: expose `province` in listing JSON (list + detail + geojson props) and as a filter param `province=` on /api/listings & /api/search; add it to /api/facets. ## Features KEPT (translate UI strings to English where user-facing) core pipeline (schema/db/ingest/normalize/dedup/quality/seo/web), fairvalue, kascores + environment + poi (pure OSM, national), building ("Building passport"), recycled ("Re-listed units"), history, imgcheck, managers (+serpapi_client), marketstats, statsdash, statsfiche (prune deleted-layer panels), kapdf (shared, keep verbatim), hiver.py → keep, rename user-facing label to **"Winter Score"** (works Canada-wide), KA ID/accounts/auth/profile/hubfav/hubprofile (site id rent-ka), fixtures.py, textmine.py → KEEP and make BILINGUAL: keep French regexes, add English equivalents for the high-value facts (heat/hydro/electricity/water included, parking, pets, furnished, laundry, A/C, elevator, balcony, dishwasher, no smoking, lease length, available date). ## Features DELETED (backend module + web.py route + pdfgen section + statsextra block + frontend component) - hydro.py (/api/hydro, HydroEstimation.tsx) — Hydro-Québec - rdl.py (/api/rdl, RegistreLoyers.tsx) — QC rent registry - tal.py (/api/tal, HistoriqueTAL.tsx) — QC tribunal - gaz.py (/api/gaz, EssenceProche.tsx) — gazquebec - air.py (/api/air, QualiteAir.tsx) — RSQAQ (maybe AQHI later) - inondation.py (/api/inondation, RisqueInondation.tsx) — BDZI - quartier.py (quartier block in /api/listings/{uid}, QuartierBlock.tsx) — QC census DB (rebuild later) - commerces.py (/api/commerces, CommercesProches.tsx) + transit.db — QC banners/transit - coutreel.py (/api/listings/{uid}/cout-reel, CoutReel.tsx) — backbone was Hydro-Québec - demenageurs.py (+ /demenageurs SSR + Demenageurs.tsx + data/demenageurs.json) - shortterm/ ENTIRELY (+ data/sources_ct.json, data/hotels_engines.json, ct_web router, seo ctdb import + /court-terme SSR + sitemap-ct, frontend Ct*.tsx, CourtTerme*.tsx, ctapi.ts, kamaps/ctAdapter.ts) - MetroLignes.tsx usage in MapView.tsx + MapSearch.tsx (Montréal métro overlay) - ingest.watch: remove hydro.precompute and tal.precompute steps (keep the rest incl. quartier→REMOVE too since module deleted) ## Connectors - KEEP the 65 ON connectors: remove the `_ONTARIO = ...` line and every `disabled = not _ONTARIO` / `"disabled": not _ONTARIO` (connector simply enabled). Platform generators (rentcafe/appfolio/rentsyncgw/buildium/liftsystem) keep reading their data/*.json registries; add support for optional per-client `"province"` (default "ON"). - KEEP + WIDEN the 11 national connectors (akelius, boardwalk, capreit, centurion, cogir, hazelview, interrent, metcap, minto, morguard, realstar): invert the gate — ROC is the default, DELETE the QC branches/city lists entirely; cover every non-QC province the landlord operates in (their sites list cities — keep existing ON lists, extend with the connector's own city discovery when it has one; set province per city). - KEEP national portals (not gated): apartments_com, rentals_ca, zumper, kijiji, fb_marketplace + actors/. Change their default regions/cities env fallbacks from QC to ROC metros: Toronto, Ottawa, Mississauga, Hamilton, London, Kitchener-Waterloo, Windsor, Vancouver, Victoria, Surrey, Burnaby, Calgary, Edmonton, Winnipeg, Saskatoon, Regina, Halifax, Moncton, Fredericton, Charlottetown, St. John's. (env names now RENTKA_*) - DELETE all other (~300) QC-only connector modules AND their data/sources.json entries. sources.json keeps: the 65 ON entries (status back to "actif", note_pause removed), the national/portal entries, minus all QC ones. Update `_comment`. - KEEP helpers: base.py, _resilient.py, _detailutil.py; DELETE _livya.py/_planpoint.py if only QC connectors import them (check first). - tests/: keep test_{normalize,quality,search,fairvalue,kascores,textmine}.py UPDATED to the new enum/rules; delete test fixtures + recorded cassettes of deleted connectors (tests/fixtures is 842 MB — purge everything belonging to deleted sources). ## Frontend design (DIFFERENT from Lou-Ka orange and House-Ka pine/Fraunces) - Keep the shared KA token system (frontend/src/ka/tokens.css UNTOUCHED — shared asset). - styles.css overrides: accent = **cobalt** `--accent:#2456e6; --accent-soft:#e9efff; --accent-deep:#1738a8; --on-accent:#ffffff` - Display font: **"Bricolage Grotesque"** (Google Fonts, weights 400..800) replaces Space Grotesk in index.html + `--font-display` override in styles.css. Body stays Inter. - Header wordmark `Rent·Ka` (the ·Ka styled with accent), tagline "Apartments and homes for rent across Canada." - The frontend should feel like its OWN site, not a translated Lou-Ka: new Home hero (search + "Browse by province" section with the province counts from /api/stats.provinces, featured cities grid), reordered nav, English MobileTabBar (Search/Map/Saved/Profile). Keep the detail-page section ORDER standard (gallery → price/address → description → amenities → practical details → analyses → map → KA Scores → footer; DOM order = visual order on ALL breakpoints, NEVER `order:`/column-reverse — Groupe KA standard 2026-08-19). - Listing page keeps (in order): gallery, price+FairValueBadge, description, amenities, practical details, PriceAnalysis, HistoriqueLouka→"Listing history", ImmeubleBloc→"Building passport", GestionnaireBloc→"Property manager", recycled block, ListingMap3D, KaScoresBlock, HiverScore→"Winter Score". All QC blocks removed (list above). - Ticker: per-province counts (ON · BC · AB · …) from /api/stats.provinces. - Footer: keep KaFooter with siteId="rent-ka" (entry added to ecosystem.json). - ALL user-facing text in English (en-CA). Currency format `$1,495`. Dates en-CA. ## Deployment (for reference — done on node M4M36 afterwards) - pm2: rent-ka-web (`run.py serve 8125`), rent-ka-sync (`run.py watch 60`), rent-ka-ngrok (`ngrok http --url=www.rent-ka.com 8125`) - git origin gitsrv:srv/git/rent-ka.git