export type DashboardQuote = { price: number; carrierName: string; }; export type DashboardSimulation = { quotedPrice: number | null; distanceKm: number | null; bestCarrierName: string | null; originLabel: string | null; destinationLabel: string | null; originZip: string; destinationZip: string; quotes: DashboardQuote[]; createdAt: Date | string; }; export type DashboardPayload = { periodDays: number; kpis: { simulations: { value: number; deltaPct: number | null }; averageCost: { value: number | null; deltaPct: number | null }; costPerKm: { value: number | null; deltaPct: number | null }; leftoverSavings: { value: number; sharePct: number | null }; }; insights: string[]; carriers: { name: string; wins: number; sharePct: number; averageWin: number }[]; routes: { label: string; count: number; averageCost: number }[]; customers: number; activeCarriers: number; }; function round2(value: number) { return Math.round(value * 100) / 100; } function average(values: number[]) { if (!values.length) { return null; } return round2(values.reduce((sum, value) => sum + value, 0) / values.length); } function deltaPct(current: number | null, previous: number | null) { if (current === null || previous === null || previous === 0) { return null; } return round2(((current - previous) / previous) * 100); } function money(value: number) { return value.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }); } function pricesOf(sim: DashboardSimulation) { if (sim.quotedPrice !== null) { return [sim.quotedPrice]; } return []; } function costPerKmValues(rows: DashboardSimulation[]) { return rows .filter((row) => row.quotedPrice !== null && row.distanceKm !== null && row.distanceKm > 0) .map((row) => (row.quotedPrice as number) / (row.distanceKm as number)); } function leftoverOf(sim: DashboardSimulation) { const prices = sim.quotes.map((quote) => quote.price).filter((price) => price > 0); if (prices.length < 2) { return 0; } return Math.max(...prices) - Math.min(...prices); } function routeKey(sim: DashboardSimulation) { const origin = sim.originLabel || sim.originZip; const destination = sim.destinationLabel || sim.destinationZip; return `${origin} → ${destination}`; } export function buildDashboard(input: { current: DashboardSimulation[]; previous: DashboardSimulation[]; customerCount: number; carrierCount: number; periodDays?: number; }): DashboardPayload { const periodDays = input.periodDays ?? 30; const currentCosts = input.current.flatMap(pricesOf); const previousCosts = input.previous.flatMap(pricesOf); const averageCost = average(currentCosts); const previousAverage = average(previousCosts); const costPerKm = average(costPerKmValues(input.current)); const previousCostPerKm = average(costPerKmValues(input.previous)); const leftoverSavings = round2(input.current.reduce((sum, sim) => sum + leftoverOf(sim), 0)); const comparable = input.current.filter((sim) => leftoverOf(sim) > 0).length; const sharePct = input.current.length ? round2((comparable / input.current.length) * 100) : null; const wins = new Map(); for (const sim of input.current) { if (!sim.bestCarrierName || sim.quotedPrice === null) { continue; } const row = wins.get(sim.bestCarrierName) ?? { wins: 0, total: 0 }; row.wins += 1; row.total += sim.quotedPrice; wins.set(sim.bestCarrierName, row); } const winTotal = [...wins.values()].reduce((sum, row) => sum + row.wins, 0); const carriers = [...wins.entries()] .map(([name, row]) => ({ name, wins: row.wins, sharePct: winTotal ? round2((row.wins / winTotal) * 100) : 0, averageWin: round2(row.total / row.wins), })) .sort((a, b) => b.wins - a.wins); const routesMap = new Map(); for (const sim of input.current) { if (sim.quotedPrice === null) { continue; } const key = routeKey(sim); const row = routesMap.get(key) ?? { count: 0, total: 0 }; row.count += 1; row.total += sim.quotedPrice; routesMap.set(key, row); } const routes = [...routesMap.entries()] .map(([label, row]) => ({ label, count: row.count, averageCost: round2(row.total / row.count), })) .sort((a, b) => b.averageCost - a.averageCost) .slice(0, 5); const insights: string[] = []; const costDelta = deltaPct(averageCost, previousAverage); if (costDelta !== null && costDelta > 0) { insights.push(`O custo médio da melhor cotação subiu ${costDelta}% frente aos ${periodDays} dias anteriores.`); } else if (costDelta !== null && costDelta < 0) { insights.push(`O custo médio da melhor cotação caiu ${Math.abs(costDelta)}% frente aos ${periodDays} dias anteriores.`); } if (leftoverSavings > 0) { insights.push( `A diferença entre a pior e a melhor opção soma ${money(leftoverSavings)} no período. Comparar cotações evita pagar o teto.`, ); } const leader = carriers[0]; if (leader && leader.sharePct >= 50) { insights.push( `${leader.name} venceu ${leader.sharePct}% das simulações. Concentração alta: negocie prazo e dependência.`, ); } else if (leader) { insights.push(`${leader.name} lidera as melhores cotações (${leader.sharePct}%). Vale cruzar com prazo, não só preço.`); } if (!input.current.length) { insights.push('Ainda não há simulações neste período. As primeiras cotações passam a alimentar custo, rota e dependência.'); } return { periodDays, kpis: { simulations: { value: input.current.length, deltaPct: deltaPct(input.current.length || null, input.previous.length || null), }, averageCost: { value: averageCost, deltaPct: costDelta }, costPerKm: { value: costPerKm, deltaPct: deltaPct(costPerKm, previousCostPerKm) }, leftoverSavings: { value: leftoverSavings, sharePct }, }, insights, carriers, routes, customers: input.customerCount, activeCarriers: input.carrierCount, }; }