Immo-Ka — agrégateur des propriétés à vendre au Québec (73 connecteurs, ~40 000 annonces, React+FastAPI)
Python 47.5%
HTML 27.9%
TypeScript 15.5%
CSS 7.2%
JavaScript 2%
1// -----------------------------------------------------------------------------2// Immo-Ka — Agrégateur de propriétés à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// fiche/ImmoKaScore.tsx : carte « Immo-Ka Score » — anneau 0-100 animé,5// libellé, composantes (emplacement / prix) et méthodologie en accordéon.6// Quand une composante manque, le score est présenté comme partiel ; quand7// aucune n'est fiable, la carte affiche la synthèse sans chiffre.8// -----------------------------------------------------------------------------9import { useEffect, useState } from "react";10import { Ico } from "../components/Icons";11import { ImmoKaScore as Score } from "./synthese";12import { Accordion, SectionCard } from "./ui";1314export function ScoreRing({ value, partial, size = 88 }: { value: number | null; partial?: boolean; size?: number }) {15 const [v, setV] = useState(0);16 useEffect(() => {17 const t = requestAnimationFrame(() => setV(value ?? 0));18 return () => cancelAnimationFrame(t);19 }, [value]);20 const r = 40, c = 2 * Math.PI * r;21 return (22 <div className="ik-score-ring" style={{ width: size, height: size }} role="img"23 aria-label={value != null ? `Immo-Ka Score ${value} sur 100${partial ? ", partiel" : ""}` : "Score non calculable"}>24 <svg viewBox="0 0 100 100" style={{ width: size, height: size }} aria-hidden="true">25 <circle className="bg" cx="50" cy="50" r={r} />26 <circle className={`arc ${partial ? "partial" : ""}`} cx="50" cy="50" r={r}27 strokeDasharray={`${(c * Math.max(0, Math.min(100, v))) / 100} ${c}`} />28 </svg>29 <div className="ik-score-val">30 <div>{value != null ? value : "—"}<small>/ 100</small></div>31 </div>32 </div>33 );34}3536export default function ImmoKaScore({ s, resume }: { s: Score; resume: string[] }) {37 return (38 <SectionCard id="score" title="Immo-Ka Score" icon={<Ico name="sparkles" size={18} />}39 sub={s.partial ? "Score partiel — une composante manque" : undefined}>40 <div className="ik-score">41 <ScoreRing value={s.value} partial={s.partial} />42 <div>43 <div className="ik-score-lbl">44 {s.value != null ? s.label : "Pas assez de données pour un score"}45 </div>46 {resume.length > 0 && <div className="ik-score-sub">{resume.join(" · ")}</div>}47 <div className="ik-score-parts">48 <span className={`ik-score-part ${s.emplacement == null ? "na" : ""}`}>49 Emplacement {s.emplacement != null ? <b>{s.emplacement}</b> : "non évalué"}50 </span>51 <span className={`ik-score-part ${s.prix == null ? "na" : ""}`}>52 Prix {s.prix != null ? <b>{s.prix}</b> : "non évalué"}53 </span>54 </div>55 </div>56 </div>57 <Accordion title="Comment est calculé ce score ?" small>58 <p>{s.explication}</p>59 <p>60 <a href="https://www.vrai-prix.com" target="_blank" rel="noopener noreferrer">Méthodologie Vrai-Prix ↗</a> · <a href="/sources">Données et sources Immo-Ka</a>61 </p>62 </Accordion>63 </SectionCard>64 );65}66