104 lines
3.5 KiB
Python
104 lines
3.5 KiB
Python
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
|