from datetime import datetime, timedelta from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy import select, desc, and_ from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models import Vehicle, Position from app.schemas import PositionResponse, PositionIngest from app.services.websocket_manager import manager from app.services.event_detector import detect_events router = APIRouter() @router.get("/vehicles/{vehicle_id}/positions", response_model=list[PositionResponse]) async def get_vehicle_positions( vehicle_id: int, from_time: Optional[datetime] = Query(None, alias="from"), to_time: Optional[datetime] = Query(None, alias="to"), limit: int = Query(1000, le=10000), db: AsyncSession = Depends(get_db) ): """Получить историю позиций транспортного средства""" # Check vehicle exists result = await db.execute(select(Vehicle).where(Vehicle.id == vehicle_id)) if not result.scalar_one_or_none(): raise HTTPException(status_code=404, detail="Vehicle not found") # Build query query = select(Position).where(Position.vehicle_id == vehicle_id) if from_time: query = query.where(Position.timestamp >= from_time) if to_time: query = query.where(Position.timestamp <= to_time) query = query.order_by(desc(Position.timestamp)).limit(limit) result = await db.execute(query) positions = result.scalars().all() return positions @router.get("/vehicles/{vehicle_id}/last-position", response_model=Optional[PositionResponse]) async def get_last_position(vehicle_id: int, db: AsyncSession = Depends(get_db)): """Получить последнюю позицию транспортного средства""" result = await db.execute( select(Position) .where(Position.vehicle_id == vehicle_id) .order_by(desc(Position.timestamp)) .limit(1) ) position = result.scalar_one_or_none() if not position: return None return position @router.post("/ingest/position", response_model=PositionResponse, status_code=201) async def ingest_position(position: PositionIngest, db: AsyncSession = Depends(get_db)): """Принять новую позицию от трекера/симулятора""" # Check vehicle exists result = await db.execute(select(Vehicle).where(Vehicle.id == position.vehicle_id)) vehicle = result.scalar_one_or_none() if not vehicle: raise HTTPException(status_code=404, detail="Vehicle not found") # Get previous position for event detection prev_result = await db.execute( select(Position) .where(Position.vehicle_id == position.vehicle_id) .order_by(desc(Position.timestamp)) .limit(1) ) prev_position = prev_result.scalar_one_or_none() # Create new position db_position = Position( vehicle_id=position.vehicle_id, timestamp=position.timestamp or datetime.utcnow(), lat=position.lat, lon=position.lon, speed=position.speed, heading=position.heading ) db.add(db_position) await db.commit() await db.refresh(db_position) # Detect events events = await detect_events(db, vehicle, db_position, prev_position) # Broadcast to WebSocket clients await manager.broadcast_position(db_position) for event in events: await manager.broadcast_event(event) return db_position