# OutputManager

## 1. High-Level Overview

**Purpose**: Handles all persistent output: detection logging (JSON-lines), frame recording (JPEG), health logging, gimbal command logging, and operator detection delivery. Manages NVMe write operations and circular buffer for storage.

**Architectural Pattern**: Facade over multiple output writers (async file I/O).

**Upstream dependencies**: Config helper (output paths, recording rates, storage limits), Types helper

**Downstream consumers**: ScanController

## 2. Internal Interfaces

### Interface: OutputManager

| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `init(output_dir)` | str | — | No | IOError |
| `log_detection(entry)` | DetectionLogEntry dict | — | No (non-blocking write) | WriteError |
| `record_frame(frame, frame_id, level)` | numpy, uint64, int | — | No (non-blocking write) | WriteError |
| `log_health(health)` | HealthLogEntry dict | — | No | WriteError |
| `log_gimbal_command(cmd_str)` | str | — | No | WriteError |
| `report_to_operator(detections)` | list[Detection] | — | No | — |
| `get_storage_status()` | — | StorageStatus | No | — |

**StorageStatus**:
```
nvme_free_pct: float (0-100)
frames_recorded: uint64
detections_logged: uint64
should_reduce_recording: bool — true if free < 20%
```

## 4. Data Access Patterns

### Storage Estimates

| Output | Write Rate | Per Hour | Per 4h Flight |
|--------|-----------|----------|---------------|
| detections.jsonl | ~1 KB/det, ~100 det/min | ~6 MB | ~24 MB |
| frames/ (L1, 2 FPS) | ~100 KB/frame | ~720 MB | ~2.9 GB |
| frames/ (L2, 30 FPS) | ~100 KB/frame | ~10.8 GB | ~43 GB |
| health.jsonl | ~200 B/s | ~720 KB | ~3 MB |
| gimbal.log | ~500 B/s | ~1.8 MB | ~7 MB |

### Circular Buffer Strategy

When NVMe free space < 20%:
1. Signal ScanController via `should_reduce_recording`
2. ScanController switches to L1 recording rate only
3. If still < 10%: stop L1 frame recording, keep detection log only
4. Never overwrite detection logs (most valuable data)

## 5. Implementation Details

**File Writers**:
- Detection log: open file handle, append JSON line, flush periodically (every 10 entries or 5s)
- Frame recorder: JPEG encode via OpenCV, write to sequential filename `{frame_id}.jpg`
- Health log: append JSON line every 1s
- Gimbal log: append text line per command

**Operator Delivery**: Format detections into existing YOLO output schema (centerX, centerY, width, height, classNum, label, confidence) and make available via the same interface the existing YOLO pipeline uses.

**Key Dependencies**:

| Library | Version | Purpose |
|---------|---------|---------|
| OpenCV | 4.x | JPEG encoding for frame recording |
| json (stdlib) | — | JSON-lines serialization |
| os (stdlib) | — | NVMe free space check (statvfs) |

**Error Handling Strategy**:
- WriteError: log to stderr, increment error counter, continue processing (recording failure must not block inference)
- NVMe full: stop recording, log warning, continue detection-only mode

## 7. Caveats & Edge Cases

**Known limitations**:
- Frame recording at 30 FPS (L2) writes ~3 MB/s — well within NVMe bandwidth but significant storage consumption
- JSON-lines flush interval means up to 10 detections or 5s of data could be lost on hard crash

## 8. Dependency Graph

**Must be implemented after**: Config helper, Types helper
**Can be implemented in parallel with**: Tier1Detector, Tier2SpatialAnalyzer, VLMClient, GimbalDriver
**Blocks**: ScanController (needs OutputManager for logging)

## 9. Logging Strategy

| Log Level | When | Example |
|-----------|------|---------|
| ERROR | NVMe write failure, disk full | `Frame write failed: No space left on device` |
| WARN | Storage low, reducing recording | `NVMe 18% free, reducing to L1 recording only` |
| INFO | Session started, stats | `Output session started: /data/output/2026-03-19T14:00/` |
