// ----------------------------------------------------------------------------- // Lou-Ka — Agrégateur de logements à louer (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // components/QuartierBlock.tsx : section « Le quartier » de la fiche — // démographie du recensement 2021 (aire de diffusion ~500 hab.), scores de // proximité StatCan, îlot de chaleur/fraîcheur INSPQ, criminalité. // ----------------------------------------------------------------------------- import { Quartier } from "../api"; const fmtMoney = (v: number | null | undefined) => v == null ? null : v.toLocaleString("fr-CA", { maximumFractionDigits: 0 }) + " $"; const fmtPct = (v: number | null | undefined) => v == null ? null : Math.round(v) + " %"; // scores PMD affichés (clé backend -> libellé) const PROX_LABELS: [string, string][] = [ ["prox_epicerie", "Épiceries"], ["prox_transport", "Transport en commun"], ["prox_parc", "Parcs"], ["prox_ecole_prim", "Écoles primaires"], ["prox_sante", "Soins de santé"], ["prox_pharmacie", "Pharmacies"], ]; function chaleurBadge(classe: number, ecart: number | null) { if (classe <= 3) return { txt: "Îlot de fraîcheur", cls: "q-badge cool", ico: "🌿" }; if (classe >= 7) return { txt: `Îlot de chaleur${ecart != null ? ` (+${ecart.toFixed(1)} °C)` : ""}`, cls: "q-badge hot", ico: "🌡", }; return { txt: "Température de quartier moyenne", cls: "q-badge neutral", ico: "🌤" }; } export default function QuartierBlock({ q }: { q: Quartier }) { const d = q.demographie; const stats: [string, string | null][] = d ? [ ["Revenu médian des ménages", fmtMoney(d.revenu_median)], ["Ménages locataires", fmtPct(d.pct_locataires)], ["Loyer moyen du secteur", fmtMoney(d.loyer_moyen)], ["Âge médian", d.age_median != null ? `${Math.round(d.age_median)} ans` : null], ["Français à la maison", fmtPct(d.pct_francais)], ["Diplôme universitaire", fmtPct(d.pct_univ)], ] : []; const statsOk = stats.filter(([, v]) => v != null) as [string, string][]; const prox = q.proximite ?? {}; const proxOk = PROX_LABELS.filter(([k]) => typeof prox[k] === "number"); if (statsOk.length === 0 && proxOk.length === 0 && !q.chaleur && !q.crime) return null; return (

Le quartier

Secteur immédiat de l'immeuble (aire de diffusion du recensement, ± 500 habitants).

{statsOk.length > 0 && (
{statsOk.map(([label, val]) => (
{val}
{label}
))}
)} {proxOk.length > 0 && (
{proxOk.map(([k, label]) => { const v = Math.max(0, Math.min(1, prox[k])); return (
{label} {Math.round(v * 100)}
); })}
Accessibilité 0–100 — mesures de proximité de Statistique Canada.
)}
{q.chaleur && (() => { const b = chaleurBadge(q.chaleur.classe, q.chaleur.ecart); return {b.ico} {b.txt}; })()} {q.crime?.type === "points" && ( 🛡 {q.crime.douze_mois} acte{q.crime.douze_mois > 1 ? "s" : ""} criminel{q.crime.douze_mois > 1 ? "s" : ""} à moins de 500 m (12 mois) {q.crime.douze_mois_precedents > 0 && ( q.crime.douze_mois <= q.crime.douze_mois_precedents ? ` · en baisse (${q.crime.douze_mois_precedents} l'année d'avant)` : ` · en hausse (${q.crime.douze_mois_precedents} l'année d'avant)` )} )} {q.crime?.type === "igc" && (() => { const c = q.crime; if (c.indice_canada != null && c.indice_canada > 0) { const delta = Math.round(100 * (c.indice - c.indice_canada) / c.indice_canada); const sous = delta <= 0; return ( 🛡 Criminalité {Math.abs(delta)} % {sous ? "sous" : "au-dessus de"} la moyenne canadienne{sous ? " ✅" : ""} ({c.ville} {c.annee} : {c.indice} · Canada : {c.indice_canada}) ); } return ( 🛡 Gravité de la criminalité ({c.ville}, {c.annee}) : {c.indice} ); })()}
Sources : Statistique Canada (Recensement 2021, licence ouverte), INSPQ (CC-BY 4.0){q.crime?.type === "points" ? ", Ville de Montréal (CC-BY 4.0)" : ""}. Statistiques du secteur, pas de l'immeuble.
); }