SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
3.9 KB · 83 lines python
Raw Blame History
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