Files
autopilot/_docs/02_document/data_model.md
T
Oleksandr Bezdieniezhnykh bc40ea7300 [AZ-626] Decompose complete: 47 tasks + docs + module layout
Greenfield Steps 1-6 baseline for the autopilot rewrite from legacy
Qt/C++ to a Rust workspace.

- Remove legacy Qt/C++ tree (ai_controller, drone_controller,
  misc/camera, python_scaffold, root Dockerfile, autopilot.pro,
  legacy main.py / requirements.txt).
- Add _docs/00_problem (problem, restrictions, acceptance criteria,
  security approach, input data + fixtures).
- Add _docs/01_solution/solution_draft01.
- Add _docs/02_document (architecture, system-flows, data_model,
  glossary, decision-rationale, deployment, 13 component descriptions,
  tests/ specs, FINAL_report, module-layout).
- Add _docs/02_tasks/todo with 47 task specs (AZ-640..AZ-686, one
  bootstrap + 46 component tasks) and _dependencies_table.md.
- Add .cursor/rules/artifact-srp.mdc (single-responsibility rule for
  canonical _docs artifacts).
- Track autodev state in _docs/_autodev_state.md (Step 6 completed,
  ready for Step 7 Implement).

Jira: bootstrap AZ-626; component epics AZ-627..AZ-639; tasks
AZ-640..AZ-686. Total complexity 173 points across 12 epics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-19 11:02:01 +03:00

385 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.01.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.01.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.01.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.01.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. |