// ----------------------------------------------------------------------------- // Lou-Ka — Location court terme // components/CtPriceAnalysis.tsx : bloc « Analyse du prix / nuit » (fiche CT) // Position du tarif dans la distribution des hébergements comparables // (segment : type + capacité + région touristique, calculé par l'API). // ----------------------------------------------------------------------------- import { CtPriceContext, fmtNight } from "../ctapi"; export function ctDealBadge(p: CtPriceContext | null) { if (!p || p.deviation == null || p.verdict == null) return null; const pct = Math.round(Math.abs(p.deviation) * 100); if (p.verdict === "sous") return { cls: "deal-good", txt: `≈ ${pct} % sous le prix médian du segment` }; if (p.verdict === "dessus") return { cls: "deal-high", txt: `≈ ${pct} % au-dessus du prix médian` }; return { cls: "deal-ok", txt: "Dans les prix du segment" }; } function Histo({ p, price }: { p: CtPriceContext; price: number }) { const bins = p.histogram; if (!bins || bins.length < 4) return null; const W = 320, H = 96, 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), medX = x(p.median); const close = Math.abs(priceX - medX) < 64; const lblAnchor = (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 inBin = price >= b.x0 && price < b.x1; return ( {`${b.x0} $ – ${b.x1} $ : ${b.n} hébergement${b.n > 1 ? "s" : ""}`} ); })} {/* fourchette interquartile (bande) */} {/* marqueur médiane */} {!close && ( Médiane )} {/* marqueur du prix demandé */} Ce prix{close ? " / médiane" : ""} {lo} $ {hi} $ ); } export default function CtPriceAnalysis({ p, price }: { p: CtPriceContext | null; price: number | null }) { if (!p || price == null) return null; const badge = ctDealBadge(p); return (

Analyse du prix Lou-Ka

{fmtNight(p.median)} / nuit
Prix médian du segment · 50 % des prix entre {fmtNight(p.p25)} et {fmtNight(p.p75)}
{badge &&
{badge.txt}
}
Segment : {p.segment} {p.n.toLocaleString("fr-CA")} hébergements comparables Moins cher que {100 - p.percentile} % du segment

Comparaison indicative des tarifs affichés (« à partir de ») par les sources pour des hébergements du même type dans la même région — les prix réels varient selon les dates et la durée du séjour.

); }