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