> ## Documentation Index
> Fetch the complete documentation index at: https://docs.a7manager.se/llms.txt
> Use this file to discover all available pages before exploring further.

# Database structure

> The entities the platform stores, and how they relate to each other across modules.

This page shows the entities the platform stores and how they connect.
For **every column and every constraint**, table by table,
see the [table reference](/data/tables/assets).

Two rules apply everywhere and are not repeated:

* **Everything belongs to a project.** Almost every table carries a direct link to its
  project, and the few that do not inherit it from their parent.
* **Every record has a unique identifier and timestamps.**

## The fleet registry

This is the backbone. Everything else in the platform ultimately points at something here.

```mermaid theme={null}
erDiagram
    VEHICLE_TYPE ||--o{ VEHICLE : "instances of"
    VEHICLE_TYPE ||--o{ PRODUCT_GROUP : "has hierarchy"
    VEHICLE_TYPE ||--o{ VEHICLE_TYPE_SLOT : "defines positions"
    PRODUCT_GROUP ||--o{ VEHICLE_TYPE_SLOT : "positions belong to"
    PRODUCT_GROUP ||--o{ PRODUCT_GROUP : "parent of"
    COMPONENT_TYPE ||--o{ COMPONENT : "instances of"
    COMPONENT_TYPE }o--o{ VEHICLE_TYPE_SLOT : "allowed in"
    VEHICLE ||--o{ VEHICLE_READING : "odometer history"

    VEHICLE_TYPE {
        string code
        string name
    }
    PRODUCT_GROUP {
        string code "EN 15380-2"
        string name
        bool is_standard
    }
    VEHICLE_TYPE_SLOT {
        string position
        string position_label
        bool is_required
    }
    COMPONENT_TYPE {
        string code
        string name
    }
    VEHICLE {
        string vehicle_number
        int current_mileage_km
        int current_operating_hours
    }
    COMPONENT {
        string serial_number
        string status
        string current_location
        int total_mileage_km
    }
    VEHICLE_READING {
        date reading_date
        int mileage_km
        int operating_hours
    }
```

Reading it in order:

<Steps>
  <Step title="A vehicle type describes a class of vehicle">
    Not an individual train — a model. Everything structural hangs off the type, so it is
    defined once and applies to every vehicle of that type.
  </Step>

  <Step title="Product groups organise the vehicle into functional groups">
    A tree following the **EN 15380-2** standard — brakes, doors, bogies, and so on, each
    subdividing further. Standard top levels are seeded automatically; the rest is defined
    per vehicle type.
  </Step>

  <Step title="Slots are the mounting positions">
    A slot is a place where a component can sit — "bogie 1, axle 2, left wheel". Each slot
    belongs to a product group and lists which component types are allowed in it. Slots can
    nest inside other slots.
  </Step>

  <Step title="Vehicles and components are the instances">
    A vehicle has a vehicle number and carries its current mileage and operating hours. A
    component has a serial number, a status, and its own lifetime mileage.
  </Step>
</Steps>

<Note>
  **A component has no direct link to a vehicle.** Which component is mounted where lives in
  a separate record with dates on it, so that mounting history is preserved rather than
  overwritten. That record is `VehicleConfiguration`, below.
</Note>

## Mounting and maintenance

```mermaid theme={null}
erDiagram
    VEHICLE ||--o{ VEHICLE_CONFIGURATION : "mounts"
    COMPONENT ||--o{ VEHICLE_CONFIGURATION : "mounted as"
    VEHICLE_TYPE_SLOT ||--o{ VEHICLE_CONFIGURATION : "position filled by"
    MAINTENANCE_PLAN ||--o{ MAINTENANCE_TASK : "contains"
    MAINTENANCE_TASK ||--o{ MAINTENANCE_RECORD : "performed as"
    VEHICLE ||--o{ MAINTENANCE_RECORD : "work done on"
    COMPONENT ||--o{ MAINTENANCE_RECORD : "work done on"
    MAINTENANCE_TASK ||--o{ PLANNED_OCCURRENCE : "scheduled as"

    VEHICLE_CONFIGURATION {
        datetime mounted_at
        datetime dismounted_at "null = still mounted"
        int mounted_mileage_km
        int component_mileage_at_mount
        string depot
    }
    MAINTENANCE_PLAN {
        string name
        string revision
    }
    MAINTENANCE_TASK {
        string name
        string interval_type "KM, DAYS, HOURS, CYCLES"
        int interval_value
        int tolerance_before
        int tolerance_after
    }
    MAINTENANCE_RECORD {
        datetime completed_at
        int performance_km
        string depot
    }
    PLANNED_OCCURRENCE {
        date due_date
        date planned_date
        bool is_overdue
    }
```

**Vehicle configuration** is the mounting log. One row per mount event, with the dismount
date left empty while the component is still fitted. Two rules are enforced by the database
itself: a component can be mounted in only one place at a time, and a slot can hold only
one component at a time.

**Maintenance plans** belong to a vehicle type *or* a component type, never both. Each plan
holds **tasks**, and a task defines an interval with a tolerance.

**Maintenance records** are the preventative work that was performed, imported daily. **Planned
occurrences** are projections of work not yet done, generated by the Planner.

## What attaches to the fleet

The remaining modules all hang off a vehicle or a component.

```mermaid theme={null}
erDiagram
    VEHICLE ||--o{ DEFECT : "fault reported on"
    COMPONENT ||--o{ DEFECT : "fault reported on"
    PRODUCT_GROUP ||--o{ DEFECT : "categorised under"
    VEHICLE_TYPE_SLOT ||--o{ DEFECT : "reported at position"
    DEFECT ||--|| WARRANTY_CLAIM : "may raise one"
    WARRANTY_CLAIM }o--o{ WARRANTY_CLAIM_GROUP : "negotiated in"
    COMPONENT ||--o{ COMPONENT_STATUS_UPDATE : "repair history"
    COMPONENT ||--o{ MEASUREMENT : "measured"
    VEHICLE ||--o{ MEASUREMENT : "measured"
    VEHICLE ||--o{ INSPECTION_REPORT : "inspected"
    INSPECTION_REPORT ||--o{ REPORT_FINDING : "records"
    VEHICLE ||--o{ VEHICLE_OUT_OF_SERVICE : "unavailable"
    VEHICLE ||--o{ CLEANING_RECORD : "cleaned"

    DEFECT {
        datetime defect_datetime
        string defect_title
        string severity
        date closed_date "null = open"
        int delay_minutes
    }
    WARRANTY_CLAIM {
        string status
        string warranty_claim_title
    }
    WARRANTY_CLAIM_GROUP {
        string status
        string type "SYSTEMATIC or MISC"
    }
    COMPONENT_STATUS_UPDATE {
        string new_status
        string message
    }
    MEASUREMENT {
        datetime measured_at
        decimal value
        string position
        string action_code
    }
    INSPECTION_REPORT {
        string status "DRAFT or SUBMITTED"
        datetime submitted_at
    }
    REPORT_FINDING {
        string severity "LOW to CRITICAL"
        string description
    }
```

## Where each entity comes from

Whether a record is imported or created in the application determines whether you can edit
it. Imported records are read-only in the platform — an edit would be overwritten by the
next day's file.

| Created in the application                            | Imported from CSV                       |
| ----------------------------------------------------- | --------------------------------------- |
| Vehicle types, product groups, component types, slots | Defects                                 |
| Vehicles, components                                  | Vehicle readings (odometer)             |
| Measurement types and thresholds                      | Vehicle configurations (mount/dismount) |
| Inspection templates                                  | Maintenance plans and tasks             |
| Warranty claims, groups, approvals                    | Maintenance records                     |
| Component repair-loop updates                         | Measurements                            |
| Inspection reports and findings                       | Out-of-service periods                  |
| Maintenance plannings                                 | Cleaning records                        |

<Note>
  Two exceptions worth knowing. **Vehicles and components** are created in the application
  but carry an external identifier so imported rows can be matched to them. And on an
  imported **maintenance task**, the manhour and cost estimates are maintained in the
  application by the Planner role — the import does not touch them.
</Note>

Each imported file has its own format, its own validation rules, and its own reasons a row
might be rejected. Start with [How ingestion works](/data/ingestion) for the rules that
apply to every file, then the individual file pages for the column-level contract.
