# Providers de taux hypothécaires Un connecteur **indépendant** par institution. Auto-découverte : tout module du dossier définissant une sous-classe de `RateProvider` avec un `provider_id` non vide est enregistré dans `PROVIDERS` automatiquement (aucun registre à éditer). La panne d'un provider n'affecte jamais les autres. **Règle absolue : aucun taux inventé.** Un produit non confirmé sur la page officielle est simplement omis — jamais deviné, jamais de valeur par défaut. ## Ajouter une institution (8 étapes) 1. **Créer `.py`** dans ce dossier, avec une sous-classe de `RateProvider` : ```python from .base import RateProvider class MaBanque(RateProvider): provider_id = "ma_banque" # slug stable (clé BD) institution = "Ma Banque" # nom d'affichage fr-CA source_url = "https://mabanque.ca/taux-hypothecaires" def fetch(self) -> list[dict]: html = self.get(self.source_url).text # ou .json() return self.parse(html) def parse(self, html: str) -> list[dict]: ... # → [self.make_product(...), ...] ``` Séparer `fetch()` (réseau) de `parse()` (pur) : les tests appellent `parse()` sur des fixtures, sans réseau. 2. **Backend réseau** : `self.get(url)` (requests + politesse `request_delay`) d'abord ; `self.get_scrapfly(url, render_js=True)` **en dernier recours seulement** si le site bloque (403/JS requis). 3. **Normaliser** chaque produit via `self.make_product(...)` : - `rate_type` : `fixed` / `variable` — ou **`other` pour tout taux préférentiel/prime/référence** (avec `purpose="unknown"`), afin qu'il ne tombe jamais dans un classement « meilleur taux d'achat » ; - `kind` : `posted` (affiché) ou `special` (offre spéciale) — ne jamais confondre ; - `insured_status` : `insured` / `insurable` / `uninsured`, ou `unknown` si la page ne le précise pas — ne pas deviner ; - `product_name` en français, explicite (ex. « Fixe fermé 5 ans ») ; - `apr` (TAP) seulement s'il est publié. 4. **Aucune écriture BD** dans le provider : le scheduler valide (`validate_batch`) puis enregistre (`store.record_observations`). Ne pas filtrer soi-même les aberrations — la validation s'en charge et journalise. 5. **Fixture** : sauvegarder la réponse réelle (HTML/JSON) dans `tests/fixtures/mortgage/.` (anonymisée si besoin, taille raisonnable — garder le bloc utile). 6. **Test** : ajouter le slug dans `EXPECTED` de `tests/test_mortgage_providers.py` (fixture + nombre exact de produits) ; le test générique vérifie déjà validation propre, `source_url`, `institution` et la règle « préférentiel → other/unknown ». Ajouter un test ciblé sur 1–2 valeurs connues de la fixture. 7. **Exécuter** : ```bash PYTHONPATH=. .venv/bin/python -P -m unittest tests.test_mortgage_providers .venv/bin/python run.py mortgage-sync ma_banque # collecte réelle .venv/bin/python run.py mortgage-status # santé ``` 8. **Vérifier en BD/API** : `GET /api/mortgage/rates?provider=ma_banque` — provenance (`source_url`), fraîcheur et nature correctes. C'est tout : ni web.py, ni le scheduler, ni le frontend n'ont besoin d'être modifiés. ## Pièges connus - `4.19 % → 419` : toujours vérifier l'échelle ; la validation rejette > 24 %, mais un « 41,9 » passerait — parser au bon endroit. - Pages avec plusieurs onglets (assuré/non assuré) : étiqueter `insured_status` correctement plutôt que de tout mélanger. - Taux « ouverts » vs « fermés » : les distinguer dans `product_name` (ex. BNC publie « Fixe ouvert 1 an » à 9,65 % — ce n'est pas une erreur). - Ne jamais soumettre de formulaire ni simuler une demande de prêt : pages publiques de taux uniquement.