# Module: `Azaion.Missions.Database.Entities` **Files (7)**: `Vehicle.cs`, `Mission.cs`, `Waypoint.cs`, `MapObject.cs`, `Media.cs`, `Annotation.cs`, `Detection.cs` > **NOTE (forward-looking)**: this doc reflects the post-rename state. Today's source still has `Aircraft.cs`, `Flight.cs`, `Orthophoto.cs`, `GpsCorrection.cs`. The renames + GPS-Denied removal are tracked under Jira AZ-EPIC children B6 (domain rename) and B7 (GPS-Denied removal). ## Purpose LinqToDB row-mapping classes. Each entity uses `[Table("snake_case_name")]` + `[Column("snake_case")]` + `[PrimaryKey]` to map to a PostgreSQL table. Two entities (`Mission`, `Waypoint`) include `[Association]`-based navigation; the rest are flat row maps. ## Public Interface ### Vehicle (table `vehicles`) ```csharp [Table("vehicles")] public class Vehicle { [PrimaryKey, Column("id")] public Guid Id; [Column("type")] public VehicleType Type; // Plane | Copter | UGV | GuidedMissile [Column("model")] public string Model = ""; [Column("name")] public string Name = ""; [Column("fuel_type")] public FuelType FuelType; [Column("battery_capacity")] public decimal BatteryCapacity; [Column("engine_consumption")] public decimal EngineConsumption; [Column("engine_consumption_idle")] public decimal EngineConsumptionIdle; [Column("is_default")] public bool IsDefault; } ``` ### Mission (table `missions`) ```csharp [Table("missions")] public class Mission { [PrimaryKey, Column("id")] public Guid Id; [Column("created_date")] public DateTime CreatedDate; [Column("name")] public string Name = ""; [Column("vehicle_id")] public Guid VehicleId; [Association(ThisKey=VehicleId, OtherKey=Vehicle.Id)] public Vehicle? Vehicle; [Association(ThisKey=Id, OtherKey=Waypoint.MissionId)] public List Waypoints = []; } ``` ### Waypoint (table `waypoints`) ```csharp [Table("waypoints")] public class Waypoint { [PrimaryKey, Column("id")] public Guid Id; [Column("mission_id")] public Guid MissionId; [Column("lat")] public decimal? Lat; [Column("lon")] public decimal? Lon; [Column("mgrs")] public string? Mgrs; [Column("waypoint_source")] public WaypointSource WaypointSource; [Column("waypoint_objective")] public WaypointObjective WaypointObjective; [Column("order_num")] public int OrderNum; [Column("height")] public decimal Height; [Association(ThisKey=MissionId, OtherKey=Mission.Id)] public Mission? Mission; } ``` ### MapObject (table `map_objects`) ```csharp [Table("map_objects")] public class MapObject { [PrimaryKey, Column("id")] public Guid Id; [Column("mission_id")] public Guid MissionId; [Column("h3_index")] public string H3Index = ""; // Uber H3 hex grid [Column("mgrs")] public string Mgrs = ""; [Column("lat")] public decimal? Lat; [Column("lon")] public decimal? Lon; [Column("class_num")] public int ClassNum; [Column("label")] public string Label = ""; [Column("size_width_m")] public decimal SizeWidthM; [Column("size_length_m")] public decimal SizeLengthM; [Column("confidence")] public decimal Confidence; [Column("object_status")] public ObjectStatus ObjectStatus; [Column("first_seen_at")] public DateTime FirstSeenAt; [Column("last_seen_at")] public DateTime LastSeenAt; } ``` ### Media / Annotation / Detection (cross-service stubs -- see "Notes / Smells") ```csharp [Table("media")] public class Media { [PrimaryKey, Column("id")] public string Id = ""; // TEXT primary key [Column("waypoint_id")] public Guid? WaypointId; } [Table("annotations")] public class Annotation { [PrimaryKey, Column("id")] public string Id = ""; // TEXT [Column("media_id")] public string MediaId = ""; // TEXT FK to media.id } [Table("detection")] // SINGULAR table name -- diverges from every other entity (owned by detection pipeline) public class Detection { [PrimaryKey, Column("id")] public Guid Id; [Column("annotation_id")] public string AnnotationId = ""; } ``` ## Internal Logic Pure POCOs. The only behavior comes from LinqToDB attribute mapping (`[Table]`, `[Column]`, `[PrimaryKey]`, `[Association]`). ## Dependencies - `LinqToDB.Mapping` (NuGet) - `Azaion.Missions.Enums` (for `Vehicle`, `Waypoint`, `MapObject`) ## Consumers - `Database.AppDataConnection` -- exposes `ITable` for each entity. - `Database.DatabaseMigrator` -- implicitly (defines DDL for the same names; does NOT reference entity types). - `Services.VehicleService`, `Services.MissionService`, `Services.WaypointService` -- entity types are returned/constructed. ## Data Model Highlights ``` vehicles --< missions --< waypoints --? media --< annotations --< detection \--< map_objects ``` - `Mission` is the central aggregate root: most domain rows hang off `mission_id`. - `Waypoint` is a sub-aggregate of `Mission` and is the join point for `Media`. - `MapObject` is detection output (class_num + confidence + spatial index) tied to a mission, NOT to a specific waypoint. **Schema is owned by this service, but rows are written by `autopilot`** (per `../../suite/_docs/06_autopilot_design.md`). ## Cross-service stubs `Media`, `Annotation`, `Detection` are intentional read-only stubs -- only `Id` and one foreign key each. They're queried/deleted by `MissionService.DeleteMission` and `WaypointService.DeleteWaypoint` but never written by this service. Schema-wise they are owned by other suite components (`annotations` for `media` + `annotations`, the detection pipeline for `detection`), per `../../suite/_docs/00_top_level_architecture.md` and `../../suite/_docs/01_annotations.md`. The shared edge-PostgreSQL pattern means each service migrates its own tables but all services see the full schema. These three are deliberately NOT in `DatabaseMigrator.Sql` -- their schema is created by the owning services on the same shared local PostgreSQL. ## Configuration / External Integrations / Security None directly. Persistence is delegated to LinqToDB + Npgsql. ## Tests None present. ## Notes / Smells 1. **`Detection` table singular** while `vehicles`, `missions`, `waypoints`, `map_objects`, `media`, `annotations` are plural. Owned by another service -- naming is THEIR call to make consistent. 2. **Mixed PK types**: `Vehicle`, `Mission`, `Waypoint`, `MapObject`, `Detection` use `Guid`; `Media`, `Annotation` use `string` (TEXT, XxHash64-based per `../../suite/_docs/00_database_schema.md`). 3. **No domain methods** -- all business rules (e.g., the "default vehicle" exclusivity in `VehicleService`) live in services, not in the entities. Consistent and intentional for a thin-data-model approach. 4. **`Media.WaypointId` is nullable** while every other foreign key here is not. Suggests `Media` can attach to a non-waypoint context (e.g., mission-level media); enforcement is on `annotations`'s side. 5. **Geopoint divergence (carry to verification log)**: spec stores `Waypoints.GPS` as a single `string GPS` field with `Lat <-> MGRS` auto-conversion (per `../../suite/_docs/02_missions.md` and `../../suite/_docs/00_database_schema.md`). Code splits it into 3 separate columns. Resolution lives outside the GPS-Denied removal scope -- carry forward.