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 · 164 lines markdown
Rendered Raw Blame History
1# Moteur hypothécaire Immo-Ka (Mortgage Intelligence Engine)23Moteur natif de collecte, d'historisation et de calcul des taux hypothécaires4canadiens, intégré au backend FastAPI d'Immo-Ka. **Aucun taux n'est jamais5inventé, codé en dur ni estimé** : tout taux affiché provient d'une page6officielle d'une institution financière, avec provenance (URL source) et7fraîcheur (horodatage de collecte).89## Architecture1011```12immoka/mortgage/13├── providers/          # 1 connecteur indépendant par institution (12)14│   ├── base.py         #   RateProvider : fetch() → produits normalisés15│   └── README.md       #   comment ajouter une banque16├── validate.py         # garde-fous anti-aberration (419 % ≠ 4,19 %…)17├── store.py            # SQLite annexe data/mortgage.db — historisation18├── scheduler.py        # orchestration : retries, backoff, santé, isolation19├── calc.py             # mathématiques hypothécaires canadiennes20├── cmhc.py             # assurance prêt (SCHL) + taxe de vente QC21└── api.py              # routes /api/mortgage/* (montées dans web.py)22```2324Le flux : `provider.fetch()` → `validate_batch()` → `store.record_observations()`.25Les providers ne touchent jamais la base ; le calculateur ne touche jamais les26scrapers — seule l'API interne les relie.2728## Institutions couvertes (12 connecteurs)2930| slug | Institution | Type de source |31|---|---|---|32| `bank_of_canada` | Banque du Canada | Valet API (JSON officiel) — taux de référence |33| `bmo` | BMO | JSON embarqué |34| `cibc` | CIBC | JSON |35| `desjardins` | Desjardins | HTML |36| `eq_bank` | Banque EQ | HTML |37| `first_national` | First National | HTML |38| `mcap` | MCAP | HTML (taux préférentiel) |39| `national_bank` | Banque Nationale | HTML |40| `rbc` | RBC | JSON |41| `scotiabank` | Banque Scotia | JSON (posted + promos) |42| `tangerine` | Tangerine | JSON |43| `td` | TD Canada Trust | JSON |4445Chaque produit est normalisé par `RateProvider.make_product()` :46`provider, institution, product_name, rate_type (fixed|variable|other),47term_months, kind (posted|special), rate, apr, insured_status48(insured|insurable|uninsured|unknown), purpose (purchase|renewal|refinance|unknown),49amortization_max_years, conditions, source_url, confidence, raw`.5051Les taux **préférentiels/prime** sont stockés en `rate_type="other"` +52`purpose="unknown"` : ils ne peuvent jamais contaminer un classement53« meilleur taux d'achat ».5455## Validation (validate.py)5657Rejette avant enregistrement :58- taux hors bornes plausibles (0,5 %–24 %) — attrape `4.19 → 419` ;59- champs requis manquants, enums invalides, termes hors 3–120 mois ;60- APR incohérent (APR < taux − 0,02 pt) ou aberrant ;61- doublons exacts dans un même lot (silencieusement dédupliqués).6263Un lot partiellement invalide n'est pas jeté : les produits sains sont64enregistrés, les problèmes journalisés.6566## Historisation (store.py — data/mortgage.db, WAL)6768- `rate_observations` : périodes de validité (`valid_from`/`valid_to`,69  `is_current`). Taux inchangé → simple mise à jour de `last_checked` ;70  taux changé → clôture de la période + nouvelle ligne. Un saut > 2,5 pts en71  < 48 h est rejeté **sans écraser** la donnée existante (garde anti-aberration72  au niveau BD).73- `provider_runs` : journal de chaque collecte (statut, durée, produits,74  changements, rejets) → santé OK / WARNING (> 24 h) / ERROR.75- `product_key` : sha1 tronqué de76  `provider|rate_type|term|kind|insured|purpose|name` — identité stable d'un77  produit à travers le temps.7879En cas de panne d'une source, **les derniers taux valides restent servis**,80avec leur âge affiché (mention « stale » au-delà de 24 h).8182## Collecte (scheduler.py)8384- `run_provider(slug)` : retries (défaut 3) avec backoff exponentiel ;85  une exception d'un provider n'affecte jamais les autres.86- `run()` : séquentiel et poli (`request_delay` par provider — jamais de87  martèlement des sites bancaires).88- `watch(min)` : boucle autonome ; `maybe_run()` est appelé depuis la boucle89  d'ingestion existante (**process PM2 `immo-ka-sync`**) et ne collecte que si90  la dernière passe date de plus de `IMMOKA_MORTGAGE_INTERVAL_MIN` minutes91  (défaut 180).9293CLI :9495```bash96python run.py mortgage-sync [slug…]   # collecte (toutes ou certaines banques)97python run.py mortgage-watch [min]    # boucle autonome98python run.py mortgage-status         # santé des providers99```100101Variables d'environnement (voir `.env.example`) :102`IMMOKA_MORTGAGE_INTERVAL_MIN`, `IMMOKA_MORTGAGE_RETRIES`,103`IMMOKA_MORTGAGE_BACKOFF`, `SCRAPFLY_KEY` (anti-bot, dernier recours).104105## Calculateur canadien (calc.py + cmhc.py)106107- **Composition semestrielle** pour les taux fixes (norme légale canadienne) :108  taux périodique = `(1 + r/2)^(2/f) − 1`. Valeur étalon vérifiée par test :109  100 000 $ à 6 % sur 25 ans = **639,81 $/mois** (≠ 644,30 $ en composition110  mensuelle américaine — testé aussi, pour prouver qu'on n'utilise pas la111  mauvaise formule). Taux variables : composition mensuelle.112- 6 fréquences : mensuelle, bimensuelle, aux 2 semaines, hebdomadaire,113  accélérée aux 2 semaines (mensualité ÷ 2), accélérée hebdo (÷ 4).114- **Test de résistance** fédéral : qualification à `max(taux + 2, 5,25 %)`.115- **SCHL** (cmhc.py) : mise de fonds légale minimale (5 % / 10 % / 20 %),116  primes par tranche RPV (0,60 % → 4,00 %), surprime +0,20 % amortissement117  30 ans (premier acheteur), plafond assurable 1,5 M$, **TVQ 9,975 % sur la118  prime payable comptant** (spécificité québécoise) — la prime s'ajoute au119  prêt, la taxe non.120- Tableau d'amortissement, résumé de terme (solde au renouvellement),121  scénarios de renouvellement (+0/+1/+2/+3 pts), ratios ABD/ATD informatifs,122  inverses (prêt max pour un versement, taux requis).123124## API interne (`/api/mortgage/*`)125126| Route | Rôle |127|---|---|128| `GET /rates` | taux courants filtrables (type, terme, kind, provider…) |129| `GET /rates/best` | meilleur taux comparable + classement par institution |130| `GET /rates/history` | périodes de validité (historique réel, jamais extrapolé) |131| `GET /providers` | santé des sources (OK/WARNING/ERROR, âge, produits) |132| `GET /market` | vue marché (meilleur/médiane/variations 7-30 j) |133| `GET /intelligence` | market + taux préférentiels (page /taux-hypothecaires) |134| `POST /calculate` | calcul complet (SCHL, stress, terme, renouvellement…) |135| `POST /affordability` | capacité d'emprunt (ABD/ATD + stress test) |136137Règle absolue : **jamais de comparaison de produits incomparables** — affiché138vs offre spéciale, assuré vs non assuré — sans l'indiquer. Le comparateur ne139garde qu'un produit comparable par institution (l'offre spéciale prime).140141## Frontend142143- **Fiche propriété** (`Financement.tsx`, section « Financer cette propriété »,144  ventes seulement) : prix prérempli, mise de fonds $/% synchronisée,145  versement + taux utilisé avec provenance/fraîcheur, SCHL détaillée,146  coût réel mensuel (+ taxes municipales/scolaires de la fiche), stress test,147  renouvellement, comparateur banques, historique SVG, amortissement.148- **Page `/taux-hypothecaires`** (`Taux.tsx`) : vue marché cliquable,149  comparateur par institution (nature + fraîcheur + source officielle),150  historique, santé des sources. Référencée (seo.py + sitemap).151152## Tests153154```bash155PYTHONPATH=. .venv/bin/python -P -m unittest discover -s tests156```15715861 tests : `test_mortgage_calc.py` (valeurs étalons, fréquences accélérées,159inverses, stress), `test_mortgage_cmhc.py` (primes, TVQ, éligibilité),160`test_mortgage_validate.py` (anti-aberration), `test_mortgage_store.py`161(historisation, garde 2,5 pts, meilleur taux), `test_mortgage_providers.py`162(chaque parseur sur fixtures HTML/JSON committées dans163`tests/fixtures/mortgage/` — aucun réseau).164