export function formatValue(value: number): string { if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(1)}k`; if (Number.isInteger(value)) return value.toLocaleString(); return value.toFixed(1); } export function formatFilterValue(value: number): string { if (Math.abs(value) >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (Math.abs(value) >= 1_000) return `${(value / 1_000).toFixed(1)}k`; if (Number.isInteger(value)) return value.toString(); return value.toFixed(2); } export function formatDuration(d: string): string { if (d === 'F') return 'Freehold'; if (d === 'L') return 'Leasehold'; return d; } export function formatAge(value: number, approximate = true): string { if (value >= 1000) return approximate ? `~${Math.round(value)}` : `${Math.round(value)}`; return Math.round(value).toString(); } // Format number with optional decimals, used in PropertyCard export function formatNumber(value: number | undefined, decimals = 0): string { if (value === undefined) return ''; return decimals > 0 ? value.toFixed(decimals) : Math.round(value).toLocaleString(); } // Calculate weighted mean from histogram export function calculateHistogramMean(histogram: { min: number; bin_width: number; counts: number[]; }): number | undefined { if (!histogram.counts.length) return undefined; const totalCount = histogram.counts.reduce((a, b) => a + b, 0); if (totalCount === 0) return undefined; let weightedSum = 0; for (let i = 0; i < histogram.counts.length; i++) { const binCenter = histogram.min + (i + 0.5) * histogram.bin_width; weightedSum += binCenter * histogram.counts[i]; } return weightedSum / totalCount; }