SPB Git forge

spb/house-ka

Public
18commits 1branches 0releases
1.9 MBsize
maindefault branch
19 days agolast push
Python 67% TypeScript 18.2% CSS 14.4%
7.7 KB

# Moteur hypothécaire Immo-Ka (Mortgage Intelligence Engine)

Moteur natif de collecte, d'historisation et de calcul des taux hypothécaires canadiens, intégré au backend FastAPI d'Immo-Ka. Aucun taux n'est jamais inventé, codé en dur ni estimé : tout taux affiché provient d'une page officielle d'une institution financière, avec provenance (URL source) et fraîcheur (horodatage de collecte).

# Architecture

text
immoka/mortgage/
├── providers/          # 1 connecteur indépendant par institution (12)
│   ├── base.py         #   RateProvider : fetch() → produits normalisés
│   └── README.md       #   comment ajouter une banque
├── validate.py         # garde-fous anti-aberration (419 % ≠ 4,19 %…)
├── store.py            # SQLite annexe data/mortgage.db — historisation
├── scheduler.py        # orchestration : retries, backoff, santé, isolation
├── calc.py             # mathématiques hypothécaires canadiennes
├── cmhc.py             # assurance prêt (SCHL) + taxe de vente QC
└── api.py              # routes /api/mortgage/* (montées dans web.py)

Le flux : provider.fetch() → validate_batch() → store.record_observations(). Les providers ne touchent jamais la base ; le calculateur ne touche jamais les scrapers — seule l'API interne les relie.

# Institutions couvertes (12 connecteurs)

slug Institution Type de source
bank_of_canada Banque du Canada Valet API (JSON officiel) — taux de référence
bmo BMO JSON embarqué
cibc CIBC JSON
desjardins Desjardins HTML
eq_bank Banque EQ HTML
first_national First National HTML
mcap MCAP HTML (taux préférentiel)
national_bank Banque Nationale HTML
rbc RBC JSON
scotiabank Banque Scotia JSON (posted + promos)
tangerine Tangerine JSON
td TD Canada Trust JSON

Chaque produit est normalisé par RateProvider.make_product() : provider, institution, product_name, rate_type (fixed|variable|other), term_months, kind (posted|special), rate, apr, insured_status (insured|insurable|uninsured|unknown), purpose (purchase|renewal|refinance|unknown), amortization_max_years, conditions, source_url, confidence, raw.

Les taux préférentiels/prime sont stockés en rate_type="other" + purpose="unknown" : ils ne peuvent jamais contaminer un classement « meilleur taux d'achat ».

# Validation (validate.py)

Rejette avant enregistrement :

  • taux hors bornes plausibles (0,5 %–24 %) — attrape 4.19 → 419 ;
  • champs requis manquants, enums invalides, termes hors 3–120 mois ;
  • APR incohérent (APR < taux − 0,02 pt) ou aberrant ;
  • doublons exacts dans un même lot (silencieusement dédupliqués).

Un lot partiellement invalide n'est pas jeté : les produits sains sont enregistrés, les problèmes journalisés.

# Historisation (store.py — data/mortgage.db, WAL)

  • rate_observations : périodes de validité (valid_from/valid_to, is_current). Taux inchangé → simple mise à jour de last_checked ; taux changé → clôture de la période + nouvelle ligne. Un saut > 2,5 pts en < 48 h est rejeté sans écraser la donnée existante (garde anti-aberration au niveau BD).
  • provider_runs : journal de chaque collecte (statut, durée, produits, changements, rejets) → santé OK / WARNING (> 24 h) / ERROR.
  • product_key : sha1 tronqué de provider|rate_type|term|kind|insured|purpose|name — identité stable d'un produit à travers le temps.

En cas de panne d'une source, les derniers taux valides restent servis, avec leur âge affiché (mention « stale » au-delà de 24 h).

# Collecte (scheduler.py)

  • run_provider(slug) : retries (défaut 3) avec backoff exponentiel ; une exception d'un provider n'affecte jamais les autres.
  • run() : séquentiel et poli (request_delay par provider — jamais de martèlement des sites bancaires).
  • watch(min) : boucle autonome ; maybe_run() est appelé depuis la boucle d'ingestion existante (process PM2 immo-ka-sync) et ne collecte que si la dernière passe date de plus de IMMOKA_MORTGAGE_INTERVAL_MIN minutes (défaut 180).

CLI :

bash
python run.py mortgage-sync [slug…]   # collecte (toutes ou certaines banques)
python run.py mortgage-watch [min]    # boucle autonome
python run.py mortgage-status         # santé des providers

Variables d'environnement (voir .env.example) : IMMOKA_MORTGAGE_INTERVAL_MIN, IMMOKA_MORTGAGE_RETRIES, IMMOKA_MORTGAGE_BACKOFF, SCRAPFLY_KEY (anti-bot, dernier recours).

# Calculateur canadien (calc.py + cmhc.py)

  • Composition semestrielle pour les taux fixes (norme légale canadienne) : taux périodique = (1 + r/2)^(2/f) − 1. Valeur étalon vérifiée par test : 100 000 $ à 6 % sur 25 ans = 639,81 $/mois (≠ 644,30 $ en composition mensuelle américaine — testé aussi, pour prouver qu'on n'utilise pas la mauvaise formule). Taux variables : composition mensuelle.
  • 6 fréquences : mensuelle, bimensuelle, aux 2 semaines, hebdomadaire, accélérée aux 2 semaines (mensualité ÷ 2), accélérée hebdo (÷ 4).
  • Test de résistance fédéral : qualification à max(taux + 2, 5,25 %).
  • SCHL (cmhc.py) : mise de fonds légale minimale (5 % / 10 % / 20 %), primes par tranche RPV (0,60 % → 4,00 %), surprime +0,20 % amortissement 30 ans (premier acheteur), plafond assurable 1,5 M$, TVQ 9,975 % sur la prime payable comptant (spécificité québécoise) — la prime s'ajoute au prêt, la taxe non.
  • Tableau d'amortissement, résumé de terme (solde au renouvellement), scénarios de renouvellement (+0/+1/+2/+3 pts), ratios ABD/ATD informatifs, inverses (prêt max pour un versement, taux requis).

# API interne (/api/mortgage/*)

Route Rôle
GET /rates taux courants filtrables (type, terme, kind, provider…)
GET /rates/best meilleur taux comparable + classement par institution
GET /rates/history périodes de validité (historique réel, jamais extrapolé)
GET /providers santé des sources (OK/WARNING/ERROR, âge, produits)
GET /market vue marché (meilleur/médiane/variations 7-30 j)
GET /intelligence market + taux préférentiels (page /taux-hypothecaires)
POST /calculate calcul complet (SCHL, stress, terme, renouvellement…)
POST /affordability capacité d'emprunt (ABD/ATD + stress test)

Règle absolue : jamais de comparaison de produits incomparables — affiché vs offre spéciale, assuré vs non assuré — sans l'indiquer. Le comparateur ne garde qu'un produit comparable par institution (l'offre spéciale prime).

# Frontend

  • Fiche propriété (Financement.tsx, section « Financer cette propriété », ventes seulement) : prix prérempli, mise de fonds $/% synchronisée, versement + taux utilisé avec provenance/fraîcheur, SCHL détaillée, coût réel mensuel (+ taxes municipales/scolaires de la fiche), stress test, renouvellement, comparateur banques, historique SVG, amortissement.
  • Page /taux-hypothecaires (Taux.tsx) : vue marché cliquable, comparateur par institution (nature + fraîcheur + source officielle), historique, santé des sources. Référencée (seo.py + sitemap).

# Tests

bash
PYTHONPATH=. .venv/bin/python -P -m unittest discover -s tests

61 tests : test_mortgage_calc.py (valeurs étalons, fréquences accélérées, inverses, stress), test_mortgage_cmhc.py (primes, TVQ, éligibilité), test_mortgage_validate.py (anti-aberration), test_mortgage_store.py (historisation, garde 2,5 pts, meilleur taux), test_mortgage_providers.py (chaque parseur sur fixtures HTML/JSON committées dans tests/fixtures/mortgage/ — aucun réseau).