# Azaion UI — Data Model

> Synthesis output of `/document` Step 3c. Consolidated from `src/types/index.ts`,
> per-component data sections, and the parent suite spec at
> `_docs/legacy/wpf-era.md` §8 + the suite-level docs (cross-referenced).
>
> The UI does NOT own a database. The entities below are the **contract shapes**
> the UI consumes from suite REST + SSE endpoints. The authoritative schema is
> server-side; this document captures the **client-side type expectations** and
> the **mismatches** between those expectations and the suite spec (enum drift,
> shape drift) for resolution at autodev Step 4.

## 1. Entities by component

| Entity | Component | Backing service | Wire shape |
|--------|-----------|-----------------|------------|
| `AuthUser` | `02_auth` | `admin/` | `{id, email, name, role, permissions[]}` |
| `User` | `08_admin` | `admin/` | `{id, name, email, role, isActive}` |
| `Aircraft` | `08_admin` / `09_settings` | `admin/` | `{id, model, type:'Plane'\|'Copter', isDefault}` |
| `Flight` | `05_flights` | `flights/` | `{id, name, createdDate, aircraftId\|null}` |
| `Waypoint` | `05_flights` | `flights/` | UI sends: `{id, flightId, name, latitude, longitude, order}`. Spec wants: `{Geopoint:{Lat,Lon,MGRS}, Source, Objective, OrderNum, Height}` — **drift, finding #20** |
| `Media` | `06_annotations` | `annotations/` | `{id, name, path, mediaType, mediaStatus, duration, annotationCount, waypointId, userId}` |
| `AnnotationListItem` | `06_annotations` | `annotations/` | `{id, mediaId, time, createdDate, userId, source, status, isSplit, splitTile, detections[]}` |
| `Detection` | `06_annotations` | `annotations/` (storage) + `detect/` (production) | `{id, classNum, label, confidence, affiliation, combatReadiness, centerX, centerY, width, height}` |
| `DetectionClass` | `08_admin` (write) + `06_annotations` (read) | `admin/` (write) + `annotations/` (read) | `{id, name, shortName, color, maxSizeM, photoMode}` |
| `DatasetItem` | `07_dataset` | `annotations/` | `{annotationId, imageName, thumbnailPath, status, createdDate, createdEmail, flightName, source, isSeed, isSplit}` |
| `ClassDistributionItem` | (currently unused — backs missing chart) | `annotations/` | `{classNum, label, color, count}` |
| `SystemSettings` | `09_settings` | `admin/` | `{id, name, militaryUnit, defaultCameraWidth, defaultCameraFoV, thumbnailWidth, thumbnailHeight, thumbnailBorder, generateAnnotatedImage, silentDetection}` |
| `DirectorySettings` | `09_settings` | `admin/` | `{id, videosDir, imagesDir, labelsDir, resultsDir, thumbnailsDir, gpsSatDir, gpsRouteDir}` |
| `CameraSettings` | `09_settings` | `admin/` | `{id, altitude, focalLength, sensorWidth}` |
| `UserSettings` | `09_settings` | `admin/` | `{id, userId, selectedFlightId, annotationsLeftPanelWidth, annotationsRightPanelWidth, datasetLeftPanelWidth, datasetRightPanelWidth}` |
| `PaginatedResponse<T>` | shared (`00_foundation`) | every list endpoint | `{items[], totalCount, page, pageSize}` |

## 2. Enums (numeric wire format)

> The suite uses **numeric** wire values for every enum. The UI types in `src/types/index.ts`
> match in *shape* but several have **wrong values** vs. spec. The state-of-the-world is captured
> in `state.json::notes[]` (2026-05-10 02:13Z entries) — Step 4 will fix the UI side.

| Enum | UI values today | Spec values | Status |
|------|-----------------|-------------|--------|
| `MediaType` | `None=0, Image=1, Video=2` | matches | ✓ |
| `MediaStatus` | `New=0, AiProcessing=1, AiProcessed=2, ManualCreated=3` | also has `None`, `Confirmed`, `Error` | **drift** — UI cannot render error state. Step 4 fix. |
| `AnnotationSource` | `AI=0, Manual=1` | matches numerically; spec doc shows strings (cross-repo doc fix replayed 2026-05-10) | ✓ (after parent-doc fix) |
| `AnnotationStatus` | `Created=0, Edited=1, Validated=2` | spec is `None=0, Created=10, Edited=20, Validated=30, Deleted=40` | **drift, severe** — wire payloads will be wrong. Step 4 PRIORITY. |
| `Affiliation` | `Unknown=0, Friendly=1, Hostile=2` | spec also has `None` | **drift** — Step 4. |
| `CombatReadiness` | `NotReady=0, Ready=1` | spec also has `Unknown` | **drift** — Step 4. |

## 3. Entity-relationship diagram

```mermaid
erDiagram
    User ||--o{ Flight : "creates"
    Flight ||--o{ Waypoint : "has"
    Flight ||--o{ Media : "captures"
    Media ||--o{ AnnotationListItem : "annotated by"
    AnnotationListItem ||--o{ Detection : "contains"
    DetectionClass ||--o{ Detection : "classifies (by classNum)"
    Aircraft }o--o| Flight : "default for"
    User ||--|| AuthUser : "session view of"
    User ||--|| UserSettings : "preferences"
    User ||--o{ DatasetItem : "validated by"
    SystemSettings ||--|| Aircraft : "may default to"

    Detection {
        string id
        int classNum "raw int including PhotoMode offset"
        string label
        float confidence
        Affiliation affiliation
        CombatReadiness combatReadiness
        float centerX "normalized 0..1"
        float centerY "normalized 0..1"
        float width "normalized 0..1"
        float height "normalized 0..1"
    }

    DetectionClass {
        int id
        string name
        string shortName
        string color "hex"
        float maxSizeM "GSD constraint"
        int photoMode "0=Regular, 1=Winter (+20), 2=Night (+40)"
    }

    Waypoint {
        string id
        string flightId
        string name
        float latitude "wire shape: drift — see finding 20"
        float longitude
        int order
    }

    AnnotationListItem {
        string id
        string mediaId
        string time "video timestamp HH:MM:SS or null"
        string createdDate "ISO 8601"
        string userId
        AnnotationSource source
        AnnotationStatus status
        bool isSplit
        string splitTile "YOLO label string or null"
    }

    Media {
        string id
        string name
        string path
        MediaType mediaType
        MediaStatus mediaStatus
        string duration "HH:MM:SS or null"
        int annotationCount
        string waypointId
        string userId
    }

    DatasetItem {
        string annotationId
        string imageName
        string thumbnailPath
        AnnotationStatus status
        string createdDate
        string createdEmail
        string flightName
        AnnotationSource source
        bool isSeed
        bool isSplit
    }
```

## 4. Migration / schema-evolution strategy

The UI does NOT own a database, so there is no client-side migration to run. However:

- **Enum drift** above is effectively a "client-side schema migration" — every drifted enum must be aligned with the suite spec in Step 4 (or in the case of `AnnotationSource`, the parent-suite doc is what's wrong and was already fixed via cross-repo doc patch on 2026-05-10).
- **Backwards compatibility** is the suite's responsibility. The UI assumes the latest contract; if the suite needs to roll out a breaking change, it must coordinate with the UI version (typically by gating the change behind a feature flag in the admin service, then deploying both at once).
- **`UserSettings.{annotationsLeftPanelWidth, ...}`** — the type exists; the wire endpoint exists; the UI does not write these today (`useResizablePanel` finding #11). The fix is purely client-side wiring.

## 5. Seed data observations

The UI has no seed data of its own. Two sources of "default" data are observable:

1. **`FALLBACK_CLASS_NAMES`** (`11_class-colors`) — 12 generic English labels (Car, Person, Truck, …) shown only when admin classes failed to load. These are NOT a seed for the admin service; they are a defensive UI fallback only.
2. **`Constants.DefaultAnnotationClasses`** in the legacy WPF — referenced in `_docs/legacy/wpf-era.md §10`. The React UI does NOT carry an equivalent (admin classes are always fetched). Acceptable.

## 6. Validation rules (client-side)

| Field | Rule | Source | Defect |
|-------|------|--------|--------|
| Login email | non-empty | `LoginPage` | none observed |
| Login password | non-empty | `LoginPage` | none observed |
| Numeric settings | `parseInt(v) \|\| 0` | `SettingsPage` | clearing field silently writes 0 — finding B4 |
| Waypoint lat/lng | implicit (Leaflet bounds) | `FlightsPage` | no explicit clamp — Step 4 |
| Detection bbox normalized 0..1 | implicit (CanvasEditor scaling) | `CanvasEditor` | normalized-coordinate clamping mentioned in `_docs/legacy/wpf-era.md §10` — verify in Step 4 |
| Class 1–9 keyboard pick | `classes[idx + photoMode]` | `DetectionClasses` | ordering vs admin contract unverified — Step 4 |
| File upload size | server cap 500 MB (nginx) | `nginx.conf` | client does not pre-validate — Step 4 cosmetic |

## 7. Wire-format gotchas

- **`time` as a string** in `AnnotationListItem` (e.g., `"00:01:23.456"`) is parsed/formatted client-side; precision is millisecond. Used for video annotation overlay window math (50 ms before / 150 ms after — currently wrong, finding #6).
- **`splitTile` as a YOLO label string** is the raw `<class> <cx> <cy> <w> <h>` text, parsed client-side in `CanvasEditor.tsx`. Format mismatch is a Step 4 verification candidate.
- **`PaginatedResponse<T>` ceiling** — `pageSize=1000` is hardcoded for flights (finding B3). Other lists vary; no canonical default.
- **Date strings** — assumed ISO 8601; no timezone handling beyond what the browser's `Date` constructor implies. Step 4 verification when timezone bugs surface.
