# Vrai-Prix
**La vraie valeur de votre propriété, sans boîte noire.**
Moteur d'évaluation immobilière **transparent** pour le Québec — **3 747 008 propriétés**,
**745 119 ventes réelles (2021-2026)**, chaque calcul montré en entier.













**En production : [www.vrai-prix.com](https://www.vrai-prix.com)**

---
## Sommaire
- [Ce que fait Vrai-Prix](#ce-que-fait-vrai-prix)
- [Fonctionnalités](#fonctionnalités)
- [Visite guidée](#visite-guidée)
- [Les chiffres qui comptent](#les-chiffres-qui-comptent)
- [Architecture](#architecture)
- [Le moteur d'estimation](#le-moteur-destimation-srclibenginets)
- [Ka — l'agent IA](#ka--lagent-ia)
- [SEO programmatique](#seo-programmatique--376-m-durls-indexables)
- [Structure du projet](#structure-du-projet)
- [Endpoints](#endpoints)
- [Démarrage local](#démarrage-local)
- [Déploiement](#déploiement)
- [Développement remote-first](#développement-remote-first)
- [Design](#design)
- [Auteur](#auteur)
## Ce que fait Vrai-Prix
Vrai-Prix estime la valeur marchande de **n'importe quelle propriété du Québec** (unifamiliale, plex, condo, chalet, terrain) et montre *pourquoi* — **chaque comparable, chaque ajustement en dollars**, la pondération du modèle, la fourchette et l'indice de confiance. Le différenciateur est la **transparence totale** : pas de score magique, **pas de boîte noire**. La méthodologie complète (normes **IAAO**) est publiée intégralement sur le site.
Sous le capot : les **6 millésimes (2021-2026)** des rôles d'évaluation foncière géoréférencés du Québec (**MAMH**, données ouvertes) — soit **22 150 285 observations** — fusionnés spatialement avec **745 119 transactions réelles** (**99,88 %** d'appariement, distance médiane **0,6 m**), alimentant un **modèle hédonique LightGBM** et un **moteur de comparables ajustés**.
## Fonctionnalités
| Fonctionnalité | Description |
|---|---|
| 🔎 **Estimation par adresse** | Recherche plein-texte (**FTS5**) parmi **3 747 008 unités d'évaluation**, tolérante aux fautes |
| 🧮 **Plan B sans adresse** | **Estimation manuelle** (municipalité + caractéristiques) quand la propriété n'est pas au registre |
| 💰 **Estimation hybride** | **65 % modèle hédonique + 35 % comparables ajustés** |
| 📊 **Fourchette + confiance** | **P10–P90** par régression quantile, indice **A–D** obligatoire |
| 🏠 Portrait de la propriété | Façade SVG dessinée par les données du registre, terrain à l'échelle, composition de la valeur |
| 🗺️ **Vrai-Prix Maps** | Recherche par **carte 3D** (Mapbox GL via le framework partagé **Ka Maps**) — grappes de valeurs estimées pilotées par le viewport |
| 📐 Plan des comparables | Carte « plan d'arpenteur » maison (SVG pur, zéro tuile) — azimut et distance réels |
| 📈 Historique 2021→2026 | **Six millésimes** d'estimations par propriété, badges YoY |
| 🤖 **Ka · agent IA** | Agent conversationnel (**Claude**) branché sur **12 outils serveur** : évaluation, comparables, stats, dossier d'expert |
| 🏢 Parc immobilier | Évaluation multi-adresses agrégée (jusqu'à **40 propriétés**) + rapport consolidé |
| 📄 Rapports PDF | Standard (3 p.), professionnel bancaire (6 p.), parc (n+1 p.), statistique provincial — **100 % vectoriels** |
| 📉 **Statistiques** | Valeur totale de la province (**2,01 billions $**), palmarès des **200** municipalités |
| 🏙️ Pages municipalités | **1 097** pages programmatiques (médiane, croissance, médianes par type) + **~5 900** pages municipalité × type |
| 🔑 KA ID | Connexion unique du **Groupe KA** (SSO signé via le hub groupe-ka.com, session sans base d'utilisateurs) |
| 🌐 Bilingue | **FR** (défaut) / **EN**, bascule instantanée côté client |
| 📱 App iOS | Compagnon natif **SwiftUI** (onglet carte MapKit) — dépôt séparé |
| ⚖️ **Méthodologie transparente** | Loi 25, sélecteur de témoins, méthodologie publique intégrale (7 sections, normes **IAAO**) |
## Visite guidée
| | |
|---|---|
| **Fiche d'estimation** — valeur, fourchette honnête, écart vs rôle, indice de confiance | **Portrait de la propriété** — bâtiment, terrain à l'échelle et valeurs dessinés par les données |
|
|
|
| **Vrai-Prix Maps** — votre quartier, estimé (grappes de valeurs 2026) | **Ka · agent IA** — évaluation en conversant, calcul montré |
|
|
|
| **Statistiques provinciales** — 2,01 billions $, palmarès des municipalités | **Pages municipalités** — 1 097 pages programmatiques indexables |
|
|
|
| **Méthodologie publique** — 7 sections, normes IAAO, rien de caché | **Mobile** — même expérience, menu plein écran pur CSS |
|
|
|
## Les chiffres qui comptent
Validation sur **102 943 ventes jamais vues** du modèle (holdout aléatoire) et **test temporel strict** (entraîné sans 2026, testé sur **68 364 ventes de 2026**), selon la **norme IAAO sur études de ratios** :
| Métrique | Holdout | Test temporel 2026 | Cible IAAO |
|---|---|---|---|
| Erreur médiane (MdAPE) | **11,0 %** | 14,0 % | — |
| À ±20 % du prix réel | **72,1 %** | 66,7 % | — |
| Ratio médian | **0,995** | 0,961 | 0,90–1,10 ✅ |
| COD | 22,7 (condos : **11,9** ✅) | 23,7 | ≤ 15–20 |
| PRD | 1,092 | 1,075 | 0,98–1,03 |
| R² (log) | **0,789** | 0,743 | — |
## Architecture
```
┌─ Données ouvertes MAMH ──────────────────────────────────────────┐
│ Rôles d'évaluation foncière géoréférencés 2021-2026 │
│ 6 millésimes × ~3,7 M unités = 22 150 285 observations │
└──────────────┬───────────────────────────────────────────────────┘
│ fusion spatiale XY (EPSG:32198)
│ kNN k=25 ≤200 m + jointure par valeur ≤500 m
│ → 99,88 % appariées, distance médiane 0,6 m
┌──────────────▼───────────────┐ ┌──────────────────────────────┐
│ 745 119 transactions │ │ Modèle hédonique LightGBM │
│ enrichies du rôle ├──▶│ log(prix), 18 variables │
└──────────────────────────────┘ │ + quantiles P10/P90 │
│ → 22 M d'estimations │
└──────────────┬───────────────┘
┌─ Application Next.js 16 ─────────────────────────▼───────────────┐
│ SQLite (units + FTS5 + transactions + indice de marché) │
│ Moteur comparables (ajust. marché/superficie/âge, médiane │
│ pondérée) · Ka Maps (Mapbox GL) · agent Ka (Claude + 12 outils) │
│ RadarMap SVG · MetricViz · rapports pdfkit · SSO KA ID │
│ SEO programmatique (1 097 municipalités · sitemaps 3,76 M URLs) │
└──────────────────────────────────────────────────────────────────┘
```
### Stack
- **Frontend / API** : **Next.js 16** (App Router), **TypeScript strict**, **Tailwind CSS 4**
- **Base** : **SQLite** (better-sqlite3) — **3,75 M unités**, **FTS5**, index spatiaux *(non incluse dans ce dépôt)*
- **Cartes** : **`@groupe-ka/ka-maps`** (framework carto partagé du Groupe KA, moteur **Mapbox GL**, style Standard 3D)
- **Agent IA** : **SDK Anthropic** (Claude Haiku 4.5, streaming SSE, boucle agentique manuelle, prompt caching)
- **Rapports** : **pdfkit**, 100 % vectoriels, polices embarquées (Space Grotesk / Inter / JetBrains Mono)
- **Modèle** (pipeline externe) : **LightGBM**, scikit-learn, pandas, pyogrio/GDAL
- **Tests** : **Vitest** (moteur d'estimation) + **Playwright** (mobile, captures d'écran)
- **Prod** : **PM2** + **ngrok** sur le nœud **M3U96a** du cluster MacLustr
## Le moteur d'estimation (`src/lib/engine.ts`)
1. **Comparables** : ventes candidates dans un rayon adaptatif (**2,2 → 28 km**), filtrées par type et superficie (±20 → ±40 %) ;
2. **Ajustements affichés en dollars** : conditions de marché (indice mensuel $/m² lissé 3 mois), superficie (50 % du $/m² du comparable, plafonné ±25 %), âge (0,5 %/an, plafonné ±10 %) ;
3. **Pondération gaussienne** : distance (σ 1 500 m), récence (σ 24 mois), similarité de superficie (σ 25 %) ;
4. **Fusion** : **65 % modèle hédonique** pré-calculé + **35 % médiane pondérée des comparables** ;
5. **Confiance** : nombre de comparables, dispersion, largeur de fourchette → **A/B/C/D**.
Chaque fiche montre le calcul **en entier** : les comparables retenus, chaque ajustement, les poids, la réconciliation. Le rapport PDF « professionnel » reprend la grille d'ajustement au format des évaluateurs (avec les encadrés « ceci n'est pas un rapport OEAQ »).
## Ka — l'agent IA
Page [`/ka`](https://www.vrai-prix.com/ka) : un agent conversationnel branché **en direct** sur le registre et le moteur. Boucle agentique manuelle (**12 tours max**) avec **12 outils serveur** : `chercher_propriete` (FTS avec relaxation progressive), `evaluer_propriete`, `dossier_complet` (évaluation + marché + tendance + comparables + synthèse en un appel), `comparables_detailles`, `indice_marche`, `stats_municipalite`, `stats_provinciales`, `evaluer_parc`, `comparer_proprietes`, `estimation_manuelle`, `chercher_proprietes_secteur`, `liens_rapports` (réfs persistantes anti-hallucination pour les PDF). **Streaming SSE**, tableaux HTML dans le fil, clarification quand l'adresse est ambiguë.
## SEO programmatique — 3,76 M d'URLs indexables
Tout le contenu est servi **en HTML complet dès la première requête** (SSR), avec un maillage interne crawlable :
- **`/municipalites`** → index A-Z des **1 097 municipalités** ;
- **`/municipalite/[slug]`** (**1 097 pages**) → médiane 2026, valeur totale, croissance 2021→2026, médianes par type, 24 fiches liées, municipalités voisines ;
- **`/municipalite/[slug]/[type]`** (**~5 900 pages**) → `maison`, `plex`, `chalet`, `condo`, `terrain`, `maison-mobile`, `autre` ;
- **`/estimation/[id]`** (**3,75 M fiches**) → title et meta description **uniques par fiche** (adresse + estimation), canonical, JSON-LD `SingleFamilyResidence`/`Residence`/`Place` + `BreadcrumbList` ;
- **Sitemaps** : `/sitemap-index.xml` → `/sitemap.xml` (7 043 pages) + `/estimation/sitemap/0-83.xml` (45 000 fiches chacun, générés à la volée depuis SQLite par plages de rowid) ;
- JSON-LD `Organization` + `WebSite` global, `robots.txt` propre, `noindex` sur les pages privées/paramétriques.
Les agrégats par municipalité (`src/lib/municipalites.ts`) sont calculés en SQL et mis en cache mémoire par processus — première visite ~1 s (Montréal), ensuite ~30 ms.
## Structure du projet
```
src/
├─ app/
│ ├─ page.tsx / HomeClient.tsx # accueil (recherche + estimation manuelle)
│ ├─ estimation/[id]/ # fiche (SSR + generateMetadata + JSON-LD)
│ ├─ estimation/manuelle/ # estimation sans adresse (noindex)
│ ├─ municipalites/ # index A-Z (programmatique)
│ ├─ municipalite/[slug]/[type]/ # 1 097 + ~5 900 pages programmatiques
│ ├─ carte/ # Vrai-Prix Maps (Ka Maps / Mapbox GL)
│ ├─ ka/ # agent IA (streaming SSE)
│ ├─ parc/ # parc immobilier multi-adresses
│ ├─ stats/ # statistiques provinciales
│ ├─ compte/ # carte de membre Groupe KA (SSO)
│ ├─ methodologie|conditions|confidentialite/
│ ├─ sitemap.ts · estimation/sitemap.ts · sitemap-index.xml/ · robots.ts
│ └─ api/ # endpoints (voir tableau)
├─ components/ # SearchBox, ResultView, MetricViz, RadarMap,
│ # KaCarte, KaChat, StatsView, LangContext…
├─ lib/
│ ├─ engine.ts # moteur comparables (testé Vitest)
│ ├─ estimator.ts # service DB ↔ moteur
│ ├─ db.ts # SQLite (better-sqlite3, WAL)
│ ├─ municipalites.ts # agrégats programmatiques (cache mémoire)
│ ├─ seo.ts # slugs, libellés, constantes SEO
│ ├─ ka/ # agent IA (outils serveur, prompt)
│ ├─ ka-auth.ts · hub-profile.ts # SSO KA ID (JWT HS256, node:crypto pur)
│ └─ report*.ts # 4 générateurs PDF (pdfkit)
└─ data/stats.json # agrégats provinciaux précalculés
```
## Endpoints
| Route | Description |
|---|---|
| `GET /api/search?q=` | Autocomplétion d'adresses (FTS5) |
| `GET /api/estimate?id=` | Estimation complète d'une unité |
| `POST /api/estimate` | Estimation manuelle (municipalité + caractéristiques) |
| `GET /api/nearby?lat&lng&halfLat&halfLng` | Unités dans une boîte englobante (carte) |
| `POST /api/ka` | Agent IA (streaming SSE, boucle d'outils) |
| `POST /api/portfolio` | Agrégation d'un parc (jusqu'à 40 ids) |
| `GET /api/report?id=` | Rapport PDF standard (3 pages) |
| `GET /api/report/pro?id=` | Rapport détaillé format professionnel (6 pages) |
| `GET /api/report/portfolio?ids=` | Rapport de parc consolidé |
| `GET /api/report/stats` | Rapport statistique provincial |
| `GET /api/stats` · `/api/stats/dashboard` | Agrégats provinciaux (métriques live du hub Groupe KA) |
| `GET /api/auth/ka/login` · `/callback` | SSO KA ID (hub groupe-ka.com) |
| `GET /api/auth/me` · `POST /api/auth/logout` | Session (cookie signé, sans table users) |
## Démarrage local
```bash
npm install
# placer la base de données dans data/vraiprix.db (non versionnée)
npm run dev # développement
npm run test # tests du moteur (Vitest)
npm run build && npm run start
```
### Variables d'environnement
| Variable | Rôle |
|---|---|
| `VRAIPRIX_DB` | Chemin de la base SQLite (défaut : `data/vraiprix.db`) |
| `NEXT_PUBLIC_SITE_URL` | URL canonique (défaut : `https://www.vrai-prix.com`) |
| `ANTHROPIC_API_KEY` | Agent IA Ka (page `/ka`) |
| `NEXT_PUBLIC_MAPBOX_TOKEN` | Vrai-Prix Maps (page `/carte`) |
| `KA_SSO_SECRET` · `KA_AUTH_SECRET` · `KA_HUB_URL` · `KA_BASE_URL` | SSO KA ID |
> **Note données** : la base SQLite (1,5 Go) et les pipelines d'entraînement ne sont pas versionnés. Données sources : [rôles d'évaluation foncière du Québec](https://www.donneesquebec.ca/recherche/dataset/roles-d-evaluation-fonciere-du-quebec) (MAMH, licence ouverte). Les renseignements des propriétaires sont caviardés à la source.
> **Captures d'écran** : `node scripts/screenshots.mjs` les régénère depuis la prod (Playwright).
## Déploiement
L'app tourne en production sur le cluster **MacLustr** :
| | |
|---|---|
| **Nœud** | **M3U96a** (Mac Studio M3 Ultra, 32 cœurs / 96 Go) — `~/apps/vrai-prix` |
| **Port** | **8090** (`next start -H 0.0.0.0 -p 8090`) |
| **Domaine** | **[www.vrai-prix.com](https://www.vrai-prix.com)** (tunnel ngrok) |
| **Processus PM2** | **`vrai-prix`** (app Next.js) + **`vrai-prix-ngrok`** (tunnel) |
```bash
# sur M3U96a, après un changement :
cd ~/apps/vrai-prix
npm run build
pm2 restart vrai-prix
pm2 logs vrai-prix --lines 30 # vérifier le démarrage
```
## Développement remote-first
⚠️ **La source de vérité est le repo git sur le nœud M3U96a** (`~/apps/vrai-prix`), **pas** une copie laptop. Toute modification se fait sur le nœud via SSH : édition, `npm run build`, `pm2 restart vrai-prix`, puis commit + push **sur le nœud**.
- Remote **`origin` = spbgit** (git perso, git.spboucher.ai) — sur **M3U96a** le remote pointe directement sur le chemin local **`/Users/simon-pierreboucher/srv/git/vrai-prix.git`** (bare repo) ;
- Remote secondaire `github` → `github.com/spboucher-ai/vrai-prix` ;
- Référence complète du workflow : `~/Desktop/cluster-skill/KA-REMOTE-DEV.md`.
```bash
ssh M3U96a
cd ~/apps/vrai-prix
# … modifier, builder, redémarrer …
git add && git commit -m "…" && git push origin main
```
## Design
Identité « éditorial sharp » : papier grainé `#f5f3ee`, encre `#141814`, **accent rouge vif `#ff5148`**, bordeaux `#9e2a25` — bordures encre 1,5 px, ombres décalées, display uppercase (Space Grotesk), micro-étiquettes mono (JetBrains Mono), ticker marquee, visualisations SVG data-driven (façade du bâtiment, terrain à l'échelle, jauge de confiance, sparklines) — **zéro image bitmap dans l'app**.
## Auteur
**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai)
*Estimations statistiques à titre indicatif — ne remplacent pas une évaluation par un évaluateur agréé (OEAQ). Voir [conditions](https://www.vrai-prix.com/conditions) et [méthodologie complète](https://www.vrai-prix.com/methodologie).*
---
Un service **Groupe Ka**