// ----------------------------------------------------------------------------- // Lou-Ka — Agrégateur de logements à louer (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // fiche/MarketPriceCard.tsx : « Prix et marché » — juste valeur estimée // (fairvalue.py) : valeur, fourchette, écart, confiance, histogramme du // segment (ce loyer en orange, juste valeur en tiret), méthodologie en // accordéon. Données : Res (squelette / vide / erreur). // ----------------------------------------------------------------------------- import { Link } from "react-router-dom"; import { FairValueDetail, Listing, fmtPrice } from "../api"; import { IcoScale } from "../components/Icons"; import { PriceCapsule } from "./PropertyHero"; import { ComparaisonPrix } from "./synthese"; import { Accordion, ErrorState, SectionCard, SkeletonLines, StatTile, NBSP } from "./ui"; import { Res } from "./useFicheData"; const CONF = { fort: "Confiance forte", moyen: "Confiance moyenne", faible: "Estimation indicative" } as const; function Histo({ d, price }: { d: FairValueDetail; price: number }) { const bins = d.histogram; if (!bins || bins.length < 4) return null; const W = 320, H = 92, top = 18, bottom = 16; const lo = bins[0].x0, hi = bins[bins.length - 1].x1; if (hi <= lo) return null; const max = Math.max(...bins.map((b) => b.n), 1); const x = (v: number) => ((Math.min(Math.max(v, lo), hi) - lo) / (hi - lo)) * W; const bw = W / bins.length; const plotH = H - top - bottom; const priceX = x(price), fvX = x(d.fv); const close = Math.abs(priceX - fvX) < 64; const anchor = (px: number) => (px < 56 ? "start" : px > W - 56 ? "end" : "middle"); return ( {bins.map((b, i) => { const h = Math.max(1.5, (b.n / max) * plotH); const on = price >= b.x0 && price < b.x1; return ( {`${b.x0} $ – ${b.x1} $ : ${b.n} annonce${b.n > 1 ? "s" : ""}`} ); })} {!close && Juste valeur} Ce loyer{close ? " / juste valeur" : ""} {lo} $ {hi} $ ); } export default function MarketPriceCard({ l, fv, cmp, onRetry }: { l: Listing; fv: Res; cmp: ComparaisonPrix | null; onRetry: () => void; }) { if (l.price == null) return null; const d = fv.status === "ok" ? fv.data : null; return ( } aside={cmp && }> {fv.status === "loading" && } {fv.status === "error" && Analyse de prix temporairement indisponible.} {(fv.status === "na" || (d && d.verdict == null)) && !d && (

Pas d'analyse de prix pour cette annonce (loyer absent ou segment sans comparables).

)} {d && ( <>
{d.verdict == null && (

{CONF[d.confidence]} — pas assez de comparables fiables pour classer ce loyer.

)}
{CONF[d.confidence]} {d.segment_n.toLocaleString("fr-CA")} annonces comparables {d.comps > 0 && {d.comps} voisines retenues}

Estimation indicative recalculée en continu à partir des annonces comparables du marché (même segment{NBSP}: nombre de chambres, ville, période), méthode {d.method}, modèle {d.model_version}. Ce n'est pas une évaluation officielle.{" "} Comment est calculée la juste valeur ?

)}
); }