import type { ToolCallView } from '@/lib/types'; import { fmtCAD, fmtNum, fmtPct } from '@/lib/format'; const LABELS: Record = { value: 'Valeur', land_value: 'Valeur du terrain', cost_new: 'Coût neuf', total_depreciation: 'Dépréciation totale', depreciated_cost: 'Coût déprécié', depreciation_ratio: 'Taux de dépréciation', ratio: 'Ratio', noi: 'RNE', cap_rate: 'TGA', gim: 'MRB', land_value_pv: 'Valeur du terrain (VA)', per_lot: 'Par lot', gross_sales: 'Recettes brutes', costs: 'Coûts', profit: 'Profit', net_undiscounted: 'Net non actualisé', building_income: 'Revenu attribuable au bâtiment', residual_income: 'Revenu résiduel (terrain)', index_ratio: 'Ratio d’indices', direct_costs: 'Coûts directs', extras: 'Extras', indirect_costs: 'Coûts indirects', entrepreneur_profit: 'Profit de l’entrepreneur', cost_per_unit_all_in: 'Coût unitaire tout compris', effective_age: 'Âge effectif', annual_rate: 'Taux annuel', depreciation: 'Dépréciation', land_ratio: 'Ratio terrain', curable_physical: 'Physique récupérable', short_lived: 'Courte vie', long_lived: 'Longue vie', functional: 'Fonctionnelle', external: 'Externe', min: 'Minimum', max: 'Maximum', mean: 'Moyenne (indicatif)', median: 'Médiane', weighted: 'Pondération réconciliée', least_adjusted: 'Comparable le moins ajusté', least_adjusted_price: 'Prix ajusté (moins ajusté)', }; const PCT = new Set(['depreciation_ratio', 'ratio', 'cap_rate', 'annual_rate', 'land_ratio', 'time_pct', 'net_pct', 'gross_pct']); const PLAIN = new Set(['gim', 'index_ratio', 'effective_age', 'unit']); function fmt(k: string, v: unknown): string { if (v === null || v === undefined) return '—'; if (typeof v === 'number') { if (PCT.has(k)) return fmtPct(v, 2); if (PLAIN.has(k)) return fmtNum(v, 2); return Math.abs(v) >= 1000 ? fmtCAD(v, 0) : fmtNum(v, 2); } return String(v); } interface Comp { name: string; price: number; time_pct: number; time_adjusted: number; adjustments: Record; net: number; adjusted_price: number; net_pct: number; gross_pct: number; reliable: boolean } export function AppraisalCalcCard({ call }: { call: ToolCallView }) { const p = call.payload as { function?: string; result?: Record }; const r = p.result || {}; const table = r.table as unknown[][] | undefined; const comps = r.comparables as Comp[] | undefined; const stats = r.stats as Record | undefined; const components = r.components as Record | undefined; const scalarKeys = Object.keys(r).filter((k) => !['formula', 'table', 'comparables', 'stats', 'components', 'note'].includes(k) && typeof r[k] !== 'object'); const adjKeys = comps ? [...new Set(comps.flatMap((c) => Object.keys(c.adjustments)))] : []; return (
{r.formula ?
{String(r.formula)}
: null} {scalarKeys.length > 0 && (
{scalarKeys.map((k) => (
{LABELS[k] || k.replace(/_/g, ' ')}
{fmt(k, r[k])}
))}
)} {components && (
{Object.entries(components).map(([k, v]) =>
{LABELS[k] || k}
{fmtCAD(v, 0)}
)}
)} {table && (
{table.map((row, i) => { const last = i === table.length - 1; return ( {row.map((c, j) => )} ); })}
{typeof c === 'number' ? fmtCAD(c, 0) : String(c ?? '')}
)} {comps && (
{adjKeys.map((k) => )} {comps.map((c) => ( {adjKeys.map((k) => )} ))}
ComparablePrixTemps{k}Prix ajustéNet %Brut %
{c.name} {fmtCAD(c.price, 0)} {fmtPct(c.time_pct, 1)} 0 ? 'text-[#1f7a1f]' : 'text-neutral-muted'}`}>{c.adjustments[k] !== undefined ? fmtCAD(c.adjustments[k], 0) : '—'}{fmtCAD(c.adjusted_price, 0)} {fmtPct(c.net_pct, 1)} {fmtPct(c.gross_pct, 1)} {c.reliable ? fiable : à pondérer}
)} {stats && (
{Object.entries(stats).map(([k, v]) =>
{LABELS[k] || k}
{typeof v === 'number' ? fmtCAD(v, 0) : String(v)}
)}
)} {r.note ?

{String(r.note)}

: null}
); }