# Module: main ## Purpose FastAPI application entry point — exposes HTTP API for object detection on images and video media, health checks, and Server-Sent Events (SSE) streaming of detection results. ## Public Interface ### API Endpoints | Method | Path | Description | |--------|------|-------------| | GET | `/health` | Returns AI engine availability status | | POST | `/detect` | Single image detection (multipart file upload) | | POST | `/detect/{media_id}` | Start async detection on media from loader service | | GET | `/detect/stream` | SSE stream of detection events | ### DTOs (Pydantic Models) | Model | Fields | Description | |-------|--------|-------------| | `DetectionDto` | centerX, centerY, width, height, classNum, label, confidence | Single detection result | | `DetectionEvent` | annotations (list[DetectionDto]), mediaId, mediaStatus, mediaPercent | SSE event payload | | `HealthResponse` | status, aiAvailability, errorMessage | Health check response | | `AIConfigDto` | frame_period_recognition, frame_recognition_seconds, probability_threshold, tracking_*, model_batch_size, big_image_tile_overlap_percent, altitude, focal_length, sensor_width, paths | Configuration input for media detection | ### Class: TokenManager | Method | Signature | Description | |--------|-----------|-------------| | `__init__` | `(str access_token, str refresh_token)` | Stores tokens | | `get_valid_token` | `() -> str` | Returns access_token; auto-refreshes if expiring within 60s | ## Internal Logic ### `/health` Returns `HealthResponse` with `status="healthy"` always. `aiAvailability` reflects the engine's `AIAvailabilityStatus`. On exception, returns `aiAvailability="None"`. ### `/detect` (single image) 1. Reads uploaded file bytes 2. Parses optional JSON config 3. Runs `inference.detect_single_image` in ThreadPoolExecutor (max 2 workers) 4. Returns list of DetectionDto Error mapping: RuntimeError("not available") → 503, RuntimeError → 422, ValueError → 400. ### `/detect/{media_id}` (async media) 1. Checks for duplicate active detection (409 if already running) 2. Extracts auth tokens from Authorization header and x-refresh-token header 3. Creates `asyncio.Task` for background detection 4. Detection runs `inference.run_detect` in ThreadPoolExecutor 5. Callbacks push `DetectionEvent` to all SSE queues 6. If auth token present, also POSTs annotations to the Annotations service 7. Returns immediately: `{"status": "started", "mediaId": media_id}` ### `/detect/stream` (SSE) - Creates asyncio.Queue per client (maxsize=100) - Yields `data: {json}\n\n` SSE format - Cleans up queue on disconnect ### Token Management - Decodes JWT exp claim from base64 payload (no signature verification) - Auto-refreshes via POST to `{ANNOTATIONS_URL}/auth/refresh` when within 60s of expiry ### Annotations Service Integration Detections posts results to the Annotations service (`POST {ANNOTATIONS_URL}/annotations`) server-to-server during async media detection (F3). This only happens when an auth token is present in the original request. **Endpoint:** `POST {ANNOTATIONS_URL}/annotations` **Headers:** | Header | Value | |--------|-------| | Authorization | `Bearer {accessToken}` (forwarded from the original client request) | | Content-Type | `application/json` | **Request body — payload sent by Detections:** | Field | Type | Description | |-------|------|-------------| | mediaId | string | ID of the media being processed | | source | int | `0` (AnnotationSource.AI) | | videoTime | string | Video playback position formatted from ms as `"HH:MM:SS"` — mapped to `Annotations.Time` | | detections | list | Detection results for this batch (see below) | | image | string (base64) | Optional — base64-encoded frame image bytes | `userId` is not included in the payload. The Annotations service resolves the user identity from the Bearer JWT. **Detection object (as sent by Detections):** | Field | Type | Description | |-------|------|-------------| | centerX | float | X center, normalized 0.0–1.0 | | centerY | float | Y center, normalized 0.0–1.0 | | width | float | Width, normalized 0.0–1.0 | | height | float | Height, normalized 0.0–1.0 | | classNum | int | Detection class number | | label | string | Human-readable class name | | confidence | float | Model confidence 0.0–1.0 | The Annotations API contract (`CreateAnnotationRequest`) also accepts `description` (string), `affiliation` (AffiliationEnum), and `combatReadiness` (CombatReadinessEnum) on each Detection, but the Detections service does not populate these — the Annotations service uses defaults. **Responses from Annotations service:** | Status | Condition | |--------|-----------| | 201 | Annotation created | | 400 | Neither image nor mediaId provided | | 404 | MediaId not found in Annotations DB | **Failure handling:** POST failures are silently caught — detection processing continues regardless. Annotations that fail to post are not retried. **Downstream pipeline (Annotations service side):** 1. Saves annotation to local PostgreSQL (image → XxHash64 ID, label file in YOLO format) 2. Publishes SSE event to UI via `GET /annotations/events` 3. Enqueues annotation ID to `annotations_queue_records` buffer table (unless SilentDetection mode is enabled in system settings) 4. `FailsafeProducer` (BackgroundService) drains the buffer to RabbitMQ Stream (`azaion-annotations`) using MessagePack + Gzip **Token refresh for long-running video:** For video detection that may outlast the JWT lifetime, the `TokenManager` auto-refreshes via `POST {ANNOTATIONS_URL}/auth/refresh` when the token is within 60s of expiry. The refresh token is provided by the client in the `X-Refresh-Token` request header. ## Dependencies - **External**: `asyncio`, `base64`, `json`, `os`, `time`, `concurrent.futures`, `typing`, `requests`, `fastapi`, `pydantic` - **Internal**: `inference` (lazy import), `constants_inf` (label lookup), `loader_http_client` (client instantiation) ## Consumers None (entry point). ## Data Models - `DetectionDto`, `DetectionEvent`, `HealthResponse`, `AIConfigDto` — Pydantic models for API - `TokenManager` — JWT token lifecycle ## Configuration | Env Var | Default | Description | |---------|---------|-------------| | `LOADER_URL` | `http://loader:8080` | Loader service base URL | | `ANNOTATIONS_URL` | `http://annotations:8080` | Annotations service base URL | ## External Integrations | Service | Protocol | Purpose | |---------|----------|---------| | Loader | HTTP (via LoaderHttpClient) | Model loading | | Annotations | HTTP POST | Auth refresh (`/auth/refresh`), annotation posting (`/annotations`) | ## Security - Bearer token from request headers, refreshed via Annotations service - JWT exp decoded (base64, no signature verification) — token validation is not performed locally - No CORS configuration - No rate limiting - No input validation on media_id path parameter beyond string type ## Tests None found.