SamanthaStorm commited on
Commit
abc5709
Β·
verified Β·
1 Parent(s): 059f356

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +39 -164
app.py CHANGED
@@ -604,14 +604,14 @@ def analyze_single_message(text, thresholds):
604
  return abuse_score, threshold_labels, top_patterns, {"label": sentiment}, stage, darvo_score, tone_tag
605
 
606
  def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
 
 
607
  none_selected_checked = answers_and_none[-1]
608
  responses_checked = any(answers_and_none[:-1])
609
  none_selected = not responses_checked and none_selected_checked
610
 
611
- if none_selected:
612
- escalation_score = None
613
- risk_level = "unknown"
614
- else:
615
  escalation_score = sum(w for (_, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]) if a)
616
 
617
  messages = [msg1, msg2, msg3]
@@ -620,22 +620,18 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
620
  if not active:
621
  return "Please enter at least one message."
622
 
623
- # Run model on messages
624
  results = [(analyze_single_message(m, THRESHOLDS.copy()), d) for m, d in active]
625
- # --- Combined Abuse Escalation Scoring ---
626
 
627
- # Extract predicted abuse labels from all messages
628
- predicted_labels = [label for r in results for label, _ in r[0][2]]
 
 
 
629
 
630
- # Categorize by severity
631
  high = {'control'}
632
- moderate = {
633
- 'gaslighting', 'dismissiveness', 'obscure language',
634
- 'insults', 'contradictory statements', 'guilt tripping'
635
- }
636
  low = {'blame shifting', 'projection', 'recovery phase'}
637
-
638
- # Count severity types
639
  counts = {'high': 0, 'moderate': 0, 'low': 0}
640
  for label in predicted_labels:
641
  if label in high:
@@ -645,126 +641,21 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
645
  elif label in low:
646
  counts['low'] += 1
647
 
648
- # Derive abuse_risk from combinations
 
649
  if counts['high'] >= 2 and counts['moderate'] >= 2:
650
- abuse_risk = 'Critical'
651
  elif (counts['high'] >= 2 and counts['moderate'] >= 1) or (counts['moderate'] >= 3) or (counts['high'] >= 1 and counts['moderate'] >= 2):
652
- abuse_risk = 'High'
653
  elif (counts['moderate'] == 2) or (counts['high'] == 1 and counts['moderate'] == 1) or (counts['moderate'] == 1 and counts['low'] >= 2) or (counts['high'] == 1 and sum(counts.values()) == 1):
654
- abuse_risk = 'Moderate'
655
- else:
656
- abuse_risk = 'Low'
657
-
658
- # Combine abuse_risk and checklist score into final risk_level
659
- if escalation_score is not None:
660
- if escalation_score >= 8 or abuse_risk == 'Critical':
661
- risk_level = 'Critical'
662
- elif escalation_score >= 5 or abuse_risk == 'High':
663
- risk_level = 'High'
664
- elif escalation_score >= 2 or abuse_risk == 'Moderate':
665
- risk_level = 'Moderate'
666
- else:
667
- risk_level = 'Low'
668
- abuse_scores = [r[0][0] for r in results]
669
- top_labels = [r[0][1][0] if r[0][1] else r[0][2][0][0] for r in results]
670
- top_scores = [r[0][2][0][1] for r in results]
671
- sentiments = [r[0][3]['label'] for r in results]
672
- stages = [r[0][4] for r in results]
673
- darvo_scores = [r[0][5] for r in results]
674
- tone_tags= [r[0][6] for r in results]
675
- dates_used = [r[1] or "Undated" for r in results] # Store dates for future mapping
676
- # Calculate escalation bump *after* model results exist
677
- escalation_bump = 0
678
- for result, _ in results:
679
- abuse_score, threshold_labels, top_patterns, sentiment, stage, darvo_score, tone_tag = result
680
- if darvo_score > 0.65:
681
- escalation_bump += 3
682
- if tone_tag in ["forced accountability flip", "emotional threat"]:
683
- escalation_bump += 2
684
- if abuse_score > 80:
685
- escalation_bump += 2
686
- if stage == 2:
687
- escalation_bump += 3
688
 
689
- # Now we can safely calculate hybrid_score
690
- hybrid_score = escalation_score + escalation_bump if escalation_score is not None else 0
691
- risk_level = (
692
- "High" if hybrid_score >= 16 else
693
- "Moderate" if hybrid_score >= 8 else
694
  "Low"
695
  )
696
 
697
- # Now compute scores and allow override
698
- abuse_scores = [r[0][0] for r in results]
699
- stages = [r[0][4] for r in results]
700
-
701
- # Post-check override (e.g. stage 2 or high abuse score forces Moderate risk)
702
- if any(score > 70 for score in abuse_scores) or any(stage == 2 for stage in stages):
703
- if risk_level == "Low":
704
- risk_level = "Moderate"
705
-
706
- for result, date in results:
707
- assert len(result) == 7, "Unexpected output from analyze_single_message"
708
- from collections import Counter
709
-
710
- # --- Step 1: Pattern-based escalation scoring ---
711
- flat_patterns = [label for r in results for (label, _) in r[0][2]]
712
-
713
- PATTERN_RISKS = {
714
- "blame shifting": "low",
715
- "contradictory statements": "moderate",
716
- "control": "high",
717
- "dismissiveness": "moderate",
718
- "gaslighting": "moderate",
719
- "guilt tripping": "moderate",
720
- "insults": "moderate",
721
- "obscure language": "low",
722
- "projection": "low",
723
- " recovery phase": "low"
724
- }
725
-
726
- risk_counts = Counter(PATTERN_RISKS.get(p, "unknown") for p in flat_patterns)
727
- num_critical = 0 # no "critical" tags defined in your label set
728
- num_high = risk_counts["high"]
729
- num_moderate = risk_counts["moderate"]
730
- num_low = risk_counts["low"]
731
-
732
- # Determine pattern-based escalation risk
733
- pattern_escalation_risk = "Low"
734
- if num_high >= 2 and num_moderate >= 2:
735
- pattern_escalation_risk = "Critical"
736
- elif num_high >= 2 and num_moderate >= 1:
737
- pattern_escalation_risk = "High"
738
- elif num_moderate >= 3:
739
- pattern_escalation_risk = "High"
740
- elif num_high == 1 and num_moderate >= 1:
741
- pattern_escalation_risk = "Moderate"
742
- elif num_moderate == 2:
743
- pattern_escalation_risk = "Moderate"
744
- elif num_moderate == 1 and num_low >= 2:
745
- pattern_escalation_risk = "Moderate"
746
- elif num_high == 1 and (num_high + num_moderate + num_low) == 1:
747
- pattern_escalation_risk = "Moderate"
748
-
749
- # --- Step 2: Checklist escalation logic ---
750
- if escalation_score is None:
751
- escalation_text = "🚫 **Escalation Potential: Unknown** (Checklist not completed)\n"
752
- escalation_text += "⚠️ This section was not completed. Escalation potential cannot be calculated.\n"
753
- hybrid_score = 0
754
- else:
755
- hybrid_score = escalation_score + escalation_bump
756
- risk_level = (
757
- "πŸ”΄ High" if hybrid_score >= 16 else
758
- "🟠 Moderate" if hybrid_score >= 8 else
759
- "🟒 Low"
760
- )
761
-
762
- escalation_text = f"πŸ“ˆ **Escalation Potential: {risk_level} ({hybrid_score}/29)**\n"
763
- escalation_text += "πŸ“‹ This score comes from your checklist *and* detected high-risk patterns.\n"
764
- escalation_text += "🧠 It reflects both situational risk and behavioral red flags."
765
-
766
-
767
- # --- Step 3: Escalation bump from DARVO, tone, abuse score, etc.
768
  escalation_bump = 0
769
  for result, _ in results:
770
  abuse_score, _, _, sentiment, stage, darvo_score, tone_tag = result
@@ -777,31 +668,37 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
777
  if stage == 2:
778
  escalation_bump += 3
779
 
780
- # --- Step 4: Final escalation risk level
781
- def rank(risk_label):
782
- return ["Low", "Moderate", "High", "Critical"].index(risk_label) if risk_label else 0
783
 
784
  combined_score = rank(pattern_escalation_risk) + rank(checklist_escalation_risk) + escalation_bump
785
-
786
  escalation_risk = (
787
  "Critical" if combined_score >= 6 else
788
  "High" if combined_score >= 4 else
789
  "Moderate" if combined_score >= 2 else
790
  "Low"
791
  )
792
- # --- Composite Abuse Score using compute_abuse_score ---
793
- composite_abuse_scores = []
794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  for result, _ in results:
796
  _, _, top_patterns, sentiment, _, _, _ = result
797
  matched_scores = [(label, score, PATTERN_WEIGHTS.get(label, 1.0)) for label, score in top_patterns]
798
  final_score = compute_abuse_score(matched_scores, sentiment["label"])
799
  composite_abuse_scores.append(final_score)
800
-
801
  composite_abuse = int(round(sum(composite_abuse_scores) / len(composite_abuse_scores)))
802
 
803
-
804
-
805
  most_common_stage = max(set(stages), key=stages.count)
806
  stage_text = RISK_STAGE_LABELS[most_common_stage]
807
 
@@ -813,39 +710,17 @@ def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
813
 
814
  out = f"Abuse Intensity: {composite_abuse}%\n"
815
  out += "πŸ“Š This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
816
-
817
- # Save this line for later use at the
818
- if escalation_score is None:
819
- escalation_text = "πŸ“‰ Escalation Potential: Unknown (Checklist not completed)\n"
820
- escalation_text += "⚠️ *This section was not completed. Escalation potential is unknown.*\n"
821
- hybrid_score = 0 # βœ… fallback so it's defined for generate_risk_snippet
822
- else:
823
- escalation_text = f"🧨 **Escalation Potential: {risk_level} ({escalation_score}/{sum(w for _, w in ESCALATION_QUESTIONS)})**\n"
824
- escalation_text += "This score comes directly from the safety checklist and functions as a standalone escalation risk score.\n"
825
- escalation_text += "It indicates how many serious risk factors are present based on your answers to the safety checklist.\n"
826
- # Derive top_label from the strongest top_patterns across all messages
827
- top_label = None
828
- if results:
829
- sorted_patterns = sorted(
830
- [(label, score) for r in results for label, score in r[0][2]],
831
- key=lambda x: x[1],
832
- reverse=True
833
- )
834
- if sorted_patterns:
835
- top_label = f"{sorted_patterns[0][0]} – {int(round(sorted_patterns[0][1] * 100))}%"
836
- if top_label is None:
837
- top_label = "Unknown – 0%"
838
- out += generate_risk_snippet(composite_abuse, top_label, hybrid_score if escalation_score is not None else 0, most_common_stage)
839
  out += f"\n\n{stage_text}"
840
  out += darvo_blurb
841
  out += "\n\n🎭 **Emotional Tones Detected:**\n"
842
  for i, tone in enumerate(tone_tags):
843
- label = tone if tone else "none"
844
- out += f"β€’ Message {i+1}: *{label}*\n"
845
- print(f"DEBUG: avg_darvo = {avg_darvo}")
846
- pattern_labels = [r[0][2][0][0] for r in results] # top label for each message
847
  timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
848
  out += "\n\n" + escalation_text
 
849
  return out, timeline_image
850
 
851
  message_date_pairs = [
 
604
  return abuse_score, threshold_labels, top_patterns, {"label": sentiment}, stage, darvo_score, tone_tag
605
 
606
  def analyze_composite(msg1, date1, msg2, date2, msg3, date3, *answers_and_none):
607
+ from collections import Counter
608
+
609
  none_selected_checked = answers_and_none[-1]
610
  responses_checked = any(answers_and_none[:-1])
611
  none_selected = not responses_checked and none_selected_checked
612
 
613
+ escalation_score = None
614
+ if not none_selected:
 
 
615
  escalation_score = sum(w for (_, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]) if a)
616
 
617
  messages = [msg1, msg2, msg3]
 
620
  if not active:
621
  return "Please enter at least one message."
622
 
 
623
  results = [(analyze_single_message(m, THRESHOLDS.copy()), d) for m, d in active]
 
624
 
625
+ abuse_scores = [r[0][0] for r in results]
626
+ stages = [r[0][4] for r in results]
627
+ darvo_scores = [r[0][5] for r in results]
628
+ tone_tags = [r[0][6] for r in results]
629
+ dates_used = [r[1] or "Undated" for r in results]
630
 
631
+ predicted_labels = [label for r in results for label, _ in r[0][2]]
632
  high = {'control'}
633
+ moderate = {'gaslighting', 'dismissiveness', 'obscure language', 'insults', 'contradictory statements', 'guilt tripping'}
 
 
 
634
  low = {'blame shifting', 'projection', 'recovery phase'}
 
 
635
  counts = {'high': 0, 'moderate': 0, 'low': 0}
636
  for label in predicted_labels:
637
  if label in high:
 
641
  elif label in low:
642
  counts['low'] += 1
643
 
644
+ # Pattern escalation logic
645
+ pattern_escalation_risk = "Low"
646
  if counts['high'] >= 2 and counts['moderate'] >= 2:
647
+ pattern_escalation_risk = "Critical"
648
  elif (counts['high'] >= 2 and counts['moderate'] >= 1) or (counts['moderate'] >= 3) or (counts['high'] >= 1 and counts['moderate'] >= 2):
649
+ pattern_escalation_risk = "High"
650
  elif (counts['moderate'] == 2) or (counts['high'] == 1 and counts['moderate'] == 1) or (counts['moderate'] == 1 and counts['low'] >= 2) or (counts['high'] == 1 and sum(counts.values()) == 1):
651
+ pattern_escalation_risk = "Moderate"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
 
653
+ checklist_escalation_risk = "Unknown" if escalation_score is None else (
654
+ "Critical" if escalation_score >= 20 else
655
+ "Moderate" if escalation_score >= 10 else
 
 
656
  "Low"
657
  )
658
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  escalation_bump = 0
660
  for result, _ in results:
661
  abuse_score, _, _, sentiment, stage, darvo_score, tone_tag = result
 
668
  if stage == 2:
669
  escalation_bump += 3
670
 
671
+ def rank(label):
672
+ return {"Low": 0, "Moderate": 1, "High": 2, "Critical": 3, "Unknown": 0}.get(label, 0)
 
673
 
674
  combined_score = rank(pattern_escalation_risk) + rank(checklist_escalation_risk) + escalation_bump
 
675
  escalation_risk = (
676
  "Critical" if combined_score >= 6 else
677
  "High" if combined_score >= 4 else
678
  "Moderate" if combined_score >= 2 else
679
  "Low"
680
  )
 
 
681
 
682
+ if escalation_score is None:
683
+ escalation_text = "🚫 **Escalation Potential: Unknown** (Checklist not completed)\n⚠️ This section was not completed. Escalation potential is estimated using message data only.\n"
684
+ hybrid_score = 0
685
+ else:
686
+ hybrid_score = escalation_score + escalation_bump
687
+ escalation_text = f"πŸ“ˆ **Escalation Potential: {escalation_risk} ({hybrid_score}/29)**\n"
688
+ escalation_text += "πŸ“‹ This score combines your safety checklist answers *and* detected high-risk behavior.\n"
689
+ escalation_text += f"β€’ Pattern Risk: {pattern_escalation_risk}\n"
690
+ escalation_text += f"β€’ Checklist Risk: {checklist_escalation_risk}\n"
691
+ escalation_text += f"β€’ Escalation Bump: +{escalation_bump} (from DARVO, tone, intensity, etc.)"
692
+
693
+ # Composite Abuse Score
694
+ composite_abuse_scores = []
695
  for result, _ in results:
696
  _, _, top_patterns, sentiment, _, _, _ = result
697
  matched_scores = [(label, score, PATTERN_WEIGHTS.get(label, 1.0)) for label, score in top_patterns]
698
  final_score = compute_abuse_score(matched_scores, sentiment["label"])
699
  composite_abuse_scores.append(final_score)
 
700
  composite_abuse = int(round(sum(composite_abuse_scores) / len(composite_abuse_scores)))
701
 
 
 
702
  most_common_stage = max(set(stages), key=stages.count)
703
  stage_text = RISK_STAGE_LABELS[most_common_stage]
704
 
 
710
 
711
  out = f"Abuse Intensity: {composite_abuse}%\n"
712
  out += "πŸ“Š This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
713
+ out += generate_risk_snippet(composite_abuse, top_labels[0], hybrid_score, most_common_stage)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  out += f"\n\n{stage_text}"
715
  out += darvo_blurb
716
  out += "\n\n🎭 **Emotional Tones Detected:**\n"
717
  for i, tone in enumerate(tone_tags):
718
+ out += f"β€’ Message {i+1}: *{tone or 'none'}*\n"
719
+
720
+ pattern_labels = [r[0][2][0][0] for r in results]
 
721
  timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
722
  out += "\n\n" + escalation_text
723
+
724
  return out, timeline_image
725
 
726
  message_date_pairs = [