"""Pydantic schemas for request/response validation.""" from datetime import datetime from typing import Literal, Optional, Any from pydantic import BaseModel, ConfigDict # --------------------------------------------------------------------------- # Auth / user schemas # --------------------------------------------------------------------------- class UserOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str role: str is_active: int class LoginResponse(BaseModel): token: str token_type: str = "bearer" username: str user: UserOut class AdminUserCreate(BaseModel): username: str password: str role: str = "annotator" is_active: bool = True class AdminUserUpdate(BaseModel): username: Optional[str] = None password: Optional[str] = None role: Optional[str] = None is_active: Optional[bool] = None class AuditLogOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int actor_user_id: Optional[int] = None action: str target_type: Optional[str] = None target_id: Optional[str] = None detail: Optional[dict[str, Any]] = None created_at: datetime class DemoFactoryResetRequest(BaseModel): confirmation: str # --------------------------------------------------------------------------- # Project schemas # --------------------------------------------------------------------------- class ProjectBase(BaseModel): name: str description: Optional[str] = None video_path: Optional[str] = None thumbnail_url: Optional[str] = None status: Optional[str] = "pending" source_type: Optional[str] = "video" original_fps: Optional[float] = None parse_fps: Optional[float] = 30.0 class ProjectCreate(ProjectBase): pass class ProjectUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None video_path: Optional[str] = None thumbnail_url: Optional[str] = None status: Optional[str] = None source_type: Optional[str] = None original_fps: Optional[float] = None parse_fps: Optional[float] = None class ProjectCopyRequest(BaseModel): mode: Literal["reset", "full"] = "reset" name: Optional[str] = None class ProjectOut(ProjectBase): model_config = ConfigDict(from_attributes=True) id: int owner_user_id: Optional[int] = None created_at: datetime updated_at: datetime frame_count: int = 0 class DemoFactoryResetOut(BaseModel): admin_user: UserOut project: ProjectOut projects: list[ProjectOut] deleted_counts: dict[str, int] message: str # --------------------------------------------------------------------------- # Frame schemas # --------------------------------------------------------------------------- class FrameBase(BaseModel): frame_index: int image_url: str width: Optional[int] = None height: Optional[int] = None timestamp_ms: Optional[float] = None source_frame_number: Optional[int] = None class FrameCreate(FrameBase): project_id: int class FrameOut(FrameBase): model_config = ConfigDict(from_attributes=True) id: int project_id: int created_at: datetime # --------------------------------------------------------------------------- # Template schemas # --------------------------------------------------------------------------- class TemplateBase(BaseModel): name: str description: Optional[str] = None color: str z_index: int = 0 mapping_rules: Optional[dict[str, Any]] = None classes: Optional[list[dict[str, Any]]] = None rules: Optional[list[dict[str, Any]]] = None class TemplateCreate(TemplateBase): pass class TemplateUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None color: Optional[str] = None z_index: Optional[int] = None mapping_rules: Optional[dict[str, Any]] = None classes: Optional[list[dict[str, Any]]] = None rules: Optional[list[dict[str, Any]]] = None class TemplateOut(TemplateBase): model_config = ConfigDict(from_attributes=True) id: int owner_user_id: Optional[int] = None created_at: datetime # --------------------------------------------------------------------------- # Annotation schemas # --------------------------------------------------------------------------- class AnnotationBase(BaseModel): project_id: int frame_id: Optional[int] = None template_id: Optional[int] = None mask_data: Optional[dict[str, Any]] = None points: Optional[list[list[float]]] = None bbox: Optional[list[float]] = None class AnnotationCreate(AnnotationBase): pass class AnnotationUpdate(BaseModel): mask_data: Optional[dict[str, Any]] = None points: Optional[list[list[float]]] = None bbox: Optional[list[float]] = None template_id: Optional[int] = None class AnnotationOut(AnnotationBase): model_config = ConfigDict(from_attributes=True) id: int created_at: datetime updated_at: datetime # --------------------------------------------------------------------------- # Mask schemas # --------------------------------------------------------------------------- class MaskBase(BaseModel): annotation_id: int mask_url: str format: str = "png" class MaskCreate(MaskBase): pass class MaskOut(MaskBase): model_config = ConfigDict(from_attributes=True) id: int created_at: datetime # --------------------------------------------------------------------------- # Processing task schemas # --------------------------------------------------------------------------- class ProcessingTaskOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int task_type: str status: str progress: int message: Optional[str] = None project_id: Optional[int] = None celery_task_id: Optional[str] = None payload: Optional[dict[str, Any]] = None result: Optional[dict[str, Any]] = None error: Optional[str] = None created_at: datetime started_at: Optional[datetime] = None finished_at: Optional[datetime] = None updated_at: datetime # --------------------------------------------------------------------------- # AI schemas # --------------------------------------------------------------------------- class PredictRequest(BaseModel): image_id: int prompt_type: str # point / box / semantic prompt_data: Any model: Optional[str] = None options: Optional[dict[str, Any]] = None class PredictResponse(BaseModel): polygons: list[list[list[float]]] scores: Optional[list[float]] = None class MaskAnalysisRequest(BaseModel): frame_id: Optional[int] = None mask_data: dict[str, Any] points: Optional[list[list[float]]] = None bbox: Optional[list[float]] = None extract_skeleton: bool = False class MaskAnalysisResponse(BaseModel): confidence: Optional[float] = None confidence_source: str topology_anchor_count: int topology_anchors: list[list[float]] area: float bbox: Optional[list[float]] = None source: Optional[str] = None message: str class SmoothMaskRequest(BaseModel): frame_id: Optional[int] = None mask_data: dict[str, Any] points: Optional[list[list[float]]] = None bbox: Optional[list[float]] = None strength: float = 0.0 method: str = "chaikin" class SmoothMaskResponse(BaseModel): polygons: list[list[list[float]]] topology_anchor_count: int topology_anchors: list[list[float]] area: float bbox: Optional[list[float]] = None smoothing: dict[str, Any] message: str class PropagationSeed(BaseModel): polygons: Optional[list[list[list[float]]]] = None holes: Optional[list[list[list[list[float]]]]] = None bbox: Optional[list[float]] = None points: Optional[list[list[float]]] = None labels: Optional[list[int]] = None label: Optional[str] = None color: Optional[str] = None class_metadata: Optional[dict[str, Any]] = None template_id: Optional[int] = None source_mask_id: Optional[str] = None source_annotation_id: Optional[int] = None source_instance_id: Optional[str] = None propagation_seed_signature: Optional[str] = None smoothing: Optional[dict[str, Any]] = None class PropagateRequest(BaseModel): project_id: int frame_id: int model: Optional[str] = "sam2.1_hiera_tiny" seed: PropagationSeed direction: str = "forward" max_frames: int = 30 include_source: bool = False save_annotations: bool = True class PropagateResponse(BaseModel): model: str direction: str source_frame_id: int processed_frame_count: int created_annotation_count: int annotations: list[AnnotationOut] class PropagateTaskStep(BaseModel): seed: PropagationSeed direction: str = "forward" max_frames: int = 30 class PropagateTaskRequest(BaseModel): project_id: int frame_id: int model: Optional[str] = "sam2.1_hiera_tiny" steps: list[PropagateTaskStep] include_source: bool = False save_annotations: bool = True class AiModelStatus(BaseModel): id: str label: str available: bool loaded: bool = False device: str supports: list[str] message: str package_available: bool = False checkpoint_exists: bool = False checkpoint_path: Optional[str] = None python_ok: bool = True torch_ok: bool = True cuda_required: bool = False external_available: bool = False external_python: Optional[str] = None class GpuStatus(BaseModel): available: bool device: str name: Optional[str] = None torch_available: bool torch_version: Optional[str] = None cuda_version: Optional[str] = None class AiRuntimeStatus(BaseModel): selected_model: str gpu: GpuStatus models: list[AiModelStatus] # --------------------------------------------------------------------------- # Export schemas # --------------------------------------------------------------------------- class ExportStatus(BaseModel): url: str format: str