From eb804d7fb03f0f0a5a413c9c5a7d06af9f19778f Mon Sep 17 00:00:00 2001 From: sujucu70 Date: Fri, 23 Jan 2026 14:02:25 +0100 Subject: [PATCH] fix: Consistent CPI score calculation using airlines benchmarks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates economy dimension score to use airlines benchmark percentiles: - p25 (€2.20) = 100 points - p50 (€3.50) = 80 points - p75 (€4.50) = 60 points - p90 (€5.50) = 40 points - >p90 = 20 points Applies to: backendMapper.ts, realDataAnalysis.ts, analysisGenerator.ts Co-Authored-By: Claude Opus 4.5 --- frontend/utils/analysisGenerator.ts | 23 ++++++++++++++++++++++- frontend/utils/backendMapper.ts | 27 ++++++++++++++------------- frontend/utils/realDataAnalysis.ts | 9 +++++---- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/frontend/utils/analysisGenerator.ts b/frontend/utils/analysisGenerator.ts index ff8fae6..0ccc836 100644 --- a/frontend/utils/analysisGenerator.ts +++ b/frontend/utils/analysisGenerator.ts @@ -840,18 +840,28 @@ export const generateAnalysis = async ( ); if (economyDimIdx >= 0 && globalCPI > 0) { // Usar benchmark de aerolíneas (€3.50) para consistencia con ExecutiveSummaryTab + // Percentiles: p25=2.20, p50=3.50, p75=4.50, p90=5.50 const CPI_BENCHMARK = 3.50; const cpiDiff = globalCPI - CPI_BENCHMARK; // Para CPI invertido: menor es mejor const cpiStatus = cpiDiff <= 0 ? 'positive' : cpiDiff <= 0.5 ? 'neutral' : 'negative'; + // Calcular score basado en percentiles aerolíneas + let newScore: number; + if (globalCPI <= 2.20) newScore = 100; + else if (globalCPI <= 3.50) newScore = 80; + else if (globalCPI <= 4.50) newScore = 60; + else if (globalCPI <= 5.50) newScore = 40; + else newScore = 20; + + mapped.dimensions[economyDimIdx].score = newScore; mapped.dimensions[economyDimIdx].kpi = { label: 'Coste por Interacción', value: `€${globalCPI.toFixed(2)}`, change: `vs benchmark €${CPI_BENCHMARK.toFixed(2)}`, changeType: cpiStatus as 'positive' | 'neutral' | 'negative' }; - console.log(`💰 CPI sincronizado: €${globalCPI.toFixed(2)} (desde heatmapData, consistente con Executive Summary)`); + console.log(`💰 CPI sincronizado: €${globalCPI.toFixed(2)}, score: ${newScore}`); } } @@ -1107,11 +1117,21 @@ export const generateAnalysisFromCache = async ( console.log(' - OLD KPI value:', oldKpi?.value); // Usar benchmark de aerolíneas (€3.50) para consistencia con ExecutiveSummaryTab + // Percentiles: p25=2.20, p50=3.50, p75=4.50, p90=5.50 const CPI_BENCHMARK = 3.50; const cpiDiff = globalCPI - CPI_BENCHMARK; // Para CPI invertido: menor es mejor const cpiStatus = cpiDiff <= 0 ? 'positive' : cpiDiff <= 0.5 ? 'neutral' : 'negative'; + // Calcular score basado en percentiles aerolíneas + let newScore: number; + if (globalCPI <= 2.20) newScore = 100; + else if (globalCPI <= 3.50) newScore = 80; + else if (globalCPI <= 4.50) newScore = 60; + else if (globalCPI <= 5.50) newScore = 40; + else newScore = 20; + + mapped.dimensions[economyDimIdx].score = newScore; mapped.dimensions[economyDimIdx].kpi = { label: 'Coste por Interacción', value: `€${globalCPI.toFixed(2)}`, @@ -1119,6 +1139,7 @@ export const generateAnalysisFromCache = async ( changeType: cpiStatus as 'positive' | 'neutral' | 'negative' }; console.log(' - NEW KPI value:', mapped.dimensions[economyDimIdx].kpi.value); + console.log(' - NEW score:', newScore); console.log(`💰 CPI sincronizado (cache): €${globalCPI.toFixed(2)}`); } else { console.warn('⚠️ CPI sync skipped: economyDimIdx=', economyDimIdx, 'globalCPI=', globalCPI); diff --git a/frontend/utils/backendMapper.ts b/frontend/utils/backendMapper.ts index 7bc5a6a..837ece9 100644 --- a/frontend/utils/backendMapper.ts +++ b/frontend/utils/backendMapper.ts @@ -637,8 +637,9 @@ function buildEconomyDimension( const op = raw?.operational_performance; const totalAnnual = safeNumber(econ?.cost_breakdown?.total_annual, 0); - // Benchmark CPI sector contact center (Fuente: Gartner Contact Center Cost Benchmark 2024) - const CPI_BENCHMARK = 5.00; + // Benchmark CPI aerolíneas (consistente con ExecutiveSummaryTab) + // p25: 2.20, p50: 3.50, p75: 4.50, p90: 5.50 + const CPI_BENCHMARK = 3.50; // p50 aerolíneas if (totalAnnual <= 0 || totalInteractions <= 0) { return undefined; @@ -651,20 +652,20 @@ function buildEconomyDimension( // Calcular CPI usando cost_volume (non-abandoned) como denominador const cpi = costVolume > 0 ? totalAnnual / costVolume : totalAnnual / totalInteractions; - // Score basado en comparación con benchmark (€5.00) - // CPI <= 4.00 = 100pts (excelente) - // CPI 4.00-5.00 = 80pts (en benchmark) - // CPI 5.00-6.00 = 60pts (por encima) - // CPI 6.00-7.00 = 40pts (alto) - // CPI > 7.00 = 20pts (crítico) + // Score basado en percentiles de aerolíneas (CPI invertido: menor = mejor) + // CPI <= 2.20 (p25) = 100pts (excelente, top 25%) + // CPI 2.20-3.50 (p25-p50) = 80pts (bueno, top 50%) + // CPI 3.50-4.50 (p50-p75) = 60pts (promedio) + // CPI 4.50-5.50 (p75-p90) = 40pts (por debajo) + // CPI > 5.50 (>p90) = 20pts (crítico) let score: number; - if (cpi <= 4.00) { + if (cpi <= 2.20) { score = 100; - } else if (cpi <= 5.00) { + } else if (cpi <= 3.50) { score = 80; - } else if (cpi <= 6.00) { + } else if (cpi <= 4.50) { score = 60; - } else if (cpi <= 7.00) { + } else if (cpi <= 5.50) { score = 40; } else { score = 20; @@ -676,7 +677,7 @@ function buildEconomyDimension( let summary = `Coste por interacción: €${cpi.toFixed(2)} vs benchmark €${CPI_BENCHMARK.toFixed(2)}. `; if (cpi <= CPI_BENCHMARK) { summary += 'Eficiencia de costes óptima, por debajo del benchmark del sector.'; - } else if (cpi <= 6.00) { + } else if (cpi <= 4.50) { summary += 'Coste ligeramente por encima del benchmark, oportunidad de optimización.'; } else { summary += 'Coste elevado respecto al sector. Priorizar iniciativas de eficiencia.'; diff --git a/frontend/utils/realDataAnalysis.ts b/frontend/utils/realDataAnalysis.ts index 475d3f4..9159450 100644 --- a/frontend/utils/realDataAnalysis.ts +++ b/frontend/utils/realDataAnalysis.ts @@ -1363,14 +1363,15 @@ function generateDimensionsFromRealData( kpi: { label: 'CSAT', value: avgCsat > 0 ? `${Math.round(avgCsat)}/100` : 'N/A' }, icon: Smile }, - // 6. ECONOMÍA - CPI + // 6. ECONOMÍA - CPI (benchmark aerolíneas: p25=2.20, p50=3.50, p75=4.50, p90=5.50) { id: 'economy_cpi', name: 'economy_cpi', title: 'Economía Operacional', - score: costPerInteraction < 4 ? 85 : costPerInteraction < 5 ? 70 : costPerInteraction < 6 ? 55 : 40, - percentile: costPerInteraction < 4.5 ? 70 : costPerInteraction < 5.5 ? 50 : 30, - summary: `CPI: €${costPerInteraction.toFixed(2)} por interacción. Coste anual: €${totalCost.toLocaleString('es-ES')}. Benchmark sector: €5.00 (Fuente: Gartner 2024).`, + // Score basado en percentiles aerolíneas (CPI invertido: menor = mejor) + score: costPerInteraction <= 2.20 ? 100 : costPerInteraction <= 3.50 ? 80 : costPerInteraction <= 4.50 ? 60 : costPerInteraction <= 5.50 ? 40 : 20, + percentile: costPerInteraction <= 2.20 ? 90 : costPerInteraction <= 3.50 ? 70 : costPerInteraction <= 4.50 ? 50 : costPerInteraction <= 5.50 ? 25 : 10, + summary: `CPI: €${costPerInteraction.toFixed(2)} por interacción. Coste anual: €${totalCost.toLocaleString('es-ES')}. Benchmark sector aerolíneas: €3.50.`, kpi: { label: 'Coste/Interacción', value: `€${costPerInteraction.toFixed(2)}` }, icon: DollarSign },