""" Schemas Pydantic para Cámara, Grabación y Evento de Video. """ from datetime import datetime from typing import List, Optional from pydantic import Field from app.schemas.base import BaseSchema, TimestampSchema # ============================================================================ # Schemas de Cámara # ============================================================================ class CamaraBase(BaseSchema): """Schema base de cámara.""" nombre: str = Field(..., min_length=2, max_length=100) posicion: str = Field(default="frontal", max_length=50) tipo: str = Field(default="ip", max_length=50) class CamaraCreate(CamaraBase): """Schema para crear cámara.""" vehiculo_id: int marca: Optional[str] = Field(None, max_length=50) modelo: Optional[str] = Field(None, max_length=50) numero_serie: Optional[str] = Field(None, max_length=100) resolucion: Optional[str] = Field(None, max_length=20) url_stream: Optional[str] = Field(None, max_length=500) puerto: Optional[int] = Field(None, ge=1, le=65535) protocolo: str = Field(default="rtsp", max_length=20) usuario: Optional[str] = Field(None, max_length=100) password: Optional[str] = Field(None, max_length=100) # Se encriptará mediamtx_path: Optional[str] = Field(None, max_length=100) grabacion_continua: bool = False grabacion_evento: bool = True duracion_pre_evento: int = Field(default=10, ge=0, le=60) duracion_post_evento: int = Field(default=20, ge=0, le=120) deteccion_colision: bool = False deteccion_distraccion: bool = False deteccion_fatiga: bool = False deteccion_cambio_carril: bool = False notas: Optional[str] = None class CamaraUpdate(BaseSchema): """Schema para actualizar cámara.""" nombre: Optional[str] = Field(None, min_length=2, max_length=100) posicion: Optional[str] = Field(None, max_length=50) tipo: Optional[str] = Field(None, max_length=50) marca: Optional[str] = Field(None, max_length=50) modelo: Optional[str] = Field(None, max_length=50) numero_serie: Optional[str] = Field(None, max_length=100) resolucion: Optional[str] = Field(None, max_length=20) url_stream: Optional[str] = Field(None, max_length=500) puerto: Optional[int] = Field(None, ge=1, le=65535) protocolo: Optional[str] = Field(None, max_length=20) usuario: Optional[str] = Field(None, max_length=100) password: Optional[str] = Field(None, max_length=100) mediamtx_path: Optional[str] = Field(None, max_length=100) grabacion_continua: Optional[bool] = None grabacion_evento: Optional[bool] = None duracion_pre_evento: Optional[int] = Field(None, ge=0, le=60) duracion_post_evento: Optional[int] = Field(None, ge=0, le=120) deteccion_colision: Optional[bool] = None deteccion_distraccion: Optional[bool] = None deteccion_fatiga: Optional[bool] = None deteccion_cambio_carril: Optional[bool] = None activa: Optional[bool] = None notas: Optional[str] = None class CamaraResponse(CamaraBase, TimestampSchema): """Schema de respuesta de cámara.""" id: int vehiculo_id: int marca: Optional[str] = None modelo: Optional[str] = None numero_serie: Optional[str] = None resolucion: Optional[str] = None url_stream: Optional[str] = None puerto: Optional[int] = None protocolo: str usuario: Optional[str] = None # password no se expone mediamtx_path: Optional[str] = None estado: str activa: bool ultima_conexion: Optional[datetime] = None grabacion_continua: bool grabacion_evento: bool duracion_pre_evento: int duracion_post_evento: int deteccion_colision: bool deteccion_distraccion: bool deteccion_fatiga: bool deteccion_cambio_carril: bool notas: Optional[str] = None class CamaraConVehiculo(CamaraResponse): """Schema de cámara con información del vehículo.""" vehiculo_nombre: Optional[str] = None vehiculo_placa: Optional[str] = None class CamaraStreamURL(BaseSchema): """Schema con URLs de streaming de una cámara.""" camara_id: int camara_nombre: str rtsp_url: Optional[str] = None hls_url: Optional[str] = None webrtc_url: Optional[str] = None estado: str # ============================================================================ # Schemas de Grabación # ============================================================================ class GrabacionBase(BaseSchema): """Schema base de grabación.""" camara_id: int vehiculo_id: int inicio_tiempo: datetime tipo: str = Field(default="continua", max_length=50) class GrabacionCreate(GrabacionBase): """Schema para crear registro de grabación.""" archivo_url: str = Field(..., max_length=500) archivo_nombre: str = Field(..., max_length=255) formato: str = Field(default="mp4", max_length=10) class GrabacionResponse(GrabacionBase, TimestampSchema): """Schema de respuesta de grabación.""" id: int fin_tiempo: Optional[datetime] = None duracion_segundos: Optional[int] = None archivo_url: str archivo_nombre: str tamaño_mb: Optional[float] = None formato: str resolucion: Optional[str] = None evento_video_id: Optional[int] = None lat: Optional[float] = None lng: Optional[float] = None estado: str thumbnail_url: Optional[str] = None notas: Optional[str] = None # Calculado duracion_formateada: str class GrabacionResumen(BaseSchema): """Schema resumido de grabación.""" id: int camara_id: int vehiculo_id: int inicio_tiempo: datetime duracion_formateada: str tipo: str thumbnail_url: Optional[str] = None # ============================================================================ # Schemas de Evento de Video # ============================================================================ class EventoVideoBase(BaseSchema): """Schema base de evento de video.""" camara_id: int vehiculo_id: int tipo: str = Field(..., max_length=50) severidad: str = Field(default="media", pattern="^(baja|media|alta|critica)$") tiempo: datetime class EventoVideoCreate(EventoVideoBase): """Schema para crear evento de video.""" lat: Optional[float] = Field(None, ge=-90, le=90) lng: Optional[float] = Field(None, ge=-180, le=180) velocidad: Optional[float] = Field(None, ge=0) descripcion: Optional[str] = None confianza: Optional[float] = Field(None, ge=0, le=100) datos_extra: Optional[str] = None snapshot_url: Optional[str] = Field(None, max_length=500) clip_url: Optional[str] = Field(None, max_length=500) clip_duracion: Optional[int] = Field(None, ge=0) class EventoVideoUpdate(BaseSchema): """Schema para actualizar evento de video.""" revisado: Optional[bool] = None notas_revision: Optional[str] = None falso_positivo: Optional[bool] = None class EventoVideoResponse(EventoVideoBase, TimestampSchema): """Schema de respuesta de evento de video.""" id: int lat: Optional[float] = None lng: Optional[float] = None velocidad: Optional[float] = None descripcion: Optional[str] = None confianza: Optional[float] = None datos_extra: Optional[str] = None revisado: bool revisado_por_id: Optional[int] = None revisado_en: Optional[datetime] = None notas_revision: Optional[str] = None falso_positivo: bool snapshot_url: Optional[str] = None clip_url: Optional[str] = None clip_duracion: Optional[int] = None class EventoVideoConRelaciones(EventoVideoResponse): """Schema con información de cámara y vehículo.""" camara_nombre: Optional[str] = None vehiculo_nombre: Optional[str] = None vehiculo_placa: Optional[str] = None class EventoVideoResumen(BaseSchema): """Schema resumido de evento de video.""" id: int tipo: str severidad: str tiempo: datetime vehiculo_nombre: str camara_nombre: str revisado: bool falso_positivo: bool snapshot_url: Optional[str] = None class TiposEventoVideoResponse(BaseSchema): """Schema con tipos de eventos de video disponibles.""" tipos: List[dict] # [{codigo, nombre, severidad}]