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%
11.2 KB · 142 lines markdown
Rendered Raw Blame History
1# Vrai-Prix — Onglet « Coût » : plan technique d'implantation23> Porté d'UQO Éval vers Vrai-Prix le 2026-09-08 (même socle Next 16 ; nœud M3U96a, `~/apps/vrai-prix`). Les chemins et noms de processus ci-dessous sont ceux de Vrai-Prix.45> Rédigé le 2026-09-06 après audit complet du dépôt. Ce document décrit ce qui6> existe, ce qui est ajouté, les sources de données réellement accessibles et7> l'ordre de réalisation. Il est la référence de la fonctionnalité « Méthode du8> coût » et de l'analyse multimodale des annonces « À vendre ».910## 1. Audit du dépôt (état au 2026-09-06)1112| Aspect | Constat |13|---|---|14| Framework | Next.js 16.3 (App Router, React 19, React Compiler lint), TypeScript strict, Tailwind v4 |15| Backend | Route handlers Node (`src/app/api/*`), aucune couche serveur séparée |16| Bases | SQLite via `better-sqlite3` : `data/vraiprix.db` (3,75 M unités MAMH + 745 k ventes, `src/lib/db.ts`), `data/immoka.db` (70 k annonces à vendre + table `vp_eval`, `src/lib/immoka.ts`) |17| UI | Design « terminal éditorial » Vrai-Prix : tokens `--accent` (rouge signal), classes `.kicker .klabel .btn .vp-card .src-table .kv-cell`, `useLang()` FR/EN (`LangContext`), sections numérotées `01 —` |18| Navigation | `HeaderNav.tsx` (desktop + menu mobile CSS), `MobileTabBar.tsx` (KaTabbar 6 onglets), `FooterNav.tsx`, `sitemap.ts` |19| Traduction | Dictionnaire `src/lib/i18n.ts` + libellés inline `fr ? … : …` dans les composants |20| PDF | `pdfkit` (`src/lib/report*.ts`), polices dans `assets/fonts`, logo Vrai-Prix, format Lettre |21| Propriété | `getUnit(id)` → `UnitRow` (aire_etages_m2, nb_etages, année, valeur_terrain, valeur_batiment, valeur_role, lat/lng, municipalite, genre_construction, lien_physique, cubf) ; `estimateByUnitId` → hédonique + comparables |22| Annonces | `getListing(uid)` → photos (`images` JSON), description, `details` JSON, caractéristiques, `eval.unit_id` (jumelage MAMH), `vp_eval` (est, model_est, comps_est, cost_est calibré, role_est) |23| Ka | `src/lib/ka/tools.ts` (`KA_TOOLS` + `runKaTool`), boucle agentique SSE dans `src/app/api/ka/route.ts` (Haiku 4.5) |24| Anthropic | `@anthropic-ai/sdk` déjà installé, `ANTHROPIC_API_KEY` dans `.env.local` (laptop et nœud) |25| Tests | vitest (`src/**/*.test.ts`), moteur d'estimation testé |26| Déploiement | Source laptop `~/Desktop/vrai-prix` → rsync staging M1M32 → `mld deploy vrai-prix --node M2U64` (hook post_sync `npm run build`), PM2 `vrai-prix` :8150, ngrok www.vrai-prix.app |2728Aucune table de coûts, aucun connecteur web, aucun vector store n'existait.2930## 2. Sources externes — faisabilité vérifiée (tests du 2026-09-06)3132| Source | Accès testé | Statut licence retenu | Rôle |33|---|---|---|---|34| **APCHQ** — grilles « Coût horaire de la main-d'œuvre » (PDF publics, media.apchq.com, secteur résidentiel léger/lourd, 2023 → 26 avril 2026) | Firecrawl parse le PDF : taux horaire, vacances 13 %, avantages sociaux, AE, RQAP, FSS, CCQ, CNESST, **total employeur** par métier | `public_open` (documents publics de l'association, métadonnées + valeurs numériques, URL conservée) | **Source principale main-d'œuvre** (coût employeur complet, historisé) |35| **CCQ** — salaires conventionnés | Site en incident de sécurité (services fermés jusqu'au 2026-09-08) ; page « Salaire et taux » accessible mais l'outil de taux est JS/indisponible | `public_open` | Connecteur implanté (détection de changement par hash), synchro reprendra à la remise en service ; APCHQ reflète déjà les conventions CCQ |36| **Statistique Canada** — 18-10-0289-01 Indices des prix de la construction de bâtiments (Montréal, Québec RMR ; résidentiel, maison individuelle, rangée, appartements ; 23 divisions), trimestriel, dernier point 2026-T2 | API WDS ouverte (`getDataFromCubePidCoordAndLatestNPeriods`) | `public_open` (licence ouverte StatCan) | Indexation temporelle, tableau de bord « Coût aujourd'hui », validation macro |37| **Canac** (canac.ca) | Firecrawl : métadonnée `product:price:amount` + unité (« / Chaque ») | `public_open` (prix affichés publiquement, robots.txt permissif) | Prix matériaux |38| **BMR** (bmr.ca) | Firecrawl : `product:price:amount` (attention aux variantes « à partir de ») | `public_open` (URL SEO permises) | Prix matériaux |39| **Patrick Morin** | Firecrawl : `product:price:amount` | `public_open` | Prix matériaux |40| **RONA** | Cloudflare ; URL FR devinée → 404, l'URL EN de la recherche existe | `public_restricted` (à valider par connecteur, faible priorité) | Prix matériaux (si stable) |41| **Home Depot Canada** | Page servie, mais magasin par défaut hors Québec (Niagara Falls) → prix non localisés | `public_restricted` | Désactivé par défaut |42| **Altus Group** — Canadian Cost Guide 2026 | PDF derrière formulaire (courriel requis) | `public_restricted` → import manuel après téléchargement légal | Benchmark $/pi² (import CSV admin) |43| **RSMeans / Gordian** | Licence payante | `licensed` / `manual_import` | Modèle prévu, import CSV/XLSX autorisé seulement |44| **Vrai-Prix (interne)** — coût calibré du duel des méthodes (`marche-stats.json`, `vp_eval.cost_est`) | Local | interne | Benchmark de cohérence (« Trois lectures de la valeur ») |4546Règle : aucun prix n'est inventé. Les quantités d'assemblage, heures de47productivité et pourcentages par défaut sont des **hypothèses documentées**48(`source_type = assumption`, badge « hypothèse ») modifiables par l'utilisateur ;49les prix de référence internes pour les articles sans observation détaillant50(béton livré, fermes, fenêtres…) sont étiquetés `internal_reference` et abaissent51la couverture/confiance. L'interface affiche « Donnée non disponible » plutôt52qu'un nombre fictif.5354## 3. Architecture ajoutée5556```57src/lib/cost/58  types.ts        types partagés (CostInput, CostEstimate, lignes, provenance)59  units.ts        conversion d'unités (source_unit → canonical_unit, facteur conservé)60  taxonomy.ts     divisions MasterFormat, catégories UX, qualités, conditions, matériaux61  db.ts           data/cost.db (schéma §4, migrations versionnées, seeds idempotents)62  seed/           sources, localisations, articles (~130), assemblages (~55), règles63  pricing.ts      prix canonique par article (médiane robuste multi-source, outliers, indexation)64  catalog.ts      accès DB : articles + prix courants, assemblages, taux, facteurs, indices, benchmarks65  geometry.ts     moteur de quantités (empreinte, périmètre, murs, toit, fondations…)66  engine.ts       calcul PUR et déterministe : assemblages → direct → indirect → RCN → dépréciation → valeur67  estimate.ts     service : entrée (propriété MAMH / construction / annonce) → estimation sauvegardée68  connectors/     firecrawl.ts (client), apchq, ccq, statcan, canac, bmr, patrickmorin, rona, homedepot, altus, rsmeans, matching69  ai/             analyse multimodale des annonces (schéma JSON, prompt, images, vecteur, mapping)70src/app/cout/                  landing + atelier (/cout), /cout/assemblage/[code], /cout/admin71src/app/api/cost/*             API publique §672src/app/api/admin/cost/*       synchro, connecteurs, anomalies, mappings (jeton COST_ADMIN_TOKEN)73src/app/a-vendre/[uid]/analyse-cout   analyse IA d'une annonce74src/app/api/listings/[uid]/*  analyse IA, estimation, overrides, similaires75scripts/cost-sync.ts          planificateur (PM2 cron sur le nœud) : connecteurs → observations → prix canoniques76prompts/property-cost-analysis-v1.md77```7879Firecrawl n'est jamais appelé dans le chemin utilisateur : l'UI lit `cost.db`.8081## 4. Schéma `data/cost.db`8283Tables (toutes avec `created_at`) : `cost_sources` (license_status), `cost_raw_observations`84(historisées, `content_hash`, `parser_version`, `status`), `cost_items`,85`cost_item_sources` (URL produit par détaillant, quantité par emballage),86`cost_item_prices` (série temporelle canonique : matériau/main-d'œuvre/équipement,87low/median/high, confiance, `price_kind` observed|derived|indexed|reference),88`labour_rates` (métier, secteur, base, vacances, avantages, cotisations, total employeur,89effective_from/to, source), `cost_locations` (facteurs matériau/main-d'œuvre/équipement),90`construction_cost_indices`, `cost_benchmarks`, `cost_assemblies`, `cost_assembly_components`,91`cost_estimates` (+ `assumptions_json`, versions méthode/base/assemblages, `cost_snapshot_date`),92`cost_estimate_lines` (`calculation_json`), `cost_estimate_snapshots`,93`component_condition_rules`, `connector_runs` (observabilité), `product_mappings`94(matching IA à valider), `listing_ai_analyses`, `listing_ai_overrides`, `listing_images`,95`property_embeddings`, `analysis_conflicts`, `ai_usage`, `cost_meta`.9697## 5. Moteur (déterministe)9899```100matériau_i   = Q × qté_i × (1 + pertes_i) × prix_i × FacteurMatériau101main-d'œuvre = Q × heures_i × taux_métier_i × FacteurMainDœuvre102équipement   = Q × équip_i × taux_équip × FacteurÉquipement103Direct       = Σ (par assemblage, regroupé en catégories UX)104Indirects    = Direct × Σ pct (plans, permis, assurances, gestion, financement…)105Frais gén.   = (Direct + Indirects) × pct ;  Profit = (… + FG) × pct ;  Contingence = (Direct + Indirects) × pct106RCN          = Direct + Indirects + FG + Profit + Contingence107D_phys       = âge-vie (âge effectif / vie économique) OU par composante (vie propre + condition → âge effectif)108V_coût       = V_terrain + RCN − (D_phys + D_fonct + D_ext)109```110Fourchette : σ par ligne (dispersion observée ou ±8 % référence / ±12 % hypothèse,111heures ±15 %) combinés en racine des carrés + composante systématique (facteur112régional, modèle d'assemblage 5 %) → P10/P90. Confiance 0-100 (fraîcheur 20,113couverture 20, localisation 15, main-d'œuvre 15, benchmarks 10, bâtiment 20) → A-D.114115Invariants testés : `RCN ≥ Direct`, `valeur dépréciée ≥ 0`, `V = terrain + dépréciée`,116même entrée + même instantané de prix = même résultat.117118## 6. API119120Publique : `GET /api/cost/overview`, `/items`, `/items/[code]`, `/assemblies`,121`/assemblies/[code]`, `/labour`, `/materials/history`, `/locations`, `/benchmarks`,122`/sources`, `/property?id=` (préremplissage MAMH), `POST /api/cost/estimate`,123`GET /api/cost/estimate/[id]`, `GET /api/cost/report?estimate=` (PDF).124Admin (jeton) : `GET /api/admin/cost/connectors`, `POST /sync/[connector]`,125`GET /anomalies`, `GET|POST /mappings`, `POST /benchmarks/import`.126Annonces : `POST|GET /api/listings/[uid]/ai-cost-analysis`, `/reanalyze`,127`PATCH /override`, `POST|GET /cost-estimate`, `GET /technical-similar`, `GET /ai-analysis-json`.128129## 7. Ordre de réalisation1301311. Audit (fait) · 2. Schéma + seeds · 3. Moteur pur + tests · 4. ≥ 40 assemblages ·1325. Main-d'œuvre APCHQ (+ CCQ stub) · 6. StatCan · 7. Détaillants Firecrawl ·1338. Benchmarks · 9. UI `/cout` · 10. Intégration propriété · 11. Dépréciation ·13412. Sources/audit · 13. PDF · 14. Ka · 15. Admin · 16. Tests, mobile, FR/EN ·13517. Analyse multimodale des annonces (Sonnet 5) · 18. Déploiement M2U64 via mld136(cron PM2 `vrai-prix-cost-sync`, `FIRECRAWL_API_KEY` côté serveur).137138## 8. Hors périmètre du MVP (phase 2/3)139140Vision à partir de plans, embeddings d'images, 500+ articles, localisation par141municipalité fine, import RSMeans réel, entraînement de modèles.142