| 1 | """Live-camera ALPR scan API: run the recognition engine over live DOT snapshot feeds. |
| 2 | |
| 3 | POST /alpr/scan-live {camera_ids?, state?, limit, min_conf} -> scan a batch, ingest any plates |
| 4 | |
| 5 | Runs synchronously (each camera = one HTTP fetch + one inference pass), so a batch can take |
| 6 | ~10-30s. NOTE: public wide-area traffic cams rarely yield readable plates - 0 results is the |
| 7 | normal outcome; recognized plates flow through the standard ingest pipeline as real reads.""" |
| 8 | from __future__ import annotations |
| 9 | |
| 10 | from fastapi import APIRouter, Depends, HTTPException |
| 11 | from pydantic import BaseModel |
| 12 | from sqlalchemy.orm import Session |
| 13 | |
| 14 | from app.api.deps import Perm, get_db, require |
| 15 | from app.models import User |
| 16 | |
| 17 | router = APIRouter(tags=["live-alpr"]) |
| 18 | |
| 19 | MAX_LIMIT = 50 |
| 20 | |
| 21 | |
| 22 | class ScanLiveReq(BaseModel): |
| 23 | camera_ids: list[str] | None = None |
| 24 | state: str | None = None |
| 25 | limit: int = 20 |
| 26 | min_conf: float = 0.5 |
| 27 | |
| 28 | |
| 29 | @router.post("/alpr/scan-live") |
| 30 | def scan_live(req: ScanLiveReq, db: Session = Depends(get_db), |
| 31 | user: User = Depends(require(Perm.INGEST))): |
| 32 | """Scan live-feed cameras for plates and ingest anything recognized (synchronous batch).""" |
| 33 | from app.services.live_alpr_scan import scan_live_cameras |
| 34 | if req.limit < 1 or req.limit > MAX_LIMIT: |
| 35 | raise HTTPException(400, f"limit must be between 1 and {MAX_LIMIT}") |
| 36 | return scan_live_cameras(db, camera_ids=req.camera_ids, state=req.state, |
| 37 | limit=req.limit, min_conf=req.min_conf, created_by=user.id) |