Spaces:
Sleeping
Sleeping
Update analyzer.py
Browse files- analyzer.py +207 -120
analyzer.py
CHANGED
|
@@ -188,136 +188,223 @@ class MessageAnalyzer:
|
|
| 188 |
}
|
| 189 |
|
| 190 |
def analyze_chat_history(self, df):
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
#
|
| 201 |
-
results_df['abuse_score'] =
|
| 202 |
-
results_df['detected_patterns'] = [
|
| 203 |
-
results_df['sentiment'] =
|
| 204 |
-
results_df['darvo_score'] =
|
| 205 |
-
results_df['emotional_tone'] =
|
| 206 |
-
results_df['boundary_health'] =
|
| 207 |
-
results_df['risk_level'] =
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
# Update dataframe with analysis results
|
| 214 |
-
results_df.at[i, 'abuse_score'] = analysis['abuse_score']
|
| 215 |
-
results_df.at[i, 'detected_patterns'] = analysis['detected_patterns']
|
| 216 |
-
results_df.at[i, 'sentiment'] = analysis['sentiment']
|
| 217 |
-
results_df.at[i, 'darvo_score'] = analysis['darvo_score']
|
| 218 |
-
results_df.at[i, 'emotional_tone'] = analysis['emotional_tone']
|
| 219 |
-
results_df.at[i, 'boundary_health'] = analysis['boundary_assessment']['assessment']
|
| 220 |
-
results_df.at[i, 'risk_level'] = analysis['risk_level']
|
| 221 |
-
|
| 222 |
-
# Calculate sender statistics
|
| 223 |
-
sender_stats = {}
|
| 224 |
-
for sender in results_df['sender'].unique():
|
| 225 |
-
sender_df = results_df[results_df['sender'] == sender]
|
| 226 |
-
|
| 227 |
-
# Calculate key metrics
|
| 228 |
-
avg_abuse = sender_df['abuse_score'].mean()
|
| 229 |
-
max_abuse = sender_df['abuse_score'].max()
|
| 230 |
-
|
| 231 |
-
# Get most common patterns
|
| 232 |
-
all_patterns = []
|
| 233 |
-
for patterns in sender_df['detected_patterns']:
|
| 234 |
-
if patterns:
|
| 235 |
-
all_patterns.extend(patterns)
|
| 236 |
-
|
| 237 |
-
pattern_counts = Counter(all_patterns)
|
| 238 |
-
most_common = pattern_counts.most_common(3)
|
| 239 |
-
|
| 240 |
-
# Calculate percentage of abusive messages
|
| 241 |
-
abusive_count = len(sender_df[sender_df['abuse_score'] >= 50])
|
| 242 |
-
abusive_pct = (abusive_count / len(sender_df)) * 100 if len(sender_df) > 0 else 0
|
| 243 |
-
|
| 244 |
-
# Store stats
|
| 245 |
-
sender_stats[sender] = {
|
| 246 |
-
'message_count': len(sender_df),
|
| 247 |
-
'avg_abuse_score': avg_abuse,
|
| 248 |
-
'max_abuse_score': max_abuse,
|
| 249 |
-
'abusive_message_count': abusive_count,
|
| 250 |
-
'abusive_message_pct': abusive_pct,
|
| 251 |
-
'common_patterns': most_common
|
| 252 |
-
}
|
| 253 |
-
|
| 254 |
-
# Detect escalation patterns
|
| 255 |
-
escalation_data = detect_escalation_patterns(results_df)
|
| 256 |
|
| 257 |
-
#
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
elif results_df['risk_level'].isin(['High']).any():
|
| 261 |
-
overall_risk = "High"
|
| 262 |
-
elif results_df['risk_level'].isin(['Moderate']).any():
|
| 263 |
-
overall_risk = "Moderate"
|
| 264 |
-
else:
|
| 265 |
-
overall_risk = "Low"
|
| 266 |
|
| 267 |
-
#
|
| 268 |
all_patterns = []
|
| 269 |
-
for patterns in
|
| 270 |
if patterns:
|
| 271 |
all_patterns.extend(patterns)
|
| 272 |
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
#
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
for sender, stats in sender_stats.items():
|
| 283 |
-
if stats['message_count'] >= 5 and stats['abusive_message_pct'] > max_abusive_pct:
|
| 284 |
-
max_abusive_pct = stats['abusive_message_pct']
|
| 285 |
-
primary_abuser = sender
|
| 286 |
-
|
| 287 |
-
# Only identify primary abuser if they have significant abusive content
|
| 288 |
-
if max_abusive_pct < 20:
|
| 289 |
-
primary_abuser = None
|
| 290 |
-
|
| 291 |
-
# Prepare summary
|
| 292 |
-
summary = {
|
| 293 |
-
'message_count': len(results_df),
|
| 294 |
-
'date_range': {
|
| 295 |
-
'start': results_df['timestamp'].min().strftime('%Y-%m-%d'),
|
| 296 |
-
'end': results_df['timestamp'].max().strftime('%Y-%m-%d')
|
| 297 |
-
},
|
| 298 |
-
'overall_risk_level': overall_risk,
|
| 299 |
-
'sender_stats': sender_stats,
|
| 300 |
-
'primary_abuser': primary_abuser,
|
| 301 |
-
'escalation_data': escalation_data,
|
| 302 |
-
'safety_plan': safety_plan,
|
| 303 |
-
'recommendations': recommendations
|
| 304 |
-
}
|
| 305 |
|
| 306 |
-
|
|
|
|
|
|
|
| 307 |
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
}
|
| 189 |
|
| 190 |
def analyze_chat_history(self, df):
|
| 191 |
+
"""Analyze entire chat history"""
|
| 192 |
+
from utils import detect_escalation_patterns, generate_safety_plan, generate_professional_recommendations
|
| 193 |
+
|
| 194 |
+
logger.info(f"Analyzing chat history with {len(df)} messages")
|
| 195 |
+
|
| 196 |
+
try:
|
| 197 |
+
# Create results dataframe
|
| 198 |
+
results_df = df.copy()
|
| 199 |
|
| 200 |
+
# Add analysis columns
|
| 201 |
+
results_df['abuse_score'] = 0.0
|
| 202 |
+
results_df['detected_patterns'] = [[] for _ in range(len(results_df))]
|
| 203 |
+
results_df['sentiment'] = "neutral"
|
| 204 |
+
results_df['darvo_score'] = 0.0
|
| 205 |
+
results_df['emotional_tone'] = "neutral"
|
| 206 |
+
results_df['boundary_health'] = "unknown"
|
| 207 |
+
results_df['risk_level'] = "Low"
|
| 208 |
|
| 209 |
+
# Analyze each message
|
| 210 |
+
for i, row in results_df.iterrows():
|
| 211 |
+
analysis = self.analyze_message(row['message'])
|
| 212 |
+
|
| 213 |
+
# Update dataframe with analysis results
|
| 214 |
+
results_df.at[i, 'abuse_score'] = analysis['abuse_score']
|
| 215 |
+
results_df.at[i, 'detected_patterns'] = analysis['detected_patterns']
|
| 216 |
+
results_df.at[i, 'sentiment'] = analysis['sentiment']
|
| 217 |
+
results_df.at[i, 'darvo_score'] = analysis['darvo_score']
|
| 218 |
+
results_df.at[i, 'emotional_tone'] = analysis['emotional_tone']
|
| 219 |
+
results_df.at[i, 'boundary_health'] = analysis['boundary_assessment']['assessment']
|
| 220 |
+
results_df.at[i, 'risk_level'] = analysis['risk_level']
|
| 221 |
+
|
| 222 |
+
# Calculate sender statistics
|
| 223 |
+
sender_stats = {}
|
| 224 |
+
for sender in results_df['sender'].unique():
|
| 225 |
+
sender_df = results_df[results_df['sender'] == sender]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
+
# Calculate key metrics
|
| 228 |
+
avg_abuse = sender_df['abuse_score'].mean()
|
| 229 |
+
max_abuse = sender_df['abuse_score'].max()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
| 231 |
+
# Get most common patterns
|
| 232 |
all_patterns = []
|
| 233 |
+
for patterns in sender_df['detected_patterns']:
|
| 234 |
if patterns:
|
| 235 |
all_patterns.extend(patterns)
|
| 236 |
|
| 237 |
+
pattern_counts = Counter(all_patterns)
|
| 238 |
+
most_common = pattern_counts.most_common(5) # Get top 5 patterns
|
| 239 |
+
|
| 240 |
+
# Calculate percentage of abusive messages
|
| 241 |
+
abusive_count = len(sender_df[sender_df['abuse_score'] >= 50])
|
| 242 |
+
abusive_pct = (abusive_count / len(sender_df)) * 100 if len(sender_df) > 0 else 0
|
| 243 |
+
|
| 244 |
+
# Calculate emotional tone distribution
|
| 245 |
+
tone_counts = Counter(sender_df['emotional_tone'])
|
| 246 |
+
most_common_tones = tone_counts.most_common(3) # Get top 3 emotional tones
|
| 247 |
+
|
| 248 |
+
# Calculate DARVO score statistics
|
| 249 |
+
avg_darvo = sender_df['darvo_score'].mean()
|
| 250 |
+
high_darvo_count = len(sender_df[sender_df['darvo_score'] >= 0.65])
|
| 251 |
+
high_darvo_pct = (high_darvo_count / len(sender_df)) * 100 if len(sender_df) > 0 else 0
|
| 252 |
+
|
| 253 |
+
# Calculate risk level distribution
|
| 254 |
+
risk_counts = Counter(sender_df['risk_level'])
|
| 255 |
+
|
| 256 |
+
# Store stats
|
| 257 |
+
sender_stats[sender] = {
|
| 258 |
+
'message_count': len(sender_df),
|
| 259 |
+
'avg_abuse_score': avg_abuse,
|
| 260 |
+
'max_abuse_score': max_abuse,
|
| 261 |
+
'abusive_message_count': abusive_count,
|
| 262 |
+
'abusive_message_pct': abusive_pct,
|
| 263 |
+
'common_patterns': most_common,
|
| 264 |
+
'emotional_tones': most_common_tones,
|
| 265 |
+
'avg_darvo_score': avg_darvo,
|
| 266 |
+
'high_darvo_count': high_darvo_count,
|
| 267 |
+
'high_darvo_pct': high_darvo_pct,
|
| 268 |
+
'risk_levels': risk_counts
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
# Detect escalation patterns
|
| 272 |
+
escalation_data = detect_escalation_patterns(results_df)
|
| 273 |
+
|
| 274 |
+
# Determine overall risk level
|
| 275 |
+
if results_df['risk_level'].isin(['Critical']).any():
|
| 276 |
+
overall_risk = "Critical"
|
| 277 |
+
elif results_df['risk_level'].isin(['High']).any():
|
| 278 |
+
overall_risk = "High"
|
| 279 |
+
elif results_df['risk_level'].isin(['Moderate']).any():
|
| 280 |
+
overall_risk = "Moderate"
|
| 281 |
+
else:
|
| 282 |
+
overall_risk = "Low"
|
| 283 |
+
|
| 284 |
+
# Generate safety plan
|
| 285 |
+
all_patterns = []
|
| 286 |
+
for patterns in results_df['detected_patterns']:
|
| 287 |
+
if patterns:
|
| 288 |
+
all_patterns.extend(patterns)
|
| 289 |
+
|
| 290 |
+
safety_plan = generate_safety_plan(overall_risk, all_patterns, escalation_data)
|
| 291 |
+
|
| 292 |
+
# Identify primary abuser (if any)
|
| 293 |
+
primary_abuser = None
|
| 294 |
+
max_abusive_pct = 0
|
| 295 |
+
|
| 296 |
+
for sender, stats in sender_stats.items():
|
| 297 |
+
if stats['message_count'] >= 5 and stats['abusive_message_pct'] > max_abusive_pct:
|
| 298 |
+
max_abusive_pct = stats['abusive_message_pct']
|
| 299 |
+
primary_abuser = sender
|
| 300 |
+
|
| 301 |
+
# Only identify primary abuser if they have significant abusive content
|
| 302 |
+
if max_abusive_pct < 20:
|
| 303 |
+
primary_abuser = None
|
| 304 |
|
| 305 |
+
# Create detailed primary abuser analysis
|
| 306 |
+
primary_abuser_analysis = None
|
| 307 |
+
if primary_abuser:
|
| 308 |
+
abuser_df = results_df[results_df['sender'] == primary_abuser]
|
| 309 |
+
|
| 310 |
+
# Get example messages for each pattern
|
| 311 |
+
pattern_examples = {}
|
| 312 |
+
for pattern in set([p for patterns in abuser_df['detected_patterns'] for p in patterns]):
|
| 313 |
+
# Find messages with this pattern
|
| 314 |
+
pattern_msgs = abuser_df[abuser_df['detected_patterns'].apply(lambda x: pattern in x)]
|
| 315 |
+
if not pattern_msgs.empty:
|
| 316 |
+
# Get highest scoring example
|
| 317 |
+
example = pattern_msgs.iloc[pattern_msgs['abuse_score'].argmax()]
|
| 318 |
+
pattern_examples[pattern] = {
|
| 319 |
+
'message': example['message'],
|
| 320 |
+
'abuse_score': example['abuse_score'],
|
| 321 |
+
'timestamp': example['timestamp'].strftime('%Y-%m-%d %H:%M')
|
| 322 |
+
}
|
| 323 |
|
| 324 |
+
# Get time-based patterns
|
| 325 |
+
abuser_df['hour'] = abuser_df['timestamp'].dt.hour
|
| 326 |
+
abuser_df['day_of_week'] = abuser_df['timestamp'].dt.day_name()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
+
# Find peak abuse hours
|
| 329 |
+
hour_abuse = abuser_df.groupby('hour')['abuse_score'].mean()
|
| 330 |
+
peak_hours = hour_abuse.nlargest(3).index.tolist() if not hour_abuse.empty else []
|
| 331 |
|
| 332 |
+
# Find peak abuse days
|
| 333 |
+
day_abuse = abuser_df.groupby('day_of_week')['abuse_score'].mean()
|
| 334 |
+
peak_days = day_abuse.nlargest(3).index.tolist() if not day_abuse.empty else []
|
| 335 |
+
|
| 336 |
+
# Calculate response patterns (how they respond to the other person)
|
| 337 |
+
response_patterns = []
|
| 338 |
+
if len(results_df) >= 10: # Need enough messages for meaningful analysis
|
| 339 |
+
# Sort by timestamp
|
| 340 |
+
sorted_df = results_df.sort_values('timestamp')
|
| 341 |
+
|
| 342 |
+
# Find messages where primary abuser responds to other person
|
| 343 |
+
for i in range(1, len(sorted_df)):
|
| 344 |
+
if (sorted_df.iloc[i]['sender'] == primary_abuser and
|
| 345 |
+
sorted_df.iloc[i-1]['sender'] != primary_abuser):
|
| 346 |
+
# This is a response
|
| 347 |
+
trigger_msg = sorted_df.iloc[i-1]
|
| 348 |
+
response_msg = sorted_df.iloc[i]
|
| 349 |
+
|
| 350 |
+
if response_msg['abuse_score'] >= 50: # Only track abusive responses
|
| 351 |
+
response_patterns.append({
|
| 352 |
+
'trigger': trigger_msg['message'],
|
| 353 |
+
'response': response_msg['message'],
|
| 354 |
+
'response_patterns': response_msg['detected_patterns'],
|
| 355 |
+
'response_score': response_msg['abuse_score'],
|
| 356 |
+
'timestamp': response_msg['timestamp'].strftime('%Y-%m-%d %H:%M')
|
| 357 |
+
})
|
| 358 |
+
|
| 359 |
+
# Store the detailed analysis
|
| 360 |
+
primary_abuser_analysis = {
|
| 361 |
+
'sender': primary_abuser,
|
| 362 |
+
'message_count': len(abuser_df),
|
| 363 |
+
'avg_abuse_score': abuser_df['abuse_score'].mean(),
|
| 364 |
+
'pattern_examples': pattern_examples,
|
| 365 |
+
'peak_hours': peak_hours,
|
| 366 |
+
'peak_days': peak_days,
|
| 367 |
+
'response_patterns': response_patterns[:5], # Limit to top 5
|
| 368 |
+
'emotional_tones': sender_stats[primary_abuser]['emotional_tones'],
|
| 369 |
+
'darvo_score': sender_stats[primary_abuser]['avg_darvo_score']
|
| 370 |
}
|
| 371 |
+
|
| 372 |
+
# Generate professional recommendations
|
| 373 |
+
recommendations = generate_professional_recommendations(results_df, escalation_data, overall_risk)
|
| 374 |
+
|
| 375 |
+
# Prepare summary
|
| 376 |
+
summary = {
|
| 377 |
+
'message_count': len(results_df),
|
| 378 |
+
'date_range': {
|
| 379 |
+
'start': results_df['timestamp'].min().strftime('%Y-%m-%d'),
|
| 380 |
+
'end': results_df['timestamp'].max().strftime('%Y-%m-%d')
|
| 381 |
+
},
|
| 382 |
+
'overall_risk_level': overall_risk,
|
| 383 |
+
'sender_stats': sender_stats,
|
| 384 |
+
'primary_abuser': primary_abuser,
|
| 385 |
+
'primary_abuser_analysis': primary_abuser_analysis, # Add detailed analysis
|
| 386 |
+
'escalation_data': escalation_data,
|
| 387 |
+
'safety_plan': safety_plan,
|
| 388 |
+
'recommendations': recommendations
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
return results_df, summary
|
| 392 |
+
|
| 393 |
+
except Exception as e:
|
| 394 |
+
logger.error(f"Error in analyze_chat_history: {e}")
|
| 395 |
+
logger.error(traceback.format_exc())
|
| 396 |
+
return df, {
|
| 397 |
+
'message_count': len(df),
|
| 398 |
+
'date_range': {
|
| 399 |
+
'start': df['timestamp'].min().strftime('%Y-%m-%d') if not df.empty else 'unknown',
|
| 400 |
+
'end': df['timestamp'].max().strftime('%Y-%m-%d') if not df.empty else 'unknown'
|
| 401 |
+
},
|
| 402 |
+
'overall_risk_level': "Unknown",
|
| 403 |
+
'sender_stats': {},
|
| 404 |
+
'primary_abuser': None,
|
| 405 |
+
'primary_abuser_analysis': None,
|
| 406 |
+
'escalation_data': {},
|
| 407 |
+
'safety_plan': "Error generating safety plan.",
|
| 408 |
+
'recommendations': []
|
| 409 |
+
}
|
| 410 |
+
|