Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -21,6 +21,40 @@ logger = logging.getLogger(__name__)
|
|
21 |
# Device configuration
|
22 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
23 |
logger.info(f"Using device: {device}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
# Model initialization
|
26 |
model_name = "SamanthaStorm/tether-multilabel-v4"
|
@@ -492,50 +526,57 @@ def generate_abuse_score_chart(dates, scores, patterns):
|
|
492 |
|
493 |
def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
494 |
"""Analyze multiple messages and checklist responses"""
|
495 |
-
logger.debug("\n
|
|
|
496 |
try:
|
497 |
# Process checklist responses
|
498 |
-
logger.debug("\n
|
|
|
499 |
none_selected_checked = answers_and_none[-1]
|
500 |
responses_checked = any(answers_and_none[:-1])
|
501 |
none_selected = not responses_checked and none_selected_checked
|
502 |
|
503 |
-
logger.debug(
|
504 |
-
logger.debug(f"
|
505 |
-
logger.debug(f"
|
|
|
506 |
|
507 |
if none_selected:
|
508 |
escalation_score = 0
|
509 |
escalation_note = "Checklist completed: no danger items reported."
|
510 |
escalation_completed = True
|
511 |
-
logger.debug("No items selected
|
512 |
elif responses_checked:
|
513 |
escalation_score = sum(w for (_, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]) if a)
|
514 |
escalation_note = "Checklist completed."
|
515 |
escalation_completed = True
|
516 |
-
logger.debug(f"Checklist
|
|
|
517 |
# Log checked items
|
518 |
-
logger.debug("
|
519 |
for (q, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]):
|
520 |
if a:
|
521 |
-
logger.debug(f"β’ {
|
522 |
else:
|
523 |
escalation_score = None
|
524 |
escalation_note = "Checklist not completed."
|
525 |
escalation_completed = False
|
526 |
-
logger.debug("Checklist
|
527 |
|
528 |
# Process messages
|
529 |
-
logger.debug("\n
|
|
|
530 |
messages = [msg1, msg2, msg3]
|
531 |
active = [(m, f"Message {i+1}") for i, m in enumerate(messages) if m.strip()]
|
532 |
-
logger.debug(f"
|
|
|
533 |
if not active:
|
534 |
-
logger.debug("No messages provided")
|
535 |
return "Please enter at least one message.", None
|
536 |
|
537 |
# Detect threats
|
538 |
-
logger.debug("\n
|
|
|
539 |
def normalize(text):
|
540 |
import unicodedata
|
541 |
text = text.lower().strip()
|
@@ -553,31 +594,40 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
553 |
threat_risk = "Yes" if flat_threats else "No"
|
554 |
|
555 |
if flat_threats:
|
556 |
-
logger.debug("
|
557 |
for threat in flat_threats:
|
558 |
-
logger.debug(f"β’ {threat}")
|
559 |
else:
|
560 |
-
logger.debug("No explicit threats detected")
|
561 |
-
|
562 |
# Analyze each message
|
563 |
-
logger.debug("\n
|
|
|
564 |
results = []
|
565 |
for m, d in active:
|
566 |
-
logger.debug(f"\
|
567 |
-
logger.debug("
|
568 |
result = analyze_single_message(m, THRESHOLDS.copy())
|
569 |
results.append((result, d))
|
570 |
|
571 |
-
#
|
572 |
abuse_score, patterns, matched_scores, sentiment, stage, darvo_score, tone = result
|
573 |
-
|
574 |
-
|
575 |
-
logger.debug(
|
576 |
-
logger.debug(f"β’
|
577 |
-
logger.debug(f"β’
|
578 |
-
logger.debug(f"β’ Stage: {stage}")
|
579 |
-
logger.debug(f"β’
|
580 |
-
logger.debug(f"β’ Tone: {tone}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
581 |
|
582 |
# Extract scores and metadata
|
583 |
abuse_scores = [r[0][0] for r in results]
|
@@ -586,87 +636,115 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
586 |
tone_tags = [r[0][6] for r in results]
|
587 |
dates_used = [r[1] for r in results]
|
588 |
|
589 |
-
|
|
|
|
|
590 |
predicted_labels = [label for r in results for label in r[0][1]]
|
591 |
-
logger.debug(f"All detected patterns: {predicted_labels}")
|
592 |
-
# Pattern severity analysis
|
593 |
-
logger.debug("\n--- Pattern Severity Analysis ---")
|
594 |
-
high = {'control'}
|
595 |
-
moderate = {'gaslighting', 'dismissiveness', 'obscure language', 'insults',
|
596 |
-
'contradictory statements', 'guilt tripping'}
|
597 |
-
low = {'blame shifting', 'projection', 'recovery'}
|
598 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
599 |
counts = {'high': 0, 'moderate': 0, 'low': 0}
|
600 |
for label in predicted_labels:
|
601 |
if label in high:
|
602 |
counts['high'] += 1
|
603 |
-
logger.debug(f"High severity pattern found: {label}")
|
604 |
elif label in moderate:
|
605 |
counts['moderate'] += 1
|
606 |
-
logger.debug(f"Moderate severity pattern found: {label}")
|
607 |
elif label in low:
|
608 |
counts['low'] += 1
|
609 |
-
logger.debug(f"Low severity pattern found: {label}")
|
610 |
|
611 |
-
logger.debug(
|
|
|
|
|
|
|
612 |
|
613 |
-
#
|
614 |
-
logger.debug("\n
|
|
|
615 |
if counts['high'] >= 2 and counts['moderate'] >= 2:
|
616 |
pattern_escalation_risk = "Critical"
|
617 |
-
logger.debug("
|
618 |
elif (counts['high'] >= 2 and counts['moderate'] >= 1) or \
|
619 |
(counts['moderate'] >= 3) or \
|
620 |
(counts['high'] >= 1 and counts['moderate'] >= 2):
|
621 |
pattern_escalation_risk = "High"
|
622 |
-
logger.debug("
|
623 |
elif (counts['moderate'] == 2) or \
|
624 |
(counts['high'] == 1 and counts['moderate'] == 1) or \
|
625 |
(counts['moderate'] == 1 and counts['low'] >= 2) or \
|
626 |
(counts['high'] == 1 and sum(counts.values()) == 1):
|
627 |
pattern_escalation_risk = "Moderate"
|
628 |
-
logger.debug("
|
629 |
else:
|
630 |
pattern_escalation_risk = "Low"
|
631 |
-
logger.debug("
|
632 |
|
633 |
-
#
|
634 |
-
logger.debug("\n
|
|
|
635 |
checklist_escalation_risk = "Unknown" if escalation_score is None else (
|
636 |
"Critical" if escalation_score >= 20 else
|
637 |
"Moderate" if escalation_score >= 10 else
|
638 |
"Low"
|
639 |
)
|
640 |
-
logger.debug(f"
|
641 |
-
|
642 |
-
|
643 |
-
|
|
|
|
|
644 |
escalation_bump = 0
|
645 |
for result, msg_id in results:
|
646 |
abuse_score, _, _, sentiment, stage, darvo_score, tone_tag = result
|
647 |
-
logger.debug(f"\
|
|
|
|
|
648 |
if darvo_score > 0.65:
|
649 |
escalation_bump += 3
|
650 |
-
|
651 |
if tone_tag in ["forced accountability flip", "emotional threat"]:
|
652 |
escalation_bump += 2
|
653 |
-
|
654 |
if abuse_score > 80:
|
655 |
escalation_bump += 2
|
656 |
-
|
657 |
if stage == 2:
|
658 |
escalation_bump += 3
|
659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
660 |
|
661 |
-
logger.debug(f"Total
|
662 |
|
663 |
-
#
|
664 |
-
logger.debug("\n
|
|
|
665 |
def rank(label):
|
666 |
return {"Low": 0, "Moderate": 1, "High": 2, "Critical": 3, "Unknown": 0}.get(label, 0)
|
667 |
|
668 |
combined_score = rank(pattern_escalation_risk) + rank(checklist_escalation_risk) + escalation_bump
|
669 |
-
logger.debug(
|
|
|
|
|
|
|
|
|
670 |
|
671 |
escalation_risk = (
|
672 |
"Critical" if combined_score >= 6 else
|
@@ -674,17 +752,18 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
674 |
"Moderate" if combined_score >= 2 else
|
675 |
"Low"
|
676 |
)
|
677 |
-
logger.debug(f"Final
|
678 |
|
679 |
-
#
|
680 |
-
logger.debug("\n
|
|
|
681 |
if escalation_score is None:
|
682 |
escalation_text = (
|
683 |
"π« **Escalation Potential: Unknown** (Checklist not completed)\n"
|
684 |
"β οΈ This section was not completed. Escalation potential is estimated using message data only.\n"
|
685 |
)
|
686 |
hybrid_score = 0
|
687 |
-
logger.debug("Generated
|
688 |
elif escalation_score == 0:
|
689 |
escalation_text = (
|
690 |
"β
**Escalation Checklist Completed:** No danger items reported.\n"
|
@@ -694,7 +773,7 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
694 |
f"β’ Escalation Bump: +{escalation_bump} (from DARVO, tone, intensity, etc.)"
|
695 |
)
|
696 |
hybrid_score = escalation_bump
|
697 |
-
logger.debug("Generated no-risk
|
698 |
else:
|
699 |
hybrid_score = escalation_score + escalation_bump
|
700 |
escalation_text = (
|
@@ -704,32 +783,36 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
704 |
f"β’ Checklist Risk: {checklist_escalation_risk}\n"
|
705 |
f"β’ Escalation Bump: +{escalation_bump} (from DARVO, tone, intensity, etc.)"
|
706 |
)
|
707 |
-
logger.debug(f"Generated
|
708 |
|
709 |
-
#
|
710 |
-
logger.debug("\n
|
|
|
711 |
composite_abuse = int(round(sum(abuse_scores) / len(abuse_scores)))
|
712 |
-
logger.debug(f"Composite
|
713 |
-
|
714 |
-
# Get most common stage
|
715 |
most_common_stage = max(set(stages), key=stages.count)
|
716 |
-
|
717 |
-
|
|
|
|
|
718 |
|
719 |
-
#
|
720 |
-
logger.debug("\n
|
|
|
721 |
out = f"Abuse Intensity: {composite_abuse}%\n"
|
722 |
out += "π This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
|
723 |
|
724 |
-
#
|
725 |
risk_level = (
|
726 |
"Critical" if composite_abuse >= 85 or hybrid_score >= 20 else
|
727 |
"High" if composite_abuse >= 70 or hybrid_score >= 15 else
|
728 |
"Moderate" if composite_abuse >= 50 or hybrid_score >= 10 else
|
729 |
"Low"
|
730 |
)
|
731 |
-
logger.debug(f"Final
|
732 |
-
|
|
|
733 |
risk_descriptions = {
|
734 |
"Critical": (
|
735 |
"π¨ **Risk Level: Critical**\n"
|
@@ -754,24 +837,23 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
754 |
}
|
755 |
|
756 |
out += risk_descriptions[risk_level]
|
757 |
-
out += f"\n\n{
|
758 |
|
759 |
-
# Add DARVO
|
760 |
-
avg_darvo = round(sum(darvo_scores) / len(darvo_scores), 3)
|
761 |
-
logger.debug(f"Average DARVO score: {avg_darvo}")
|
762 |
if avg_darvo > 0.25:
|
763 |
level = "moderate" if avg_darvo < 0.65 else "high"
|
764 |
out += f"\n\nπ **DARVO Score: {avg_darvo}** β This indicates a **{level} likelihood** of narrative reversal (DARVO), where the speaker may be denying, attacking, or reversing blame."
|
|
|
765 |
|
766 |
-
# Add
|
767 |
-
logger.debug("\n
|
768 |
out += "\n\nπ **Emotional Tones Detected:**\n"
|
769 |
for i, tone in enumerate(tone_tags):
|
770 |
out += f"β’ Message {i+1}: *{tone or 'none'}*\n"
|
771 |
logger.debug(f"Message {i+1} tone: {tone}")
|
772 |
|
773 |
-
# Add
|
774 |
-
logger.debug("\n
|
775 |
if flat_threats:
|
776 |
out += "\n\nπ¨ **Immediate Danger Threats Detected:**\n"
|
777 |
for t in set(flat_threats):
|
@@ -783,8 +865,8 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
783 |
out += "This does *not* rule out risk, but no direct threat phrases were matched."
|
784 |
logger.debug("No threats to add")
|
785 |
|
786 |
-
# Generate
|
787 |
-
logger.debug("\n
|
788 |
pattern_labels = [
|
789 |
pats[0][0] if (pats := r[0][2]) else "none"
|
790 |
for r in results
|
@@ -792,18 +874,23 @@ def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
|
792 |
timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
|
793 |
logger.debug("Timeline generated successfully")
|
794 |
|
795 |
-
# Add
|
796 |
out += "\n\n" + escalation_text
|
797 |
|
798 |
-
logger.debug("\n
|
|
|
799 |
return out, timeline_image
|
800 |
|
801 |
except Exception as e:
|
802 |
-
logger.error(
|
803 |
-
logger.error(
|
|
|
|
|
|
|
804 |
return "An error occurred during analysis.", None
|
805 |
|
806 |
|
|
|
807 |
# Gradio Interface Setup
|
808 |
def create_interface():
|
809 |
try:
|
|
|
21 |
# Device configuration
|
22 |
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
23 |
logger.info(f"Using device: {device}")
|
24 |
+
# Set up custom logging
|
25 |
+
class CustomFormatter(logging.Formatter):
|
26 |
+
"""Custom formatter with colors and better formatting"""
|
27 |
+
grey = "\x1b[38;21m"
|
28 |
+
blue = "\x1b[38;5;39m"
|
29 |
+
yellow = "\x1b[38;5;226m"
|
30 |
+
red = "\x1b[38;5;196m"
|
31 |
+
bold_red = "\x1b[31;1m"
|
32 |
+
reset = "\x1b[0m"
|
33 |
+
|
34 |
+
format_str = "%(message)s"
|
35 |
+
|
36 |
+
FORMATS = {
|
37 |
+
logging.DEBUG: blue + format_str + reset,
|
38 |
+
logging.INFO: grey + format_str + reset,
|
39 |
+
logging.WARNING: yellow + format_str + reset,
|
40 |
+
logging.ERROR: red + format_str + reset,
|
41 |
+
logging.CRITICAL: bold_red + format_str + reset
|
42 |
+
}
|
43 |
+
|
44 |
+
def format(self, record):
|
45 |
+
log_fmt = self.FORMATS.get(record.levelno)
|
46 |
+
formatter = logging.Formatter(log_fmt)
|
47 |
+
return formatter.format(record)
|
48 |
+
|
49 |
+
# Setup logger
|
50 |
+
logger = logging.getLogger(__name__)
|
51 |
+
logger.setLevel(logging.DEBUG)
|
52 |
+
|
53 |
+
# Create console handler with custom formatter
|
54 |
+
ch = logging.StreamHandler()
|
55 |
+
ch.setLevel(logging.DEBUG)
|
56 |
+
ch.setFormatter(CustomFormatter())
|
57 |
+
logger.handlers = [ch]
|
58 |
|
59 |
# Model initialization
|
60 |
model_name = "SamanthaStorm/tether-multilabel-v4"
|
|
|
526 |
|
527 |
def analyze_composite(msg1, msg2, msg3, *answers_and_none):
|
528 |
"""Analyze multiple messages and checklist responses"""
|
529 |
+
logger.debug("\nπ STARTING NEW ANALYSIS")
|
530 |
+
logger.debug("=" * 50)
|
531 |
try:
|
532 |
# Process checklist responses
|
533 |
+
logger.debug("\nπ CHECKLIST PROCESSING")
|
534 |
+
logger.debug("=" * 50)
|
535 |
none_selected_checked = answers_and_none[-1]
|
536 |
responses_checked = any(answers_and_none[:-1])
|
537 |
none_selected = not responses_checked and none_selected_checked
|
538 |
|
539 |
+
logger.debug("Checklist Status:")
|
540 |
+
logger.debug(f" β’ None Selected Box: {'β' if none_selected_checked else 'β'}")
|
541 |
+
logger.debug(f" β’ Has Responses: {'β' if responses_checked else 'β'}")
|
542 |
+
logger.debug(f" β’ Final Status: {'None Selected' if none_selected else 'Has Selections'}")
|
543 |
|
544 |
if none_selected:
|
545 |
escalation_score = 0
|
546 |
escalation_note = "Checklist completed: no danger items reported."
|
547 |
escalation_completed = True
|
548 |
+
logger.debug("\nβ Checklist: No items selected")
|
549 |
elif responses_checked:
|
550 |
escalation_score = sum(w for (_, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]) if a)
|
551 |
escalation_note = "Checklist completed."
|
552 |
escalation_completed = True
|
553 |
+
logger.debug(f"\nπ Checklist Score: {escalation_score}")
|
554 |
+
|
555 |
# Log checked items
|
556 |
+
logger.debug("\nβ οΈ Selected Risk Factors:")
|
557 |
for (q, w), a in zip(ESCALATION_QUESTIONS, answers_and_none[:-1]):
|
558 |
if a:
|
559 |
+
logger.debug(f" β’ [{w} points] {q}")
|
560 |
else:
|
561 |
escalation_score = None
|
562 |
escalation_note = "Checklist not completed."
|
563 |
escalation_completed = False
|
564 |
+
logger.debug("\nβ Checklist: Not completed")
|
565 |
|
566 |
# Process messages
|
567 |
+
logger.debug("\nπ MESSAGE PROCESSING")
|
568 |
+
logger.debug("=" * 50)
|
569 |
messages = [msg1, msg2, msg3]
|
570 |
active = [(m, f"Message {i+1}") for i, m in enumerate(messages) if m.strip()]
|
571 |
+
logger.debug(f"Active Messages: {len(active)} of 3")
|
572 |
+
|
573 |
if not active:
|
574 |
+
logger.debug("β Error: No messages provided")
|
575 |
return "Please enter at least one message.", None
|
576 |
|
577 |
# Detect threats
|
578 |
+
logger.debug("\nπ¨ THREAT DETECTION")
|
579 |
+
logger.debug("=" * 50)
|
580 |
def normalize(text):
|
581 |
import unicodedata
|
582 |
text = text.lower().strip()
|
|
|
594 |
threat_risk = "Yes" if flat_threats else "No"
|
595 |
|
596 |
if flat_threats:
|
597 |
+
logger.debug("β οΈ DETECTED THREATS:")
|
598 |
for threat in flat_threats:
|
599 |
+
logger.debug(f" β’ {threat}")
|
600 |
else:
|
601 |
+
logger.debug("β No explicit threats detected")
|
|
|
602 |
# Analyze each message
|
603 |
+
logger.debug("\nπ INDIVIDUAL MESSAGE ANALYSIS")
|
604 |
+
logger.debug("=" * 50)
|
605 |
results = []
|
606 |
for m, d in active:
|
607 |
+
logger.debug(f"\nπ ANALYZING {d}")
|
608 |
+
logger.debug("=" * 40)
|
609 |
result = analyze_single_message(m, THRESHOLDS.copy())
|
610 |
results.append((result, d))
|
611 |
|
612 |
+
# Unpack results for cleaner logging
|
613 |
abuse_score, patterns, matched_scores, sentiment, stage, darvo_score, tone = result
|
614 |
+
|
615 |
+
# Log core metrics
|
616 |
+
logger.debug("\nπ CORE METRICS")
|
617 |
+
logger.debug(f" β’ Abuse Score: {abuse_score:.1f}%")
|
618 |
+
logger.debug(f" β’ DARVO Score: {darvo_score:.3f}")
|
619 |
+
logger.debug(f" β’ Risk Stage: {stage}")
|
620 |
+
logger.debug(f" β’ Sentiment: {sentiment['label']}")
|
621 |
+
logger.debug(f" β’ Tone: {tone}")
|
622 |
+
|
623 |
+
# Log detected patterns with scores
|
624 |
+
if patterns:
|
625 |
+
logger.debug("\nπ― DETECTED PATTERNS")
|
626 |
+
for label, score, weight in matched_scores:
|
627 |
+
severity = "βHIGH" if label in high else "β οΈ MODERATE" if label in moderate else "π LOW"
|
628 |
+
logger.debug(f" β’ {severity} | {label}: {score:.3f} (weight: {weight})")
|
629 |
+
else:
|
630 |
+
logger.debug("\nβ No abuse patterns detected")
|
631 |
|
632 |
# Extract scores and metadata
|
633 |
abuse_scores = [r[0][0] for r in results]
|
|
|
636 |
tone_tags = [r[0][6] for r in results]
|
637 |
dates_used = [r[1] for r in results]
|
638 |
|
639 |
+
# Pattern Analysis
|
640 |
+
logger.debug("\nπ PATTERN ANALYSIS SUMMARY")
|
641 |
+
logger.debug("=" * 50)
|
642 |
predicted_labels = [label for r in results for label in r[0][1]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
643 |
|
644 |
+
if predicted_labels:
|
645 |
+
logger.debug("Detected Patterns Across All Messages:")
|
646 |
+
for label in set(predicted_labels):
|
647 |
+
count = predicted_labels.count(label)
|
648 |
+
if label in high:
|
649 |
+
logger.debug(f" β HIGH: {label} (Γ{count})")
|
650 |
+
elif label in moderate:
|
651 |
+
logger.debug(f" β οΈ MODERATE: {label} (Γ{count})")
|
652 |
+
elif label in low:
|
653 |
+
logger.debug(f" π LOW: {label} (Γ{count})")
|
654 |
+
else:
|
655 |
+
logger.debug("β No patterns detected across messages")
|
656 |
+
|
657 |
+
# Pattern Severity Analysis
|
658 |
+
logger.debug("\nβοΈ SEVERITY ANALYSIS")
|
659 |
+
logger.debug("=" * 50)
|
660 |
counts = {'high': 0, 'moderate': 0, 'low': 0}
|
661 |
for label in predicted_labels:
|
662 |
if label in high:
|
663 |
counts['high'] += 1
|
|
|
664 |
elif label in moderate:
|
665 |
counts['moderate'] += 1
|
|
|
666 |
elif label in low:
|
667 |
counts['low'] += 1
|
|
|
668 |
|
669 |
+
logger.debug("Pattern Distribution:")
|
670 |
+
logger.debug(f" β High Severity: {counts['high']}")
|
671 |
+
logger.debug(f" β οΈ Moderate Severity: {counts['moderate']}")
|
672 |
+
logger.debug(f" π Low Severity: {counts['low']}")
|
673 |
|
674 |
+
# Risk Assessment
|
675 |
+
logger.debug("\nπ― RISK ASSESSMENT")
|
676 |
+
logger.debug("=" * 50)
|
677 |
if counts['high'] >= 2 and counts['moderate'] >= 2:
|
678 |
pattern_escalation_risk = "Critical"
|
679 |
+
logger.debug("β CRITICAL RISK: Multiple high and moderate patterns")
|
680 |
elif (counts['high'] >= 2 and counts['moderate'] >= 1) or \
|
681 |
(counts['moderate'] >= 3) or \
|
682 |
(counts['high'] >= 1 and counts['moderate'] >= 2):
|
683 |
pattern_escalation_risk = "High"
|
684 |
+
logger.debug("β οΈ HIGH RISK: Significant pattern combination")
|
685 |
elif (counts['moderate'] == 2) or \
|
686 |
(counts['high'] == 1 and counts['moderate'] == 1) or \
|
687 |
(counts['moderate'] == 1 and counts['low'] >= 2) or \
|
688 |
(counts['high'] == 1 and sum(counts.values()) == 1):
|
689 |
pattern_escalation_risk = "Moderate"
|
690 |
+
logger.debug("β‘ MODERATE RISK: Concerning pattern combination")
|
691 |
else:
|
692 |
pattern_escalation_risk = "Low"
|
693 |
+
logger.debug("π LOW RISK: Limited pattern severity")
|
694 |
|
695 |
+
# Checklist Risk Assessment
|
696 |
+
logger.debug("\nπ CHECKLIST RISK ASSESSMENT")
|
697 |
+
logger.debug("=" * 50)
|
698 |
checklist_escalation_risk = "Unknown" if escalation_score is None else (
|
699 |
"Critical" if escalation_score >= 20 else
|
700 |
"Moderate" if escalation_score >= 10 else
|
701 |
"Low"
|
702 |
)
|
703 |
+
logger.debug(f"Risk Level: {checklist_escalation_risk}")
|
704 |
+
if escalation_score is not None:
|
705 |
+
logger.debug(f"Score: {escalation_score}/29")
|
706 |
+
# Escalation Analysis
|
707 |
+
logger.debug("\nπ ESCALATION ANALYSIS")
|
708 |
+
logger.debug("=" * 50)
|
709 |
escalation_bump = 0
|
710 |
for result, msg_id in results:
|
711 |
abuse_score, _, _, sentiment, stage, darvo_score, tone_tag = result
|
712 |
+
logger.debug(f"\nπ Message {msg_id} Escalation Factors:")
|
713 |
+
|
714 |
+
factors = []
|
715 |
if darvo_score > 0.65:
|
716 |
escalation_bump += 3
|
717 |
+
factors.append("β² +3: High DARVO score ({darvo_score:.3f})")
|
718 |
if tone_tag in ["forced accountability flip", "emotional threat"]:
|
719 |
escalation_bump += 2
|
720 |
+
factors.append(f"β² +2: Concerning tone ({tone_tag})")
|
721 |
if abuse_score > 80:
|
722 |
escalation_bump += 2
|
723 |
+
factors.append(f"β² +2: High abuse score ({abuse_score:.1f}%)")
|
724 |
if stage == 2:
|
725 |
escalation_bump += 3
|
726 |
+
factors.append("β² +3: Escalation stage")
|
727 |
+
|
728 |
+
if factors:
|
729 |
+
for factor in factors:
|
730 |
+
logger.debug(f" {factor}")
|
731 |
+
else:
|
732 |
+
logger.debug(" β No escalation factors")
|
733 |
|
734 |
+
logger.debug(f"\nπ Total Escalation Bump: +{escalation_bump}")
|
735 |
|
736 |
+
# Combined Risk Calculation
|
737 |
+
logger.debug("\nπ― FINAL RISK CALCULATION")
|
738 |
+
logger.debug("=" * 50)
|
739 |
def rank(label):
|
740 |
return {"Low": 0, "Moderate": 1, "High": 2, "Critical": 3, "Unknown": 0}.get(label, 0)
|
741 |
|
742 |
combined_score = rank(pattern_escalation_risk) + rank(checklist_escalation_risk) + escalation_bump
|
743 |
+
logger.debug("Risk Components:")
|
744 |
+
logger.debug(f" β’ Pattern Risk: {pattern_escalation_risk} (+{rank(pattern_escalation_risk)})")
|
745 |
+
logger.debug(f" β’ Checklist Risk: {checklist_escalation_risk} (+{rank(checklist_escalation_risk)})")
|
746 |
+
logger.debug(f" β’ Escalation Bump: +{escalation_bump}")
|
747 |
+
logger.debug(f" = Combined Score: {combined_score}")
|
748 |
|
749 |
escalation_risk = (
|
750 |
"Critical" if combined_score >= 6 else
|
|
|
752 |
"Moderate" if combined_score >= 2 else
|
753 |
"Low"
|
754 |
)
|
755 |
+
logger.debug(f"\nβ οΈ Final Escalation Risk: {escalation_risk}")
|
756 |
|
757 |
+
# Generate Output Text
|
758 |
+
logger.debug("\nπ GENERATING OUTPUT")
|
759 |
+
logger.debug("=" * 50)
|
760 |
if escalation_score is None:
|
761 |
escalation_text = (
|
762 |
"π« **Escalation Potential: Unknown** (Checklist not completed)\n"
|
763 |
"β οΈ This section was not completed. Escalation potential is estimated using message data only.\n"
|
764 |
)
|
765 |
hybrid_score = 0
|
766 |
+
logger.debug("Generated output for incomplete checklist")
|
767 |
elif escalation_score == 0:
|
768 |
escalation_text = (
|
769 |
"β
**Escalation Checklist Completed:** No danger items reported.\n"
|
|
|
773 |
f"β’ Escalation Bump: +{escalation_bump} (from DARVO, tone, intensity, etc.)"
|
774 |
)
|
775 |
hybrid_score = escalation_bump
|
776 |
+
logger.debug("Generated output for no-risk checklist")
|
777 |
else:
|
778 |
hybrid_score = escalation_score + escalation_bump
|
779 |
escalation_text = (
|
|
|
783 |
f"β’ Checklist Risk: {checklist_escalation_risk}\n"
|
784 |
f"β’ Escalation Bump: +{escalation_bump} (from DARVO, tone, intensity, etc.)"
|
785 |
)
|
786 |
+
logger.debug(f"Generated output with hybrid score: {hybrid_score}/29")
|
787 |
|
788 |
+
# Final Metrics
|
789 |
+
logger.debug("\nπ FINAL METRICS")
|
790 |
+
logger.debug("=" * 50)
|
791 |
composite_abuse = int(round(sum(abuse_scores) / len(abuse_scores)))
|
792 |
+
logger.debug(f"Composite Abuse Score: {composite_abuse}%")
|
793 |
+
|
|
|
794 |
most_common_stage = max(set(stages), key=stages.count)
|
795 |
+
logger.debug(f"Most Common Stage: {most_common_stage}")
|
796 |
+
|
797 |
+
avg_darvo = round(sum(darvo_scores) / len(darvo_scores), 3)
|
798 |
+
logger.debug(f"Average DARVO Score: {avg_darvo}")
|
799 |
|
800 |
+
# Generate Final Output
|
801 |
+
logger.debug("\nπ GENERATING FINAL REPORT")
|
802 |
+
logger.debug("=" * 50)
|
803 |
out = f"Abuse Intensity: {composite_abuse}%\n"
|
804 |
out += "π This reflects the strength and severity of detected abuse patterns in the message(s).\n\n"
|
805 |
|
806 |
+
# Risk Level Assessment
|
807 |
risk_level = (
|
808 |
"Critical" if composite_abuse >= 85 or hybrid_score >= 20 else
|
809 |
"High" if composite_abuse >= 70 or hybrid_score >= 15 else
|
810 |
"Moderate" if composite_abuse >= 50 or hybrid_score >= 10 else
|
811 |
"Low"
|
812 |
)
|
813 |
+
logger.debug(f"Final Risk Level: {risk_level}")
|
814 |
+
|
815 |
+
# Add Risk Description
|
816 |
risk_descriptions = {
|
817 |
"Critical": (
|
818 |
"π¨ **Risk Level: Critical**\n"
|
|
|
837 |
}
|
838 |
|
839 |
out += risk_descriptions[risk_level]
|
840 |
+
out += f"\n\n{RISK_STAGE_LABELS[most_common_stage]}"
|
841 |
|
842 |
+
# Add DARVO Analysis
|
|
|
|
|
843 |
if avg_darvo > 0.25:
|
844 |
level = "moderate" if avg_darvo < 0.65 else "high"
|
845 |
out += f"\n\nπ **DARVO Score: {avg_darvo}** β This indicates a **{level} likelihood** of narrative reversal (DARVO), where the speaker may be denying, attacking, or reversing blame."
|
846 |
+
logger.debug(f"Added DARVO analysis ({level} level)")
|
847 |
|
848 |
+
# Add Emotional Tones
|
849 |
+
logger.debug("\nπ Adding Emotional Tones")
|
850 |
out += "\n\nπ **Emotional Tones Detected:**\n"
|
851 |
for i, tone in enumerate(tone_tags):
|
852 |
out += f"β’ Message {i+1}: *{tone or 'none'}*\n"
|
853 |
logger.debug(f"Message {i+1} tone: {tone}")
|
854 |
|
855 |
+
# Add Threats Section
|
856 |
+
logger.debug("\nβ οΈ Adding Threat Analysis")
|
857 |
if flat_threats:
|
858 |
out += "\n\nπ¨ **Immediate Danger Threats Detected:**\n"
|
859 |
for t in set(flat_threats):
|
|
|
865 |
out += "This does *not* rule out risk, but no direct threat phrases were matched."
|
866 |
logger.debug("No threats to add")
|
867 |
|
868 |
+
# Generate Timeline
|
869 |
+
logger.debug("\nπ Generating Timeline")
|
870 |
pattern_labels = [
|
871 |
pats[0][0] if (pats := r[0][2]) else "none"
|
872 |
for r in results
|
|
|
874 |
timeline_image = generate_abuse_score_chart(dates_used, abuse_scores, pattern_labels)
|
875 |
logger.debug("Timeline generated successfully")
|
876 |
|
877 |
+
# Add Escalation Text
|
878 |
out += "\n\n" + escalation_text
|
879 |
|
880 |
+
logger.debug("\nβ
ANALYSIS COMPLETE")
|
881 |
+
logger.debug("=" * 50)
|
882 |
return out, timeline_image
|
883 |
|
884 |
except Exception as e:
|
885 |
+
logger.error("\nβ ERROR IN ANALYSIS")
|
886 |
+
logger.error("=" * 50)
|
887 |
+
logger.error(f"Error type: {type(e).__name__}")
|
888 |
+
logger.error(f"Error message: {str(e)}")
|
889 |
+
logger.error(f"Traceback:\n{traceback.format_exc()}")
|
890 |
return "An error occurred during analysis.", None
|
891 |
|
892 |
|
893 |
+
|
894 |
# Gradio Interface Setup
|
895 |
def create_interface():
|
896 |
try:
|