""" Enhanced API Models for HCFS Production API. Comprehensive Pydantic models for request/response validation, API versioning, and enterprise-grade data validation. """ from typing import List, Optional, Dict, Any, Union from datetime import datetime from enum import Enum from pydantic import BaseModel, Field, validator, ConfigDict import uuid class APIVersion(str, Enum): """API version enumeration.""" V1 = "v1" V2 = "v2" class SearchType(str, Enum): """Search type enumeration.""" SEMANTIC = "semantic" HYBRID = "hybrid" KEYWORD = "keyword" SIMILARITY = "similarity" class SortOrder(str, Enum): """Sort order enumeration.""" ASC = "asc" DESC = "desc" class ContextStatus(str, Enum): """Context status enumeration.""" ACTIVE = "active" ARCHIVED = "archived" DRAFT = "draft" DELETED = "deleted" # Base Models class BaseResponse(BaseModel): """Base response model with metadata.""" model_config = ConfigDict(from_attributes=True) success: bool = True message: Optional[str] = None timestamp: datetime = Field(default_factory=datetime.utcnow) request_id: str = Field(default_factory=lambda: str(uuid.uuid4())) api_version: APIVersion = APIVersion.V1 class PaginationParams(BaseModel): """Pagination parameters.""" page: int = Field(default=1, ge=1, description="Page number (1-based)") page_size: int = Field(default=20, ge=1, le=100, description="Items per page") @property def offset(self) -> int: """Calculate offset from page and page_size.""" return (self.page - 1) * self.page_size class PaginationMeta(BaseModel): """Pagination metadata.""" page: int page_size: int total_items: int total_pages: int has_next: bool has_previous: bool # Context Models class ContextBase(BaseModel): """Base context model with common fields.""" path: str = Field(..., description="Hierarchical path for the context") content: str = Field(..., description="Main content of the context") summary: Optional[str] = Field(None, description="Brief summary of the content") author: Optional[str] = Field(None, description="Author or creator of the context") tags: Optional[List[str]] = Field(default_factory=list, description="Tags associated with the context") metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional metadata") status: ContextStatus = Field(default=ContextStatus.ACTIVE, description="Context status") @validator('path') def validate_path(cls, v): """Validate path format.""" if not v.startswith('/'): raise ValueError('Path must start with /') if '//' in v: raise ValueError('Path cannot contain double slashes') return v @validator('content') def validate_content(cls, v): """Validate content is not empty.""" if not v.strip(): raise ValueError('Content cannot be empty') return v.strip() class ContextCreate(ContextBase): """Model for creating a new context.""" pass class ContextUpdate(BaseModel): """Model for updating an existing context.""" content: Optional[str] = None summary: Optional[str] = None author: Optional[str] = None tags: Optional[List[str]] = None metadata: Optional[Dict[str, Any]] = None status: Optional[ContextStatus] = None @validator('content') def validate_content(cls, v): """Validate content if provided.""" if v is not None and not v.strip(): raise ValueError('Content cannot be empty') return v.strip() if v else v class ContextResponse(ContextBase): """Model for context responses.""" id: int = Field(..., description="Unique context identifier") created_at: datetime = Field(..., description="Creation timestamp") updated_at: datetime = Field(..., description="Last update timestamp") version: int = Field(..., description="Context version number") embedding_model: Optional[str] = Field(None, description="Embedding model used") similarity_score: Optional[float] = Field(None, description="Similarity score (for search results)") class ContextListResponse(BaseResponse): """Response model for context list operations.""" data: List[ContextResponse] pagination: PaginationMeta class ContextDetailResponse(BaseResponse): """Response model for single context operations.""" data: ContextResponse # Search Models class SearchRequest(BaseModel): """Model for search requests.""" query: str = Field(..., description="Search query text") search_type: SearchType = Field(default=SearchType.SEMANTIC, description="Type of search to perform") path_prefix: Optional[str] = Field(None, description="Limit search to paths with this prefix") top_k: int = Field(default=10, ge=1, le=100, description="Maximum number of results to return") min_similarity: float = Field(default=0.0, ge=0.0, le=1.0, description="Minimum similarity threshold") semantic_weight: float = Field(default=0.7, ge=0.0, le=1.0, description="Weight for semantic vs keyword search") include_content: bool = Field(default=True, description="Whether to include full content in results") filters: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Additional search filters") @validator('query') def validate_query(cls, v): """Validate query is not empty.""" if not v.strip(): raise ValueError('Query cannot be empty') return v.strip() class SearchResult(BaseModel): """Individual search result.""" context: ContextResponse score: float = Field(..., description="Relevance score") highlight: Optional[Dict[str, List[str]]] = Field(None, description="Highlighted matching text") explanation: Optional[str] = Field(None, description="Explanation of why this result was returned") class SearchResponse(BaseResponse): """Response model for search operations.""" data: List[SearchResult] query: str search_type: SearchType total_results: int search_time_ms: float filters_applied: Dict[str, Any] # Version Models class VersionResponse(BaseModel): """Model for context version information.""" version_id: int version_number: int context_id: int author: str message: Optional[str] created_at: datetime content_hash: str metadata: Optional[Dict[str, Any]] = None class VersionListResponse(BaseResponse): """Response model for version history.""" data: List[VersionResponse] context_id: int total_versions: int class VersionCreateRequest(BaseModel): """Request model for creating a new version.""" message: Optional[str] = Field(None, description="Version commit message") metadata: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Version metadata") class RollbackRequest(BaseModel): """Request model for version rollback.""" target_version: int = Field(..., description="Target version number to rollback to") message: Optional[str] = Field(None, description="Rollback commit message") # Analytics Models class ContextStats(BaseModel): """Context statistics model.""" total_contexts: int contexts_by_status: Dict[ContextStatus, int] contexts_by_author: Dict[str, int] average_content_length: float most_active_paths: List[Dict[str, Union[str, int]]] recent_activity: List[Dict[str, Any]] class SearchStats(BaseModel): """Search statistics model.""" total_searches: int searches_by_type: Dict[SearchType, int] average_response_time_ms: float popular_queries: List[Dict[str, Union[str, int]]] search_success_rate: float class SystemStats(BaseModel): """System statistics model.""" uptime_seconds: float memory_usage_mb: float active_connections: int cache_hit_rate: float embedding_model_info: Dict[str, Any] database_size_mb: float class StatsResponse(BaseResponse): """Response model for statistics.""" context_stats: ContextStats search_stats: SearchStats system_stats: SystemStats # Batch Operations Models class BatchContextCreate(BaseModel): """Model for batch context creation.""" contexts: List[ContextCreate] = Field(..., max_items=100, description="List of contexts to create") @validator('contexts') def validate_contexts_not_empty(cls, v): """Validate contexts list is not empty.""" if not v: raise ValueError('Contexts list cannot be empty') return v class BatchOperationResult(BaseModel): """Result of batch operation.""" success_count: int error_count: int total_items: int errors: List[Dict[str, Any]] = Field(default_factory=list) created_ids: List[int] = Field(default_factory=list) class BatchResponse(BaseResponse): """Response model for batch operations.""" data: BatchOperationResult # WebSocket Models class WebSocketMessage(BaseModel): """WebSocket message model.""" type: str = Field(..., description="Message type") data: Dict[str, Any] = Field(..., description="Message data") timestamp: datetime = Field(default_factory=datetime.utcnow) message_id: str = Field(default_factory=lambda: str(uuid.uuid4())) class SubscriptionRequest(BaseModel): """WebSocket subscription request.""" path_prefix: str = Field(..., description="Path prefix to subscribe to") event_types: List[str] = Field(default_factory=lambda: ["created", "updated", "deleted"]) filters: Optional[Dict[str, Any]] = Field(default_factory=dict) # Health Check Models class HealthStatus(str, Enum): """Health status enumeration.""" HEALTHY = "healthy" DEGRADED = "degraded" UNHEALTHY = "unhealthy" class ComponentHealth(BaseModel): """Individual component health.""" name: str status: HealthStatus response_time_ms: Optional[float] = None error_message: Optional[str] = None last_check: datetime class HealthResponse(BaseModel): """System health response.""" status: HealthStatus timestamp: datetime = Field(default_factory=datetime.utcnow) version: str uptime_seconds: float components: List[ComponentHealth] # Error Models class ErrorDetail(BaseModel): """Detailed error information.""" field: Optional[str] = None message: str error_code: Optional[str] = None class ErrorResponse(BaseModel): """Error response model.""" success: bool = False error: str error_details: Optional[List[ErrorDetail]] = None timestamp: datetime = Field(default_factory=datetime.utcnow) request_id: str = Field(default_factory=lambda: str(uuid.uuid4())) api_version: APIVersion = APIVersion.V1