"""The /analyze response contract — the shape the Node VisionClient adapter consumes. Mirrors the first-cut API in wiki/entities/opencv-anpr-service.md: { plate: {text, confidence, bbox}|null, vehicle: {...}|null, modelVersion, tookMs } Job 2 (vehicle attributes) is scaffolded as an optional field, not yet populated — fast-alpr is plate-only; the vehicle stage is built later on the same ONNX runtime. """ from __future__ import annotations from pydantic import BaseModel, Field class BBox(BaseModel): """Plate bounding box in pixels (top-left origin).""" x1: int y1: int x2: int y2: int class PlateResult(BaseModel): text: str # The plate's confidence = the MIN of fast-alpr's per-character confidences (a plate # is only as trustworthy as its weakest character). See recognizer.py. confidence: float = Field(ge=0.0, le=1.0) bbox: BBox | None = None # Predicted issuing region/country (advisory; fast-alpr's global model emits this). region: str | None = None class VehicleResult(BaseModel): """Job 2 — vehicle attributes / fingerprint (anti-spoofing). Not yet produced.""" colour: str | None = None body_type: str | None = None make: str | None = None model: str | None = None class AnalyzeResponse(BaseModel): # The single best plate, or null when none was found. plate: PlateResult | None = None # All plates found (a frame may contain several vehicles). plates: list[PlateResult] = Field(default_factory=list) vehicle: VehicleResult | None = None # True when the best plate is below the confidence floor — Node should treat the # read as advisory only and prefer the ticket path. See fail-state-safety. low_confidence: bool = False model_version: str took_ms: float class HealthResponse(BaseModel): status: str recognizer: str ready: bool model_version: str detail: str | None = None