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.
161 lines
4.8 KiB
Python
161 lines
4.8 KiB
Python
"""
|
|
Schemas Pydantic para Geocerca.
|
|
"""
|
|
|
|
from typing import List, Optional
|
|
|
|
from pydantic import Field, field_validator
|
|
|
|
from app.schemas.base import BaseSchema, TimestampSchema
|
|
|
|
|
|
class GeocercaBase(BaseSchema):
|
|
"""Schema base de geocerca."""
|
|
|
|
nombre: str = Field(..., min_length=2, max_length=100)
|
|
descripcion: Optional[str] = None
|
|
tipo: str = Field(default="circular", pattern="^(circular|poligono)$")
|
|
color: str = Field(default="#3B82F6", pattern=r"^#[0-9A-Fa-f]{6}$")
|
|
opacidad: float = Field(default=0.3, ge=0, le=1)
|
|
color_borde: str = Field(default="#1D4ED8", pattern=r"^#[0-9A-Fa-f]{6}$")
|
|
categoria: Optional[str] = Field(None, max_length=50)
|
|
|
|
|
|
class GeocercaCircularCreate(GeocercaBase):
|
|
"""Schema para crear geocerca circular."""
|
|
|
|
tipo: str = "circular"
|
|
centro_lat: float = Field(..., ge=-90, le=90)
|
|
centro_lng: float = Field(..., ge=-180, le=180)
|
|
radio_metros: float = Field(..., gt=0, le=100000)
|
|
|
|
# Configuración de alertas
|
|
alerta_entrada: bool = True
|
|
alerta_salida: bool = True
|
|
velocidad_maxima: Optional[float] = Field(None, ge=0)
|
|
|
|
# Horario (opcional, JSON)
|
|
horario_json: Optional[str] = None
|
|
|
|
# Vehículos asignados (opcional, vacío = todos)
|
|
vehiculos_ids: Optional[List[int]] = None
|
|
|
|
|
|
class GeocercaPoligonoCreate(GeocercaBase):
|
|
"""Schema para crear geocerca poligonal."""
|
|
|
|
tipo: str = "poligono"
|
|
coordenadas: List[List[float]] # [[lat, lng], [lat, lng], ...]
|
|
|
|
# Configuración de alertas
|
|
alerta_entrada: bool = True
|
|
alerta_salida: bool = True
|
|
velocidad_maxima: Optional[float] = Field(None, ge=0)
|
|
|
|
# Horario (opcional, JSON)
|
|
horario_json: Optional[str] = None
|
|
|
|
# Vehículos asignados (opcional, vacío = todos)
|
|
vehiculos_ids: Optional[List[int]] = None
|
|
|
|
@field_validator("coordenadas")
|
|
@classmethod
|
|
def validate_coordenadas(cls, v: List[List[float]]) -> List[List[float]]:
|
|
if len(v) < 3:
|
|
raise ValueError("Un polígono debe tener al menos 3 puntos")
|
|
for coord in v:
|
|
if len(coord) != 2:
|
|
raise ValueError("Cada coordenada debe tener [lat, lng]")
|
|
if not (-90 <= coord[0] <= 90):
|
|
raise ValueError("Latitud debe estar entre -90 y 90")
|
|
if not (-180 <= coord[1] <= 180):
|
|
raise ValueError("Longitud debe estar entre -180 y 180")
|
|
return v
|
|
|
|
|
|
class GeocercaUpdate(BaseSchema):
|
|
"""Schema para actualizar geocerca."""
|
|
|
|
nombre: Optional[str] = Field(None, min_length=2, max_length=100)
|
|
descripcion: Optional[str] = None
|
|
color: Optional[str] = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
|
|
opacidad: Optional[float] = Field(None, ge=0, le=1)
|
|
color_borde: Optional[str] = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
|
|
categoria: Optional[str] = Field(None, max_length=50)
|
|
|
|
# Para circular
|
|
centro_lat: Optional[float] = Field(None, ge=-90, le=90)
|
|
centro_lng: Optional[float] = Field(None, ge=-180, le=180)
|
|
radio_metros: Optional[float] = Field(None, gt=0, le=100000)
|
|
|
|
# Para polígono
|
|
coordenadas: Optional[List[List[float]]] = None
|
|
|
|
# Configuración
|
|
alerta_entrada: Optional[bool] = None
|
|
alerta_salida: Optional[bool] = None
|
|
velocidad_maxima: Optional[float] = Field(None, ge=0)
|
|
horario_json: Optional[str] = None
|
|
activa: Optional[bool] = None
|
|
|
|
|
|
class GeocercaResponse(GeocercaBase, TimestampSchema):
|
|
"""Schema de respuesta de geocerca."""
|
|
|
|
id: int
|
|
centro_lat: Optional[float] = None
|
|
centro_lng: Optional[float] = None
|
|
radio_metros: Optional[float] = None
|
|
coordenadas_json: Optional[str] = None
|
|
alerta_entrada: bool
|
|
alerta_salida: bool
|
|
velocidad_maxima: Optional[float] = None
|
|
horario_json: Optional[str] = None
|
|
activa: bool
|
|
|
|
# Calculado
|
|
aplica_todos_vehiculos: bool
|
|
|
|
|
|
class GeocercaConVehiculos(GeocercaResponse):
|
|
"""Schema de geocerca con lista de vehículos asignados."""
|
|
|
|
vehiculos_asignados: List["VehiculoResumen"] = []
|
|
|
|
|
|
class GeocercaGeoJSON(BaseSchema):
|
|
"""Schema de geocerca en formato GeoJSON."""
|
|
|
|
type: str = "Feature"
|
|
geometry: dict
|
|
properties: dict
|
|
|
|
|
|
class AsignarVehiculosRequest(BaseSchema):
|
|
"""Schema para asignar vehículos a una geocerca."""
|
|
|
|
vehiculos_ids: List[int]
|
|
reemplazar: bool = False # True = reemplaza todos, False = agrega a existentes
|
|
|
|
|
|
class VerificarPuntoRequest(BaseSchema):
|
|
"""Schema para verificar si un punto está dentro de una geocerca."""
|
|
|
|
lat: float = Field(..., ge=-90, le=90)
|
|
lng: float = Field(..., ge=-180, le=180)
|
|
|
|
|
|
class VerificarPuntoResponse(BaseSchema):
|
|
"""Schema de respuesta de verificación de punto."""
|
|
|
|
dentro: bool
|
|
geocerca_id: int
|
|
geocerca_nombre: str
|
|
distancia_metros: Optional[float] = None # Distancia al borde si está fuera
|
|
|
|
|
|
# Import fix
|
|
from app.schemas.vehiculo import VehiculoResumen # noqa: E402
|
|
|
|
GeocercaConVehiculos.model_rebuild()
|