Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -346,34 +346,21 @@ def get_risk_stage(patterns, sentiment):
|
|
346 |
elif sentiment == "supportive" and any(p in patterns for p in ["projection", "dismissiveness"]):
|
347 |
return 4
|
348 |
return 1
|
349 |
-
# Use pattern scores from the top-scoring message for risk snippet
|
350 |
-
index_top_msg = abuse_scores.index(max(abuse_scores))
|
351 |
-
pattern_scores_for_snippet = {label: score for label, score in results[index_top_msg][0][2]}
|
352 |
-
def generate_risk_snippet(abuse_score, top_label, escalation_score, stage, pattern_scores):
|
353 |
-
aggression_score = pattern_scores.get("aggression", 0)
|
354 |
|
355 |
-
|
356 |
if abuse_score >= 85 or escalation_score >= 16:
|
357 |
risk_level = "high"
|
358 |
-
elif abuse_score >= 60 or escalation_score >= 8
|
359 |
risk_level = "moderate"
|
360 |
elif stage == 2 and abuse_score >= 40:
|
361 |
-
risk_level = "moderate"
|
362 |
else:
|
363 |
risk_level = "low"
|
364 |
-
|
365 |
-
|
366 |
-
base = f"\n\n🛑 Risk Level: {risk_level.capitalize()}\n"
|
367 |
-
base += f"This message shows strong indicators of **{top_label}**. "
|
368 |
-
|
369 |
-
if risk_level == "high":
|
370 |
-
base += "The language may reflect patterns of emotional control, even when expressed in soft or caring terms.\n"
|
371 |
-
elif risk_level == "moderate":
|
372 |
-
base += "There are signs of emotional pressure or verbal aggression that may escalate if repeated.\n"
|
373 |
else:
|
374 |
-
|
375 |
-
|
376 |
-
return risk_level, base
|
377 |
|
378 |
WHY_FLAGGED = {
|
379 |
"control": "This message may reflect efforts to restrict someone’s autonomy, even if it's framed as concern or care.",
|
@@ -562,50 +549,22 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
|
|
562 |
sentiments = [r[0][3]['label'] for r in results]
|
563 |
stages = [r[0][4] for r in results]
|
564 |
darvo_scores = [r[0][5] for r in results]
|
565 |
-
tone_tags
|
566 |
dates_used = [r[1] or "Undated" for r in results] # Store dates for future mapping
|
567 |
-
|
568 |
-
# Calculate escalation bump
|
569 |
escalation_bump = 0
|
570 |
for result, _ in results:
|
571 |
abuse_score, threshold_labels, top_patterns, sentiment, stage, darvo_score, tone_tag = result
|
572 |
-
|
573 |
if darvo_score > 0.65:
|
574 |
escalation_bump += 3
|
575 |
-
|
576 |
-
if tone_tag in ["forced accountability flip", "emotional threat", "aggressive dismissal", "mocking detachment"]:
|
577 |
escalation_bump += 2
|
578 |
-
|
579 |
if abuse_score > 80:
|
580 |
escalation_bump += 2
|
581 |
-
|
582 |
if stage == 2:
|
583 |
escalation_bump += 3
|
584 |
|
585 |
-
|
586 |
-
escalation_bump += 4
|
587 |
-
|
588 |
-
# Helper: score trend of pattern severity
|
589 |
-
def message_severity_index(threshold_labels):
|
590 |
-
weights = {
|
591 |
-
"recovery": 0,
|
592 |
-
"dismissiveness": 1,
|
593 |
-
"deflection": 1,
|
594 |
-
"guilt tripping": 2,
|
595 |
-
"control": 3,
|
596 |
-
"gaslighting": 3,
|
597 |
-
"aggression": 4,
|
598 |
-
"threat": 5,
|
599 |
-
}
|
600 |
-
return max([weights.get(label, 0) for label in threshold_labels], default=0)
|
601 |
-
|
602 |
-
severity_levels = [message_severity_index(r[0][1]) for r in results]
|
603 |
-
if len(severity_levels) >= 2 and severity_levels == sorted(severity_levels):
|
604 |
-
escalation_bump += 2
|
605 |
-
|
606 |
-
escalation_bump = min(escalation_bump, 10)
|
607 |
-
|
608 |
-
# Final escalation score
|
609 |
hybrid_score = escalation_score + escalation_bump if escalation_score is not None else 0
|
610 |
risk_level = (
|
611 |
"High" if hybrid_score >= 16 else
|
@@ -613,6 +572,10 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
|
|
613 |
"Low"
|
614 |
)
|
615 |
|
|
|
|
|
|
|
|
|
616 |
# Post-check override (e.g. stage 2 or high abuse score forces Moderate risk)
|
617 |
if any(score > 70 for score in abuse_scores) or any(stage == 2 for stage in stages):
|
618 |
if risk_level == "Low":
|
@@ -623,18 +586,27 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
|
|
623 |
|
624 |
# --- Composite Abuse Score with Weighted Patterns ---
|
625 |
composite_abuse_scores = []
|
|
|
626 |
for result, _ in results:
|
627 |
abuse_score, threshold_labels, top_patterns, _, _, _, _ = result
|
628 |
weighted_score = 0
|
629 |
total_weight = 0
|
|
|
630 |
for label, score in top_patterns:
|
631 |
weight = PATTERN_WEIGHTS.get(label, 1.0)
|
632 |
weighted_score += score * weight
|
633 |
total_weight += weight
|
634 |
-
|
|
|
|
|
|
|
|
|
|
|
635 |
composite_abuse_scores.append(final_score)
|
636 |
|
637 |
composite_abuse = int(round(sum(composite_abuse_scores) / len(composite_abuse_scores)))
|
|
|
|
|
638 |
most_common_stage = max(set(stages), key=stages.count)
|
639 |
stage_text = RISK_STAGE_LABELS[most_common_stage]
|
640 |
|
@@ -647,41 +619,34 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
|
|
647 |
out = f"Abuse Intensity: {composite_abuse}%\n"
|
648 |
out += "📊 This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
|
649 |
|
|
|
650 |
if escalation_score is None:
|
651 |
escalation_text = "📉 Escalation Potential: Unknown (Checklist not completed)\n"
|
652 |
escalation_text += "⚠️ *This section was not completed. Escalation potential is unknown.*\n"
|
|
|
653 |
else:
|
654 |
escalation_text = f"🧨 **Escalation Potential: {risk_level} ({escalation_score}/{sum(w for _, w in ESCALATION_QUESTIONS)})**\n"
|
655 |
escalation_text += "This score comes directly from the safety checklist and functions as a standalone escalation risk score.\n"
|
656 |
escalation_text += "It indicates how many serious risk factors are present based on your answers to the safety checklist.\n"
|
657 |
-
|
658 |
-
# Derive top label
|
659 |
top_label = None
|
660 |
if results:
|
661 |
sorted_patterns = sorted(
|
662 |
[(label, score) for r in results for label, score in r[0][2]],
|
663 |
key=lambda x: x[1],
|
664 |
reverse=True
|
665 |
-
|
666 |
if sorted_patterns:
|
667 |
top_label = f"{sorted_patterns[0][0]} – {int(round(sorted_patterns[0][1] * 100))}%"
|
668 |
if top_label is None:
|
669 |
top_label = "Unknown – 0%"
|
670 |
-
|
671 |
-
out += generate_risk_snippet(
|
672 |
-
composite_abuse,
|
673 |
-
top_label,
|
674 |
-
hybrid_score if escalation_score is not None else 0,
|
675 |
-
most_common_stage,
|
676 |
-
pattern_scores_for_snippet
|
677 |
-
)
|
678 |
out += f"\n\n{stage_text}"
|
679 |
out += darvo_blurb
|
680 |
out += "\n\n🎭 **Emotional Tones Detected:**\n"
|
681 |
for i, tone in enumerate(tone_tags):
|
682 |
label = tone if tone else "none"
|
683 |
out += f"• Message {i+1}: *{label}*\n"
|
684 |
-
|
685 |
print(f"DEBUG: avg_darvo = {avg_darvo}")
|
686 |
pattern_labels = [r[0][2][0][0] for r in results] # top label for each message
|
687 |
timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
|
|
|
346 |
elif sentiment == "supportive" and any(p in patterns for p in ["projection", "dismissiveness"]):
|
347 |
return 4
|
348 |
return 1
|
|
|
|
|
|
|
|
|
|
|
349 |
|
350 |
+
def generate_risk_snippet(abuse_score, top_label, escalation_score, stage):
|
351 |
if abuse_score >= 85 or escalation_score >= 16:
|
352 |
risk_level = "high"
|
353 |
+
elif abuse_score >= 60 or escalation_score >= 8:
|
354 |
risk_level = "moderate"
|
355 |
elif stage == 2 and abuse_score >= 40:
|
356 |
+
risk_level = "moderate" # 🔧 New rule for escalation stage
|
357 |
else:
|
358 |
risk_level = "low"
|
359 |
+
if isinstance(top_label, str) and " – " in top_label:
|
360 |
+
pattern_label, pattern_score = top_label.split(" – ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
361 |
else:
|
362 |
+
pattern_label = str(top_label) if top_label is not None else "Unknown"
|
363 |
+
pattern_score = ""
|
|
|
364 |
|
365 |
WHY_FLAGGED = {
|
366 |
"control": "This message may reflect efforts to restrict someone’s autonomy, even if it's framed as concern or care.",
|
|
|
549 |
sentiments = [r[0][3]['label'] for r in results]
|
550 |
stages = [r[0][4] for r in results]
|
551 |
darvo_scores = [r[0][5] for r in results]
|
552 |
+
tone_tags= [r[0][6] for r in results]
|
553 |
dates_used = [r[1] or "Undated" for r in results] # Store dates for future mapping
|
554 |
+
# Calculate escalation bump *after* model results exist
|
|
|
555 |
escalation_bump = 0
|
556 |
for result, _ in results:
|
557 |
abuse_score, threshold_labels, top_patterns, sentiment, stage, darvo_score, tone_tag = result
|
|
|
558 |
if darvo_score > 0.65:
|
559 |
escalation_bump += 3
|
560 |
+
if tone_tag in ["forced accountability flip", "emotional threat"]:
|
|
|
561 |
escalation_bump += 2
|
|
|
562 |
if abuse_score > 80:
|
563 |
escalation_bump += 2
|
|
|
564 |
if stage == 2:
|
565 |
escalation_bump += 3
|
566 |
|
567 |
+
# Now we can safely calculate hybrid_score
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
568 |
hybrid_score = escalation_score + escalation_bump if escalation_score is not None else 0
|
569 |
risk_level = (
|
570 |
"High" if hybrid_score >= 16 else
|
|
|
572 |
"Low"
|
573 |
)
|
574 |
|
575 |
+
# Now compute scores and allow override
|
576 |
+
abuse_scores = [r[0][0] for r in results]
|
577 |
+
stages = [r[0][4] for r in results]
|
578 |
+
|
579 |
# Post-check override (e.g. stage 2 or high abuse score forces Moderate risk)
|
580 |
if any(score > 70 for score in abuse_scores) or any(stage == 2 for stage in stages):
|
581 |
if risk_level == "Low":
|
|
|
586 |
|
587 |
# --- Composite Abuse Score with Weighted Patterns ---
|
588 |
composite_abuse_scores = []
|
589 |
+
|
590 |
for result, _ in results:
|
591 |
abuse_score, threshold_labels, top_patterns, _, _, _, _ = result
|
592 |
weighted_score = 0
|
593 |
total_weight = 0
|
594 |
+
|
595 |
for label, score in top_patterns:
|
596 |
weight = PATTERN_WEIGHTS.get(label, 1.0)
|
597 |
weighted_score += score * weight
|
598 |
total_weight += weight
|
599 |
+
|
600 |
+
if total_weight > 0:
|
601 |
+
final_score = (weighted_score / total_weight) * 100
|
602 |
+
else:
|
603 |
+
final_score = 0
|
604 |
+
|
605 |
composite_abuse_scores.append(final_score)
|
606 |
|
607 |
composite_abuse = int(round(sum(composite_abuse_scores) / len(composite_abuse_scores)))
|
608 |
+
|
609 |
+
|
610 |
most_common_stage = max(set(stages), key=stages.count)
|
611 |
stage_text = RISK_STAGE_LABELS[most_common_stage]
|
612 |
|
|
|
619 |
out = f"Abuse Intensity: {composite_abuse}%\n"
|
620 |
out += "📊 This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
|
621 |
|
622 |
+
# Save this line for later use at the
|
623 |
if escalation_score is None:
|
624 |
escalation_text = "📉 Escalation Potential: Unknown (Checklist not completed)\n"
|
625 |
escalation_text += "⚠️ *This section was not completed. Escalation potential is unknown.*\n"
|
626 |
+
hybrid_score = 0 # ✅ fallback so it's defined for generate_risk_snippet
|
627 |
else:
|
628 |
escalation_text = f"🧨 **Escalation Potential: {risk_level} ({escalation_score}/{sum(w for _, w in ESCALATION_QUESTIONS)})**\n"
|
629 |
escalation_text += "This score comes directly from the safety checklist and functions as a standalone escalation risk score.\n"
|
630 |
escalation_text += "It indicates how many serious risk factors are present based on your answers to the safety checklist.\n"
|
631 |
+
# Derive top_label from the strongest top_patterns across all messages
|
|
|
632 |
top_label = None
|
633 |
if results:
|
634 |
sorted_patterns = sorted(
|
635 |
[(label, score) for r in results for label, score in r[0][2]],
|
636 |
key=lambda x: x[1],
|
637 |
reverse=True
|
638 |
+
)
|
639 |
if sorted_patterns:
|
640 |
top_label = f"{sorted_patterns[0][0]} – {int(round(sorted_patterns[0][1] * 100))}%"
|
641 |
if top_label is None:
|
642 |
top_label = "Unknown – 0%"
|
643 |
+
out += generate_risk_snippet(composite_abuse, top_label, hybrid_score if escalation_score is not None else 0, most_common_stage)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
out += f"\n\n{stage_text}"
|
645 |
out += darvo_blurb
|
646 |
out += "\n\n🎭 **Emotional Tones Detected:**\n"
|
647 |
for i, tone in enumerate(tone_tags):
|
648 |
label = tone if tone else "none"
|
649 |
out += f"• Message {i+1}: *{label}*\n"
|
|
|
650 |
print(f"DEBUG: avg_darvo = {avg_darvo}")
|
651 |
pattern_labels = [r[0][2][0][0] for r in results] # top label for each message
|
652 |
timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
|