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/humano.py : connecteur Humano District (humanodistrict.ca)5# Appartements locatifs au cœur de Sherbrooke — 2 bâtiments (Galt, 1820 rue6# Galt Ouest, et Maison Générale) sur l'ancien campus des sœurs. Le site WP7# embarque le widget SmartCondo Plans (silo.immo) : l'API JSON8# /v2/building/get-project-data?project=humano-district renvoie toutes les9# unités (prix, superficie, chambres, sdb, étage, disponibilité 1=dispo /10# 2=loué / 3=réservé + availability_date, plan PDF, photo). Granularité :11# unité.12# -----------------------------------------------------------------------------13from __future__ import annotations1415import re16import unicodedata1718from .base import BaseConnector19from ..schema import Listing2021SITE = "https://humanodistrict.ca/appartements/"22API = ("https://smartcondoplans.silo.immo/v2/building/get-project-data"23 "?project=humano-district&language=fr")24CDN = "https://smartcondoplans.silo.immo/"2526CITY = "Sherbrooke"27ADDRESSES = {28 "galt": "1820, rue Galt Ouest, Sherbrooke",29 "maison-generale": "rue Galt Ouest, Sherbrooke",30}3132TYPE_BY_ROOMS = {0: "Studio", 1: "3½", 2: "4½", 3: "5½"}333435def _slug(s: str) -> str:36 s = unicodedata.normalize("NFKD", str(s)).encode("ascii", "ignore").decode()37 return re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", s.lower())).strip("-")383940class HumanoConnector(BaseConnector):41 source_id = "humano"42 request_delay = 0.64344 def fetch(self) -> list[Listing]:45 listings: list[Listing] = []46 try:47 data = self.get(API).json()48 except Exception:49 return listings5051 # url de bâtiment par id (les unités référencent building via floors)52 bld_by_id = {b.get("id"): b for b in data.get("buildings", [])}5354 for u in data.get("project_units", []):55 try:56 if u.get("hide") or u.get("isnonunit") or u.get("not_unit"):57 continue58 avail = u.get("availability")59 date = (u.get("availability_date") or "").strip()60 if avail == 1:61 availability = (f"Disponible le {date}" if date62 else "Disponible")63 elif avail == 2 and date:64 availability = f"Disponible le {date}" # loué → se libère65 else:66 continue # loué sans date / réservé6768 name = str(u.get("name") or "").strip()69 if not name:70 continue71 bname = (u.get("building_name") or "").strip()72 burl = _slug(bname) if bname else "x"73 # retrouver l'url canonique du bâtiment si connue74 for b in bld_by_id.values():75 if b.get("name") == bname and b.get("url"):76 burl = b["url"]77 break7879 rooms = u.get("room")80 bathrooms = u.get("bathroom")81 unit_type = TYPE_BY_ROOMS.get(rooms, "")82 price = float(u["price"]) if u.get("price") else None83 area = float(u["area"]) if u.get("area") else None8485 furnished = bool(re.search(r"meubl", name, re.I))86 unit_no = re.sub(r"\s*-\s*Meubl[ée]s?$", "", name, flags=re.I)8788 details: dict = {}89 if u.get("floor_name"):90 details["floor"] = u["floor_name"]91 if bname:92 details["building"] = bname93 if furnished:94 details["furnished"] = True95 if u.get("file"):96 details["plan_pdf"] = CDN + u["file"].lstrip("/")9798 images = []99 if u.get("image"):100 images.append(CDN + u["image"].lstrip("/"))101102 listings.append(Listing(103 source=self.source_id,104 external_id=f"{burl}-{_slug(name)}",105 url=SITE,106 title=f"Unité {unit_no}"107 + (f" ({unit_type})" if unit_type else "")108 + (f" — {bname}, Humano District" if bname109 else " — Humano District"),110 address=ADDRESSES.get(burl, "rue Galt Ouest, Sherbrooke"),111 city=CITY,112 unit_type=unit_type,113 bedrooms=rooms if isinstance(rooms, int) else None,114 bathrooms=bathrooms if isinstance(bathrooms, int) else None,115 price=price,116 availability=availability,117 area_sqft=area,118 details=details,119 images=images,120 ))121 except Exception:122 continue123 return listings124