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
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 delast_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é deprovider|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_delaypar provider — jamais de martèlement des sites bancaires).watch(min): boucle autonome ;maybe_run()est appelé depuis la boucle d'ingestion existante (process PM2immo-ka-sync) et ne collecte que si la dernière passe date de plus deIMMOKA_MORTGAGE_INTERVAL_MINminutes (défaut 180).
CLI :
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 providersVariables 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
PYTHONPATH=. .venv/bin/python -P -m unittest discover -s tests61 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).