SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
5.0 KB · 126 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Rent-Ka — Rental listings aggregator (Canada, outside Québec)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// components/KaScoresBlock.tsx: "KA Scores" section of the listing page.5//   Global badge + 5 gauges (Walk/Transit/Bike/Quiet/Services), honest6//   detail per score, personalized score from the user's weights7//   (/ka-scores), link to the methodology.8// -----------------------------------------------------------------------------9import { Link } from "react-router-dom";10import { KaScores, KA_DEFAULT_WEIGHTS, fmtDist, kaGlobal, kaWeights } from "../api";11import KaScoreBadge, { KaScoreCircle } from "./KaScoreBadge";1213const CAT_LABELS: Record<string, string> = {14  epicerie: "Grocery", pharmacie: "Pharmacy", parc: "Park", cafe: "Café",15  ecole: "School", clinique: "Clinic", garderie: "Daycare",16  depanneur: "Convenience store", gym: "Gym", bibliotheque: "Library",17};18const FAM_LABELS: Record<string, string> = {19  commerces: "shops", sante: "health", education: "education",20  loisirs: "leisure",21};2223export default function KaScoresBlock({ ks }: { ks: KaScores }) {24  const weights = kaWeights();25  const custom = JSON.stringify(weights) !== JSON.stringify(KA_DEFAULT_WEIGHTS);26  const perso = custom ? kaGlobal(ks, weights) : null;27  const d = ks.details ?? {};28  const walkCats = (d.walk?.cats ?? []).filter((c) => c.dist_m != null).slice(0, 5);2930  return (31    <section className="f-bloc f-kascores" id="ka-scores">32      <h2>33        KA Scores34        {ks.global != null && <KaScoreBadge score={ks.global} />}35        {perso != null && (36          <span className="ka-perso" title="Global score recomputed from your priorities (set on the KA Scores page)">37            you: <b>{Math.round(perso)}</b>38          </span>39        )}40      </h2>4142      <div className="ka-circles">43        <KaScoreCircle score={ks.walk} nom="Walk" />44        <KaScoreCircle score={ks.transit} nom="Transit"45          note={ks.transit == null ? "Not served" : undefined} />46        <KaScoreCircle score={ks.bike} nom="Bike" />47        <KaScoreCircle score={ks.calme} nom="Quiet" />48        <KaScoreCircle score={ks.services} nom="Services" />49      </div>5051      <details className="ka-detail">52        <summary>The detail behind this area's scores</summary>53        <div className="ka-detail-grille">54          {walkCats.length > 0 && (55            <div>56              <h4>Walk</h4>57              <ul>58                {walkCats.map((c) => (59                  <li key={c.cat}>60                    {CAT_LABELS[c.cat] ?? c.cat} : {fmtDist(c.dist_m as number)}61                    {" "}{c.pts >= 99 ? "✓" : c.pts >= 50 ? "~" : "·"}62                  </li>63                ))}64              </ul>65            </div>66          )}67          <div>68            <h4>Transit</h4>69            <ul>70              {d.transit?.arret_bus_m != null && (71                <li>Bus stop at {fmtDist(d.transit.arret_bus_m)}</li>72              )}73              {d.transit?.station_metro_m != null && (74                <li>Subway station at {fmtDist(d.transit.station_metro_m)}</li>75              )}76              {d.transit?.pmd_percentile != null && (77                <li>Area transit service: {d.transit.pmd_percentile}th Canadian percentile (StatCan)</li>78              )}79              {d.transit?.raison && <li>{d.transit.raison}</li>}80            </ul>81            <h4>Bike</h4>82            <ul>83              {d.bike?.km_cyclables_1km != null && (84                <li>{d.bike.km_cyclables_1km.toLocaleString("en-CA")} km of bike lanes within 1 km</li>85              )}86              {d.bike?.raison && <li>{d.bike.raison}</li>}87            </ul>88          </div>89          <div>90            <h4>Quiet</h4>91            <ul>92              {(d.calme?.sources_bruit ?? []).length === 0 && (93                <li>No major noise source detected nearby</li>94              )}95              {(d.calme?.sources_bruit ?? []).map((s) => (96                <li key={s.source}>97                  {s.source}{s.dist_m != null ? ` at ${fmtDist(s.dist_m)}` : ""}98                </li>99              ))}100              {(d.calme?.bonus_parc ?? 0) > 0 && <li>Park nearby ✓</li>}101            </ul>102            {d.services?.familles && (103              <>104                <h4>Services</h4>105                <ul>106                  {Object.entries(d.services.familles).map(([f, n]) => (107                    <li key={f}>{n} {FAM_LABELS[f] ?? f} in the area</li>108                  ))}109                </ul>110              </>111            )}112          </div>113        </div>114      </details>115116      <p className="fine">117        0-100 scores computed from OpenStreetMap and Statistics Canada118        proximity measures — Quiet is an environment estimate, not a sound119        measurement. Computed on{" "}120        {new Date(ks.computed_at * 1000).toLocaleDateString("en-CA")} (scale {ks.version}).{" "}121        <Link to="/ka-scores">How are KA Scores calculated? — and set your priorities</Link>122      </p>123    </section>124  );125}126