Python 67%
TypeScript 18.2%
CSS 14.4%
1# Providers de taux hypothécaires23Un connecteur **indépendant** par institution. Auto-découverte : tout module du4dossier définissant une sous-classe de `RateProvider` avec un `provider_id`5non vide est enregistré dans `PROVIDERS` automatiquement (aucun registre à6éditer). La panne d'un provider n'affecte jamais les autres.78**Règle absolue : aucun taux inventé.** Un produit non confirmé sur la page9officielle est simplement omis — jamais deviné, jamais de valeur par défaut.1011## Ajouter une institution (8 étapes)12131. **Créer `<slug>.py`** dans ce dossier, avec une sous-classe de14 `RateProvider` :1516 ```python17 from .base import RateProvider1819 class MaBanque(RateProvider):20 provider_id = "ma_banque" # slug stable (clé BD)21 institution = "Ma Banque" # nom d'affichage fr-CA22 source_url = "https://mabanque.ca/taux-hypothecaires"2324 def fetch(self) -> list[dict]:25 html = self.get(self.source_url).text # ou .json()26 return self.parse(html)2728 def parse(self, html: str) -> list[dict]:29 ... # → [self.make_product(...), ...]30 ```3132 Séparer `fetch()` (réseau) de `parse()` (pur) : les tests appellent33 `parse()` sur des fixtures, sans réseau.34352. **Backend réseau** : `self.get(url)` (requests + politesse `request_delay`)36 d'abord ; `self.get_scrapfly(url, render_js=True)` **en dernier recours37 seulement** si le site bloque (403/JS requis).38393. **Normaliser** chaque produit via `self.make_product(...)` :40 - `rate_type` : `fixed` / `variable` — ou **`other` pour tout taux41 préférentiel/prime/référence** (avec `purpose="unknown"`), afin qu'il ne42 tombe jamais dans un classement « meilleur taux d'achat » ;43 - `kind` : `posted` (affiché) ou `special` (offre spéciale) — ne jamais44 confondre ;45 - `insured_status` : `insured` / `insurable` / `uninsured`, ou `unknown`46 si la page ne le précise pas — ne pas deviner ;47 - `product_name` en français, explicite (ex. « Fixe fermé 5 ans ») ;48 - `apr` (TAP) seulement s'il est publié.49504. **Aucune écriture BD** dans le provider : le scheduler valide51 (`validate_batch`) puis enregistre (`store.record_observations`). Ne pas52 filtrer soi-même les aberrations — la validation s'en charge et journalise.53545. **Fixture** : sauvegarder la réponse réelle (HTML/JSON) dans55 `tests/fixtures/mortgage/<slug>.<ext>` (anonymisée si besoin, taille56 raisonnable — garder le bloc utile).57586. **Test** : ajouter le slug dans `EXPECTED` de59 `tests/test_mortgage_providers.py` (fixture + nombre exact de produits) ;60 le test générique vérifie déjà validation propre, `source_url`,61 `institution` et la règle « préférentiel → other/unknown ». Ajouter un62 test ciblé sur 1–2 valeurs connues de la fixture.63647. **Exécuter** :6566 ```bash67 PYTHONPATH=. .venv/bin/python -P -m unittest tests.test_mortgage_providers68 .venv/bin/python run.py mortgage-sync ma_banque # collecte réelle69 .venv/bin/python run.py mortgage-status # santé70 ```71728. **Vérifier en BD/API** : `GET /api/mortgage/rates?provider=ma_banque` —73 provenance (`source_url`), fraîcheur et nature correctes. C'est tout :74 ni web.py, ni le scheduler, ni le frontend n'ont besoin d'être modifiés.7576## Pièges connus7778- `4.19 % → 419` : toujours vérifier l'échelle ; la validation rejette79 > 24 %, mais un « 41,9 » passerait — parser au bon endroit.80- Pages avec plusieurs onglets (assuré/non assuré) : étiqueter81 `insured_status` correctement plutôt que de tout mélanger.82- Taux « ouverts » vs « fermés » : les distinguer dans `product_name`83 (ex. BNC publie « Fixe ouvert 1 an » à 9,65 % — ce n'est pas une erreur).84- Ne jamais soumettre de formulaire ni simuler une demande de prêt : pages85 publiques de taux uniquement.86