# autopilot — Data Model

**Status**: forward-looking design (Rust). This is the canonical entity catalogue.

The autopilot binary itself has **one** persistent store: the on-device `mapobjects_store` (engine TBD — `architecture.md §8 Q3`). Everything else is in-memory only. Mission state and the central MapObjects state are pulled from the external `missions` API on start; there is no in-process mission database. The on-device `mapobjects_store` is a working copy of the central MapObjects state for the active mission's bounding box; the central observation log is the source of truth across missions (per `architecture.md §7.13`).

---

## 1. Entity Map

```mermaid
erDiagram
    Frame ||--o{ Detection : "produced by detection_client"
    Frame ||--o{ MovementCandidate : "produced by movement_detector (zoom-out + zoom-in)"
    Detection ||--|| BoundingBox : "bbox_normalized"
    DetectionBatch ||--o{ Detection : "contains"

    POI ||--o| Tier2Evidence : "zoom-in Tier 2"
    POI ||--o| VlmAssessment : "Tier 3 (optional, zoom-in)"
    POI }o--o| MapObject : "lookup by H3 + class"
    POI }o--o| IgnoredItem : "decline_suppressed"

    MapObject ||--o{ MapObjectObservation : "history (central append-only log)"
    MapObjectsBundle ||--o{ MapObject : "pre-flight pull"
    MapObjectsBundle ||--o{ MapObjectObservation : "post-flight push"
    MapObjectsBundle ||--o{ IgnoredItem : "ignored items round-trip"

    OperatorCommand ||--o| POI : "confirm/decline target"

    MissionItem ||--o{ MissionWaypoint : "translates to"
    MissionItem ||--o{ Geofence : "carries"
    Geofence }o--o{ Coordinate : "polygon"
    MissionWaypoint ||--|| Coordinate : "at"
```

---

## 2. Perception entities

### `Frame`

A decoded video frame. Produced by `frame_ingest`; consumed by `detection_client`, `movement_detector`, `telemetry_stream`.

| Field | Type | Notes |
|---|---|---|
| `seq` | u64 | Monotonic sequence number; primary key for cross-component correlation. |
| `capture_ts_monotonic_ns` | u64 | Wall-clock-independent timestamp at the earliest practical point in the pipeline. |
| `decode_ts_monotonic_ns` | u64 | When `frame_ingest` finished decoding. |
| `pixels` | `Arc<Bytes>` | Raw pixel data; consumers do not copy. |
| `width`, `height` | u32 | |
| `pix_fmt` | enum | `NV12` \| `YUV420P` \| `RGB24` (decoder dependent). |
| `ai_locked` | bool | If set, downstream consumers skip detection (operator-side or supervisor gating). |

In-memory only.

### `BoundingBox`

| Field | Type | Notes |
|---|---|---|
| `x_min`, `y_min`, `x_max`, `y_max` | f32 | Normalised to `[0.0, 1.0]` in image coordinates. |

### `Detection`

One Tier-1 detection. Mirrors the `../detections` contract; carries through to operator overlay unchanged.

| Field | Type | Notes |
|---|---|---|
| `class_id` | u32 | |
| `class_name` | string | Human-readable label. |
| `confidence` | f32 | 0.0–1.0. |
| `bbox_normalized` | `BoundingBox` | |
| `mask_or_polyline` | optional bytes | For polyline classes (e.g. footpaths). |
| `source_frame_seq` | u64 | Foreign key into `Frame`. |

### `DetectionBatch`

| Field | Type | Notes |
|---|---|---|
| `frame_seq` | u64 | |
| `detections` | `Vec<Detection>` | |
| `latency_ms` | u32 | Tier-1 round-trip; observed for budget compliance. |
| `model_version` | string | Reported by `../detections`; logged on change. |

### `MovementCandidate`

A residual-motion cluster surviving ego-motion compensation in `movement_detector`.

| Field | Type | Notes |
|---|---|---|
| `frame_seq` | u64 | |
| `bbox_normalized` | `BoundingBox` | |
| `residual_velocity_estimate` | optional struct | Direction + magnitude in image coords; used for prioritisation. |
| `telemetry_quality` | enum | `synced` \| `degraded` \| `unsynced` (drives whether the candidate may be surfaced at all). |
| `source_frame_ts_monotonic_ns` | u64 | |
| `source_zoom_band` | enum | `zoomed_out` \| `zoomed_in`. Drives `scan_controller`'s queueing logic (per `system-flows.md §F2`): zoom-out candidates enter the POI queue normally; zoom-in candidates may bump current-ROI confidence or enter the queue with their own priority. |

### `Tier2Evidence`

Output of `semantic_analyzer` for a single zoom-in ROI hold.

| Field | Type | Notes |
|---|---|---|
| `roi_id` | uuid | Stable identifier within a zoom-in hold. |
| `path_freshness` | f32 \| null | 0.0 = no path / not applicable; 1.0 = fresh. |
| `endpoint_score` | f32 \| null | Concealed-position likelihood at an endpoint (branch pile / dark entrance). |
| `concealment_score` | f32 \| null | General concealment-POI score. |
| `recommended_next_action` | enum | `PanFollowFootpath` \| `HoldEndpoint` \| `PanBroad` \| `ReturnToZoomOut`. |
| `source_detections` | `Vec<DetectionId>` | For audit / replay. |
| `status` | enum | `ok` \| `timeout` \| `oversize` \| `error`. |

### `VlmAssessment`

Validated, structured response from `vlm_client`. Free-form VLM text is **not** a downstream API.

| Field | Type | Notes |
|---|---|---|
| `label` | enum | `confirmed_concealed_position` \| `rejected` \| `inconclusive` \| `error`. |
| `confidence` | f32 | 0.0–1.0; VLM-reported or derived. |
| `evidence_spans` | `Vec<string>` | Short justifications, bounded length. |
| `reason` | string | One-line rationale; bounded length. |
| `status` | enum | `ok` \| `timeout` \| `schema_invalid` \| `ipc_error` \| `disabled`. |
| `latency_ms` | u32 | Round-trip including IPC. |
| `model_version` | string | Reported by the NanoLLM process for the loaded weights; logged on change for forensic correlation. |

`status` semantics: any value other than `ok` MUST result in `label = inconclusive` (or `error` for a critical failure). `scan_controller` MUST NOT promote a POI to a confirmed target on a non-`ok` `VlmAssessment`.

---

## 3. Decision entities

### `POI`

A Point-of-Interest enqueued by `scan_controller`. Source: a Tier-1 detection, a movement candidate from `movement_detector`, or a Tier-2 semantic finding.

| Field | Type | Notes |
|---|---|---|
| `id` | uuid | Stable for the POI's lifetime. |
| `confidence` | f32 (0.0–1.0) | Composite of detection / motion / Tier-2 score. |
| `mgrs` | string | MGRS coordinate from the GPS-Denied service or autopilot GPS. |
| `class` | string | Concrete class. |
| `class_group` | enum | Per `mapobjects_store` config (e.g. `military_vehicle_group`, `concealed_position_group`, `movement_candidate`). |
| `source_detection_ids` | `Vec<DetectionId>` | For audit / replay. |
| `enqueued_at` | timestamp | For queue ageing. |
| `priority` | f32 | `confidence × proximity_to_current_camera × age_factor`. |
| `decline_suppressed` | bool | True if `(MGRS, class_group)` matches an existing `IgnoredItem`. |
| `vlm_status` | enum | Mirrors `VlmAssessment.status` (or `not_requested` / `pending`). |
| `tier2_evidence` | optional `Tier2Evidence` | |
| `deadline` | timestamp | Per the confidence-scaled operator-decision window. |

Field `queue_position` is **not** stored; it is computed at read time from `priority` + `enqueued_at`.

### `MapObject`

A persisted map entry, indexed by H3 cell. Owned by `mapobjects_store`; written on each `NEW` / `MOVED` classification, read on each new detection. The on-device `MapObject` is a **working copy** of the central state for the active mission.

| Field | Type | Notes |
|---|---|---|
| `h3_cell` | u64 | H3 cell index at the configured resolution (default `res 10`, ~15 m edge). |
| `mgrs_key` | string | MGRS coordinate; together with `class` forms the hashtable composite key. |
| `class` | string | Concrete class (not the group). |
| `class_group` | string | Group used for matching during `EXISTING` / `MOVED` / `NEW` classification. |
| `gps_lat`, `gps_lon` | f64 | For distance calculation against incoming detections. |
| `size_width_m`, `size_length_m` | f32 | Bounding area on the ground. |
| `confidence` | f32 | Latest observation confidence (or running average, per implementation). |
| `first_seen`, `last_seen` | timestamp | Earliest and most recent observation; `last_seen` drives the `REMOVED` candidate diff at region-end. |
| `mission_id` | string | For the `DELETE /missions/{id}` cascade. |
| `source` | enum | `central_pulled` (came from pre-flight pull) \| `local_observed` (added during this mission). On post-flight push only `local_observed` records become new observations centrally. |
| `pending_upload` | bool | True for any `local_observed` entry not yet pushed centrally. Cleared on successful `POST /missions/{id}/mapobjects` ack. |

Persisted in `mapobjects_store` (engine TBD per `architecture.md §8 Q3`).

### `MapObjectObservation`

A single per-detection record. The on-device store appends one of these per NEW / MOVED / EXISTING / REMOVED-CANDIDATE classification; the post-flight push uploads the unflushed list to the central `missions` API. The central side stores all observations append-only as the source of truth (per `architecture.md §7.13`).

| Field | Type | Notes |
|---|---|---|
| `id` | uuid | Locally generated; stable across the mission. |
| `h3_cell` | u64 | |
| `class` | string | Concrete class. |
| `class_group` | string | Group used for the diff. |
| `mission_id` | string | |
| `uav_id` | string | Identifies the airframe; assigned at provisioning. |
| `observed_at_monotonic_ns` | u64 | Local monotonic at observation. |
| `observed_at_wallclock` | timestamp | Bound from GPS or NTP per the wall-clock policy. |
| `gps_lat`, `gps_lon` | f64 | |
| `mgrs` | string | |
| `size_width_m`, `size_length_m` | f32 | |
| `confidence` | f32 | |
| `diff_kind` | enum | `NEW` \| `MOVED` \| `EXISTING` \| `REMOVED_CANDIDATE`. |
| `photo_ref` | string \| null | URL or compact reference; uploaded out-of-band per the central API contract (Q7). |
| `raw_evidence` | json \| null | Audit payload; size-capped. |

In-memory; durably persisted in `mapobjects_store` until the post-flight push acknowledges. On the central side, `map_object_observations` is the corresponding table (see `architecture.md §7.13`).

### `MapObjectsBundle`

The wire shape for both the pre-flight pull (response body) and the post-flight push (request body) on `/missions/{id}/mapobjects`.

| Field | Type | Notes |
|---|---|---|
| `schema_version` | string | Semver; mismatched versions are rejected. |
| `mission_id` | string | |
| `bbox` | `Coordinate[2]` (NW + SE) | The mission area; used by the central API to scope the response. |
| `map_objects` | `Vec<MapObject>` | Pre-flight: current view from the central store. Post-flight push uses `MapObjectObservation` instead (see below). |
| `observations` | `Vec<MapObjectObservation>` | Post-flight: the full pass diff. |
| `ignored_items` | `Vec<IgnoredItem>` | Pre-flight: union-merged from the central store. Post-flight: only items appended during this mission. |
| `as_of` | timestamp | Pre-flight: when the central store snapshot was computed. Post-flight: when the on-device flush started. |
| `freshness` | enum (pre-flight only) | `fresh` (≤ configured staleness window) \| `stale` (operator must acknowledge to use). |

### `IgnoredItem`

A scene the operator declined; consulted by `scan_controller` before promoting any future detection to a POI. Union-merged across missions on the central side (per `architecture.md §7.13` conflict resolution).

| Field | Type | Notes |
|---|---|---|
| `id` | uuid | Locally generated. |
| `mgrs` | string | Decline location. |
| `h3_cell` | u64 | For central-side indexing. |
| `class_group` | string | Class group of the declined detection. |
| `decline_time` | timestamp | Wall-clock at decline (operator-side). |
| `operator_id` | string \| null | If known from the Ground Station session. |
| `mission_id` | string | The mission during which the decline happened. |
| `retention_scope` | enum | `mission` (cleared at mission end on-device, retained centrally indexed by mission) \| `session` (cleared at session end on-device) \| `until_expiry` (carries `expires_at`). |
| `expires_at` | timestamp \| null | Required when `retention_scope = until_expiry`. |
| `source` | enum | `central_pulled` (pre-flight pull) \| `local_appended` (during this mission). Only `local_appended` is uploaded to central in the post-flight push. |
| `pending_upload` | bool | True for any `local_appended` entry not yet pushed centrally. |

Lookup key: `(MGRS, class_group)` exact match (subject to the same H3 k-ring widening as `MapObject` lookups, when configured).

Persisted in `mapobjects_store`. Central-side table: `map_object_ignored` per `architecture.md §7.13`.

---

## 4. Action / piloting entities

### `Coordinate`

| Field | Type | Notes |
|---|---|---|
| `latitude` | f64 | Geographic; degrees. |
| `longitude` | f64 | Geographic; degrees. |
| `altitude_m` | f32 | Above ground or above home, depending on usage; the carrying entity defines the frame. |

### `Geofence`

A polygon on the mission. Both INCLUSION and EXCLUSION are honoured by `mission_executor`.

| Field | Type | Notes |
|---|---|---|
| `kind` | enum | `INCLUSION` \| `EXCLUSION`. |
| `vertices` | `Vec<Coordinate>` | Polygon vertices in order. |

### `MissionItem`

The business-level mission item: what the `missions` API delivers and what the operator authored. **Owned by `mission-schema`**, the artefact shared with the `missions` repo (extraction location TBD — `architecture.md §8 Q5`).

| Field | Type | Notes |
|---|---|---|
| `id` | uuid | |
| `kind` | enum | `waypoint` \| `search` \| `region_search` \| `return` \| `target_follow_breakpoint`. |
| `at` | optional `Coordinate` | For `waypoint` / `return`. |
| `region` | optional polygon | For `region_search`. |
| `cruise_speed_mps` | optional f32 | If set, `mission_executor` emits a `MAV_CMD_DO_CHANGE_SPEED` waypoint before the affected items. |
| `target_classes` | optional `Vec<string>` | Per-item search hint (e.g. `tank`, `artillery`). |

### `MissionWaypoint`

The MAVLink-level wire item: what `mavlink_layer` sends to ArduPilot / PX4. **Owned by `mavlink_layer`**.

| Field | Type | Notes |
|---|---|---|
| `seq` | u16 | MAVLink mission item sequence number. |
| `frame` | enum | `MAV_FRAME_GLOBAL_RELATIVE_ALT` (system default; no terrain-following). |
| `command` | enum | One of: `MAV_CMD_NAV_TAKEOFF`, `MAV_CMD_NAV_WAYPOINT`, `MAV_CMD_NAV_LAND`, `MAV_CMD_DO_CHANGE_SPEED`, `MAV_CMD_NAV_RETURN_TO_LAUNCH`, `MAV_CMD_DO_SET_MODE`. |
| `current` | bool | True only for the very first item in a fresh upload. |
| `auto_continue` | bool | True for everything except the final item. |
| `param_1..param_4` | f32 | Command-specific. |
| `lat_deg_e7`, `lon_deg_e7` | i32 | Scaled-integer geographic coordinates. |
| `alt_m` | f32 | Above home (relative). |

### Translation contract — `MissionItem` → `MissionWaypoint`

Owner: `mission_executor`, variant-aware (multirotor / fixed-wing).

| Source `MissionItem.kind` | Resulting `MissionWaypoint`(s) |
|---|---|
| `waypoint` | exactly one `MAV_CMD_NAV_WAYPOINT` |
| `region_search` | sequence of `MAV_CMD_NAV_WAYPOINT`s computed per the sweep pattern (`architecture.md §8 Q1`) |
| `return` | one `MAV_CMD_NAV_RETURN_TO_LAUNCH` (or `MAV_CMD_NAV_LAND` at the explicit return point) |
| `target_follow_breakpoint` | (none) — used only as a structural marker for re-upload; not sent to MAVLink |
| (cruise speed carried by a `MissionItem`) | one `MAV_CMD_DO_CHANGE_SPEED` placed **before** the affected `MAV_CMD_NAV_WAYPOINT`s |

Multirotor variants prepend `MAV_CMD_NAV_TAKEOFF` and append `MAV_CMD_NAV_LAND`. Fixed-wing variants do neither (the airframe is RC-launched and put into AUTO by the operator); they only upload + start the mission.

The cruise-speed translation is required to **reach the autopilot**. If a `MissionItem` declares a cruise speed, the corresponding `MAV_CMD_DO_CHANGE_SPEED` MUST be present in the uploaded sequence with the speed in `param_1`. Conformance test in `deployment/ci_cd_pipeline.md §5`.

### `OperatorCommand`

Every command from the Ground Station to autopilot is wrapped in this authenticated envelope. The principle is committed (`architecture.md §5`); the exact signature scheme is open per Q9. `operator_bridge` rejects any command that fails signature validation, replay-protection check, or session validation.

| Field | Type | Notes |
|---|---|---|
| `command_id` | uuid | Idempotency key; cached for 60 s by `operator_bridge`. |
| `session_token` | string | Opaque session token issued by the Ground Station at operator login; bound to `operator_id`. |
| `sequence_number` | u64 | Monotonically increasing per-session; replay-protection. Lower-or-equal numbers per session are rejected. |
| `issued_at_wallclock` | timestamp | Operator-side wall-clock. Used for forensic audit; not used for trust decisions. |
| `kind` | enum | `confirm_poi` \| `decline_poi` \| `start_target_follow` \| `release_target_follow` \| `acknowledge_bit_degraded` \| `safety_override` \| `mission_abort`. |
| `payload` | json | Action-specific body. |
| `signature` | bytes | Signature over (`session_token`, `sequence_number`, `kind`, `payload`). Scheme TBD per Q9. |

`scan_controller` and `mission_executor` see only the validated payload; the auth envelope is opaque to them. Audit logs record `command_id`, `operator_id` (resolved from session token), `kind`, and result.

### `GimbalState`

| Field | Type | Notes |
|---|---|---|
| `yaw`, `pitch` | f32 | Degrees. |
| `zoom` | f32 | Effective focal length or zoom factor (vendor-specific). |
| `ts_monotonic_ns` | u64 | Stamp at the moment the gimbal feedback was received. |
| `command_in_flight` | bool | True between command issuance and feedback that motion completed. |

In-memory only; consumed by `frame_ingest` and `movement_detector` for telemetry-skew compensation.

---

## 5. Sync / wire formats

### MGRS sync message — wire format

The operator round trip (`telemetry_stream` ⇄ Ground Station) uses MGRS-encoded payloads in both directions. Field separator is `::`.

**Drone → Operator (detection report):**

| Position | Field | Type | Notes |
|---|---|---|---|
| 1 | `missionId` | string | Server-assigned mission UUID. |
| 2 | `MGRS(encoded)` | string | MGRS coordinate (compact, military-grid). |
| 3 | `class` | string | Concrete detection class. |
| 4 | `confidence` | f32 | 0.0–1.0. |
| 5 | `size_width_m` | f32 | Ground-projected width. |
| 6 | `size_length_m` | f32 | Ground-projected length. |
| 7 | `photo_metadata` | string | URL or compact reference to the snapshot frame. |
| 8 | `flags` | bitmask | Reserved (e.g. `target_follow_active`, `vlm_used`, `movement_origin`). |

**Operator → Drone (command / acknowledgment):**

| Position | Field | Type | Notes |
|---|---|---|---|
| 1 | `missionId` | string | Must match the drone-side mission. |
| 2 | `Encoded(GroundMGRS :: Time)` | string | Operator's ground location + decision timestamp. |
| 3 | (variable) | … | Action-specific payload (POI ID, action enum, follow-toggle, etc.). |
| N | `missionId2` | string | Echo of `missionId` for stream-multiplexing safety. |

The exact serialisation of position 3 (action payload) is left to the Ground Station API contract (open question; see `architecture.md §8 Q2`).

---

## 6. Persistence and lifecycle

| Entity | Persisted? | Where | Lifecycle |
|---|---|---|---|
| `Frame`, `Detection`, `DetectionBatch`, `MovementCandidate`, `Tier2Evidence`, `VlmAssessment`, `GimbalState` | no | in-memory | per frame / per ROI / per command — dropped on state change. |
| `POI` | no | in-memory inside `scan_controller` | enqueued, surfaced, decided (confirm / decline / timeout), then dropped. |
| `MapObject` | yes | `mapobjects_store` (working copy of central state) | mission-scoped on-device; appended to central observation log via post-flight push (F8); cleared on `DELETE /missions/{id}` cascade. |
| `MapObjectObservation` | yes | `mapobjects_store` until acknowledged centrally | per-detection append-log; durable across in-flight crash; cleared per record on `POST /missions/{id}/mapobjects` ack. |
| `IgnoredItem` | yes | `mapobjects_store` (working copy + post-flight upload of locally-appended items) | per `retention_scope`; central side union-merged. |
| `MissionItem` | no in autopilot | source of truth is the `missions` API | pulled on start; refreshed on middle-waypoint POST. |
| `MissionWaypoint` | no | in-memory inside `mavlink_layer` | re-derived from `MissionItem`s on each upload / re-upload. |
| `OperatorCommand` | partial | command-id cache (60 s) for idempotency; full audit log persisted on disk | per-command; audit-retained per configured policy. |

---

## 7. Versioning and contracts

| Contract | Owner | Versioning |
|---|---|---|
| `mission-schema` (the `MissionItem` shape) | shared between `autopilot` and `missions` repos; extraction location TBD (`architecture.md §8 Q5`) | semantic versioning; `mission_client` validates `schema_version` on fetch. |
| MapObjects bundle schema (`MapObjectsBundle` for pull/push, `MapObjectObservation` for the central observation log) | shared between `autopilot` and `missions` repos as part of the §7.13 endpoint extension | semantic versioning; `mission_client` validates `schema_version`; central side rejects mismatches with 4xx (`architecture.md §8 Q7`). |
| `../detections` gRPC contract | `../detections` repo (per `../_docs/03_detections.md`) | versioned; `detection_client` rejects schema mismatches (`architecture.md §8 Q4`). |
| `VlmAssessment` schema | autopilot-internal (this document is the source of truth) | versioned; `vlm_client` rejects schema-invalid responses. The `model_version` field correlates assessments with VLM weights. |
| MGRS sync wire format | autopilot-internal (this document is the source of truth) | versioned; field-position changes are breaking. |
| MAVLink command surface | per `architecture.md §7.7` | adding messages requires explicit design review. |
| `OperatorCommand` envelope (signature scheme) | open per `architecture.md §8 Q9` | once chosen, versioned; both Ground Station and `operator_bridge` must agree. |
