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%

README ultra-détaillé — captures d'écran, visite guidée, badges enrichis

10 captures Playwright prises en prod (docs/screenshots/, desktop @1600px
+ mobile @780px, régénérables via scripts/screenshots.mjs) : accueil,
fiche+portrait, Vrai-Prix Maps, Ka, stats, page municipalité,
méthodologie, mobile. Nouveau README : sommaire, visite guidée en
galerie, sections Ka (12 outils), SEO programmatique (3,76 M d'URLs,
sitemaps), structure du projet, endpoints complets (nearby/ka/auth),
variables d'environnement, badges municipalités/URLs/Tailwind/SQLite/iOS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 1 mo ago (Aug 13, 2026) parent 418cc84

12 changed files +190 −22

modified README.md +138 −22
@@ -1,38 +1,85 @@
1 +<div align="center">
2 +
1 3 # Vrai-Prix
2 4
3 −> **La vraie valeur de votre propriété, sans boîte noire.**
4 −> Moteur d'évaluation immobilière transparent pour le Québec — 3,7 millions de propriétés, 745 119 ventes réelles, chaque calcul montré en entier.
5 +**La vraie valeur de votre propriété, sans boîte noire.**
6 +
7 +Moteur d'évaluation immobilière transparent pour le Québec — 3,7 millions de propriétés,
8 +745 119 ventes réelles, chaque calcul montré en entier.
5 9
6 10 ![Propriétés](https://img.shields.io/badge/propri%C3%A9t%C3%A9s-3%C2%A0747%C2%A0008-ff5148?style=flat-square)
7 11 ![Ventes](https://img.shields.io/badge/ventes_r%C3%A9elles-745%C2%A0119-ff5148?style=flat-square)
12 +![Municipalités](https://img.shields.io/badge/municipalit%C3%A9s-1%C2%A0097-ff5148?style=flat-square)
8 13 ![MdAPE](https://img.shields.io/badge/MdAPE-11,0%25-9e2a25?style=flat-square)
9 14 ![Ratio médian](https://img.shields.io/badge/ratio_m%C3%A9dian-0,995-9e2a25?style=flat-square)
10 15 ![Appariement](https://img.shields.io/badge/fusion_spatiale-99,88%25-9e2a25?style=flat-square)
16 +![URLs indexables](https://img.shields.io/badge/URLs_indexables-3,76_M-9e2a25?style=flat-square)
11 17 ![Tests](https://img.shields.io/badge/tests-15%2F15-141814?style=flat-square)
12 18 ![Next.js](https://img.shields.io/badge/Next.js-16-141814?style=flat-square&logo=nextdotjs)
13 19 ![TypeScript](https://img.shields.io/badge/TypeScript-strict-141814?style=flat-square&logo=typescript)
20 +![Tailwind](https://img.shields.io/badge/Tailwind-4-141814?style=flat-square&logo=tailwindcss)
21 +![SQLite](https://img.shields.io/badge/SQLite-FTS5-141814?style=flat-square&logo=sqlite)
22 +![iOS](https://img.shields.io/badge/iOS-SwiftUI-141814?style=flat-square&logo=apple)
14 23
15 24 **En production : [www.vrai-prix.com](https://www.vrai-prix.com)**
16 25
26 +<img src="docs/screenshots/accueil.png" alt="Accueil Vrai-Prix — La vraie valeur, sans boîte noire" width="920">
27 +
28 +</div>
29 +
17 30 ---
18 31
32 +## Sommaire
33 +
34 +- [Ce que fait Vrai-Prix](#ce-que-fait-vrai-prix)
35 +- [Visite guidée](#visite-guidée)
36 +- [Les chiffres qui comptent](#les-chiffres-qui-comptent)
37 +- [Architecture](#architecture)
38 +- [Le moteur d'estimation](#le-moteur-destimation-srclibenginets)
39 +- [Ka — l'agent IA](#ka--lagent-ia)
40 +- [SEO programmatique](#seo-programmatique--376-m-durls-indexables)
41 +- [Structure du projet](#structure-du-projet)
42 +- [Endpoints](#endpoints)
43 +- [Démarrage](#démarrage)
44 +- [Design](#design)
45 +- [Auteur](#auteur)
46 +
19 47 ## Ce que fait Vrai-Prix
20 48
21 49 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.
22 50
23 51 | Fonctionnalité | Description |
24 52 |---|---|
25 −| 🔎 Recherche d'adresse | Plein-texte (FTS5) parmi 3 747 008 unités d'évaluation |
53 +| 🔎 Recherche d'adresse | Plein-texte (FTS5) parmi 3 747 008 unités d'évaluation, tolérante aux fautes |
26 54 | 💰 Estimation hybride | 65 % modèle hédonique + 35 % comparables ajustés |
27 55 | 📊 Fourchette + confiance | P10–P90 par régression quantile, indice A–D obligatoire |
28 56 | 🏠 Portrait de la propriété | Façade SVG dessinée par les données du registre, terrain à l'échelle, composition de la valeur |
29 −| 🗺️ Plan des comparables | Carte « plan d'arpenteur » maison (SVG pur, zéro tuile) — azimut et distance réels |
30 −| 📈 Historique 2021→2026 | Six millésimes d'estimations par propriété |
57 +| 🗺️ 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 |
58 +| 📐 Plan des comparables | Carte « plan d'arpenteur » maison (SVG pur, zéro tuile) — azimut et distance réels |
59 +| 📈 Historique 2021→2026 | Six millésimes d'estimations par propriété, badges YoY |
60 +| 🤖 Ka · agent IA | Agent conversationnel (Claude) branché sur 12 outils serveur : évaluation, comparables, stats, dossier d'expert |
31 61 | 🏢 Parc immobilier | Évaluation multi-adresses agrégée (jusqu'à 40 propriétés) + rapport consolidé |
32 62 | 📄 Rapports PDF | Standard (3 p.), professionnel bancaire (6 p.), parc (n+1 p.), statistique provincial — 100 % vectoriels |
33 63 | 📉 Statistiques | Valeur totale de la province (**2,01 billions $**), palmarès des 200 municipalités |
64 +| 🏙️ Pages municipalités | 1 097 pages programmatiques (médiane, croissance, médianes par type) + ~5 900 pages municipalité × type |
65 +| 🔑 KA ID | Connexion unique du Groupe KA (SSO signé via le hub groupe-ka.com, session sans base d'utilisateurs) |
66 +| 🌐 Bilingue | FR (défaut) / EN, bascule instantanée côté client |
67 +| 📱 App iOS | Compagnon natif SwiftUI (onglet carte MapKit) — dépôt séparé |
34 68 | ⚖️ Conformité | Loi 25, sélecteur de témoins, méthodologie publique intégrale |
35 69
70 +## Visite guidée
71 +
72 +| | |
73 +|---|---|
74 +| **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 |
75 +| <img src="docs/screenshots/fiche-estimation.png" alt="Fiche d'estimation" width="460"> | <img src="docs/screenshots/fiche-portrait.png" alt="Portrait de la propriété" width="460"> |
76 +| **Vrai-Prix Maps** — votre quartier, estimé (grappes de valeurs 2026) | **Ka · agent IA** — évaluation en conversant, calcul montré |
77 +| <img src="docs/screenshots/carte.png" alt="Recherche par carte" width="460"> | <img src="docs/screenshots/ka-agent.png" alt="Agent IA Ka" width="460"> |
78 +| **Statistiques provinciales** — 2,01 billions $, palmarès des municipalités | **Pages municipalités** — 1 097 pages programmatiques indexables |
79 +| <img src="docs/screenshots/stats.png" alt="Statistiques provinciales" width="460"> | <img src="docs/screenshots/municipalite.png" alt="Page municipalité Gatineau" width="460"> |
80 +| **Méthodologie publique** — 7 sections, normes IAAO, rien de caché | **Mobile** — même expérience, menu plein écran pur CSS |
81 +| <img src="docs/screenshots/methodologie.png" alt="Méthodologie" width="460"> | <img src="docs/screenshots/accueil-mobile.png" alt="Accueil mobile" width="230"> <img src="docs/screenshots/fiche-mobile.png" alt="Fiche mobile" width="230"> |
82 +
36 83 ## Les chiffres qui comptent
37 84
38 85 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** :
@@ -65,7 +112,9 @@ Validation sur **102 943 ventes jamais vues** du modèle (holdout aléatoire) et
65 112 ┌─ Application Next.js 16 ─────────────────────────▼───────────────┐
66 113 │ SQLite (units + FTS5 + transactions + indice de marché) │
67 114 │ Moteur comparables (ajust. marché/superficie/âge, médiane │
68 −│ pondérée) · RadarMap SVG · MetricViz · rapports pdfkit │
115 +│ pondérée) · Ka Maps (Mapbox GL) · agent Ka (Claude + 12 outils) │
116 +│ RadarMap SVG · MetricViz · rapports pdfkit · SSO KA ID │
117 +│ SEO programmatique (1 097 municipalités · sitemaps 3,76 M URLs) │
69 118 └──────────────────────────────────────────────────────────────────┘
70 119 ```
71 120
@@ -73,13 +122,11 @@ Validation sur **102 943 ventes jamais vues** du modèle (holdout aléatoire) et
73 122
74 123 - **Frontend / API** : Next.js 16 (App Router), TypeScript strict, Tailwind CSS 4
75 124 - **Base** : SQLite (better-sqlite3) — 3,75 M unités, FTS5, index spatiaux *(non incluse dans ce dépôt)*
125 +- **Cartes** : [`@groupe-ka/ka-maps`](https://github.com/spboucher-ai) (framework carto partagé du Groupe KA, moteur Mapbox GL, style Standard 3D)
126 +- **Agent IA** : SDK Anthropic (Claude Haiku 4.5, streaming SSE, boucle agentique manuelle, prompt caching)
76 127 - **Rapports** : pdfkit, 100 % vectoriels, polices embarquées (Space Grotesk / Inter / JetBrains Mono)
77 128 - **Modèle** (pipeline externe) : LightGBM, scikit-learn, pandas, pyogrio/GDAL
78 −- **Tests** : Vitest (moteur d'estimation) + Playwright (mobile)
79 −
80 −### Design
81 −
82 −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, micro-étiquettes mono.
129 +- **Tests** : Vitest (moteur d'estimation) + Playwright (mobile, captures d'écran)
83 130
84 131 ## Le moteur d'estimation (`src/lib/engine.ts`)
85 132
@@ -89,19 +136,56 @@ Identité « éditorial sharp » : papier grainé `#f5f3ee`, encre `#141814`, **
89 136 4. **Fusion** : 65 % modèle hédonique pré-calculé + 35 % médiane pondérée des comparables ;
90 137 5. **Confiance** : nombre de comparables, dispersion, largeur de fourchette → A/B/C/D.
91 138
92 −## Démarrage
139 +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 »).
93 140
94 −```bash
95 −npm install
96 −# placer la base de données dans data/vraiprix.db (non versionnée)
97 −npm run dev # développement
98 −npm run test # tests du moteur (Vitest)
99 −npm run build && npm run start
100 −```
141 +## Ka — l'agent IA
101 142
102 −Variables d'environnement : `VRAIPRIX_DB` (chemin de la base SQLite), `PORT`.
143 +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ë.
103 144
104 −> **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.
145 +## SEO programmatique — 3,76 M d'URLs indexables
146 +
147 +Tout le contenu est servi **en HTML complet dès la première requête** (SSR), avec un maillage interne crawlable :
148 +
149 +- **`/municipalites`** → index A-Z des 1 097 municipalités ;
150 +- **`/municipalite/[slug]`** (1 097 pages) → médiane 2026, valeur totale, croissance 2021→2026, médianes par type, 24 fiches liées, municipalités voisines ;
151 +- **`/municipalite/[slug]/[type]`** (~5 900 pages) → `maison`, `plex`, `chalet`, `condo`, `terrain`, `maison-mobile`, `autre` ;
152 +- **`/estimation/[id]`** (3,75 M fiches) → title et meta description **uniques par fiche** (adresse + estimation), canonical, JSON-LD `SingleFamilyResidence`/`Residence`/`Place` + `BreadcrumbList` ;
153 +- **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) ;
154 +- JSON-LD `Organization` + `WebSite` global, `robots.txt` propre, `noindex` sur les pages privées/paramétriques.
155 +
156 +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.
157 +
158 +## Structure du projet
159 +
160 +```
161 +src/
162 +├─ app/
163 +│ ├─ page.tsx / HomeClient.tsx # accueil (recherche + estimation manuelle)
164 +│ ├─ estimation/[id]/ # fiche (SSR + generateMetadata + JSON-LD)
165 +│ ├─ estimation/manuelle/ # estimation sans adresse (noindex)
166 +│ ├─ municipalites/ # index A-Z (programmatique)
167 +│ ├─ municipalite/[slug]/[type]/ # 1 097 + ~5 900 pages programmatiques
168 +│ ├─ carte/ # Vrai-Prix Maps (Ka Maps / Mapbox GL)
169 +│ ├─ ka/ # agent IA (streaming SSE)
170 +│ ├─ parc/ # parc immobilier multi-adresses
171 +│ ├─ stats/ # statistiques provinciales
172 +│ ├─ compte/ # carte de membre Groupe KA (SSO)
173 +│ ├─ methodologie|conditions|confidentialite/
174 +│ ├─ sitemap.ts · estimation/sitemap.ts · sitemap-index.xml/ · robots.ts
175 +│ └─ api/ # endpoints (voir tableau)
176 +├─ components/ # SearchBox, ResultView, MetricViz, RadarMap,
177 +│ # KaCarte, KaChat, StatsView, LangContext…
178 +├─ lib/
179 +│ ├─ engine.ts # moteur comparables (testé Vitest)
180 +│ ├─ estimator.ts # service DB ↔ moteur
181 +│ ├─ db.ts # SQLite (better-sqlite3, WAL)
182 +│ ├─ municipalites.ts # agrégats programmatiques (cache mémoire)
183 +│ ├─ seo.ts # slugs, libellés, constantes SEO
184 +│ ├─ ka/ # agent IA (outils serveur, prompt)
185 +│ ├─ ka-auth.ts · hub-profile.ts # SSO KA ID (JWT HS256, node:crypto pur)
186 +│ └─ report*.ts # 4 générateurs PDF (pdfkit)
187 +└─ data/stats.json # agrégats provinciaux précalculés
188 +```
105 189
106 190 ## Endpoints
107 191
@@ -110,14 +194,46 @@ Variables d'environnement : `VRAIPRIX_DB` (chemin de la base SQLite), `PORT`.
110 194 | `GET /api/search?q=` | Autocomplétion d'adresses (FTS5) |
111 195 | `GET /api/estimate?id=` | Estimation complète d'une unité |
112 196 | `POST /api/estimate` | Estimation manuelle (municipalité + caractéristiques) |
197 +| `GET /api/nearby?lat&lng&halfLat&halfLng` | Unités dans une boîte englobante (carte) |
198 +| `POST /api/ka` | Agent IA (streaming SSE, boucle d'outils) |
113 199 | `POST /api/portfolio` | Agrégation d'un parc (jusqu'à 40 ids) |
114 200 | `GET /api/report?id=` | Rapport PDF standard (3 pages) |
115 201 | `GET /api/report/pro?id=` | Rapport détaillé format professionnel (6 pages) |
116 202 | `GET /api/report/portfolio?ids=` | Rapport de parc consolidé |
117 203 | `GET /api/report/stats` | Rapport statistique provincial |
204 +| `GET /api/auth/ka/login` · `/callback` | SSO KA ID (hub groupe-ka.com) |
205 +| `GET /api/auth/me` · `POST /api/auth/logout` | Session (cookie signé, sans table users) |
206 +
207 +## Démarrage
208 +
209 +```bash
210 +npm install
211 +# placer la base de données dans data/vraiprix.db (non versionnée)
212 +npm run dev # développement
213 +npm run test # tests du moteur (Vitest)
214 +npm run build && npm run start
215 +```
216 +
217 +### Variables d'environnement
218 +
219 +| Variable | Rôle |
220 +|---|---|
221 +| `VRAIPRIX_DB` | Chemin de la base SQLite (défaut : `data/vraiprix.db`) |
222 +| `NEXT_PUBLIC_SITE_URL` | URL canonique (défaut : `https://www.vrai-prix.com`) |
223 +| `ANTHROPIC_API_KEY` | Agent IA Ka (page `/ka`) |
224 +| `NEXT_PUBLIC_MAPBOX_TOKEN` | Vrai-Prix Maps (page `/carte`) |
225 +| `KA_SSO_SECRET` · `KA_AUTH_SECRET` · `KA_HUB_URL` · `KA_BASE_URL` | SSO KA ID |
226 +
227 +> **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.
228 +
229 +> **Captures d'écran** : `node scripts/screenshots.mjs` les régénère depuis la prod (Playwright).
230 +
231 +## Design
232 +
233 +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**.
118 234
119 235 ## Auteur
120 236
121 −**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai)
237 +**Simon-Pierre Boucher** — [contact@spboucher.ai](mailto:contact@spboucher.ai) · Groupe KA
122 238
123 239 *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).*
added docs/screenshots/accueil-mobile.png +0 −0

Binary file not shown.

added docs/screenshots/accueil.png +0 −0

Binary file not shown.

added docs/screenshots/carte.png +0 −0

Binary file not shown.

added docs/screenshots/fiche-estimation.png +0 −0

Binary file not shown.

added docs/screenshots/fiche-mobile.png +0 −0

Binary file not shown.

added docs/screenshots/fiche-portrait.png +0 −0

Binary file not shown.

added docs/screenshots/ka-agent.png +0 −0

Binary file not shown.

added docs/screenshots/methodologie.png +0 −0

Binary file not shown.

added docs/screenshots/municipalite.png +0 −0

Binary file not shown.

added docs/screenshots/stats.png +0 −0

Binary file not shown.

added scripts/screenshots.mjs +52 −0
@@ -0,0 +1,52 @@
1 +// Captures d'écran du site en prod pour le README (jetable).
2 +import { chromium } from "playwright";
3 +import { mkdirSync } from "fs";
4 +
5 +const OUT = "docs/screenshots";
6 +mkdirSync(OUT, { recursive: true });
7 +const BASE = "https://www.vrai-prix.com";
8 +const FICHE = "/estimation/66023993631880550000000";
9 +
10 +const SHOTS = [
11 + { path: "/", name: "accueil", wait: 2500 },
12 + { path: FICHE, name: "fiche-estimation", wait: 3500 },
13 + { path: FICHE, name: "fiche-portrait", wait: 3500, scrollTo: 1800 },
14 + { path: "/carte", name: "carte", wait: 9000 },
15 + { path: "/stats", name: "stats", wait: 3200 },
16 + { path: "/ka", name: "ka-agent", wait: 2500 },
17 + { path: "/municipalite/gatineau", name: "municipalite", wait: 2000 },
18 + { path: "/methodologie", name: "methodologie", wait: 2000 },
19 + { path: "/", name: "accueil-mobile", wait: 2500, mobile: true },
20 + { path: FICHE, name: "fiche-mobile", wait: 3500, mobile: true },
21 +];
22 +
23 +const browser = await chromium.launch();
24 +for (const s of SHOTS) {
25 + const ctx = await browser.newContext({
26 + viewport: s.mobile ? { width: 390, height: 844 } : { width: 1440, height: 900 },
27 + deviceScaleFactor: 2,
28 + locale: "fr-CA",
29 + isMobile: !!s.mobile,
30 + });
31 + // masque le bandeau de témoins (consentement déjà donné)
32 + await ctx.addInitScript(() => {
33 + try {
34 + localStorage.setItem(
35 + "vp-cookie-consent",
36 + JSON.stringify({ necessary: true, analytics: false, ts: 1755000000000 })
37 + );
38 + localStorage.setItem("vp-lang", "fr");
39 + } catch {}
40 + });
41 + const page = await ctx.newPage();
42 + await page.goto(BASE + s.path, { waitUntil: "networkidle", timeout: 60000 }).catch(() => {});
43 + await page.waitForTimeout(s.wait);
44 + if (s.scrollTo) {
45 + await page.evaluate((y) => window.scrollTo({ top: y, behavior: "instant" }), s.scrollTo);
46 + await page.waitForTimeout(1200);
47 + }
48 + await page.screenshot({ path: `${OUT}/${s.name}.png` });
49 + console.log("✓", s.name);
50 + await ctx.close();
51 +}
52 +await browser.close();
53