# 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/` |