SPB Git forge

spb/house-ka

Public
18commits 1branches 0releases
1.9 MBsize
maindefault branch
20 days agolast push
Python 67% TypeScript 18.2% CSS 14.4%
4.3 KB · 95 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Immo-Ka — Agrégateur de maisons à vendre (province de Québec)3# Auteur : Simon-Pierre Boucher — contact@spboucher.ai4# mortgage/providers/eq_bank.py : Banque EQ / Équitable — dictionnaire JSON5#   global embarqué dans le payload Next.js de la page des taux (marqueur6#   \"rates\":{ échappé). Clés CMS stables ; les clés « Adjustable » sont des7#   ÉCARTS vs prime (jamais interprétées comme des taux).8# -----------------------------------------------------------------------------9from __future__ import annotations1011import json1213from .base import RateProvider1415# clé CMS -> (rate_type, terme mois, kind, nom, clé APR éventuelle)16KEY_MAP: dict[str, tuple] = {17    "mortgage-rate-fixed-5-year": ("fixed", 60, "special",18                                   "Fixe 5 ans (vitrine EQ)",19                                   "mortgage-rate-fixed-5-year-APR"),20    "mortgage-rate-variable-5-year": ("variable", 60, "special",21                                      "Variable 5 ans (vitrine EQ)",22                                      "mortgage-rate-variable-5-year-APR"),23    "es-fixed-12-month": ("fixed", 12, "special", "Evolution Suite — fixe 1 an", None),24    "es-fixed-24-month": ("fixed", 24, "special", "Evolution Suite — fixe 2 ans", None),25    "es-fixed-36-month": ("fixed", 36, "special", "Evolution Suite — fixe 3 ans", None),26    "es-fixed-48-month": ("fixed", 48, "special", "Evolution Suite — fixe 4 ans", None),27    "es-fixed-60-month": ("fixed", 60, "special", "Evolution Suite — fixe 5 ans", None),28    "Standard-Mortgage-Rate-1-Year-Fixed": ("fixed", 12, "posted", "Fixe affiché 1 an", None),29    "Standard-Mortgage-Rate-2-Year-Fixed": ("fixed", 24, "posted", "Fixe affiché 2 ans", None),30    "Standard-Mortgage-Rate-3-Year-Fixed": ("fixed", 36, "posted", "Fixe affiché 3 ans", None),31    "Standard-Mortgage-Rate-4-Year-Fixed": ("fixed", 48, "posted", "Fixe affiché 4 ans", None),32    "Standard-Mortgage-Rate-5-Year-Fixed": ("fixed", 60, "posted", "Fixe affiché 5 ans", None),33}34PRIME_KEY = "equitable-prime-rate"353637def extract_rates_blob(html: str) -> dict:38    """Isole le dictionnaire {clé: {name, rate}} du payload Next.js.39    Le JSON est échappé dans le HTML (\\\" -> \")."""40    marker = '\\"rates\\":{'41    i = html.find(marker)42    if i < 0:43        marker = '"rates":{'44        i = html.find(marker)45        if i < 0:46            raise ValueError("marqueur rates introuvable (structure changée)")47    seg = html[i:i + 400_000].replace('\\"', '"')48    start = seg.find("{")49    depth = 050    for j, ch in enumerate(seg[start:], start):51        if ch == "{":52            depth += 153        elif ch == "}":54            depth -= 155            if depth == 0:56                return json.loads(seg[start:j + 1])57    raise ValueError("JSON rates non équilibré (structure changée)")585960class EqBankProvider(RateProvider):61    provider_id = "eq_bank"62    institution = "Banque EQ"63    source_url = "https://www.eqbank.ca/residential/mortgage-rates"64    request_delay = 1.06566    def fetch(self) -> list[dict]:67        return self.parse(self.get(self.source_url).text)6869    def parse(self, payload: str) -> list[dict]:70        rates = extract_rates_blob(payload)7172        def val(key: str) -> float | None:73            cell = rates.get(key)74            if isinstance(cell, dict) and isinstance(cell.get("rate"), (int, float)):75                return float(cell["rate"])76            return None7778        out: list[dict] = []79        for key, (rtype, term, kind, name, apr_key) in KEY_MAP.items():80            rate = val(key)81            if rate is None:82                continue  # clé absente : produit omis, jamais deviné83            apr = val(apr_key) if apr_key else None84            out.append(self.make_product(85                rate=rate, rate_type=rtype, term_months=term, kind=kind,86                product_name=name, apr=apr,87                raw={"key": key, "rate": rate, "apr": apr}))88        prime = val(PRIME_KEY)89        if prime is not None:90            out.append(self.make_product(91                rate=prime, rate_type="other", term_months=12, kind="posted",92                product_name="Taux préférentiel Banque Équitable",93                purpose="unknown", raw={"key": PRIME_KEY, "rate": prime}))94        return out95