Files
JARVIS/backend/app/services/memory/frequency_tracker.py
WIN-JHFT4D3SIVT\caoxiaozhu 9bfa0dcc11 feat(memory): Day M.1 complete - importance scoring system
- Add FrequencyTracker: increment(), get_frequency_score(), get_recency_score(), get_time_decay()
- Add EmotionAnalyzer: EMOTION_KEYWORDS dict, extract(), calculate_score(), get_emotion_profile()
- Add ImpactEvaluator: evaluate(), get_topic_overlap(), rank_by_impact()
- Add ImportanceScorer: composite scoring (freq 35% + recency 20% + emotion 25% + impact 20%)
- Update UserMemory model: frequency_count, emotion_tags, importance_score, importance_level, associated_topics
- Integrate ImportanceScorer into memory_service.py (recall + importance update)
- Add 37 tests for all memory scoring components
- Fix urgency patterns: remove overly broad '今天' that matched neutral text
- Update memory-update checklist: mark all M.1 tasks complete
2026-04-05 13:22:23 +08:00

85 lines
2.8 KiB
Python

"""
FrequencyTracker
Tracks how often a memory is recalled and calculates frequency/recency scores.
"""
from datetime import UTC, datetime
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from app.models.memory import UserMemory
class FrequencyTracker:
"""Track and score memory recall frequency"""
# Score weights
MAX_FREQUENCY = 10 # Cap frequency count for scoring
RECENCY_DECAY_DAYS = 30 # After 30 days, recency score drops significantly
def increment(self, memory: "UserMemory") -> "UserMemory":
"""Increment recall count and update last recalled timestamp"""
memory.frequency_count = (memory.frequency_count or 0) + 1
memory.last_recalled_at = datetime.now(UTC)
return memory
def get_frequency_score(self, memory: "UserMemory") -> float:
"""Calculate normalized frequency score (0.0 - 1.0)
Uses logarithmic scaling to prevent high-frequency memories
from dominating completely.
"""
count = memory.frequency_count or 0
if count == 0:
return 0.0
# Logarithmic scaling: more recalls have diminishing returns
# log(1+x) / log(1+MAX) gives 0-1 range
import math
score = math.log(1 + count) / math.log(1 + self.MAX_FREQUENCY)
return min(1.0, max(0.0, score))
def get_recency_score(self, memory: "UserMemory") -> float:
"""Calculate recency score (0.0 - 1.0)
Memory recalled recently scores higher. Uses exponential decay.
"""
last_recalled = memory.last_recalled_at
if last_recalled is None:
return 0.0
now = datetime.now(UTC)
if isinstance(last_recalled, datetime):
if last_recalled.tzinfo is None:
last_recalled = last_recalled.replace(tzinfo=UTC)
days_since = (now - last_recalled).total_seconds() / 86400
else:
days_since = self.RECENCY_DECAY_DAYS
# Exponential decay: half-life of RECENCY_DECAY_DAYS
import math
decay = math.exp(-days_since / self.RECENCY_DECAY_DAYS)
return min(1.0, max(0.0, decay))
def get_time_decay(self, memory: "UserMemory") -> float:
"""Calculate time-based decay factor for forgetting curve"""
last_accessed = getattr(memory, "last_accessed_at", None)
if last_accessed is None:
last_accessed = memory.last_recalled_at
if last_accessed is None:
return 1.0
now = datetime.now(UTC)
if isinstance(last_accessed, datetime):
if last_accessed.tzinfo is None:
last_accessed = last_accessed.replace(tzinfo=UTC)
days_since = (now - last_accessed).total_seconds() / 86400
else:
days_since = 0
import math
return math.exp(-days_since / self.RECENCY_DECAY_DAYS)