/** * Agentic Readiness Score v2.0 * Algorithm based on 6-dimension methodology with continuous normalization */ import type { TierKey, SubFactor, AgenticReadinessResult, CustomerSegment } from '../types'; import { AGENTIC_READINESS_WEIGHTS, AGENTIC_READINESS_THRESHOLDS } from '../constants'; export interface AgenticReadinessInput { // Basic data (SILVER) volumen_mes: number; aht_values: number[]; escalation_rate: number; cpi_humano: number; volumen_anual: number; // Advanced data (GOLD) structured_fields_pct?: number; exception_rate?: number; hourly_distribution?: number[]; off_hours_pct?: number; csat_values?: number[]; motivo_contacto_entropy?: number; resolucion_entropy?: number; // Tier tier: TierKey; } /** * SUB-FACTOR 1: REPEATABILITY (25%) * Based on monthly volume with logistic normalization */ function calculateRepeatabilityScore(volumen_mes: number): SubFactor { const { k, x0 } = AGENTIC_READINESS_THRESHOLDS.repetitividad; // Logistic function: score = 10 / (1 + exp(-k * (volume - x0))) const score = 10 / (1 + Math.exp(-k * (volumen_mes - x0))); return { name: 'repeatability', displayName: 'Repeatability', score: Math.round(score * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.repetitividad, description: `Monthly volume: ${volumen_mes} interactions`, details: { volumen_mes, threshold_medio: x0 } }; } /** * SUB-FACTOR 2: PREDICTABILITY (20%) * Based on AHT variability + escalation rate + input/output variability */ function calculatePredictabilityScore( aht_values: number[], escalation_rate: number, motivo_contacto_entropy?: number, resolucion_entropy?: number ): SubFactor { const thresholds = AGENTIC_READINESS_THRESHOLDS.predictibilidad; // 1. AHT VARIABILITY (40%) const aht_mean = aht_values.reduce((a, b) => a + b, 0) / aht_values.length; const aht_variance = aht_values.reduce((sum, val) => sum + Math.pow(val - aht_mean, 2), 0) / aht_values.length; const aht_std = Math.sqrt(aht_variance); const cv_aht = aht_std / aht_mean; // Normalize CV to 0-10 scale const score_aht = Math.max(0, Math.min(10, 10 * (1 - (cv_aht - thresholds.cv_aht_excellent) / (thresholds.cv_aht_poor - thresholds.cv_aht_excellent)) )); // 2. ESCALATION RATE (30%) const score_escalacion = Math.max(0, Math.min(10, 10 * (1 - escalation_rate / thresholds.escalation_poor) )); // 3. INPUT/OUTPUT VARIABILITY (30%) let score_variabilidad: number; if (motivo_contacto_entropy !== undefined && resolucion_entropy !== undefined) { // High input entropy + Low output entropy = GOOD for automation const input_normalized = Math.min(motivo_contacto_entropy / 3.0, 1.0); const output_normalized = Math.min(resolucion_entropy / 3.0, 1.0); score_variabilidad = 10 * (input_normalized * (1 - output_normalized)); } else { // If no entropy data, use average of AHT and escalation score_variabilidad = (score_aht + score_escalacion) / 2; } // FINAL WEIGHTING const predictabilidad = ( 0.40 * score_aht + 0.30 * score_escalacion + 0.30 * score_variabilidad ); return { name: 'predictability', displayName: 'Predictability', score: Math.round(predictabilidad * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.predictibilidad, description: `AHT CV: ${(cv_aht * 100).toFixed(1)}%, Escalation: ${(escalation_rate * 100).toFixed(1)}%`, details: { cv_aht: Math.round(cv_aht * 1000) / 1000, escalation_rate, score_aht: Math.round(score_aht * 10) / 10, score_escalacion: Math.round(score_escalacion * 10) / 10, score_variabilidad: Math.round(score_variabilidad * 10) / 10 } }; } /** * SUB-FACTOR 3: STRUCTURING (15%) * Percentage of structured fields vs free text */ function calculateStructuringScore(structured_fields_pct: number): SubFactor { const score = structured_fields_pct * 10; return { name: 'structuring', displayName: 'Structuring', score: Math.round(score * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.estructuracion, description: `${(structured_fields_pct * 100).toFixed(0)}% structured fields`, details: { structured_fields_pct } }; } /** * SUB-FACTOR 4: INVERSE COMPLEXITY (15%) * Based on exception rate */ function calculateInverseComplexityScore(exception_rate: number): SubFactor { // Lower exception rate → Higher score // < 5% → Excellent (score 10) // > 30% → Very complex (score 0) const score_excepciones = Math.max(0, Math.min(10, 10 * (1 - exception_rate / 0.30))); return { name: 'inverseComplexity', displayName: 'Inverse Complexity', score: Math.round(score_excepciones * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.complejidad_inversa, description: `${(exception_rate * 100).toFixed(1)}% exceptions`, details: { exception_rate } }; } /** * SUB-FACTOR 5: STABILITY (10%) * Based on hourly distribution and % off-hours calls */ function calculateStabilityScore( hourly_distribution: number[], off_hours_pct: number ): SubFactor { // 1. HOURLY DISTRIBUTION UNIFORMITY (60%) // Calculate Shannon entropy const total = hourly_distribution.reduce((a, b) => a + b, 0); let score_uniformidad = 0; let entropy_normalized = 0; if (total > 0) { const probs = hourly_distribution.map(v => v / total).filter(p => p > 0); const entropy = -probs.reduce((sum, p) => sum + p * Math.log2(p), 0); const max_entropy = Math.log2(hourly_distribution.length); entropy_normalized = entropy / max_entropy; score_uniformidad = entropy_normalized * 10; } // 2. % OFF-HOURS CALLS (40%) // More off-hours calls → Higher agent need → Higher score const score_off_hours = Math.min(10, (off_hours_pct / 0.30) * 10); // WEIGHTING const estabilidad = ( 0.60 * score_uniformidad + 0.40 * score_off_hours ); return { name: 'stability', displayName: 'Stability', score: Math.round(estabilidad * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.estabilidad, description: `${(off_hours_pct * 100).toFixed(1)}% off-hours`, details: { entropy_normalized: Math.round(entropy_normalized * 1000) / 1000, off_hours_pct, score_uniformidad: Math.round(score_uniformidad * 10) / 10, score_off_hours: Math.round(score_off_hours * 10) / 10 } }; } /** * SUB-FACTOR 6: ROI (15%) * Based on annual potential savings */ function calculateROIScore( volumen_anual: number, cpi_humano: number, automation_savings_pct: number = 0.70 ): SubFactor { const ahorro_anual = volumen_anual * cpi_humano * automation_savings_pct; // Logistic normalization const { k, x0 } = AGENTIC_READINESS_THRESHOLDS.roi; const score = 10 / (1 + Math.exp(-k * (ahorro_anual - x0))); return { name: 'roi', displayName: 'ROI', score: Math.round(score * 10) / 10, weight: AGENTIC_READINESS_WEIGHTS.roi, description: `€${(ahorro_anual / 1000).toFixed(0)}K annual potential savings`, details: { ahorro_anual: Math.round(ahorro_anual), volumen_anual, cpi_humano, automation_savings_pct } }; } /** * CSAT DISTRIBUTION ADJUSTMENT (Optional, ±10%) * Normal distribution → Stable process */ function calculateCSATDistributionAdjustment(csat_values: number[]): number { // Simplified normality test (based on skewness and kurtosis) const n = csat_values.length; const mean = csat_values.reduce((a, b) => a + b, 0) / n; const variance = csat_values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / n; const std = Math.sqrt(variance); // Skewness const skewness = csat_values.reduce((sum, val) => sum + Math.pow((val - mean) / std, 3), 0) / n; // Kurtosis const kurtosis = csat_values.reduce((sum, val) => sum + Math.pow((val - mean) / std, 4), 0) / n; // Normality: skewness close to 0, kurtosis close to 3 const skewness_score = Math.max(0, 1 - Math.abs(skewness)); const kurtosis_score = Math.max(0, 1 - Math.abs(kurtosis - 3) / 3); const normality_score = (skewness_score + kurtosis_score) / 2; // Adjustment: +5% if very normal, -5% if very abnormal const adjustment = 1 + ((normality_score - 0.5) * 0.10); return adjustment; } /** * COMPLETE ALGORITHM (Tier GOLD) */ export function calculateAgenticReadinessScoreGold(data: AgenticReadinessInput): AgenticReadinessResult { const sub_factors: SubFactor[] = []; // 1. REPEATABILITY sub_factors.push(calculateRepeatabilityScore(data.volumen_mes)); // 2. PREDICTABILITY sub_factors.push(calculatePredictabilityScore( data.aht_values, data.escalation_rate, data.motivo_contacto_entropy, data.resolucion_entropy )); // 3. STRUCTURING sub_factors.push(calculateStructuringScore(data.structured_fields_pct || 0.5)); // 4. INVERSE COMPLEXITY sub_factors.push(calculateInverseComplexityScore(data.exception_rate || 0.15)); // 5. STABILITY sub_factors.push(calculateStabilityScore( data.hourly_distribution || Array(24).fill(1), data.off_hours_pct || 0.2 )); // 6. ROI sub_factors.push(calculateROIScore( data.volumen_anual, data.cpi_humano )); // BASE WEIGHTING const agentic_readiness_base = sub_factors.reduce( (sum, factor) => sum + (factor.score * factor.weight), 0 ); // CSAT DISTRIBUTION ADJUSTMENT (Optional) let agentic_readiness_final = agentic_readiness_base; if (data.csat_values && data.csat_values.length > 10) { const adjustment = calculateCSATDistributionAdjustment(data.csat_values); agentic_readiness_final = agentic_readiness_base * adjustment; } // Limit to 0-10 range agentic_readiness_final = Math.max(0, Math.min(10, agentic_readiness_final)); // Interpretation let interpretation = ''; let confidence: 'high' | 'medium' | 'low' = 'high'; if (agentic_readiness_final >= 8) { interpretation = 'Excellent candidate for complete automation (Automate)'; } else if (agentic_readiness_final >= 5) { interpretation = 'Good candidate for agentic assistance (Assist)'; } else if (agentic_readiness_final >= 3) { interpretation = 'Candidate for human augmentation (Augment)'; } else { interpretation = 'Not recommended for automation at this time'; } return { score: Math.round(agentic_readiness_final * 10) / 10, sub_factors, tier: 'gold', confidence, interpretation }; } /** * SIMPLIFIED ALGORITHM (Tier SILVER) */ export function calculateAgenticReadinessScoreSilver(data: AgenticReadinessInput): AgenticReadinessResult { const sub_factors: SubFactor[] = []; // 1. REPEATABILITY (30%) const repeatability = calculateRepeatabilityScore(data.volumen_mes); repeatability.weight = 0.30; sub_factors.push(repeatability); // 2. SIMPLIFIED PREDICTABILITY (30%) const predictability = calculatePredictabilityScore( data.aht_values, data.escalation_rate ); predictability.weight = 0.30; sub_factors.push(predictability); // 3. ROI (40%) const roi = calculateROIScore(data.volumen_anual, data.cpi_humano); roi.weight = 0.40; sub_factors.push(roi); // SIMPLIFIED WEIGHTING const agentic_readiness = sub_factors.reduce( (sum, factor) => sum + (factor.score * factor.weight), 0 ); // Interpretation let interpretation = ''; if (agentic_readiness >= 7) { interpretation = 'Good candidate for automation'; } else if (agentic_readiness >= 4) { interpretation = 'Candidate for agentic assistance'; } else { interpretation = 'Requires deeper analysis (consider GOLD)'; } return { score: Math.round(agentic_readiness * 10) / 10, sub_factors, tier: 'silver', confidence: 'medium', interpretation }; } /** * MAIN FUNCTION - Selects algorithm based on tier */ export function calculateAgenticReadinessScore(data: AgenticReadinessInput): AgenticReadinessResult { if (data.tier === 'gold') { return calculateAgenticReadinessScoreGold(data); } else if (data.tier === 'silver') { return calculateAgenticReadinessScoreSilver(data); } else { // BRONZE: No Agentic Readiness return { score: 0, sub_factors: [], tier: 'bronze', confidence: 'low', interpretation: 'Bronze analysis does not include Agentic Readiness Score' }; } }