sinka/api/src/dashboard/dashboard-metrics.ts
armando 8e4203e4c9 sinka
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:56:18 -03:00

182 lines
6.0 KiB
TypeScript

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<string, { wins: number; total: number }>();
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<string, { count: number; total: number }>();
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,
};
}