# 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.
