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

# 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

text
[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.js

# 1. 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 dans scripts/).

# 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.ca avec ~/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.

bash
# 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/-wal présents sur le nœud) ; remplacer le .db quand l'app est arrêtée ou juste avant un pm2 restart pour é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)

  1. 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.
  2. 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 livrable vraiprix.db est sur le nœud (et le repo git ne contient PAS la BD ni les données brutes). → Recommandation : archiver qc_house_eval/scripts/ + house/ (au moins les scripts) sur le NAS ou dans un repo gitsrv dédié.
  3. 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.
  4. 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 table transactions avance donc au fil de l'eau, alors que les units/est_*/market_index restent figées jusqu'au prochain build complet.

# 7. Procédure de rafraîchissement pas à pas

Sur le laptop :

  1. Transactions — recapturer le token : Safari → qub.ca (section immobilier) → inspecteur web → requête vers api.qub.ca → copier le header Authorization: Bearer … → le mettre dans ~/Desktop/house/scrape_province.py → lancer le scraping quadtree (long ; repriseable). Vérifier le volume obtenu vs ~745 k.
  2. Rôles — si nouveau millésime MAMH : télécharger manuellement les FGDB/GPKG depuis Données Québec dans ~/Desktop/qc_house_eval/.
  3. 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.
  4. Contrôles qualité sur le vraiprix.db produit : 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 dans units_fts.
  5. Livraison : scp du fichier vers M3U96a:apps/vrai-prix/data/vraiprix.db puis ssh M3U96a 'pm2 restart vrai-prix'.
  6. 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 (login connect.qub.ca protégé par Akamai + reCAPTCHA v3 → réessais sur sessions neuves), puis lit le jeton dans GET /api/checklogin (champ userToken). Jeton valide 1 h.
  • scripts/ingest-jdm.mjs — le connecteur : liste les ~1478 secteurs de /v1/locations/all, interroge /v1/map par secteur (résultats triés du plus récent au plus ancien, plafond 500 → quadtree si saturé), mappe vers le schéma transactions, joint spatialement à units (id_provinc, valeur_role) et INSERT OR IGNORE (dédup par id). 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

bash
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) et market_index d'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 dans units_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 :

sql
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).