// Auteur : Simon-Pierre Boucher — contact@spboucher.ai "use client"; import { useState } from "react"; import Link from "next/link"; import SearchBox from "@/components/SearchBox"; import EstimateForm from "@/components/EstimateForm"; import { locNum, MarketPulseChart } from "@/components/MetricViz"; import { useLang } from "@/components/LangContext"; /** Indice de marché d'un grand type — série + variations (calculé côté serveur). */ export interface PulseItem { type: "unifamilial" | "condo" | "plex"; series: { month: string; idx: number }[]; pct12m: number | null; since2021: number | null; } /** Vente réelle récente appariée à une unité d'évaluation. */ export interface RecentSaleItem { id: string; date: string; amount: number; street: string | null; city: string | null; propertyType: string | null; idProvinc: string | null; vsRolePct: number | null; } const PULSE_LABELS: Record = { unifamilial: { fr: "Unifamiliale", en: "Single-family" }, condo: { fr: "Condo", en: "Condo" }, plex: { fr: "Plex", en: "Plex" }, }; /* attribution FIXE des couleurs de série (jamais recyclée) */ const PULSE_COLORS: Record = { unifamilial: "var(--chart-1)", condo: "var(--chart-2)", plex: "var(--chart-3)", }; /** Variation signée — l'encre porte le positif, le rouge danger le négatif. */ function Pct({ v, digits = 1 }: { v: number | null; digits?: number }) { if (v == null) return —; return ( = 0 ? "text-ink" : "text-[var(--danger)]"}> {v >= 0 ? "+" : "−"} {Math.abs(v).toFixed(digits)} % ); } /** En-tête de section éditorial : filet fort, numéro mono rouge, titre display. */ function SectionHead({ num, kicker, title, lede, }: { num: string; kicker: string; title: string; lede?: string; }) { return (
{num} — {kicker}

{title}

{lede &&

{lede}

}
); } const PERIODS = [ { key: "12", months: 12, fr: "12 mois", en: "12 mo" }, { key: "24", months: 24, fr: "24 mois", en: "24 mo" }, { key: "all", months: null as number | null, fr: "Depuis 2021", en: "Since 2021" }, ] as const; export default function HomeClient({ pulse, recent, salesLast30d, }: { pulse: PulseItem[]; recent: RecentSaleItem[]; salesLast30d: number; }) { const { lang, t } = useLang(); const fr = lang === "fr"; const [period, setPeriod] = useState<(typeof PERIODS)[number]["key"]>("24"); const money = (v: number) => new Intl.NumberFormat(fr ? "fr-CA" : "en-CA", { style: "currency", currency: "CAD", maximumFractionDigits: 0, }).format(v); const dateFmt = (d: string) => new Date(`${d}T12:00:00`).toLocaleDateString(fr ? "fr-CA" : "en-CA", { day: "numeric", month: "short", }); const months = PERIODS.find((p) => p.key === period)?.months ?? 24; const chartData = pulse.map((p) => ({ key: p.type, label: PULSE_LABELS[p.type][lang], color: PULSE_COLORS[p.type], series: p.series, })); const proofs: [string, string][] = [ ["3 747 008", fr ? "propriétés estimées" : "estimated properties"], ["745 119", fr ? "ventes réelles 2021-2026" : "real sales 2021-2026"], [locNum(salesLast30d, lang, { maxFrac: 0 }), fr ? "ventes captées / 30 jours" : "sales captured / 30 days"], ["1 100", fr ? "municipalités couvertes" : "municipalities covered"], ]; return (
{/* ================= HÉRO — terminal encre pleine largeur ================= */}
{fr ? "Terminal public de valeur immobilière — Québec" : "Public real-estate value terminal — Québec"}

{fr ? ( <> La vraie valeur.
Sans boîte noire. ) : ( <> The true value.
No black box. )}

{t("sub")}

{/* -------- la recherche : l'action centrale de la page -------- */}

{fr ? "Entrez une adresse au Québec" : "Enter a Québec address"}

{t("dataNote")}

{/* -------- preuves de profondeur de données, intégrées -------- */}
{proofs.map(([num, label], i) => (

{num} {i === 2 && (

{label}

))}
{/* ============ le principe, en une ligne (pas de boîtes) ============ */}
{(fr ? [ ["01", "Cherchez l'adresse", "3,7 M d'unités d'évaluation, recherche tolérante aux fautes."], ["02", "Deux sources croisées", "Modèle hédonique (690 k ventes) + comparables réels ajustés en dollars."], ["03", "Fourchette + confiance", "P10-P90, indice A-D — le calcul montré en entier."], ] : [ ["01", "Search the address", "3.7M assessment units, typo-tolerant search."], ["02", "Two sources, crossed", "Hedonic model (690k sales) + real comparables adjusted in dollars."], ["03", "Range + confidence", "P10-P90, A-D index — the full math, shown."], ] ).map(([num, title, body]) => (
{num}

{title} {body}

))}
{/* ================= MARCHÉ — data visualization éditoriale ================= */}
{/* contrôles : période (une rangée, au-dessus du graphique) */}
{PERIODS.map((p) => ( ))}
{/* légende — l'identité ne repose jamais sur la couleur seule */}
{chartData.map((s) => ( {s.label} ))}
{/* lecture par type : variations en colonnes de données */}
{pulse.map((p) => (

{PULSE_LABELS[p.type][lang]}

12 {fr ? "mois" : "mo"}  2021→ 

))}
{/* ================= VENTES RÉELLES — flux prix-d'abord ================= */} {recent.length > 0 && (
{recent.map((s) => { const inner = (
{/* le prix d'abord — la donnée mène */}

{money(s.amount)}

{s.street} {s.city ? `, ${s.city}` : ""}

{dateFmt(s.date)} {s.propertyType && s.propertyType !== "indéterminé" ? ` · ${s.propertyType}` : ""} {s.vsRolePct != null && Math.abs(s.vsRolePct) < 400 && ( <> {" · "} = 0 ? "text-ink-2" : "text-[var(--danger)]"}> {s.vsRolePct >= 0 ? "+" : "−"} {Math.abs(s.vsRolePct).toFixed(0)} % {fr ? "vs rôle" : "vs roll"} )}

{s.idProvinc && (

{fr ? "Voir l'estimation" : "See the estimate"} →

)}
); return s.idProvinc ? ( {inner} ) : (
{inner}
); })}
)} {/* ================= LE CALCUL, MONTRÉ — promesse centrale ================= */}
    {(fr ? [ ["Propriété identifiée", "3,7 M d'unités d'évaluation géoréférencées (MAMH)."], ["Rôle d'évaluation récupéré", "Valeurs terrain + bâtiment, six millésimes 2021-2026."], ["Ventes comparables sélectionnées", "Rayon adaptatif, même famille de type, aire ±20 %."], ["Caractéristiques ajustées", "Marché, superficie, âge — chaque ajustement affiché en dollars."], ["Estimation calculée", "65 % modèle hédonique + 35 % comparables pondérés."], ["Intervalle de confiance produit", "Fourchette P10-P90 et indice A-D, toujours affichés."], ] : [ ["Property identified", "3.7M georeferenced assessment units (MAMH)."], ["Assessment roll retrieved", "Land + building values, six vintages 2021-2026."], ["Comparable sales selected", "Adaptive radius, same type family, area ±20%."], ["Characteristics adjusted", "Market, area, age — every adjustment shown in dollars."], ["Estimate computed", "65% hedonic model + 35% weighted comparables."], ["Confidence interval produced", "P10-P90 range and A-D index, always shown."], ] ).map(([title, body], i, arr) => (
  1. 0{i + 1} {title}

    {body}

  2. ))}
{(lang === "fr" ? [ ["Le calcul, montré", "Chaque ajustement de chaque comparable est affiché en dollars. Pas de score magique."], ["Fourchette honnête", "Un prix unique est un mensonge statistique. On donne P10-P90 et un indice de confiance A-D."], ["Données réelles", "Rôles fonciers officiels (MAMH) et ventes publiées — locales et récentes en priorité."], ] : [ ["The math, shown", "Every adjustment on every comparable is displayed in dollars. No magic score."], ["Honest range", "A single price is a statistical lie. We give P10-P90 and an A-D confidence index."], ["Real data", "Official assessment rolls (MAMH) and published sales — local and recent first."], ] ).map(([title, body]) => (

{title}

{body}

))}

{fr ? "Méthodologie complète — niveau rapport technique" : "Full methodology — technical-report depth"} →

{/* ================= PLAN B — estimation sans adresse ================= */}
); }