fix: Consistent CPI score calculation using airlines benchmarks
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 <noreply@anthropic.com>
This commit is contained in:
@@ -840,18 +840,28 @@ export const generateAnalysis = async (
|
|||||||
);
|
);
|
||||||
if (economyDimIdx >= 0 && globalCPI > 0) {
|
if (economyDimIdx >= 0 && globalCPI > 0) {
|
||||||
// Usar benchmark de aerolíneas (€3.50) para consistencia con ExecutiveSummaryTab
|
// 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 CPI_BENCHMARK = 3.50;
|
||||||
const cpiDiff = globalCPI - CPI_BENCHMARK;
|
const cpiDiff = globalCPI - CPI_BENCHMARK;
|
||||||
// Para CPI invertido: menor es mejor
|
// Para CPI invertido: menor es mejor
|
||||||
const cpiStatus = cpiDiff <= 0 ? 'positive' : cpiDiff <= 0.5 ? 'neutral' : 'negative';
|
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 = {
|
mapped.dimensions[economyDimIdx].kpi = {
|
||||||
label: 'Coste por Interacción',
|
label: 'Coste por Interacción',
|
||||||
value: `€${globalCPI.toFixed(2)}`,
|
value: `€${globalCPI.toFixed(2)}`,
|
||||||
change: `vs benchmark €${CPI_BENCHMARK.toFixed(2)}`,
|
change: `vs benchmark €${CPI_BENCHMARK.toFixed(2)}`,
|
||||||
changeType: cpiStatus as 'positive' | 'neutral' | 'negative'
|
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);
|
console.log(' - OLD KPI value:', oldKpi?.value);
|
||||||
|
|
||||||
// Usar benchmark de aerolíneas (€3.50) para consistencia con ExecutiveSummaryTab
|
// 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 CPI_BENCHMARK = 3.50;
|
||||||
const cpiDiff = globalCPI - CPI_BENCHMARK;
|
const cpiDiff = globalCPI - CPI_BENCHMARK;
|
||||||
// Para CPI invertido: menor es mejor
|
// Para CPI invertido: menor es mejor
|
||||||
const cpiStatus = cpiDiff <= 0 ? 'positive' : cpiDiff <= 0.5 ? 'neutral' : 'negative';
|
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 = {
|
mapped.dimensions[economyDimIdx].kpi = {
|
||||||
label: 'Coste por Interacción',
|
label: 'Coste por Interacción',
|
||||||
value: `€${globalCPI.toFixed(2)}`,
|
value: `€${globalCPI.toFixed(2)}`,
|
||||||
@@ -1119,6 +1139,7 @@ export const generateAnalysisFromCache = async (
|
|||||||
changeType: cpiStatus as 'positive' | 'neutral' | 'negative'
|
changeType: cpiStatus as 'positive' | 'neutral' | 'negative'
|
||||||
};
|
};
|
||||||
console.log(' - NEW KPI value:', mapped.dimensions[economyDimIdx].kpi.value);
|
console.log(' - NEW KPI value:', mapped.dimensions[economyDimIdx].kpi.value);
|
||||||
|
console.log(' - NEW score:', newScore);
|
||||||
console.log(`💰 CPI sincronizado (cache): €${globalCPI.toFixed(2)}`);
|
console.log(`💰 CPI sincronizado (cache): €${globalCPI.toFixed(2)}`);
|
||||||
} else {
|
} else {
|
||||||
console.warn('⚠️ CPI sync skipped: economyDimIdx=', economyDimIdx, 'globalCPI=', globalCPI);
|
console.warn('⚠️ CPI sync skipped: economyDimIdx=', economyDimIdx, 'globalCPI=', globalCPI);
|
||||||
|
|||||||
@@ -637,8 +637,9 @@ function buildEconomyDimension(
|
|||||||
const op = raw?.operational_performance;
|
const op = raw?.operational_performance;
|
||||||
const totalAnnual = safeNumber(econ?.cost_breakdown?.total_annual, 0);
|
const totalAnnual = safeNumber(econ?.cost_breakdown?.total_annual, 0);
|
||||||
|
|
||||||
// Benchmark CPI sector contact center (Fuente: Gartner Contact Center Cost Benchmark 2024)
|
// Benchmark CPI aerolíneas (consistente con ExecutiveSummaryTab)
|
||||||
const CPI_BENCHMARK = 5.00;
|
// 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) {
|
if (totalAnnual <= 0 || totalInteractions <= 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -651,20 +652,20 @@ function buildEconomyDimension(
|
|||||||
// Calcular CPI usando cost_volume (non-abandoned) como denominador
|
// Calcular CPI usando cost_volume (non-abandoned) como denominador
|
||||||
const cpi = costVolume > 0 ? totalAnnual / costVolume : totalAnnual / totalInteractions;
|
const cpi = costVolume > 0 ? totalAnnual / costVolume : totalAnnual / totalInteractions;
|
||||||
|
|
||||||
// Score basado en comparación con benchmark (€5.00)
|
// Score basado en percentiles de aerolíneas (CPI invertido: menor = mejor)
|
||||||
// CPI <= 4.00 = 100pts (excelente)
|
// CPI <= 2.20 (p25) = 100pts (excelente, top 25%)
|
||||||
// CPI 4.00-5.00 = 80pts (en benchmark)
|
// CPI 2.20-3.50 (p25-p50) = 80pts (bueno, top 50%)
|
||||||
// CPI 5.00-6.00 = 60pts (por encima)
|
// CPI 3.50-4.50 (p50-p75) = 60pts (promedio)
|
||||||
// CPI 6.00-7.00 = 40pts (alto)
|
// CPI 4.50-5.50 (p75-p90) = 40pts (por debajo)
|
||||||
// CPI > 7.00 = 20pts (crítico)
|
// CPI > 5.50 (>p90) = 20pts (crítico)
|
||||||
let score: number;
|
let score: number;
|
||||||
if (cpi <= 4.00) {
|
if (cpi <= 2.20) {
|
||||||
score = 100;
|
score = 100;
|
||||||
} else if (cpi <= 5.00) {
|
} else if (cpi <= 3.50) {
|
||||||
score = 80;
|
score = 80;
|
||||||
} else if (cpi <= 6.00) {
|
} else if (cpi <= 4.50) {
|
||||||
score = 60;
|
score = 60;
|
||||||
} else if (cpi <= 7.00) {
|
} else if (cpi <= 5.50) {
|
||||||
score = 40;
|
score = 40;
|
||||||
} else {
|
} else {
|
||||||
score = 20;
|
score = 20;
|
||||||
@@ -676,7 +677,7 @@ function buildEconomyDimension(
|
|||||||
let summary = `Coste por interacción: €${cpi.toFixed(2)} vs benchmark €${CPI_BENCHMARK.toFixed(2)}. `;
|
let summary = `Coste por interacción: €${cpi.toFixed(2)} vs benchmark €${CPI_BENCHMARK.toFixed(2)}. `;
|
||||||
if (cpi <= CPI_BENCHMARK) {
|
if (cpi <= CPI_BENCHMARK) {
|
||||||
summary += 'Eficiencia de costes óptima, por debajo del benchmark del sector.';
|
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.';
|
summary += 'Coste ligeramente por encima del benchmark, oportunidad de optimización.';
|
||||||
} else {
|
} else {
|
||||||
summary += 'Coste elevado respecto al sector. Priorizar iniciativas de eficiencia.';
|
summary += 'Coste elevado respecto al sector. Priorizar iniciativas de eficiencia.';
|
||||||
|
|||||||
@@ -1363,14 +1363,15 @@ function generateDimensionsFromRealData(
|
|||||||
kpi: { label: 'CSAT', value: avgCsat > 0 ? `${Math.round(avgCsat)}/100` : 'N/A' },
|
kpi: { label: 'CSAT', value: avgCsat > 0 ? `${Math.round(avgCsat)}/100` : 'N/A' },
|
||||||
icon: Smile
|
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',
|
id: 'economy_cpi',
|
||||||
name: 'economy_cpi',
|
name: 'economy_cpi',
|
||||||
title: 'Economía Operacional',
|
title: 'Economía Operacional',
|
||||||
score: costPerInteraction < 4 ? 85 : costPerInteraction < 5 ? 70 : costPerInteraction < 6 ? 55 : 40,
|
// Score basado en percentiles aerolíneas (CPI invertido: menor = mejor)
|
||||||
percentile: costPerInteraction < 4.5 ? 70 : costPerInteraction < 5.5 ? 50 : 30,
|
score: costPerInteraction <= 2.20 ? 100 : costPerInteraction <= 3.50 ? 80 : costPerInteraction <= 4.50 ? 60 : costPerInteraction <= 5.50 ? 40 : 20,
|
||||||
summary: `CPI: €${costPerInteraction.toFixed(2)} por interacción. Coste anual: €${totalCost.toLocaleString('es-ES')}. Benchmark sector: €5.00 (Fuente: Gartner 2024).`,
|
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)}` },
|
kpi: { label: 'Coste/Interacción', value: `€${costPerInteraction.toFixed(2)}` },
|
||||||
icon: DollarSign
|
icon: DollarSign
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user