""" EmotionAnalyzer Extracts emotional intensity from text and calculates emotion-based importance scores. """ import re from typing import TYPE_CHECKING if TYPE_CHECKING: from app.models.memory import UserMemory class EmotionAnalyzer: """Analyze emotional keywords in memory content""" # Emotion keyword weights (higher = more important) EMOTION_KEYWORDS = { # High intensity "急": 1.0, "紧急": 1.0, "很重要": 0.9, "非常重要": 1.0, "必须": 0.9, # Medium-high intensity "困扰": 0.8, "烦恼": 0.7, "担心": 0.7, "焦虑": 0.8, "害怕": 0.8, "恐惧": 0.9, # Medium intensity "想解决": 0.6, "希望": 0.5, "想要": 0.4, "需要": 0.4, "渴望": 0.6, # Low intensity (casual/neutral) "无所谓": 0.1, "随便": 0.1, "都行": 0.1, "还好": 0.2, # Negative valence "讨厌": 0.6, "不喜欢": 0.5, "恨": 0.8, "不喜欢": 0.5, # Positive valence "喜欢": 0.5, "爱": 0.7, "开心": 0.4, "高兴": 0.4, } # Urgency patterns — only match when there's an explicit time-bound word # NOTE: "今天" alone is too common and matches neutral sentences like "今天天气不错" URGENCY_PATTERNS = [ (re.compile(r"马上|立刻|立即|赶紧"), 1.0), (re.compile(r"今天内|今天必须|今日必须"), 0.8), (re.compile(r"明天|明天之前|明日"), 0.6), (re.compile(r"这周|本周"), 0.4), (re.compile(r"尽快|早点"), 0.7), ] def extract(self, text: str) -> list[str]: """Extract emotion keywords from text Returns: List of matched emotion keywords """ if not text: return [] matched = [] text_lower = text.lower() for keyword, weight in self.EMOTION_KEYWORDS.items(): if keyword in text_lower: matched.append(keyword) # Check urgency patterns for pattern, weight in self.URGENCY_PATTERNS: if pattern.search(text): matched.append(f"[URGENCY:{weight}]") return matched def calculate_score(self, memory: "UserMemory") -> float: """Calculate emotion-based importance score (0.0 - 1.0) Uses the highest-weighted emotion keyword found in the content. """ content = memory.content or "" emotion_tags = memory.emotion_tags or [] # Check emotion_tags first (pre-extracted) if emotion_tags: max_weight = 0.0 for tag in emotion_tags: if tag in self.EMOTION_KEYWORDS: max_weight = max(max_weight, self.EMOTION_KEYWORDS[tag]) if max_weight > 0: return max_weight # Extract from content matched = self.extract(content) if not matched: return 0.0 # Get highest weight max_weight = 0.0 for keyword in matched: if keyword.startswith("[URGENCY:"): # Extract urgency weight try: weight = float(keyword.split(":")[1].rstrip("]")) max_weight = max(max_weight, weight) except (ValueError, IndexError): pass elif keyword in self.EMOTION_KEYWORDS: max_weight = max(max_weight, self.EMOTION_KEYWORDS[keyword]) return min(1.0, max_weight) def get_emotion_profile(self, text: str) -> dict: """Get detailed emotion profile for text Returns: Dict with matched keywords, max_weight, and sentiment """ matched = self.extract(text) weights = [] for keyword in matched: if keyword.startswith("[URGENCY:"): try: weights.append(float(keyword.split(":")[1].rstrip("]"))) except (ValueError, IndexError): pass elif keyword in self.EMOTION_KEYWORDS: weights.append(self.EMOTION_KEYWORDS[keyword]) return { "matched_keywords": matched, "max_weight": max(weights) if weights else 0.0, "avg_weight": sum(weights) / len(weights) if weights else 0.0, "sentiment": "positive" if (weights and sum(weights) / len(weights) > 0.5) else "neutral", }