Translate Phase 2 medium-priority files (frontend utils + backend dimensions)
Phase 2 of Spanish-to-English translation for medium-priority files: Frontend utils (2 files): - dataTransformation.ts: Translated ~72 occurrences (comments, docs, console logs) - segmentClassifier.ts: Translated ~20 occurrences (JSDoc, inline comments, UI strings) Backend dimensions (3 files): - OperationalPerformance.py: Translated ~117 lines (docstrings, comments) - SatisfactionExperience.py: Translated ~33 lines (docstrings, comments) - EconomyCost.py: Translated ~79 lines (docstrings, comments) All function names and variable names preserved for API compatibility. Frontend and backend compilation tested and verified successful. Related to TRANSLATION_STATUS.md Phase 2 objectives. https://claude.ai/code/session_01GNbnkFoESkRcnPr3bLCYDg
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
// utils/dataTransformation.ts
|
||||
// Pipeline de transformación de datos raw a métricas procesadas
|
||||
// Raw data to processed metrics transformation pipeline
|
||||
|
||||
import type { RawInteraction } from '../types';
|
||||
|
||||
/**
|
||||
* Paso 1: Limpieza de Ruido
|
||||
* Elimina interacciones con duration < 10 segundos (falsos contactos o errores de sistema)
|
||||
* Step 1: Noise Cleanup
|
||||
* Removes interactions with duration < 10 seconds (false contacts or system errors)
|
||||
*/
|
||||
export function cleanNoiseFromData(interactions: RawInteraction[]): RawInteraction[] {
|
||||
const MIN_DURATION_SECONDS = 10;
|
||||
@@ -22,30 +22,30 @@ export function cleanNoiseFromData(interactions: RawInteraction[]): RawInteracti
|
||||
const removedCount = interactions.length - cleaned.length;
|
||||
const removedPercentage = ((removedCount / interactions.length) * 100).toFixed(1);
|
||||
|
||||
console.log(`🧹 Limpieza de Ruido: ${removedCount} interacciones eliminadas (${removedPercentage}% del total)`);
|
||||
console.log(`✅ Interacciones limpias: ${cleaned.length}`);
|
||||
console.log(`🧹 Noise Cleanup: ${removedCount} interactions removed (${removedPercentage}% of total)`);
|
||||
console.log(`✅ Clean interactions: ${cleaned.length}`);
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Métricas base calculadas por skill
|
||||
* Base metrics calculated by skill
|
||||
*/
|
||||
export interface SkillBaseMetrics {
|
||||
skill: string;
|
||||
volume: number; // Número de interacciones
|
||||
aht_mean: number; // AHT promedio (segundos)
|
||||
aht_std: number; // Desviación estándar del AHT
|
||||
transfer_rate: number; // Tasa de transferencia (0-100)
|
||||
total_cost: number; // Coste total (€)
|
||||
volume: number; // Number of interactions
|
||||
aht_mean: number; // Average AHT (seconds)
|
||||
aht_std: number; // AHT standard deviation
|
||||
transfer_rate: number; // Transfer rate (0-100)
|
||||
total_cost: number; // Total cost (€)
|
||||
|
||||
// Datos auxiliares para cálculos posteriores
|
||||
aht_values: number[]; // Array de todos los AHT para percentiles
|
||||
// Auxiliary data for subsequent calculations
|
||||
aht_values: number[]; // Array of all AHT values for percentiles
|
||||
}
|
||||
|
||||
/**
|
||||
* Paso 2: Calcular Métricas Base por Skill
|
||||
* Agrupa por skill y calcula volumen, AHT promedio, desviación estándar, tasa de transferencia y coste
|
||||
* Step 2: Calculate Base Metrics by Skill
|
||||
* Groups by skill and calculates volume, average AHT, standard deviation, transfer rate and cost
|
||||
*/
|
||||
export function calculateSkillBaseMetrics(
|
||||
interactions: RawInteraction[],
|
||||
@@ -53,7 +53,7 @@ export function calculateSkillBaseMetrics(
|
||||
): SkillBaseMetrics[] {
|
||||
const COST_PER_SECOND = costPerHour / 3600;
|
||||
|
||||
// Agrupar por skill
|
||||
// Group by skill
|
||||
const skillGroups = new Map<string, RawInteraction[]>();
|
||||
|
||||
interactions.forEach(interaction => {
|
||||
@@ -64,31 +64,31 @@ export function calculateSkillBaseMetrics(
|
||||
skillGroups.get(skill)!.push(interaction);
|
||||
});
|
||||
|
||||
// Calcular métricas por skill
|
||||
// Calculate metrics per skill
|
||||
const metrics: SkillBaseMetrics[] = [];
|
||||
|
||||
skillGroups.forEach((skillInteractions, skill) => {
|
||||
const volume = skillInteractions.length;
|
||||
|
||||
// Calcular AHT para cada interacción
|
||||
// Calculate AHT for each interaction
|
||||
const ahtValues = skillInteractions.map(i =>
|
||||
i.duration_talk + i.hold_time + i.wrap_up_time
|
||||
);
|
||||
|
||||
// AHT promedio
|
||||
// Average AHT
|
||||
const ahtMean = ahtValues.reduce((sum, val) => sum + val, 0) / volume;
|
||||
|
||||
// Desviación estándar del AHT
|
||||
// AHT standard deviation
|
||||
const variance = ahtValues.reduce((sum, val) =>
|
||||
sum + Math.pow(val - ahtMean, 2), 0
|
||||
) / volume;
|
||||
const ahtStd = Math.sqrt(variance);
|
||||
|
||||
// Tasa de transferencia
|
||||
// Transfer rate
|
||||
const transferCount = skillInteractions.filter(i => i.transfer_flag).length;
|
||||
const transferRate = (transferCount / volume) * 100;
|
||||
|
||||
// Coste total
|
||||
// Total cost
|
||||
const totalCost = ahtValues.reduce((sum, aht) =>
|
||||
sum + (aht * COST_PER_SECOND), 0
|
||||
);
|
||||
@@ -104,82 +104,82 @@ export function calculateSkillBaseMetrics(
|
||||
});
|
||||
});
|
||||
|
||||
// Ordenar por volumen descendente
|
||||
// Sort by descending volume
|
||||
metrics.sort((a, b) => b.volume - a.volume);
|
||||
|
||||
console.log(`📊 Métricas Base calculadas para ${metrics.length} skills`);
|
||||
console.log(`📊 Base Metrics calculated for ${metrics.length} skills`);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dimensiones transformadas para Agentic Readiness Score
|
||||
* Transformed dimensions for Agentic Readiness Score
|
||||
*/
|
||||
export interface SkillDimensions {
|
||||
skill: string;
|
||||
volume: number;
|
||||
|
||||
// Dimensión 1: Predictibilidad (0-10)
|
||||
// Dimension 1: Predictability (0-10)
|
||||
predictability_score: number;
|
||||
predictability_cv: number; // Coeficiente de Variación (para referencia)
|
||||
predictability_cv: number; // Coefficient of Variation (for reference)
|
||||
|
||||
// Dimensión 2: Complejidad Inversa (0-10)
|
||||
// Dimension 2: Inverse Complexity (0-10)
|
||||
complexity_inverse_score: number;
|
||||
complexity_transfer_rate: number; // Tasa de transferencia (para referencia)
|
||||
complexity_transfer_rate: number; // Transfer rate (for reference)
|
||||
|
||||
// Dimensión 3: Repetitividad/Impacto (0-10)
|
||||
// Dimension 3: Repetitiveness/Impact (0-10)
|
||||
repetitivity_score: number;
|
||||
|
||||
// Datos auxiliares
|
||||
// Auxiliary data
|
||||
aht_mean: number;
|
||||
total_cost: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paso 3: Transformar Métricas Base a Dimensiones
|
||||
* Aplica las fórmulas de normalización para obtener scores 0-10
|
||||
* Step 3: Transform Base Metrics to Dimensions
|
||||
* Applies normalization formulas to obtain 0-10 scores
|
||||
*/
|
||||
export function transformToDimensions(
|
||||
baseMetrics: SkillBaseMetrics[]
|
||||
): SkillDimensions[] {
|
||||
return baseMetrics.map(metric => {
|
||||
// Dimensión 1: Predictibilidad (Proxy: Variabilidad del AHT)
|
||||
// CV = desviación estándar / media
|
||||
// Dimension 1: Predictability (Proxy: AHT Variability)
|
||||
// CV = standard deviation / mean
|
||||
const cv = metric.aht_std / metric.aht_mean;
|
||||
|
||||
// Normalización: CV <= 0.3 → 10, CV >= 1.5 → 0
|
||||
// Fórmula: MAX(0, MIN(10, 10 - ((CV - 0.3) / 1.2 * 10)))
|
||||
// Normalization: CV <= 0.3 → 10, CV >= 1.5 → 0
|
||||
// Formula: MAX(0, MIN(10, 10 - ((CV - 0.3) / 1.2 * 10)))
|
||||
const predictabilityScore = Math.max(0, Math.min(10,
|
||||
10 - ((cv - 0.3) / 1.2 * 10)
|
||||
));
|
||||
|
||||
// Dimensión 2: Complejidad Inversa (Proxy: Tasa de Transferencia)
|
||||
// T = tasa de transferencia (%)
|
||||
// Dimension 2: Inverse Complexity (Proxy: Transfer Rate)
|
||||
// T = transfer rate (%)
|
||||
const transferRate = metric.transfer_rate;
|
||||
|
||||
// Normalización: T <= 5% → 10, T >= 30% → 0
|
||||
// Fórmula: MAX(0, MIN(10, 10 - ((T - 0.05) / 0.25 * 10)))
|
||||
// Normalization: T <= 5% → 10, T >= 30% → 0
|
||||
// Formula: MAX(0, MIN(10, 10 - ((T - 0.05) / 0.25 * 10)))
|
||||
const complexityInverseScore = Math.max(0, Math.min(10,
|
||||
10 - ((transferRate / 100 - 0.05) / 0.25 * 10)
|
||||
));
|
||||
|
||||
// Dimensión 3: Repetitividad/Impacto (Proxy: Volumen)
|
||||
// Normalización fija: > 5,000 llamadas/mes = 10, < 100 = 0
|
||||
// Dimension 3: Repetitiveness/Impact (Proxy: Volume)
|
||||
// Fixed normalization: > 5,000 calls/month = 10, < 100 = 0
|
||||
let repetitivityScore: number;
|
||||
if (metric.volume >= 5000) {
|
||||
repetitivityScore = 10;
|
||||
} else if (metric.volume <= 100) {
|
||||
repetitivityScore = 0;
|
||||
} else {
|
||||
// Interpolación lineal entre 100 y 5000
|
||||
// Linear interpolation between 100 and 5000
|
||||
repetitivityScore = ((metric.volume - 100) / (5000 - 100)) * 10;
|
||||
}
|
||||
|
||||
return {
|
||||
skill: metric.skill,
|
||||
volume: metric.volume,
|
||||
predictability_score: Math.round(predictabilityScore * 10) / 10, // 1 decimal
|
||||
predictability_cv: Math.round(cv * 100) / 100, // 2 decimales
|
||||
predictability_score: Math.round(predictabilityScore * 10) / 10, // 1 decimal place
|
||||
predictability_cv: Math.round(cv * 100) / 100, // 2 decimal places
|
||||
complexity_inverse_score: Math.round(complexityInverseScore * 10) / 10,
|
||||
complexity_transfer_rate: Math.round(transferRate * 10) / 10,
|
||||
repetitivity_score: Math.round(repetitivityScore * 10) / 10,
|
||||
@@ -190,7 +190,7 @@ export function transformToDimensions(
|
||||
}
|
||||
|
||||
/**
|
||||
* Resultado final con Agentic Readiness Score
|
||||
* Final result with Agentic Readiness Score
|
||||
*/
|
||||
export interface SkillAgenticReadiness extends SkillDimensions {
|
||||
agentic_readiness_score: number; // 0-10
|
||||
@@ -199,28 +199,28 @@ export interface SkillAgenticReadiness extends SkillDimensions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Paso 4: Calcular Agentic Readiness Score
|
||||
* Promedio ponderado de las 3 dimensiones
|
||||
* Step 4: Calculate Agentic Readiness Score
|
||||
* Weighted average of the 3 dimensions
|
||||
*/
|
||||
export function calculateAgenticReadinessScore(
|
||||
dimensions: SkillDimensions[],
|
||||
weights?: { predictability: number; complexity: number; repetitivity: number }
|
||||
): SkillAgenticReadiness[] {
|
||||
// Pesos por defecto (ajustables)
|
||||
// Default weights (adjustable)
|
||||
const w = weights || {
|
||||
predictability: 0.40, // 40% - Más importante
|
||||
predictability: 0.40, // 40% - Most important
|
||||
complexity: 0.35, // 35%
|
||||
repetitivity: 0.25 // 25%
|
||||
};
|
||||
|
||||
return dimensions.map(dim => {
|
||||
// Promedio ponderado
|
||||
// Weighted average
|
||||
const score =
|
||||
dim.predictability_score * w.predictability +
|
||||
dim.complexity_inverse_score * w.complexity +
|
||||
dim.repetitivity_score * w.repetitivity;
|
||||
|
||||
// Categorizar
|
||||
// Categorize
|
||||
let category: 'automate_now' | 'assist_copilot' | 'optimize_first';
|
||||
let label: string;
|
||||
|
||||
@@ -245,29 +245,29 @@ export function calculateAgenticReadinessScore(
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipeline completo: Raw Data → Agentic Readiness Score
|
||||
* Complete pipeline: Raw Data → Agentic Readiness Score
|
||||
*/
|
||||
export function transformRawDataToAgenticReadiness(
|
||||
rawInteractions: RawInteraction[],
|
||||
costPerHour: number,
|
||||
weights?: { predictability: number; complexity: number; repetitivity: number }
|
||||
): SkillAgenticReadiness[] {
|
||||
console.log(`🚀 Iniciando pipeline de transformación con ${rawInteractions.length} interacciones...`);
|
||||
console.log(`🚀 Starting transformation pipeline with ${rawInteractions.length} interactions...`);
|
||||
|
||||
// Paso 1: Limpieza de ruido
|
||||
// Step 1: Noise cleanup
|
||||
const cleanedData = cleanNoiseFromData(rawInteractions);
|
||||
|
||||
// Paso 2: Calcular métricas base
|
||||
// Step 2: Calculate base metrics
|
||||
const baseMetrics = calculateSkillBaseMetrics(cleanedData, costPerHour);
|
||||
|
||||
// Paso 3: Transformar a dimensiones
|
||||
// Step 3: Transform to dimensions
|
||||
const dimensions = transformToDimensions(baseMetrics);
|
||||
|
||||
// Paso 4: Calcular Agentic Readiness Score
|
||||
// Step 4: Calculate Agentic Readiness Score
|
||||
const agenticReadiness = calculateAgenticReadinessScore(dimensions, weights);
|
||||
|
||||
console.log(`✅ Pipeline completado: ${agenticReadiness.length} skills procesados`);
|
||||
console.log(`📈 Distribución:`);
|
||||
console.log(`✅ Pipeline completed: ${agenticReadiness.length} skills processed`);
|
||||
console.log(`📈 Distribution:`);
|
||||
const automateCount = agenticReadiness.filter(s => s.readiness_category === 'automate_now').length;
|
||||
const assistCount = agenticReadiness.filter(s => s.readiness_category === 'assist_copilot').length;
|
||||
const optimizeCount = agenticReadiness.filter(s => s.readiness_category === 'optimize_first').length;
|
||||
@@ -279,7 +279,7 @@ export function transformRawDataToAgenticReadiness(
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilidad: Generar resumen de estadísticas
|
||||
* Utility: Generate statistics summary
|
||||
*/
|
||||
export function generateTransformationSummary(
|
||||
originalCount: number,
|
||||
@@ -300,11 +300,11 @@ export function generateTransformationSummary(
|
||||
const optimizePercent = skillsCount > 0 ? ((optimizeCount/skillsCount)*100).toFixed(0) : '0';
|
||||
|
||||
return `
|
||||
📊 Resumen de Transformación:
|
||||
• Interacciones originales: ${originalCount.toLocaleString()}
|
||||
• Ruido eliminado: ${removedCount.toLocaleString()} (${removedPercentage}%)
|
||||
• Interacciones limpias: ${cleanedCount.toLocaleString()}
|
||||
• Skills únicos: ${skillsCount}
|
||||
📊 Transformation Summary:
|
||||
• Original interactions: ${originalCount.toLocaleString()}
|
||||
• Noise removed: ${removedCount.toLocaleString()} (${removedPercentage}%)
|
||||
• Clean interactions: ${cleanedCount.toLocaleString()}
|
||||
• Unique skills: ${skillsCount}
|
||||
|
||||
🎯 Agentic Readiness:
|
||||
• 🟢 Automate Now: ${automateCount} skills (${automatePercent}%)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// utils/segmentClassifier.ts
|
||||
// Utilidad para clasificar colas/skills en segmentos de cliente
|
||||
// Utility to classify queues/skills into customer segments
|
||||
|
||||
import type { CustomerSegment, RawInteraction, StaticConfig } from '../types';
|
||||
|
||||
@@ -10,8 +10,8 @@ export interface SegmentMapping {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsea string de colas separadas por comas
|
||||
* Ejemplo: "VIP, Premium, Enterprise" → ["VIP", "Premium", "Enterprise"]
|
||||
* Parses queue string separated by commas
|
||||
* Example: "VIP, Premium, Enterprise" → ["VIP", "Premium", "Enterprise"]
|
||||
*/
|
||||
export function parseQueueList(input: string): string[] {
|
||||
if (!input || input.trim().length === 0) {
|
||||
@@ -25,13 +25,13 @@ export function parseQueueList(input: string): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clasifica una cola según el mapeo proporcionado
|
||||
* Usa matching parcial y case-insensitive
|
||||
* Classifies a queue according to the provided mapping
|
||||
* Uses partial and case-insensitive matching
|
||||
*
|
||||
* Ejemplo:
|
||||
* Example:
|
||||
* - queue: "VIP_Support" + mapping.high: ["VIP"] → "high"
|
||||
* - queue: "Soporte_General_N1" + mapping.medium: ["Soporte_General"] → "medium"
|
||||
* - queue: "Retencion" (no match) → "medium" (default)
|
||||
* - queue: "General_Support_L1" + mapping.medium: ["General_Support"] → "medium"
|
||||
* - queue: "Retention" (no match) → "medium" (default)
|
||||
*/
|
||||
export function classifyQueue(
|
||||
queue: string,
|
||||
@@ -39,7 +39,7 @@ export function classifyQueue(
|
||||
): CustomerSegment {
|
||||
const normalizedQueue = queue.toLowerCase().trim();
|
||||
|
||||
// Buscar en high value
|
||||
// Search in high value
|
||||
for (const highQueue of mapping.high_value_queues) {
|
||||
const normalizedHigh = highQueue.toLowerCase().trim();
|
||||
if (normalizedQueue.includes(normalizedHigh) || normalizedHigh.includes(normalizedQueue)) {
|
||||
@@ -47,7 +47,7 @@ export function classifyQueue(
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar en low value
|
||||
// Search in low value
|
||||
for (const lowQueue of mapping.low_value_queues) {
|
||||
const normalizedLow = lowQueue.toLowerCase().trim();
|
||||
if (normalizedQueue.includes(normalizedLow) || normalizedLow.includes(normalizedQueue)) {
|
||||
@@ -55,7 +55,7 @@ export function classifyQueue(
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar en medium value (explícito)
|
||||
// Search in medium value (explicit)
|
||||
for (const mediumQueue of mapping.medium_value_queues) {
|
||||
const normalizedMedium = mediumQueue.toLowerCase().trim();
|
||||
if (normalizedQueue.includes(normalizedMedium) || normalizedMedium.includes(normalizedQueue)) {
|
||||
@@ -63,13 +63,13 @@ export function classifyQueue(
|
||||
}
|
||||
}
|
||||
|
||||
// Default: medium (para colas no mapeadas)
|
||||
// Default: medium (for unmapped queues)
|
||||
return 'medium';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clasifica todas las colas únicas de un conjunto de interacciones
|
||||
* Retorna un mapa de cola → segmento
|
||||
* Classifies all unique queues from a set of interactions
|
||||
* Returns a map of queue → segment
|
||||
*/
|
||||
export function classifyAllQueues(
|
||||
interactions: RawInteraction[],
|
||||
@@ -77,10 +77,10 @@ export function classifyAllQueues(
|
||||
): Map<string, CustomerSegment> {
|
||||
const queueSegments = new Map<string, CustomerSegment>();
|
||||
|
||||
// Obtener colas únicas
|
||||
// Get unique queues
|
||||
const uniqueQueues = [...new Set(interactions.map(i => i.queue_skill))];
|
||||
|
||||
// Clasificar cada cola
|
||||
// Classify each queue
|
||||
uniqueQueues.forEach(queue => {
|
||||
queueSegments.set(queue, classifyQueue(queue, mapping));
|
||||
});
|
||||
@@ -89,8 +89,8 @@ export function classifyAllQueues(
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera estadísticas de segmentación
|
||||
* Retorna conteo, porcentaje y lista de colas por segmento
|
||||
* Generates segmentation statistics
|
||||
* Returns count, percentage and list of queues by segment
|
||||
*/
|
||||
export function getSegmentationStats(
|
||||
interactions: RawInteraction[],
|
||||
@@ -108,13 +108,13 @@ export function getSegmentationStats(
|
||||
total: interactions.length
|
||||
};
|
||||
|
||||
// Contar interacciones por segmento
|
||||
// Count interactions by segment
|
||||
interactions.forEach(interaction => {
|
||||
const segment = queueSegments.get(interaction.queue_skill) || 'medium';
|
||||
stats[segment].count++;
|
||||
});
|
||||
|
||||
// Calcular porcentajes
|
||||
// Calculate percentages
|
||||
const total = interactions.length;
|
||||
if (total > 0) {
|
||||
stats.high.percentage = Math.round((stats.high.count / total) * 100);
|
||||
@@ -122,7 +122,7 @@ export function getSegmentationStats(
|
||||
stats.low.percentage = Math.round((stats.low.count / total) * 100);
|
||||
}
|
||||
|
||||
// Obtener colas por segmento (únicas)
|
||||
// Get queues by segment (unique)
|
||||
queueSegments.forEach((segment, queue) => {
|
||||
if (!stats[segment].queues.includes(queue)) {
|
||||
stats[segment].queues.push(queue);
|
||||
@@ -133,7 +133,7 @@ export function getSegmentationStats(
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida que el mapeo tenga al menos una cola en algún segmento
|
||||
* Validates that the mapping has at least one queue in some segment
|
||||
*/
|
||||
export function isValidMapping(mapping: SegmentMapping): boolean {
|
||||
return (
|
||||
@@ -144,8 +144,8 @@ export function isValidMapping(mapping: SegmentMapping): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea un mapeo desde StaticConfig
|
||||
* Si no hay segment_mapping, retorna mapeo vacío
|
||||
* Creates a mapping from StaticConfig
|
||||
* If there is no segment_mapping, returns empty mapping
|
||||
*/
|
||||
export function getMappingFromConfig(config: StaticConfig): SegmentMapping | null {
|
||||
if (!config.segment_mapping) {
|
||||
@@ -160,8 +160,8 @@ export function getMappingFromConfig(config: StaticConfig): SegmentMapping | nul
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el segmento para una cola específica desde el config
|
||||
* Si no hay mapeo, retorna 'medium' por defecto
|
||||
* Gets the segment for a specific queue from the config
|
||||
* If there is no mapping, returns 'medium' by default
|
||||
*/
|
||||
export function getSegmentForQueue(
|
||||
queue: string,
|
||||
@@ -177,7 +177,7 @@ export function getSegmentForQueue(
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea estadísticas para mostrar en UI
|
||||
* Formats statistics for display in UI
|
||||
*/
|
||||
export function formatSegmentationSummary(
|
||||
stats: ReturnType<typeof getSegmentationStats>
|
||||
@@ -185,15 +185,15 @@ export function formatSegmentationSummary(
|
||||
const parts: string[] = [];
|
||||
|
||||
if (stats.high.count > 0) {
|
||||
parts.push(`${stats.high.percentage}% High Value (${stats.high.count} interacciones)`);
|
||||
parts.push(`${stats.high.percentage}% High Value (${stats.high.count} interactions)`);
|
||||
}
|
||||
|
||||
if (stats.medium.count > 0) {
|
||||
parts.push(`${stats.medium.percentage}% Medium Value (${stats.medium.count} interacciones)`);
|
||||
parts.push(`${stats.medium.percentage}% Medium Value (${stats.medium.count} interactions)`);
|
||||
}
|
||||
|
||||
if (stats.low.count > 0) {
|
||||
parts.push(`${stats.low.percentage}% Low Value (${stats.low.count} interacciones)`);
|
||||
parts.push(`${stats.low.percentage}% Low Value (${stats.low.count} interactions)`);
|
||||
}
|
||||
|
||||
return parts.join(' | ');
|
||||
|
||||
Reference in New Issue
Block a user