Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
1# -----------------------------------------------------------------------------2# Lou-Ka — Agrégateur de logements à louer (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# connectors/vi_laval.py : connecteur Vï Condos Locatifs (vicondoslocatifs.com,5# Danam Lacourse) — condos locatifs tout inclus de la Rive-Nord : Terrebonne6# (225, carré Denise-Pelletier, Urbanova) et Mascouche (1505, place de7# l'Esplanade). NB : slug historique « vi_laval » de la campagne Laval/8# Rive-Nord, mais les projets sont à TERREBONNE et MASCOUCHE (Lanaudière).9# Les pages projet embarquent le widget Planpoint (app.planpoint.io) : on10# rejoue l'API JSON publique /api/projects/find (voir _planpoint.py).11# Particularités Vï chez Planpoint : disponibilité en FRANÇAIS12# (« Disponible ») à Terrebonne, chambres en « 1cc/2cc/3cc » ou13# « n chambre(s) (+ DEN) », champ type = numéro de modèle.14# Granularité UNITÉ : prix (Terrebonne), pi², étage, plans par unité.15# -----------------------------------------------------------------------------16from __future__ import annotations1718import re1920from ..schema import Listing21from ._planpoint import planpoint_projects, unit_listing22from .base import BaseConnector2324# (namespace, hostName, url de la page publique, adresse, ville)25PROJECTS = [26 ("vi-condos-locatifs", "vi-condos-locatifs",27 "https://www.vicondoslocatifs.com/vi-terrebonne/",28 "225 carré Denise-Pelletier", "Terrebonne"),29 ("danamlacourse", "Place-de-l-Esplanade",30 "https://www.vicondoslocatifs.com/vi-mascouche/",31 "1505 place de l'Esplanade", "Mascouche"),32]3334_CC_RE = re.compile(r"^\s*(\d+)\s*cc\s*(\+\s*DEN)?\s*$", re.I)353637class ViLavalConnector(BaseConnector):38 source_id = "vi_laval"39 request_delay = 0.84041 def fetch(self) -> list[Listing]:42 listings: list[Listing] = []43 for namespace, host, page_url, address, city in PROJECTS:44 try:45 projects = planpoint_projects(self, "projects", namespace, host)46 except Exception:47 continue # un projet cassé ne bloque pas48 for project in projects:49 # adresse Planpoint incomplète (« Terrebonne »/None) :50 # on impose l'adresse civique publiée sur le site public51 project["address"] = f"{address}, {city}"52 for floor in project.get("floors") or []:53 for unit in floor.get("units") or []:54 self._normalize(unit)55 lst = unit_listing(self.source_id, project, floor,56 unit, page_url=page_url,57 default_city=city)58 if lst is not None:59 lst.city = city60 listings.append(lst)61 return listings6263 @staticmethod64 def _normalize(unit: dict) -> None:65 """Adapte les champs Vï au patron générique de _planpoint.py."""66 # « Disponible » (fr) -> « Available » ; « Sold » = loué, exclu67 if (unit.get("availability") or "").strip().lower() == "disponible":68 unit["availability"] = "Available"69 # chambres « 1cc/2cc/3cc » -> « n chambres » (normalize_unit_type70 # sait ensuite dériver 3½/4½/5½)71 beds = str(unit.get("bedrooms") or "").strip()72 m = _CC_RE.match(beds)73 if m:74 n = int(m.group(1))75 unit["bedrooms"] = (f"{n} chambre" + ("s" if n > 1 else "")76 + (" + DEN" if m.group(2) else ""))77 # le champ type est un numéro de modèle (« 38 ») : déplacé pour que78 # unit_listing dérive le type d'unité des chambres, pas du modèle79 model = str(unit.get("type") or "").strip()80 if model and not re.search(r"[½/]", model):81 unit["type"] = ""82 unit.setdefault("_model", model)83