SPB Git forge

spb/vrai-prix

Public

Vrai-Prix — l'évaluation du vrai prix des propriétés résidentielles au Québec.

60commits 1branches 0releases
12.3 MBsize
maindefault branch
17 days agolast push
TypeScript 90.2% JavaScript 3.5% Python 3.4% CSS 1.9% HTML 0.6%
14.9 KB · 278 lines markdown
Rendered Raw Blame History
1# Vrai-Prix — Pipeline de données (documentation de continuité)23> Rédigé le 2026-08-18. **Important : contrairement aux autres apps Ka, Vrai-Prix4> n'a AUCUN connecteur runtime.** Toute la donnée est produite par un pipeline5> **batch qui tourne sur le laptop** (`~/Desktop/qc_house_eval/` et6> `~/Desktop/house/`), puis livrée au nœud M3U96a sous forme d'un unique7> fichier SQLite (`data/vraiprix.db`, ~1,5 Go). Ce document existe pour que8> n'importe quelle session future puisse comprendre, auditer et rejouer le9> pipeline même sans accès à l'historique de conversation.1011## Vue d'ensemble1213```14[MAMH — rôles d'évaluation 2021-2026]        [api.qub.ca — transactions]15  Données Québec (FGDB/GPKG, licence            scraping quadtree, token16  ouverte, téléchargement MANUEL)               Bearer recapturé via Safari17        │                                             │18        │  extract_roles.py                           │  ~/Desktop/house/scrape_province.py19        ▼                                             ▼20   unités normalisées  ◄──── merge_transactions.py ──── transactions brutes21                              (fusion spatiale kNN, 2 passes, 99,88 %)22        │23        ▼24   hedonic.py — LightGBM, 4 modèles hédoniques (estimation de valeur)25        │26        ▼27   build_vrai_prix_db.py — assemble data/vraiprix.db (units, transactions,28        │                   market_index, FTS5 units_fts, leads)29        ▼30   build_stats.py — agrégats / index de marché31        │32        ▼33   scp vraiprix.db (~1,5 Go) → M3U96a:~/apps/vrai-prix/data/  → app Next.js34```3536## 1. Sources amont3738### 1.1 Rôles d'évaluation foncière (MAMH)3940- **Quoi** : rôles d'évaluation municipale du Québec, millésime **2021-2026**41  (MAMH — ministère des Affaires municipales et de l'Habitation).42- **Où** : [Données Québec](https://www.donneesquebec.ca/) — jeu « Rôles43  d'évaluation foncière ». **Licence ouverte** (réutilisation permise avec44  attribution — Données Québec / MAMH).45- **Format** : **FGDB** (Esri File Geodatabase) et/ou **GPKG** (GeoPackage),46  **téléchargés manuellement** (pas d'API d'ingestion automatique — les47  fichiers sont volumineux et versionnés par millésime).48- **Contenu utile** : chaque unité d'évaluation (adresse, matricule, usage,49  géométrie/GPS, valeurs au rôle terrain/bâtiment, caractéristiques physiques50  — frontage, superficie, année de construction, nb logements…).51- **Stockage laptop** : sous `~/Desktop/qc_house_eval/` (données brutes +52  scripts dans `scripts/`).5354### 1.2 Transactions immobilières (api.qub.ca)5556- **Quoi** : transactions de vente résidentielles (prix réel, date d'acte),57  utilisées pour entraîner les modèles hédoniques et calculer l'indice de58  marché.59- **Comment** : **scraping par quadtree** de l'API `api.qub.ca` avec60  `~/Desktop/house/scrape_province.py` (sur le **laptop**) : découpage61  récursif du territoire en tuiles jusqu'à passer sous le plafond de62  résultats par requête, couverture provinciale complète.63- **Auth** : jeton **Bearer** requis, **recapturé manuellement via Safari**64  (ouvrir le site qub.ca, inspecteur web → copier l'en-tête Authorization65  d'une requête à `api.qub.ca`, le coller dans le script). Le jeton expire :66  c'est l'étape manuelle fragile du pipeline (voir « Risques »).6768## 2. Étapes du pipeline (laptop — `~/Desktop/qc_house_eval/scripts/`)6970Ordre d'exécution (chaque étape lit la sortie de la précédente) :7172| # | Script | Rôle |73|---|---|---|74| 0 | `~/Desktop/house/scrape_province.py` | scraping quadtree des transactions api.qub.ca (token Bearer manuel) — préalable, peut tourner indépendamment |75| 1 | `extract_roles.py` | extraction/normalisation des unités d'évaluation depuis les FGDB/GPKG MAMH (usages résidentiels, champs canoniques, GPS) |76| 2 | `merge_transactions.py` | **fusion spatiale kNN en 2 passes** des transactions sur les unités du rôle — taux d'appariement **99,88 %** |77| 3 | `hedonic.py` | entraînement **LightGBM — 4 modèles hédoniques** (par grande famille de propriété) → valeur estimée « vrai prix » par unité |78| 4 | `build_vrai_prix_db.py` | assemblage du SQLite final `vraiprix.db` : tables `units`, `transactions`, `market_index`, index plein-texte FTS5 (`units_fts*`), table applicative `leads` |79| 5 | `build_stats.py` | agrégats statistiques / indice de marché (alimente `market_index` et la page /stats) |8081## 3. Livraison au nœud8283Le nœud **ne fait que servir** la base — l'app Next.js (M3U96a, ce repo) lit84`data/vraiprix.db` en lecture seule.8586```bash87# depuis le laptop, après un build complet :88scp ~/Desktop/qc_house_eval/…/vraiprix.db M3U96a:apps/vrai-prix/data/vraiprix.db89ssh M3U96a 'pm2 restart vrai-prix'   # recharger l'app après remplacement de la BD90```9192- Taille du livrable : **~1,5 Go** (dernier fichier livré : 2026-08-08).93- La BD est en WAL (`-shm`/`-wal` présents sur le nœud) ; remplacer le `.db`94  quand l'app est arrêtée ou juste avant un `pm2 restart` pour éviter tout95  mélange de journaux.9697## 4. Volumétrie (build livré, vérifiée live sur le nœud 2026-08-18)9899| Table | Lignes | Note |100|---|---|---|101| `units` | **3 747 008** | unités d'évaluation résidentielles, province entière |102| `transactions` | **745 119** | ventes appariées (kNN 2 passes, 99,88 %) |103| `market_index` | **268** | lignes d'indice de marché (build_stats.py) |104| `units_fts` (+`_data`/`_idx`/`_config`/`_docsize`) | — | index FTS5 de recherche d'adresses |105| `leads` | — | table applicative (demandes des visiteurs), remplie par l'app, PAS par le pipeline |106107- **Dernier build : 2026-08-08** (mtime du fichier sur le nœud : 8 août 18:22).108109## 5. Complétude des champs (build 2026-08-08)110111| Champ | Complétude |112|---|---|113| GPS (lat/lng des unités) | **100 %** |114| Frontage | **83 %** |115| Année de construction | **79 %** |116117Les champs manquants sont des trous du rôle MAMH lui-même (certaines118municipalités ne publient pas toutes les caractéristiques) — les modèles119hédoniques les traitent comme valeurs manquantes natives LightGBM.120121## 6. Risques & limites (à connaître avant tout rafraîchissement)1221231. **Token qub.ca manuel** : le Bearer d'api.qub.ca doit être recapturé à la124   main via Safari à chaque campagne de scraping ; s'il expire en cours de125   quadtree, relancer `scrape_province.py` (il est repriseable par tuiles).126   Aucun renouvellement automatique — point de fragilité n° 1.1272. **Pipeline NON reproductible sur le nœud** : les données brutes (FGDB/GPKG128   MAMH, dumps qub.ca), les scripts et les environnements Python (GDAL/129   pyogrio pour lire les FGDB, LightGBM) ne vivent que sur le **laptop**130   (`~/Desktop/qc_house_eval/`, `~/Desktop/house/`). Si le laptop est perdu,131   le pipeline doit être reconstruit ; seul le livrable `vraiprix.db` est sur132   le nœud (et le repo git ne contient PAS la BD ni les données brutes).133   → Recommandation : archiver `qc_house_eval/scripts/` + `house/` (au moins134   les scripts) sur le NAS ou dans un repo gitsrv dédié.1353. **Rôle 2027 à venir** : le millésime MAMH suivant (rôles 2024-2029 /136   publication « 2027 ») exigera de rejouer tout le pipeline : re-télécharger137   les FGDB/GPKG, revalider les schémas (les noms de couches/champs bougent138   entre millésimes), ré-entraîner les 4 modèles.1394. **Décalage de fraîcheur** : la BD servie date du dernier build complet140   (2026-08-08). Le **pipeline batch** (rôles + modèles hédoniques) reste sans141   mise à jour incrémentale — c'est voulu (livraison atomique d'un fichier).142   **Depuis 2026-08-22, les ventes récentes sont toutefois rafraîchies en143   continu directement sur le nœud** par le connecteur incrémental144   `scripts/ingest-jdm.mjs` (voir section 9) : la table `transactions` avance145   donc au fil de l'eau, alors que les `units`/`est_*`/`market_index` restent146   figées jusqu'au prochain build complet.147148## 7. Procédure de rafraîchissement pas à pas149150Sur le **laptop** :1511521. **Transactions** — recapturer le token : Safari → qub.ca (section153   immobilier) → inspecteur web → requête vers `api.qub.ca` → copier le154   header `Authorization: Bearer …` → le mettre dans155   `~/Desktop/house/scrape_province.py` → lancer le scraping quadtree156   (long ; repriseable). Vérifier le volume obtenu vs ~745 k.1572. **Rôles** — si nouveau millésime MAMH : télécharger manuellement les158   FGDB/GPKG depuis Données Québec dans `~/Desktop/qc_house_eval/`.1593. `cd ~/Desktop/qc_house_eval/scripts/` puis, dans l'ordre :160   `python3 extract_roles.py` → `python3 merge_transactions.py` (contrôler le161   taux d'appariement, attendu ≥ 99,8 %) → `python3 hedonic.py` (4 modèles ;162   contrôler les métriques de validation avant de continuer) →163   `python3 build_vrai_prix_db.py` → `python3 build_stats.py`.1644. **Contrôles qualité** sur le `vraiprix.db` produit :165   `SELECT COUNT(*) FROM units;` (~3,7 M), `SELECT COUNT(*) FROM transactions;`166   (~745 k+), `SELECT COUNT(*) FROM market_index;`, complétude GPS = 100 %,167   spot-check de quelques adresses connues dans `units_fts`.1685. **Livraison** : `scp` du fichier vers `M3U96a:apps/vrai-prix/data/vraiprix.db`169   puis `ssh M3U96a 'pm2 restart vrai-prix'`.1706. **Vérification en prod** : ouvrir le site (vrai-prix), chercher une adresse,171   vérifier /stats, puis noter la date de build ici (section 4) et committer172   la mise à jour de ce document **sur le nœud** (remote-first :173   `git add docs/PIPELINE-DONNEES.md && git commit && git push origin main`).174175## 8. Attribution176177Les données du rôle d'évaluation proviennent du **MAMH via Données Québec**178(licence ouverte — attribution requise). Les transactions servent au calcul179de modèles et d'indices agrégés ; les pages publiques n'exposent pas la180source brute qub.ca.181182## 9. Connecteur incrémental « nouvelles ventes » (sur le nœud) — 2026-08-22183184Contrairement au reste du pipeline (batch, laptop), ce connecteur tourne185**directement sur le nœud** et **ajoute les ventes récentes dans186`data/vraiprix.db` sans reconstruire la base**. Il alimente la même source que187le widget « Transactions immobilières » du Journal de Montréal188(https://www.journaldemontreal.com/argent/immobilier/transactions-immobilieres),189c.-à-d. l'API `api.qub.ca/real-estate-service` — la même que `scrape_province.py`,190donc les `id` sont compatibles et le dédoublonnage est naturel.191192### Fichiers193- `scripts/qub-token.mjs` — obtient un Bearer QUB (id-token Cognito). Se connecte194  au compte Québecor via **Scrapfly** (login `connect.qub.ca` protégé par Akamai +195  reCAPTCHA v3 → réessais sur sessions neuves), puis lit le jeton dans196  `GET /api/checklogin` (champ `userToken`). Jeton valide 1 h.197- `scripts/ingest-jdm.mjs` — le connecteur : liste les ~1478 secteurs de198  `/v1/locations/all`, interroge `/v1/map` par secteur (résultats triés du plus199  récent au plus ancien, plafond 500 → **quadtree** si saturé), mappe vers le200  schéma `transactions`, **joint spatialement à `units`** (id_provinc, valeur_role)201  et `INSERT OR IGNORE` (dédup par `id`). Charge automatiquement `.env.local`.202203### Mapping API → colonnes `transactions`204`id`→id · `date`→date · `amount`→amount · `address.street/city`→street/city ·205`geometries[0].coordinates`→lng,lat · `propertyType`→property_type ·206`ar.yearBuilt`→year_built · `ar.floorArea`→floor_area · `ar.buildingType`→207building_type · `ar.parcelArea`→land_area · unité appariée→id_provinc,valeur_role.208209### Utilisation210```bash211cd ~/apps/vrai-prix212node scripts/ingest-jdm.mjs                       # défaut : ventes depuis max(date)-45j213node scripts/ingest-jdm.mjs --since=2026-07-15    # date plancher explicite214node scripts/ingest-jdm.mjs --region="Montréal"   # un secteur (sous-chaîne)215node scripts/ingest-jdm.mjs --dry-run             # ne rien écrire (audit)216```217L'app lit la BD en WAL : les nouvelles lignes sont visibles **sans redémarrage**.218219### Planification220Exécution quotidienne via **pm2** (08:00, nœud) :221`pm2 start scripts/ingest-jdm.mjs --name vrai-prix-ingest --cwd ~/apps/vrai-prix --no-autorestart --cron-restart="0 8 * * *" --interpreter node` (puis `pm2 save`).222Logs : `pm2 logs vrai-prix-ingest`.223224### Secrets (dans `.env.local`, non versionné)225`SCRAPFLY_KEY`, `QUB_EMAIL`, `QUB_PASSWORD`, `QUB_SESSION` (préfixe de session).226227### Limites228- Login QUB reCAPTCHA v3 : réussite probabiliste (boucle de 6 essais sur229  sessions neuves ; ~1 essai suffit en général).230- N'alimente QUE `transactions`. Les estimations hédoniques (`est_*`, `p10/p90`)231  et `market_index` d'une vente toute neuve restent celles du dernier build232  complet tant que le pipeline batch n'a pas été rejoué (voir toutefois §10 :233  ré-estimation partielle possible sur le nœud).234- Historique de premier comblement : gap 2026-07-27 → 2026-08-12 rempli235  province-wide le 2026-08-22 (+4189 ventes, appariement units 100 %).236237## 10. Ré-estimation hédonique sur le nœud (`scripts/hedonic-retrain.py`) — 2026-08-22238239Grâce aux ventes fraîches du connecteur (§9), on dispose d'un jeu de test240« jamais vu » par le modèle laptop (ventes > 2026-07-27, build du 2026-08-08).241`scripts/hedonic-retrain.py` (sklearn HistGradientBoostingRegressor ≈ LightGBM,242cible log-prix, features rôle + encodages locaux par unité de voisinage +243tendance temporelle, pondération de récence) permet d'évaluer et de corriger le244modèle en place sans le pipeline laptop :245246- `--eval` : bench équitable laptop vs challenger sur les ventes jamais vues247  (n=4849, aucune écriture).248- `--apply` : ré-entraîne sur tout l'historique et applique la stratégie249  composite validée, avec sauvegarde intégrale dans `units_est_prev`.250251### Verdict du bench 2026-08-22 (MdAPE sur ventes jamais vues)252| Segment | laptop | challenger nœud |253|---|---|---|254| unifamilial (n=4420) | **10,3 %** | 12,8-13,0 % |255| terrain (n=139) | 62,2 % | **36-37 %** |256| autre (n=150) | 79,3 % | **52-54 %** |257258→ Le modèle laptop reste MEILLEUR sur l'unifamilial (conservé) ; le challenger259est appliqué UNIQUEMENT sur `terrain` + `autre` (926 432 unités, 25 % du parc).260261### Autres corrections appliquées le 2026-08-22262- **Recalibrage P10/P90** du modèle laptop (couverture observée 18,5 %/16,8 %263  hors bornes vs cible 10/10) : élargissement `p' = est×(p/est)^α`,264  α_lo=1,754 / α_hi=1,255 → couverture après : **10,1 % / 9,8 %**. Les265  fourchettes affichées sont plus larges mais honnêtes.266- **Clamp p10 ≤ est ≤ p90** partout (38 826 violations dans le build laptop,267  0 après).268269### Rollback270`units_est_prev` contient les (est_2026, p10, p90) d'avant intervention :271```sql272UPDATE units SET est_2026=(SELECT est_2026 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc),273  p10=(SELECT p10 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc),274  p90=(SELECT p90 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc);275```276NB : le prochain build complet laptop écrasera tout — reporter au besoin ces277améliorations dans `hedonic.py` (recalibrage quantiles + segments terrain/autre).278