FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
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.
This commit is contained in:
156
backend/app/models/evento_video.py
Normal file
156
backend/app/models/evento_video.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
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",
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user