Atlas / analytics /models.py
findEthics
Restore app.py and analytics folder to commit 439ebb4
838cd23
Raw
History Blame Contribute Delete
4.79 kB
"""
Data models for analytics collections.
"""
import uuid
from datetime import datetime
from typing import Optional, Dict, Any
from pydantic import BaseModel, Field, field_validator
class Session(BaseModel):
"""Session analytics model"""
session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
start_time: datetime = Field(default_factory=datetime.utcnow)
end_time: Optional[datetime] = None
message_count: int = 0
search_used: bool = False
user_agent: Optional[str] = None
status: str = "active" # active, ended
user_id: Optional[str] = None
@field_validator('user_id')
@classmethod
def validate_user_id(cls, v):
"""Validate user_id format"""
if v is not None:
if not isinstance(v, str):
raise ValueError('user_id must be a string')
if v.strip() == '':
return None # Treat empty string as None (anonymous)
if len(v) > 255:
raise ValueError('user_id must be 255 characters or less')
# Allow ASCII alphanumeric, hyphens, and underscores
if not all((c.isascii() and c.isalnum()) or c in '-_' for c in v):
raise ValueError('user_id can only contain alphanumeric characters, hyphens, and underscores')
return v
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for MongoDB insertion"""
data = self.dict()
data["_id"] = self.session_id
return data
def end_session(self):
"""Mark session as ended"""
self.end_time = datetime.utcnow()
self.status = "ended"
@property
def duration_seconds(self) -> Optional[int]:
"""Calculate session duration in seconds"""
if self.end_time:
return int((self.end_time - self.start_time).total_seconds())
return None
class Message(BaseModel):
"""Message analytics model"""
message_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
session_id: str
timestamp: datetime = Field(default_factory=datetime.utcnow)
prompt_length: int
response_length: int
used_search: bool = False
response_time_ms: int
max_tokens: int = 500
temperature: float = 0.7
success: bool = True
error_message: Optional[str] = None
user_id: Optional[str] = None
@field_validator('user_id')
@classmethod
def validate_user_id(cls, v):
"""Validate user_id format"""
if v is not None:
if not isinstance(v, str):
raise ValueError('user_id must be a string')
if v.strip() == '':
return None # Treat empty string as None (anonymous)
if len(v) > 255:
raise ValueError('user_id must be 255 characters or less')
# Allow ASCII alphanumeric, hyphens, and underscores
if not all((c.isascii() and c.isalnum()) or c in '-_' for c in v):
raise ValueError('user_id can only contain alphanumeric characters, hyphens, and underscores')
return v
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for MongoDB insertion"""
data = self.dict()
data["_id"] = self.message_id
return data
class SearchAnalytics(BaseModel):
"""Search analytics model"""
search_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
message_id: str
timestamp: datetime = Field(default_factory=datetime.utcnow)
search_query: str
search_terms: list[str] = []
brave_results: int = 0
duckduckgo_results: int = 0
total_unique_results: int = 0
brave_response_time_ms: int = 0
duckduckgo_response_time_ms: int = 0
search_engines_used: list[str] = []
search_success: bool = True
fallback_used: bool = False
user_id: Optional[str] = None
@field_validator('user_id')
@classmethod
def validate_user_id(cls, v):
"""Validate user_id format"""
if v is not None:
if not isinstance(v, str):
raise ValueError('user_id must be a string')
if v.strip() == '':
return None # Treat empty string as None (anonymous)
if len(v) > 255:
raise ValueError('user_id must be 255 characters or less')
# Allow ASCII alphanumeric, hyphens, and underscores
if not all((c.isascii() and c.isalnum()) or c in '-_' for c in v):
raise ValueError('user_id can only contain alphanumeric characters, hyphens, and underscores')
return v
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for MongoDB insertion"""
data = self.dict()
data["_id"] = self.search_id
return data