fix: Centralize CPI calculation for fresh data consistency
- Calculate CPI once in main function from heatmapData - Pass globalCPI to generateDimensionsFromRealData - This ensures dimension.kpi.value matches ExecutiveSummaryTab's calculation - Both now use identical formula: weighted avg of (cpi * cost_volume) / total_cost_volume Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -189,6 +189,17 @@ export function generateAnalysisFromRealData(
|
|||||||
// Coste total
|
// Coste total
|
||||||
const totalCost = Math.round(skillMetrics.reduce((sum, s) => sum + s.total_cost, 0));
|
const totalCost = Math.round(skillMetrics.reduce((sum, s) => sum + s.total_cost, 0));
|
||||||
|
|
||||||
|
// === CPI CENTRALIZADO: Calcular UNA sola vez desde heatmapData ===
|
||||||
|
// Esta es la ÚNICA fuente de verdad para CPI, igual que ExecutiveSummaryTab
|
||||||
|
const totalCostVolume = heatmapData.reduce((sum, h) => sum + (h.cost_volume || h.volume), 0);
|
||||||
|
const totalAnnualCost = heatmapData.reduce((sum, h) => sum + (h.annual_cost || 0), 0);
|
||||||
|
const hasCpiField = heatmapData.some(h => h.cpi !== undefined && h.cpi > 0);
|
||||||
|
const globalCPI = hasCpiField
|
||||||
|
? (totalCostVolume > 0
|
||||||
|
? heatmapData.reduce((sum, h) => sum + (h.cpi || 0) * (h.cost_volume || h.volume), 0) / totalCostVolume
|
||||||
|
: 0)
|
||||||
|
: (totalCostVolume > 0 ? totalAnnualCost / totalCostVolume : 0);
|
||||||
|
|
||||||
// KPIs principales
|
// KPIs principales
|
||||||
const summaryKpis: Kpi[] = [
|
const summaryKpis: Kpi[] = [
|
||||||
{ label: "Interacciones Totales", value: totalInteractions.toLocaleString('es-ES') },
|
{ label: "Interacciones Totales", value: totalInteractions.toLocaleString('es-ES') },
|
||||||
@@ -200,13 +211,14 @@ export function generateAnalysisFromRealData(
|
|||||||
// Health Score basado en métricas reales
|
// Health Score basado en métricas reales
|
||||||
const overallHealthScore = calculateHealthScore(heatmapData);
|
const overallHealthScore = calculateHealthScore(heatmapData);
|
||||||
|
|
||||||
// Dimensiones (simplificadas para datos reales)
|
// Dimensiones (simplificadas para datos reales) - pasar CPI centralizado
|
||||||
const dimensions: DimensionAnalysis[] = generateDimensionsFromRealData(
|
const dimensions: DimensionAnalysis[] = generateDimensionsFromRealData(
|
||||||
interactions,
|
interactions,
|
||||||
skillMetrics,
|
skillMetrics,
|
||||||
avgCsat,
|
avgCsat,
|
||||||
avgAHT,
|
avgAHT,
|
||||||
hourlyDistribution
|
hourlyDistribution,
|
||||||
|
globalCPI // CPI calculado desde heatmapData
|
||||||
);
|
);
|
||||||
|
|
||||||
// Agentic Readiness Score
|
// Agentic Readiness Score
|
||||||
@@ -1212,7 +1224,8 @@ function generateDimensionsFromRealData(
|
|||||||
metrics: SkillMetrics[],
|
metrics: SkillMetrics[],
|
||||||
avgCsat: number,
|
avgCsat: number,
|
||||||
avgAHT: number,
|
avgAHT: number,
|
||||||
hourlyDistribution: { hourly: number[]; off_hours_pct: number; peak_hours: number[] }
|
hourlyDistribution: { hourly: number[]; off_hours_pct: number; peak_hours: number[] },
|
||||||
|
globalCPI: number // CPI calculado centralmente desde heatmapData
|
||||||
): DimensionAnalysis[] {
|
): DimensionAnalysis[] {
|
||||||
const totalVolume = interactions.length;
|
const totalVolume = interactions.length;
|
||||||
const avgCV = metrics.reduce((sum, m) => sum + m.cv_aht, 0) / metrics.length;
|
const avgCV = metrics.reduce((sum, m) => sum + m.cv_aht, 0) / metrics.length;
|
||||||
@@ -1270,17 +1283,10 @@ function generateDimensionsFromRealData(
|
|||||||
|
|
||||||
volumetryScore = Math.max(0, Math.min(100, Math.round(volumetryScore)));
|
volumetryScore = Math.max(0, Math.min(100, Math.round(volumetryScore)));
|
||||||
|
|
||||||
// === CPI: Coste por interacción (IDÉNTICO a Executive Summary) ===
|
// === CPI: Usar el valor centralizado pasado como parámetro ===
|
||||||
// Usar cost_volume (non-abandon) como denominador
|
// globalCPI ya fue calculado en generateAnalysisFromRealData desde heatmapData
|
||||||
const totalCostVolume = metrics.reduce((sum, m) => sum + (m.cost_volume || m.volume), 0);
|
// Esto garantiza consistencia con ExecutiveSummaryTab
|
||||||
const totalAnnualCost = metrics.reduce((sum, m) => sum + (m.total_cost || 0), 0);
|
const costPerInteraction = globalCPI;
|
||||||
// Usar CPI pre-calculado si disponible, sino calcular desde total_cost / cost_volume
|
|
||||||
const hasCpiField = metrics.some(m => m.cpi !== undefined && m.cpi > 0);
|
|
||||||
const costPerInteraction = hasCpiField
|
|
||||||
? (totalCostVolume > 0
|
|
||||||
? metrics.reduce((sum, m) => sum + (m.cpi || 0) * (m.cost_volume || m.volume), 0) / totalCostVolume
|
|
||||||
: 0)
|
|
||||||
: (totalCostVolume > 0 ? totalAnnualCost / totalCostVolume : 0);
|
|
||||||
|
|
||||||
// Calcular Agentic Score
|
// Calcular Agentic Score
|
||||||
const predictability = Math.max(0, Math.min(10, 10 - ((avgCV - 0.3) / 1.2 * 10)));
|
const predictability = Math.max(0, Math.min(10, 10 - ((avgCV - 0.3) / 1.2 * 10)));
|
||||||
|
|||||||
Reference in New Issue
Block a user