Files
JARVIS/backend/app/services/memory/frequency_tracker.py

85 lines
2.8 KiB
Python
Raw Normal View History

"""
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)