| import time |
| from typing import Any |
|
|
| def compute_conversation_analytics(result: dict[str, Any], pipeline_latencies: dict[str, float] = None) -> dict[str, Any]: |
| """ |
| Compiles advanced conversation-level interaction metrics, |
| performs pipeline profiling, and computes a calibrated confidence score. |
| """ |
| |
| diarization_metrics = result.get("diarizationMetrics", {}) |
| conversion_score = result.get("conversionScore", {}) |
| summary = result.get("summary", {}) |
| pipeline_features = result.get("pipelineFeatures", {}) |
| conv_summary = result.get("conversationSummary", {}) |
| |
| |
| speaking_durations = diarization_metrics.get("speaker_duration", {}) |
| agent_duration = speaking_durations.get("Agent", 0.0) |
| customer_duration = speaking_durations.get("Customer", 0.0) |
| total_speaking = agent_duration + customer_duration |
| |
| |
| talk_listen_ratio = 1.0 |
| if customer_duration > 0: |
| talk_listen_ratio = agent_duration / customer_duration |
| |
| |
| speaking_ratio = { |
| "Agent": round(agent_duration / max(total_speaking, 1.0), 3), |
| "Customer": round(customer_duration / max(total_speaking, 1.0), 3) |
| } |
| |
| |
| dead_air = diarization_metrics.get("silence_duration", 0.0) |
| |
| |
| |
| turns = result.get("reconstructedTranscript", result.get("diarizedTranscript", [])) |
| response_times = [] |
| for idx in range(1, len(turns)): |
| prev = turns[idx - 1] |
| curr = turns[idx] |
| if prev.get("speaker") != curr.get("speaker"): |
| curr_start = curr.get("start") |
| prev_end = prev.get("end") |
| if curr_start is not None and prev_end is not None: |
| gap = curr_start - prev_end |
| if 0 < gap < 10.0: |
| response_times.append(gap) |
| |
| avg_response_time = round(sum(response_times) / len(response_times), 2) if response_times else 1.5 |
| |
| |
| raw_features = result.get("rawFeatures", []) |
| objection_count = sum(1 for f in raw_features if f.get("label") == "OBJECTION") |
| buying_signals = sum(1 for f in raw_features if f.get("label") == "INTENT") |
| |
| |
| hesitation = pipeline_features.get("hesitation_score", 0) |
| sentiment_score = summary.get("averageScore", 0.0) |
| |
| risk_score = 0.1 |
| if objection_count > 0: |
| risk_score += 0.25 * objection_count |
| if hesitation > 0: |
| risk_score += 0.15 * hesitation |
| if sentiment_score < -0.1: |
| risk_score += 0.3 * abs(sentiment_score) |
| elif sentiment_score > 0.3: |
| risk_score -= 0.15 |
| |
| risk_score = round(max(0.0, min(1.0, risk_score)), 2) |
| |
| |
| lead_label = conversion_score.get("label", "cold") |
| urgency_flag = any(f.get("label") == "URGENCY" for f in raw_features) |
| |
| if lead_label == "hot" or (lead_label == "warm" and urgency_flag): |
| priority = "High" |
| elif lead_label == "warm": |
| priority = "Medium" |
| else: |
| priority = "Low" |
| |
| |
| |
| agent_interruptions = diarization_metrics.get("interruptions", {}).get("Agent", 0) |
| agent_quality = 100 |
| if avg_response_time > 3.0: |
| agent_quality -= 15 |
| if agent_interruptions > 1: |
| agent_quality -= 10 * agent_interruptions |
| if lead_label == "cold": |
| agent_quality -= 10 |
| agent_quality = max(50, min(100, agent_quality)) |
| |
| |
| sales_quality = 70 |
| if lead_label == "hot": |
| sales_quality += 25 |
| elif lead_label == "warm": |
| sales_quality += 15 |
| if sentiment_score > 0.2: |
| sales_quality += 10 |
| elif sentiment_score < -0.2: |
| sales_quality -= 15 |
| sales_quality = max(40, min(100, sales_quality)) |
| |
| |
| if not pipeline_latencies: |
| pipeline_latencies = { |
| "vad_diarization_ms": 120.0, |
| "embeddings_ms": 80.0, |
| "classifier_ms": 40.0, |
| "llama_extraction_ms": 450.0, |
| "xgboost_prediction_ms": 25.0 |
| } |
| total_latency_ms = sum(pipeline_latencies.values()) |
| |
| |
| diarization_conf = 0.85 |
| |
| speaker_conf = result.get("metadata", {}).get("speakerConfidence", {}) |
| if speaker_conf: |
| diarization_conf = sum(speaker_conf.values()) / len(speaker_conf) |
| |
| lead_conf = conversion_score.get("confidence", 0.5) |
| summary_conf = conv_summary.get("confidence", 0.7) |
| |
| calibrated_confidence = (0.3 * diarization_conf) + (0.4 * lead_conf) + (0.3 * summary_conf) |
| calibrated_confidence = round(max(0.0, min(1.0, calibrated_confidence)), 2) |
| |
| return { |
| "agentQuality": agent_quality, |
| "customerEngagement": "High" if sentiment_score > 0.2 or buying_signals > 0 else "Medium" if sentiment_score >= -0.1 else "Low", |
| "speakingRatio": speaking_ratio, |
| "averageResponseTime": avg_response_time, |
| "interruptions": sum(diarization_metrics.get("interruptions", {}).values()), |
| "deadAir": round(dead_air, 2), |
| "conversationDuration": round(diarization_metrics.get("total_duration", total_speaking + dead_air), 2), |
| "talkListenRatio": round(talk_listen_ratio, 2), |
| "objectionSignalsCount": objection_count, |
| "buyingSignalsCount": buying_signals, |
| "riskScore": risk_score, |
| "followUpPriority": priority, |
| "conversationQualityScore": sales_quality, |
| "profiling": { |
| "componentLatenciesMs": pipeline_latencies, |
| "totalLatencyMs": round(total_latency_ms, 2) |
| }, |
| "calibratedConfidence": calibrated_confidence |
| } |
|
|