# Semantic Detection System — Data Model

## Design Principle

This is a real-time streaming pipeline on an edge device, not a CRUD application. There is no database. Data falls into two categories:

1. **Runtime structs** — in-memory only, exist for one processing cycle, then discarded
2. **Persistent logs** — append-only flat files on NVMe SSD

## Runtime Structs (in-memory only)

These are C/Cython structs or Python dataclasses. They are created, consumed by the next pipeline stage, and garbage-collected. No persistence.

### FrameContext

Wraps a camera frame with metadata for the current processing cycle.

| Field | Type | Description |
|-------|------|-------------|
| frame_id | uint64 | Sequential counter |
| timestamp | float64 | Capture time (epoch seconds) |
| image | numpy array (H,W,3) | Raw frame pixels |
| scan_level | uint8 | 1 or 2 |
| quality_score | float32 | Laplacian variance (computed on capture) |
| pan | float32 | Gimbal pan at capture |
| tilt | float32 | Gimbal tilt at capture |
| zoom | float32 | Zoom level at capture |

### YoloDetection (external input)

Received from existing YOLO pipeline. Consumed by semantic pipeline, not stored.

| Field | Type | Description |
|-------|------|-------------|
| centerX | float32 | Normalized center X (0-1) |
| centerY | float32 | Normalized center Y (0-1) |
| width | float32 | Normalized width (0-1) |
| height | float32 | Normalized height (0-1) |
| classNum | int32 | Class index |
| label | string | Class label |
| confidence | float32 | 0-1 |
| mask | numpy array (H,W) | Segmentation mask (if seg model) |

### POI

In-memory queue entry. Created when Tier 1 detects a point of interest, removed after investigation or timeout. Max size configurable (default 10).

| Field | Type | Description |
|-------|------|-------------|
| poi_id | uint64 | Counter |
| frame_id | uint64 | Frame that triggered this POI |
| trigger_class | string | Class that triggered (footpath_winter, branch_pile, etc.) |
| scenario_name | string | Which search scenario triggered this POI |
| investigation_type | string | "path_follow", "area_sweep", or "zoom_classify" |
| confidence | float32 | Trigger confidence |
| bbox | float32[4] | Bounding box in frame |
| priority | float32 | Computed: confidence × priority_boost × recency |
| status | enum | queued / investigating / done / timeout |

### GimbalState

Current gimbal position. Single instance, updated on every gimbal feedback message.

| Field | Type | Description |
|-------|------|-------------|
| pan | float32 | Current pan angle |
| tilt | float32 | Current tilt angle |
| zoom | float32 | Current zoom level |
| target_pan | float32 | Commanded pan |
| target_tilt | float32 | Commanded tilt |
| target_zoom | float32 | Commanded zoom |
| last_heartbeat | float64 | Last response timestamp |

## Persistent Data (NVMe flat files)

### DetectionLogEntry → `detections.jsonl`

One JSON line per confirmed detection. This is the primary system output.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| ts | string (ISO 8601) | Yes | Detection timestamp |
| frame_id | uint64 | Yes | Source frame |
| gps_denied_lat | float64 | No | GPS-denied latitude (null if unavailable) |
| gps_denied_lon | float64 | No | GPS-denied longitude |
| tier | uint8 | Yes | 1, 2, or 3 |
| class | string | Yes | Detection class label |
| confidence | float32 | Yes | 0-1 |
| bbox | float32[4] | Yes | centerX, centerY, width, height (normalized) |
| freshness | string | No | "high_contrast" / "low_contrast" (footpaths only) |
| tier2_result | string | No | Tier 2 classification |
| tier2_confidence | float32 | No | Tier 2 confidence |
| tier3_used | bool | Yes | Whether VLM was invoked |
| thumbnail_path | string | No | Saved ROI thumbnail path |

### HealthLogEntry → `health.jsonl`

One JSON line per second. System health snapshot.

| Field | Type | Description |
|-------|------|-------------|
| ts | string (ISO 8601) | Timestamp |
| t_junction | float32 | Junction temperature °C |
| power_watts | float32 | Power draw |
| gpu_mem_mb | uint32 | GPU memory used |
| vlm_available | bool | VLM capability flag |
| gimbal_available | bool | Gimbal capability flag |
| semantic_available | bool | Semantic capability flag |

### RecordedFrame → `frames/{frame_id}.jpg`

JPEG file per recorded frame. Metadata embedded in filename (frame_id). Correlation to detections via frame_id in `detections.jsonl`.

### Config → `config.yaml`

Single YAML file with all runtime parameters. Versioned (`version: 1` field). Updated via USB.

## What Is NOT Stored

| Item | Why not |
|------|---------|
| FootpathMask (segmentation mask) | Transient. Exists ~50ms during Tier 2 processing. Too large to log (HxW binary). |
| PathSkeleton | Transient. Derivative of mask. |
| EndpointROI crop | Thumbnail saved only if detection confirmed. Raw crop discarded. |
| YoloDetection input | External system's data. We consume it, don't archive it. |
| POI queue state | Runtime queue. Not useful after flight. Detections capture the outcomes. |
| Raw VLM response text | Optionally logged inside DetectionLogEntry if tier3_used=true. Not stored separately. |

## Storage Budget (256GB NVMe)

| Data | Write Rate | Per Hour | 4-Hour Flight |
|------|-----------|----------|---------------|
| detections.jsonl | ~1 KB/detection, ~100 detections/min | ~6 MB | ~24 MB |
| health.jsonl | ~200 bytes/s | ~720 KB | ~3 MB |
| frames/ (L1, 2 FPS) | ~100 KB/frame, 2 FPS | ~720 MB | ~2.9 GB |
| frames/ (L2, 30 FPS) | ~100 KB/frame, 30 FPS | ~10.8 GB | ~43 GB (if L2 100% of time) |
| gimbal.log | ~50 bytes/command, 10 Hz | ~1.8 MB | ~7 MB |
| **Total (typical L1-heavy)** | | **~1.5 GB** | **~6 GB** |
| **Total (L2-heavy)** | | **~11 GB** | **~46 GB** |

256GB NVMe comfortably supports 5+ typical flights or 5+ hours of L2-heavy operation before circular buffer kicks in.

## Migration Strategy

Not applicable — no relational database. Config changes handled by YAML versioning:
- `version: 1` field in config.yaml
- New fields get defaults (backward-compatible)
- Breaking changes: bump version, include migration notes in USB update package
