SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
8.4 KB · 193 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/KaScores.tsx : « Comment sont calculés les KA Scores ? »5//   Méthodologie publique (transparence obligatoire du barème), couverture6//   du parc en direct, et réglage des priorités personnelles (les curseurs7//   repondèrent le KA Score global affiché sur les fiches).8// -----------------------------------------------------------------------------9import { useEffect, useMemo, useState } from "react";10import {11  KA_DEFAULT_WEIGHTS, KaScoresStats, fetchKaScoresStats, kaWeights,12} from "../api";13import { KaScoreCircle } from "../components/KaScoreBadge";1415const SCORES: { key: string; nom: string; mesure: string; methode: string }[] = [16  {17    key: "walk", nom: "KA Walk Score", mesure: "Tout faire à pied.",18    methode: "Distance de marche estimée au plus proche de chaque besoin "19      + "quotidien (épicerie, pharmacie, parc, café, école, clinique, garderie, "20      + "dépanneur, gym, bibliothèque), pondérée par son importance — une "21      + "épicerie pèse trois fois plus qu'un gym. Pleine note en deçà d'un "22      + "seuil par catégorie (ex. épicerie ≤ 400 m), décroissance linéaire "23      + "ensuite. Petit bonus quand le choix existe (3 épiceries ou cafés à "24      + "moins de 800 m).",25  },26  {27    key: "transit", nom: "KA Transit Score", mesure: "La desserte en transport collectif.",28    methode: "Deux volets : la distance de marche au plus proche arrêt de bus "29      + "ou station de métro (OpenStreetMap), et la qualité de desserte du "30      + "secteur mesurée par Statistique Canada (base des mesures de "31      + "proximité 2021, percentile canadien). Un territoire sans arrêt à "32      + "distance de marche affiche « Non desservi », pas un 0 brut.",33  },34  {35    key: "bike", nom: "KA Bike Score", mesure: "Le vélo au quotidien.",36    methode: "Kilomètres de voies cyclables (pistes et bandes OSM) à moins "37      + "de 1 km, plus l'accessibilité des besoins quotidiens à distance de "38      + "vélo. Le dénivelé n'est pas encore pris en compte (v1).",39  },40  {41    key: "calme", nom: "KA Calme Score", mesure: "La tranquillité estimée du secteur.",42    methode: "Pénalités décroissantes selon la distance aux sources de bruit "43      + "cartographiées : autoroutes, artères principales, voies ferrées, "44      + "aéroports et héliports, zones industrielles, concentration de bars. "45      + "Bonus pour un parc tout proche. C'est une estimation basée sur "46      + "l'environnement, PAS une mesure sonore.",47  },48  {49    key: "services", nom: "KA Services Score", mesure: "La richesse globale des services.",50    methode: "Comptage pondéré des points d'intérêt par famille — commerces "51      + "(1 km), santé (3 km), éducation (3 km), loisirs (1 km) — avec "52      + "rendement décroissant : passer de 0 à 3 épiceries compte beaucoup "53      + "plus que de 12 à 15.",54  },55];5657const NOMS_COURTS: Record<string, string> = {58  walk: "Marche", transit: "Transport", bike: "Vélo",59  calme: "Calme", services: "Services",60};6162export default function KaScoresPage() {63  const [stats, setStats] = useState<KaScoresStats | null>(null);64  const [weights, setWeights] = useState<Record<string, number>>(() => kaWeights());6566  useEffect(() => {67    fetchKaScoresStats().then(setStats).catch(() => {});68    document.title = "KA Scores — méthodologie | Lou-Ka";69  }, []);7071  const somme = useMemo(72    () => Object.values(weights).reduce((a, b) => a + b, 0), [weights]);7374  const setW = (k: string, v: number) => {75    const next = { ...weights, [k]: v };76    setWeights(next);77    try { localStorage.setItem("louka_ks_poids", JSON.stringify(next)); } catch { /* privé */ }78  };79  const reset = () => {80    setWeights(KA_DEFAULT_WEIGHTS);81    try { localStorage.removeItem("louka_ks_poids"); } catch { /* privé */ }82  };8384  return (85    <div className="container page-doc">86      <section className="hero hero-doc">87        <span className="kicker">Transparence</span>88        <h1>Comment sont calculés les <span className="hl">KA Scores</span> ?</h1>89        <p className="lede">90          Cinq scores maison de 0 à 100 évaluent l'emplacement de chaque91          logement : marche, transport collectif, vélo, calme et services.92          Voici exactement comment — méthode, sources et limites.93        </p>94        {stats && (95          <div className="stat-row">96            <span className="stat-chip">97              <b>{stats.couverture_pct.toLocaleString("fr-CA")} %</b> du parc scoré98              ({stats.avec_score.toLocaleString("fr-CA")} annonces)99            </span>100            {stats.moyennes.global?.moyenne != null && (101              <span className="stat-chip">102                score global moyen <b>{stats.moyennes.global.moyenne}</b>103              </span>104            )}105            <span className="stat-chip">barème <b>{stats.version}</b></span>106          </div>107        )}108      </section>109110      <div className="ka-demo-circles" aria-hidden="true">111        <KaScoreCircle score={92} nom="Marche" />112        <KaScoreCircle score={74} nom="Transport" />113        <KaScoreCircle score={61} nom="Vélo" />114        <KaScoreCircle score={45} nom="Calme" />115        <KaScoreCircle score={83} nom="Services" />116      </div>117118      {SCORES.map((s) => (119        <section className="f-bloc doc-bloc" key={s.key}>120          <h2>{s.nom}</h2>121          <p><b>Ce qu'il mesure :</b> {s.mesure}</p>122          <p>{s.methode}</p>123          {stats?.moyennes[s.key]?.moyenne != null && (124            <p className="fine">125              Moyenne du parc : {stats.moyennes[s.key]!.moyenne} —{" "}126              {stats.moyennes[s.key]!.n.toLocaleString("fr-CA")} immeubles évalués.127            </p>128          )}129        </section>130      ))}131132      <section className="f-bloc doc-bloc" id="priorites">133        <h2>Vos priorités, votre score</h2>134        <p>135          Le KA Score global affiché partout est une moyenne pondérée des cinq136          scores. Réglez ici ce qui compte pour <em>vous</em> — les fiches137          afficheront votre score personnalisé à côté du score standard.138          Ces réglages restent sur votre appareil.139        </p>140        <div className="ka-poids">141          {Object.keys(KA_DEFAULT_WEIGHTS).map((k) => (142            <label key={k} className="ka-poids-row">143              <span>{NOMS_COURTS[k]}</span>144              <input145                type="range" min={0} max={50} step={5}146                value={Math.round((weights[k] ?? 0) * 100)}147                onChange={(e) => setW(k, Number(e.target.value) / 100)}148                aria-label={`Importance de ${NOMS_COURTS[k]}`}149              />150              <b>{Math.round(((weights[k] ?? 0) / (somme || 1)) * 100)} %</b>151            </label>152          ))}153        </div>154        <button className="btn btn-ghost" onClick={reset}>155          Revenir aux pondérations standard156        </button>157      </section>158159      <section className="f-bloc doc-bloc">160        <h2>Sources, honnêteté et limites</h2>161        <ul className="doc-liste">162          <li>163            <b>Sources :</b> points d'intérêt, routes, rails et voies cyclables164            © contributeurs <a href="https://www.openstreetmap.org/copyright"165            target="_blank" rel="noreferrer">OpenStreetMap</a> (ODbL) ;166            desserte en transport : Base de données des mesures de proximité,167            Statistique Canada (2021).168          </li>169          <li>170            <b>Distances :</b> à vol d'oiseau multipliées par 1,3 (facteur171            réseau usuel) — pas un routage piéton exact.172          </li>173          <li>174            <b>Données insuffisantes :</b> un secteur mal cartographié (zone175            rurale) affiche « Données insuffisantes » plutôt qu'un score176            trompeur ; un territoire sans transport collectif affiche177            « Non desservi ».178          </li>179          <li>180            <b>Cohérence :</b> les scores sont calculés par immeuble ; deux181            logements du même immeuble partagent exactement les mêmes scores.182          </li>183          <li>184            <b>Versionnage :</b> le barème est versionné ; tout changement de185            méthode recalcule l'ensemble du parc et la date de calcul est186            affichée sur chaque fiche.187          </li>188        </ul>189      </section>190    </div>191  );192}193