""" Modelo de Evento de Video para registrar eventos detectados por cámaras. """ from datetime import datetime from sqlalchemy import ( Boolean, DateTime, Float, ForeignKey, Index, String, Text, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base from app.models.base import TimestampMixin class EventoVideo(Base, TimestampMixin): """Modelo para eventos detectados por cámaras (AI/ADAS).""" __tablename__ = "eventos_video" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) # Relaciones camara_id: Mapped[int] = mapped_column( ForeignKey("camaras.id", ondelete="CASCADE"), nullable=False, index=True, ) vehiculo_id: Mapped[int] = mapped_column( ForeignKey("vehiculos.id", ondelete="CASCADE"), nullable=False, index=True, ) # Tipo de evento tipo: Mapped[str] = mapped_column( String(50), nullable=False, ) # colision, distraccion, fatiga, cambio_carril, exceso_velocidad, objeto_detectado # Severidad severidad: Mapped[str] = mapped_column( String(20), default="media", nullable=False, ) # baja, media, alta, critica # Tiempo del evento tiempo: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) # Ubicación lat: Mapped[float | None] = mapped_column(Float, nullable=True) lng: Mapped[float | None] = mapped_column(Float, nullable=True) velocidad: Mapped[float | None] = mapped_column(Float, nullable=True) # Descripción descripcion: Mapped[str | None] = mapped_column(Text, nullable=True) # Confianza de la detección (si es detección AI) confianza: Mapped[float | None] = mapped_column(Float, nullable=True) # 0-100% # Datos adicionales (JSON) datos_extra: Mapped[str | None] = mapped_column(Text, nullable=True) # Estado de revisión revisado: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) revisado_por_id: Mapped[int | None] = mapped_column( ForeignKey("usuarios.id", ondelete="SET NULL"), nullable=True, ) revisado_en: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) notas_revision: Mapped[str | None] = mapped_column(Text, nullable=True) # Falso positivo falso_positivo: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) # Snapshot del momento snapshot_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # Clip de video asociado clip_url: Mapped[str | None] = mapped_column(String(500), nullable=True) clip_duracion: Mapped[int | None] = mapped_column(default=None, nullable=True) # segundos # Relaciones ORM camara: Mapped["Camara"] = relationship( "Camara", back_populates="eventos_video", lazy="selectin", ) # Índices __table_args__ = ( Index("idx_eventos_video_camara_tiempo", "camara_id", "tiempo"), Index("idx_eventos_video_vehiculo_tiempo", "vehiculo_id", "tiempo"), Index("idx_eventos_video_tipo", "tipo"), Index("idx_eventos_video_revisado", "revisado"), ) def __repr__(self) -> str: return f"" # Tipos de eventos de video predefinidos TIPOS_EVENTO_VIDEO = [ { "codigo": "COLISION_FRONTAL", "nombre": "Posible colisión frontal", "severidad": "critica", }, { "codigo": "DISTRACCION_CONDUCTOR", "nombre": "Distracción del conductor", "severidad": "alta", }, { "codigo": "FATIGA_CONDUCTOR", "nombre": "Fatiga del conductor", "severidad": "alta", }, { "codigo": "CAMBIO_CARRIL_PELIGROSO", "nombre": "Cambio de carril peligroso", "severidad": "media", }, { "codigo": "SEGUIMIENTO_CERCANO", "nombre": "Seguimiento muy cercano", "severidad": "media", }, { "codigo": "PEATON_DETECTADO", "nombre": "Peatón detectado", "severidad": "media", }, { "codigo": "USO_CELULAR", "nombre": "Uso de celular", "severidad": "alta", }, { "codigo": "SIN_CINTURON", "nombre": "Sin cinturón de seguridad", "severidad": "media", }, { "codigo": "FUMANDO", "nombre": "Conductor fumando", "severidad": "baja", }, ]