Vrai-Prix — Pipeline de données (documentation de continuité)
Rédigé le 2026-08-18. Important : contrairement aux autres apps Ka, Vrai-Prix n'a AUCUN connecteur runtime. Toute la donnée est produite par un pipeline batch qui tourne sur le laptop (
~/Desktop/qc_house_eval/et~/Desktop/house/), puis livrée au nœud M3U96a sous forme d'un unique fichier SQLite (data/vraiprix.db, ~1,5 Go). Ce document existe pour que n'importe quelle session future puisse comprendre, auditer et rejouer le pipeline même sans accès à l'historique de conversation.
Vue d'ensemble
[MAMH — rôles d'évaluation 2021-2026] [api.qub.ca — transactions]
Données Québec (FGDB/GPKG, licence scraping quadtree, token
ouverte, téléchargement MANUEL) Bearer recapturé via Safari
│ │
│ extract_roles.py │ ~/Desktop/house/scrape_province.py
▼ ▼
unités normalisées ◄──── merge_transactions.py ──── transactions brutes
(fusion spatiale kNN, 2 passes, 99,88 %)
│
▼
hedonic.py — LightGBM, 4 modèles hédoniques (estimation de valeur)
│
▼
build_vrai_prix_db.py — assemble data/vraiprix.db (units, transactions,
│ market_index, FTS5 units_fts, leads)
▼
build_stats.py — agrégats / index de marché
│
▼
scp vraiprix.db (~1,5 Go) → M3U96a:~/apps/vrai-prix/data/ → app Next.js1. Sources amont
1.1 Rôles d'évaluation foncière (MAMH)
- Quoi : rôles d'évaluation municipale du Québec, millésime 2021-2026 (MAMH — ministère des Affaires municipales et de l'Habitation).
- Où : Données Québec — jeu « Rôles d'évaluation foncière ». Licence ouverte (réutilisation permise avec attribution — Données Québec / MAMH).
- Format : FGDB (Esri File Geodatabase) et/ou GPKG (GeoPackage), téléchargés manuellement (pas d'API d'ingestion automatique — les fichiers sont volumineux et versionnés par millésime).
- Contenu utile : chaque unité d'évaluation (adresse, matricule, usage, géométrie/GPS, valeurs au rôle terrain/bâtiment, caractéristiques physiques — frontage, superficie, année de construction, nb logements…).
- Stockage laptop : sous
~/Desktop/qc_house_eval/(données brutes + scripts dansscripts/).
1.2 Transactions immobilières (api.qub.ca)
- Quoi : transactions de vente résidentielles (prix réel, date d'acte), utilisées pour entraîner les modèles hédoniques et calculer l'indice de marché.
- Comment : scraping par quadtree de l'API
api.qub.caavec~/Desktop/house/scrape_province.py(sur le laptop) : découpage récursif du territoire en tuiles jusqu'à passer sous le plafond de résultats par requête, couverture provinciale complète. - Auth : jeton Bearer requis, recapturé manuellement via Safari
(ouvrir le site qub.ca, inspecteur web → copier l'en-tête Authorization
d'une requête à
api.qub.ca, le coller dans le script). Le jeton expire : c'est l'étape manuelle fragile du pipeline (voir « Risques »).
2. Étapes du pipeline (laptop — ~/Desktop/qc_house_eval/scripts/)
Ordre d'exécution (chaque étape lit la sortie de la précédente) :
| # | Script | Rôle |
|---|---|---|
| 0 | ~/Desktop/house/scrape_province.py |
scraping quadtree des transactions api.qub.ca (token Bearer manuel) — préalable, peut tourner indépendamment |
| 1 | extract_roles.py |
extraction/normalisation des unités d'évaluation depuis les FGDB/GPKG MAMH (usages résidentiels, champs canoniques, GPS) |
| 2 | merge_transactions.py |
fusion spatiale kNN en 2 passes des transactions sur les unités du rôle — taux d'appariement 99,88 % |
| 3 | hedonic.py |
entraînement LightGBM — 4 modèles hédoniques (par grande famille de propriété) → valeur estimée « vrai prix » par unité |
| 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 |
| 5 | build_stats.py |
agrégats statistiques / indice de marché (alimente market_index et la page /stats) |
3. Livraison au nœud
Le nœud ne fait que servir la base — l'app Next.js (M3U96a, ce repo) lit
data/vraiprix.db en lecture seule.
# depuis le laptop, après un build complet :
scp ~/Desktop/qc_house_eval/…/vraiprix.db M3U96a:apps/vrai-prix/data/vraiprix.db
ssh M3U96a 'pm2 restart vrai-prix' # recharger l'app après remplacement de la BD- Taille du livrable : ~1,5 Go (dernier fichier livré : 2026-08-08).
- La BD est en WAL (
-shm/-walprésents sur le nœud) ; remplacer le.dbquand l'app est arrêtée ou juste avant unpm2 restartpour éviter tout mélange de journaux.
4. Volumétrie (build livré, vérifiée live sur le nœud 2026-08-18)
| Table | Lignes | Note |
|---|---|---|
units |
3 747 008 | unités d'évaluation résidentielles, province entière |
transactions |
745 119 | ventes appariées (kNN 2 passes, 99,88 %) |
market_index |
268 | lignes d'indice de marché (build_stats.py) |
units_fts (+_data/_idx/_config/_docsize) |
— | index FTS5 de recherche d'adresses |
leads |
— | table applicative (demandes des visiteurs), remplie par l'app, PAS par le pipeline |
- Dernier build : 2026-08-08 (mtime du fichier sur le nœud : 8 août 18:22).
5. Complétude des champs (build 2026-08-08)
| Champ | Complétude |
|---|---|
| GPS (lat/lng des unités) | 100 % |
| Frontage | 83 % |
| Année de construction | 79 % |
Les champs manquants sont des trous du rôle MAMH lui-même (certaines municipalités ne publient pas toutes les caractéristiques) — les modèles hédoniques les traitent comme valeurs manquantes natives LightGBM.
6. Risques & limites (à connaître avant tout rafraîchissement)
- Token qub.ca manuel : le Bearer d'api.qub.ca doit être recapturé à la
main via Safari à chaque campagne de scraping ; s'il expire en cours de
quadtree, relancer
scrape_province.py(il est repriseable par tuiles). Aucun renouvellement automatique — point de fragilité n° 1. - Pipeline NON reproductible sur le nœud : les données brutes (FGDB/GPKG
MAMH, dumps qub.ca), les scripts et les environnements Python (GDAL/
pyogrio pour lire les FGDB, LightGBM) ne vivent que sur le laptop
(
~/Desktop/qc_house_eval/,~/Desktop/house/). Si le laptop est perdu, le pipeline doit être reconstruit ; seul le livrablevraiprix.dbest sur le nœud (et le repo git ne contient PAS la BD ni les données brutes). → Recommandation : archiverqc_house_eval/scripts/+house/(au moins les scripts) sur le NAS ou dans un repo gitsrv dédié. - Rôle 2027 à venir : le millésime MAMH suivant (rôles 2024-2029 / publication « 2027 ») exigera de rejouer tout le pipeline : re-télécharger les FGDB/GPKG, revalider les schémas (les noms de couches/champs bougent entre millésimes), ré-entraîner les 4 modèles.
- Décalage de fraîcheur : la BD servie date du dernier build complet
(2026-08-08). Le pipeline batch (rôles + modèles hédoniques) reste sans
mise à jour incrémentale — c'est voulu (livraison atomique d'un fichier).
Depuis 2026-08-22, les ventes récentes sont toutefois rafraîchies en
continu directement sur le nœud par le connecteur incrémental
scripts/ingest-jdm.mjs(voir section 9) : la tabletransactionsavance donc au fil de l'eau, alors que lesunits/est_*/market_indexrestent figées jusqu'au prochain build complet.
7. Procédure de rafraîchissement pas à pas
Sur le laptop :
- Transactions — recapturer le token : Safari → qub.ca (section
immobilier) → inspecteur web → requête vers
api.qub.ca→ copier le headerAuthorization: Bearer …→ le mettre dans~/Desktop/house/scrape_province.py→ lancer le scraping quadtree (long ; repriseable). Vérifier le volume obtenu vs ~745 k. - Rôles — si nouveau millésime MAMH : télécharger manuellement les
FGDB/GPKG depuis Données Québec dans
~/Desktop/qc_house_eval/. cd ~/Desktop/qc_house_eval/scripts/puis, dans l'ordre :python3 extract_roles.py→python3 merge_transactions.py(contrôler le taux d'appariement, attendu ≥ 99,8 %) →python3 hedonic.py(4 modèles ; contrôler les métriques de validation avant de continuer) →python3 build_vrai_prix_db.py→python3 build_stats.py.- Contrôles qualité sur le
vraiprix.dbproduit :SELECT COUNT(*) FROM units;(~3,7 M),SELECT COUNT(*) FROM transactions;(~745 k+),SELECT COUNT(*) FROM market_index;, complétude GPS = 100 %, spot-check de quelques adresses connues dansunits_fts. - Livraison :
scpdu fichier versM3U96a:apps/vrai-prix/data/vraiprix.dbpuisssh M3U96a 'pm2 restart vrai-prix'. - Vérification en prod : ouvrir le site (vrai-prix), chercher une adresse,
vérifier /stats, puis noter la date de build ici (section 4) et committer
la mise à jour de ce document sur le nœud (remote-first :
git add docs/PIPELINE-DONNEES.md && git commit && git push origin main).
8. Attribution
Les données du rôle d'évaluation proviennent du MAMH via Données Québec (licence ouverte — attribution requise). Les transactions servent au calcul de modèles et d'indices agrégés ; les pages publiques n'exposent pas la source brute qub.ca.
9. Connecteur incrémental « nouvelles ventes » (sur le nœud) — 2026-08-22
Contrairement au reste du pipeline (batch, laptop), ce connecteur tourne
directement sur le nœud et ajoute les ventes récentes dans
data/vraiprix.db sans reconstruire la base. Il alimente la même source que
le widget « Transactions immobilières » du Journal de Montréal
(https://www.journaldemontreal.com/argent/immobilier/transactions-immobilieres),
c.-à-d. l'API api.qub.ca/real-estate-service — la même que scrape_province.py,
donc les id sont compatibles et le dédoublonnage est naturel.
Fichiers
scripts/qub-token.mjs— obtient un Bearer QUB (id-token Cognito). Se connecte au compte Québecor via Scrapfly (loginconnect.qub.caprotégé par Akamai + reCAPTCHA v3 → réessais sur sessions neuves), puis lit le jeton dansGET /api/checklogin(champuserToken). Jeton valide 1 h.scripts/ingest-jdm.mjs— le connecteur : liste les ~1478 secteurs de/v1/locations/all, interroge/v1/mappar secteur (résultats triés du plus récent au plus ancien, plafond 500 → quadtree si saturé), mappe vers le schématransactions, joint spatialement àunits(id_provinc, valeur_role) etINSERT OR IGNORE(dédup parid). Charge automatiquement.env.local.
Mapping API → colonnes transactions
id→id · date→date · amount→amount · address.street/city→street/city ·
geometries[0].coordinates→lng,lat · propertyType→property_type ·
ar.yearBuilt→year_built · ar.floorArea→floor_area · ar.buildingType→
building_type · ar.parcelArea→land_area · unité appariée→id_provinc,valeur_role.
Utilisation
cd ~/apps/vrai-prix
node scripts/ingest-jdm.mjs # défaut : ventes depuis max(date)-45j
node scripts/ingest-jdm.mjs --since=2026-07-15 # date plancher explicite
node scripts/ingest-jdm.mjs --region="Montréal" # un secteur (sous-chaîne)
node scripts/ingest-jdm.mjs --dry-run # ne rien écrire (audit)L'app lit la BD en WAL : les nouvelles lignes sont visibles sans redémarrage.
Planification
Exécution quotidienne via pm2 (08:00, nœud) :
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).
Logs : pm2 logs vrai-prix-ingest.
Secrets (dans .env.local, non versionné)
SCRAPFLY_KEY, QUB_EMAIL, QUB_PASSWORD, QUB_SESSION (préfixe de session).
Limites
- Login QUB reCAPTCHA v3 : réussite probabiliste (boucle de 6 essais sur sessions neuves ; ~1 essai suffit en général).
- N'alimente QUE
transactions. Les estimations hédoniques (est_*,p10/p90) etmarket_indexd'une vente toute neuve restent celles du dernier build complet tant que le pipeline batch n'a pas été rejoué (voir toutefois §10 : ré-estimation partielle possible sur le nœud). - Historique de premier comblement : gap 2026-07-27 → 2026-08-12 rempli province-wide le 2026-08-22 (+4189 ventes, appariement units 100 %).
10. Ré-estimation hédonique sur le nœud (scripts/hedonic-retrain.py) — 2026-08-22
Grâce aux ventes fraîches du connecteur (§9), on dispose d'un jeu de test
« jamais vu » par le modèle laptop (ventes > 2026-07-27, build du 2026-08-08).
scripts/hedonic-retrain.py (sklearn HistGradientBoostingRegressor ≈ LightGBM,
cible log-prix, features rôle + encodages locaux par unité de voisinage +
tendance temporelle, pondération de récence) permet d'évaluer et de corriger le
modèle en place sans le pipeline laptop :
--eval: bench équitable laptop vs challenger sur les ventes jamais vues (n=4849, aucune écriture).--apply: ré-entraîne sur tout l'historique et applique la stratégie composite validée, avec sauvegarde intégrale dansunits_est_prev.
Verdict du bench 2026-08-22 (MdAPE sur ventes jamais vues)
| Segment | laptop | challenger nœud |
|---|---|---|
| unifamilial (n=4420) | 10,3 % | 12,8-13,0 % |
| terrain (n=139) | 62,2 % | 36-37 % |
| autre (n=150) | 79,3 % | 52-54 % |
→ Le modèle laptop reste MEILLEUR sur l'unifamilial (conservé) ; le challenger
est appliqué UNIQUEMENT sur terrain + autre (926 432 unités, 25 % du parc).
Autres corrections appliquées le 2026-08-22
- Recalibrage P10/P90 du modèle laptop (couverture observée 18,5 %/16,8 %
hors bornes vs cible 10/10) : élargissement
p' = est×(p/est)^α, α_lo=1,754 / α_hi=1,255 → couverture après : 10,1 % / 9,8 %. Les fourchettes affichées sont plus larges mais honnêtes. - Clamp p10 ≤ est ≤ p90 partout (38 826 violations dans le build laptop, 0 après).
Rollback
units_est_prev contient les (est_2026, p10, p90) d'avant intervention :
UPDATE units SET est_2026=(SELECT est_2026 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc),
p10=(SELECT p10 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc),
p90=(SELECT p90 FROM units_est_prev p WHERE p.id_provinc=units.id_provinc);NB : le prochain build complet laptop écrasera tout — reporter au besoin ces
améliorations dans hedonic.py (recalibrage quantiles + segments terrain/autre).