|
|
""" |
|
|
Risk Predictor Module |
|
|
Predicts likely outcomes and next actions. |
|
|
""" |
|
|
|
|
|
from typing import List, Dict, Any |
|
|
from sentiment_analyzer import SentimentAnalyzer |
|
|
|
|
|
|
|
|
class RiskPredictor: |
|
|
"""Predicts next actions and outcomes.""" |
|
|
|
|
|
def __init__(self): |
|
|
"""Initialize risk predictor.""" |
|
|
self.sentiment_analyzer = SentimentAnalyzer() |
|
|
|
|
|
self.churn_indicators = [ |
|
|
'cancel', 'leave', 'stop', 'switch', 'competitor', 'alternative', |
|
|
'expensive', 'slow', 'disappointed', 'problem', 'issue' |
|
|
] |
|
|
|
|
|
self.resolution_indicators = [ |
|
|
'understand', 'sorry', 'appreciate', 'help', 'support', 'solution', |
|
|
'fix', 'improve', 'better', 'thanks' |
|
|
] |
|
|
|
|
|
def predict_action(self, messages: List[Dict[str, Any]], |
|
|
context: str = "general") -> Dict[str, Any]: |
|
|
""" |
|
|
Predict likely next action or outcome. |
|
|
|
|
|
Args: |
|
|
messages: List of messages |
|
|
context: Type of relationship |
|
|
|
|
|
Returns: |
|
|
Prediction with confidence and recommendations |
|
|
""" |
|
|
if not messages: |
|
|
return self._empty_prediction() |
|
|
|
|
|
|
|
|
sentiment_data = self.sentiment_analyzer.analyze_evolution(messages) |
|
|
sentiments = [item['sentiment_score'] for item in sentiment_data['timeline']] |
|
|
current_sentiment = sentiment_data['current_sentiment'] |
|
|
trend = sentiment_data['trend'] |
|
|
|
|
|
|
|
|
final_messages = messages[-3:] if len(messages) >= 3 else messages |
|
|
final_text = ' '.join([ |
|
|
m.get('text', '') if isinstance(m, dict) else m |
|
|
for m in final_messages |
|
|
]).lower() |
|
|
|
|
|
|
|
|
churn_score = self._score_churn_risk(final_text) |
|
|
resolution_score = self._score_resolution_likelihood(final_text) |
|
|
|
|
|
|
|
|
action = self._predict_primary_action( |
|
|
current_sentiment, trend, churn_score, resolution_score, context |
|
|
) |
|
|
|
|
|
|
|
|
confidence = self._calculate_confidence(sentiments, churn_score, resolution_score) |
|
|
|
|
|
|
|
|
timeline = self._predict_timeline( |
|
|
trend, current_sentiment, churn_score, context |
|
|
) |
|
|
|
|
|
|
|
|
urgency = self._assess_urgency(current_sentiment, churn_score, action) |
|
|
|
|
|
|
|
|
interventions = self._generate_interventions( |
|
|
action, context, urgency, current_sentiment |
|
|
) |
|
|
|
|
|
|
|
|
success_rate = self._calculate_intervention_success( |
|
|
context, urgency, action |
|
|
) |
|
|
|
|
|
explanation = self._generate_explanation( |
|
|
action, current_sentiment, trend, churn_score |
|
|
) |
|
|
|
|
|
return { |
|
|
'action': action, |
|
|
'confidence': round(confidence, 1), |
|
|
'timeline': timeline, |
|
|
'urgency': urgency, |
|
|
'interventions': interventions, |
|
|
'success_rate': round(success_rate, 1), |
|
|
'explanation': explanation, |
|
|
'sentiment_trajectory': { |
|
|
'initial': sentiment_data['initial_sentiment'], |
|
|
'current': current_sentiment, |
|
|
'trend': trend, |
|
|
'overall_change': sentiment_data['overall_change'] |
|
|
} |
|
|
} |
|
|
|
|
|
def _score_churn_risk(self, text: str) -> float: |
|
|
"""Score risk of churn/leaving.""" |
|
|
score = 0 |
|
|
for indicator in self.churn_indicators: |
|
|
if indicator in text: |
|
|
score += 15 |
|
|
return min(100, score) |
|
|
|
|
|
def _score_resolution_likelihood(self, text: str) -> float: |
|
|
"""Score likelihood of resolution.""" |
|
|
score = 0 |
|
|
for indicator in self.resolution_indicators: |
|
|
if indicator in text: |
|
|
score += 15 |
|
|
return min(100, score) |
|
|
|
|
|
def _predict_primary_action(self, current_sentiment: float, trend: str, |
|
|
churn_score: float, resolution_score: float, |
|
|
context: str) -> str: |
|
|
"""Predict primary action.""" |
|
|
|
|
|
|
|
|
if churn_score > 50 and current_sentiment < 40: |
|
|
return "LIKELY_CHURN" |
|
|
|
|
|
|
|
|
if resolution_score > 50 and trend == "IMPROVING": |
|
|
return "LIKELY_RESOLUTION" |
|
|
|
|
|
|
|
|
if current_sentiment > 50 and trend != "DECLINING": |
|
|
return "LIKELY_STAY" |
|
|
|
|
|
|
|
|
if current_sentiment < 30 and trend == "DECLINING": |
|
|
return "ESCALATION_NEEDED" |
|
|
|
|
|
|
|
|
return "MONITOR_CLOSELY" |
|
|
|
|
|
def _calculate_confidence(self, sentiments: List[float], |
|
|
churn_score: float, resolution_score: float) -> float: |
|
|
"""Calculate confidence in prediction.""" |
|
|
base_confidence = 50 |
|
|
|
|
|
|
|
|
if len(sentiments) >= 5: |
|
|
base_confidence += 20 |
|
|
elif len(sentiments) >= 3: |
|
|
base_confidence += 10 |
|
|
|
|
|
|
|
|
if len(sentiments) >= 3: |
|
|
trend_strength = abs(sum(sentiments[-2:]) - sum(sentiments[:2])) / len(sentiments) |
|
|
base_confidence += min(20, trend_strength) |
|
|
|
|
|
|
|
|
if max(churn_score, resolution_score) > 60: |
|
|
base_confidence += 15 |
|
|
|
|
|
return min(100, base_confidence) |
|
|
|
|
|
def _predict_timeline(self, trend: str, current_sentiment: float, |
|
|
churn_score: float, context: str) -> str: |
|
|
"""Predict timeline to action.""" |
|
|
|
|
|
|
|
|
if current_sentiment < 20 and churn_score > 80: |
|
|
return "IMMEDIATE (0-24 hours)" |
|
|
|
|
|
|
|
|
if trend == "DECLINING" and churn_score > 60: |
|
|
return "VERY_SOON (1-3 days)" |
|
|
|
|
|
|
|
|
if churn_score > 50 or current_sentiment < 40: |
|
|
return "SOON (3-7 days)" |
|
|
|
|
|
|
|
|
if current_sentiment < 50: |
|
|
return "MEDIUM_TERM (1-4 weeks)" |
|
|
|
|
|
|
|
|
return "EXTENDED (1-3 months)" |
|
|
|
|
|
def _assess_urgency(self, current_sentiment: float, churn_score: float, |
|
|
action: str) -> str: |
|
|
"""Assess urgency level.""" |
|
|
|
|
|
if current_sentiment < 20 or churn_score > 80 or action == "LIKELY_CHURN": |
|
|
return "CRITICAL" |
|
|
|
|
|
if current_sentiment < 40 or churn_score > 60 or action == "ESCALATION_NEEDED": |
|
|
return "HIGH" |
|
|
|
|
|
if current_sentiment < 50 or churn_score > 40: |
|
|
return "MEDIUM" |
|
|
|
|
|
return "LOW" |
|
|
|
|
|
def _generate_interventions(self, action: str, context: str, |
|
|
urgency: str, sentiment: float) -> List[str]: |
|
|
"""Generate intervention recommendations.""" |
|
|
interventions = [] |
|
|
|
|
|
if action == "LIKELY_CHURN": |
|
|
interventions.append("🚨 Immediate outreach required") |
|
|
interventions.append("Prepare retention offer") |
|
|
interventions.append("Escalate to senior management") |
|
|
|
|
|
elif action == "ESCALATION_NEEDED": |
|
|
interventions.append("⚠️ Schedule urgent meeting") |
|
|
interventions.append("Identify root cause") |
|
|
interventions.append("Prepare solution options") |
|
|
|
|
|
elif action == "LIKELY_RESOLUTION": |
|
|
interventions.append("✅ Prepare resolution proposal") |
|
|
interventions.append("Schedule follow-up") |
|
|
|
|
|
|
|
|
if context == "customer": |
|
|
if urgency in ["CRITICAL", "HIGH"]: |
|
|
interventions.append("Offer priority support/upgrade") |
|
|
interventions.append("Consider special pricing") |
|
|
|
|
|
elif context == "employee": |
|
|
if urgency in ["CRITICAL", "HIGH"]: |
|
|
interventions.append("Schedule HR meeting") |
|
|
interventions.append("Assess job satisfaction") |
|
|
|
|
|
return interventions[:4] |
|
|
|
|
|
def _calculate_intervention_success(self, context: str, urgency: str, |
|
|
action: str) -> float: |
|
|
"""Calculate likelihood of successful intervention.""" |
|
|
|
|
|
base_success = 60 |
|
|
|
|
|
|
|
|
urgency_map = { |
|
|
"CRITICAL": -20, |
|
|
"HIGH": -10, |
|
|
"MEDIUM": 0, |
|
|
"LOW": 10 |
|
|
} |
|
|
base_success += urgency_map.get(urgency, 0) |
|
|
|
|
|
|
|
|
action_map = { |
|
|
"LIKELY_RESOLUTION": 20, |
|
|
"LIKELY_STAY": 15, |
|
|
"MONITOR_CLOSELY": 5, |
|
|
"ESCALATION_NEEDED": -5, |
|
|
"LIKELY_CHURN": -15 |
|
|
} |
|
|
base_success += action_map.get(action, 0) |
|
|
|
|
|
|
|
|
if context in ["customer", "employee"]: |
|
|
base_success += 10 |
|
|
|
|
|
return max(20, min(95, base_success)) |
|
|
|
|
|
def _generate_explanation(self, action: str, sentiment: float, |
|
|
trend: str, churn_score: float) -> str: |
|
|
"""Generate explanation of prediction.""" |
|
|
|
|
|
explanation = f"Based on current sentiment ({sentiment:.0f}/100) and {trend.lower()} trend, " |
|
|
|
|
|
if action == "LIKELY_CHURN": |
|
|
explanation += f"the subject shows strong churn indicators ({churn_score:.0f}/100). " |
|
|
explanation += "Immediate action strongly recommended to prevent departure." |
|
|
|
|
|
elif action == "LIKELY_RESOLUTION": |
|
|
explanation += "the situation appears to be resolving. " |
|
|
explanation += "Continue supportive approach and follow up soon." |
|
|
|
|
|
elif action == "LIKELY_STAY": |
|
|
explanation += "the relationship appears stable. " |
|
|
explanation += "Maintain current level of service and monitor for changes." |
|
|
|
|
|
elif action == "ESCALATION_NEEDED": |
|
|
explanation += "the situation has deteriorated significantly. " |
|
|
explanation += "Escalation and intervention are necessary." |
|
|
|
|
|
else: |
|
|
explanation += "signals are mixed. Continue monitoring closely." |
|
|
|
|
|
return explanation |
|
|
|
|
|
def _empty_prediction(self) -> Dict[str, Any]: |
|
|
"""Return empty prediction.""" |
|
|
return { |
|
|
'action': 'UNKNOWN', |
|
|
'confidence': 0, |
|
|
'timeline': 'UNKNOWN', |
|
|
'urgency': 'UNKNOWN', |
|
|
'interventions': [], |
|
|
'success_rate': 0, |
|
|
'explanation': 'No data provided' |
|
|
} |
|
|
|