// Vrai-Prix — méthode du coût : marqueurs de provenance (● observé ◐ calculé ○ indexé ◇ référence/hypothèse · AI), // badges de couche (OBSERVÉ · INFÉRÉ PAR IA · CALCULÉ · PRIX SOURCÉ · HYPOTHÈSE) et origine des caractéristiques. "use client"; import type { AttributeSource, Layer, PriceKind } from "@/lib/cost/types"; export function kindGlyph(kind: PriceKind | null | undefined): string { switch (kind) { case "observed": case "official": return "●"; case "derived": return "◐"; case "indexed": return "○"; case "reference": case "assumption": return "◇"; default: return "·"; } } export function kindLabel(kind: PriceKind | null | undefined, fr: boolean): string { switch (kind) { case "observed": return fr ? "Observé directement" : "Directly observed"; case "official": return fr ? "Publication officielle" : "Official publication"; case "derived": return fr ? "Calculé depuis composants" : "Computed from components"; case "indexed": return fr ? "Indexé" : "Indexed"; case "reference": return fr ? "Référence interne (hypothèse)" : "Internal reference (assumption)"; case "assumption": return fr ? "Hypothèse" : "Assumption"; default: return fr ? "Donnée non disponible" : "Data not available"; } } export function kindTooltip(p: { kind: PriceKind | null | undefined; source?: string | null; observedAt?: string | null; note?: string | null }, fr: boolean): string { const d = p.observedAt ? fmtDate(p.observedAt, fr) : null; switch (p.kind) { case "observed": return fr ? `Observé directement chez ${p.source ?? "un fournisseur"}${d ? ` le ${d}` : ""}.` : `Directly observed at ${p.source ?? "a supplier"}${d ? ` on ${d}` : ""}.`; case "official": return fr ? `Taux publié par ${p.source ?? "une source officielle"}${d ? ` (en vigueur le ${d})` : ""}.` : `Rate published by ${p.source ?? "an official source"}${d ? ` (effective ${d})` : ""}.`; case "indexed": return p.note ?? (fr ? "Observation ancienne actualisée avec un indice Statistique Canada." : "Older observation updated with a Statistics Canada index."); case "derived": return fr ? "Calculé à partir de composants eux-mêmes sourcés." : "Computed from components that are themselves sourced."; case "reference": return fr ? "Prix de référence interne — hypothèse documentée, non observé." : "Internal reference price — documented assumption, not observed."; case "assumption": return p.note ?? (fr ? "Hypothèse documentée (productivité, pourcentage)." : "Documented assumption (productivity, percentage)."); default: return fr ? "Donnée non disponible." : "Data not available."; } } export function fmtDate(iso: string | null | undefined, fr: boolean): string { if (!iso) return "—"; const d = new Date(iso.length <= 10 ? `${iso}T12:00:00` : iso); if (Number.isNaN(d.getTime())) return iso; return d.toLocaleDateString(fr ? "fr-CA" : "en-CA", { year: "numeric", month: "long", day: "numeric" }); } /** Icône de provenance avec infobulle native (title). */ export function KindIcon({ kind, fr, source, observedAt, note, className = "" }: { kind: PriceKind | null | undefined; fr: boolean; source?: string | null; observedAt?: string | null; note?: string | null; className?: string }) { const tone = kind === "observed" || kind === "official" ? "text-ink" : kind === "indexed" || kind === "derived" ? "text-accent" : "text-[var(--amber)]"; return ( {kindGlyph(kind)} ); } /** Légende des icônes de provenance. */ export function KindLegend({ fr }: { fr: boolean }) { const rows: [PriceKind, string][] = [["observed", fr ? "Observé directement" : "Observed directly"], ["derived", fr ? "Calculé depuis composants" : "Computed from components"], ["indexed", fr ? "Indexé (StatCan)" : "Indexed (StatCan)"], ["reference", fr ? "Référence interne · hypothèse" : "Internal reference · assumption"]]; return (
{rows.map(([k, l]) => ( {l} ))} AI{fr ? "Classifié / inféré par IA" : "AI-classified / inferred"}
); } /** Badge de couche (§208) : OBSERVÉ · INFÉRÉ PAR IA · CALCULÉ · PRIX SOURCÉ · HYPOTHÈSE. */ export function LayerBadge({ layer, fr }: { layer: Layer; fr: boolean }) { const map: Record = { observed: { fr: "Observé", en: "Observed", cls: "border-ink bg-ink text-paper" }, ai_inferred: { fr: "Inféré par IA", en: "AI-inferred", cls: "border-accent text-accent" }, computed: { fr: "Calculé", en: "Computed", cls: "border-[var(--line-strong)] text-ink" }, sourced_price: { fr: "Prix sourcé", en: "Sourced price", cls: "border-[var(--line-strong)] text-ink-2" }, assumption: { fr: "Hypothèse", en: "Assumption", cls: "border-[var(--amber)] text-[#8a5a12] bg-[var(--amber-soft)]" }, }; const m = map[layer]; return {fr ? m.fr : m.en}; } /** Origine d'une caractéristique du bâtiment : MAMH / saisi / IA / dérivé / supposé / annonce. */ export function AttrBadge({ source, fr }: { source: AttributeSource | null | undefined; fr: boolean }) { const map: Record = { MAMH: { fr: "MAMH", en: "MAMH", cls: "border-ink bg-ink text-paper" }, cadastral: { fr: "Cadastre", en: "Cadastral", cls: "border-ink bg-ink text-paper" }, listing: { fr: "Annonce", en: "Listing", cls: "border-[var(--line-strong)] text-ink" }, user: { fr: "Saisi", en: "Entered", cls: "border-[var(--line-strong)] text-ink" }, AI: { fr: "IA", en: "AI", cls: "border-accent text-accent" }, derived: { fr: "Dérivé", en: "Derived", cls: "border-[var(--line)] text-ink-2" }, assumed: { fr: "Supposé", en: "Assumed", cls: "border-[var(--amber)] text-[#8a5a12] bg-[var(--amber-soft)]" }, }; const m = map[source ?? "assumed"] ?? map.assumed; return {fr ? m.fr : m.en}; }