Sistema completo para monitoreo y gestion de flotas de vehiculos con: - Backend FastAPI con PostgreSQL/TimescaleDB - Frontend React con TypeScript y TailwindCSS - App movil React Native con Expo - Soporte para dispositivos GPS, Meshtastic y celulares - Video streaming en vivo con MediaMTX - Geocercas, alertas, viajes y reportes - Autenticacion JWT y WebSockets en tiempo real Documentacion completa y guias de usuario incluidas.
157 lines
4.5 KiB
Python
157 lines
4.5 KiB
Python
"""
|
|
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"<EventoVideo(id={self.id}, tipo='{self.tipo}', severidad='{self.severidad}')>"
|
|
|
|
|
|
# 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",
|
|
},
|
|
]
|