Files
leistungsbilanz-ts/docs/circuit-list-editor-api.md
T
2026-07-23 22:18:36 +02:00

6.2 KiB

Circuit List Editor API

Scope

The circuit-first editor uses tree, circuit, row and project-device endpoints. All paths below are mounted below /api. There is no Consumer application API.

Project responses from GET /projects and GET /projects/:projectId include currentRevision. Versioned project commands use this value for optimistic concurrency checks.

Project Commands and History

  • GET /projects/:projectId/history
    • returns currentRevision, undoDepth, redoDepth and the current top change-set id for both persistent stacks
  • POST /projects/:projectId/commands
    • executes a supported versioned command as a new user revision
    • body: { "expectedRevision": 0, "command": { ... } }
  • POST /projects/:projectId/history/undo
  • POST /projects/:projectId/history/redo
    • body: { "expectedRevision": 1 }
    • executes the eligible inverse or forward command as a new auditable revision

The public dispatcher currently supports circuit.update, circuit.insert, circuit.delete, circuit-device-row.update, circuit-device-row.insert and circuit-device-row.delete, all with schema version 1. Other command types are rejected. Insert commands contain the complete entity or circuit block with stable ids; delete commands include the expected parent identity. Circuit snapshots contain zero, one or multiple complete device rows. This makes deletion undoable without generating replacement identities or renumbering circuits. The editor does not consume these endpoints yet.

Example:

{
  "expectedRevision": 0,
  "description": "Rename circuit",
  "command": {
    "schemaVersion": 1,
    "type": "circuit.update",
    "payload": {
      "circuitId": "cir_1",
      "changes": [
        { "field": "displayName", "value": "Sockets East" }
      ]
    }
  }
}

A stale expectedRevision returns HTTP 409 with PROJECT_REVISION_CONFLICT. Undo or redo without an eligible stack entry returns HTTP 409 with PROJECT_HISTORY_OPERATION_UNAVAILABLE.

Circuit-First Endpoints

Distribution Board Setup

  • POST /projects/:projectId/distribution-boards
    • atomically creates the distribution board, its circuit list and all default circuit sections

Tree Endpoint

  • GET /projects/:projectId/circuit-lists/:circuitListId/tree
  • Purpose: returns section/circuit/device-row tree with calculated row and circuit totals.

Response sketch:

{
  "circuitListId": "cl_1",
  "sections": [
    {
      "id": "sec_1",
      "key": "lighting",
      "prefix": "-1F",
      "circuits": [
        {
          "id": "cir_1",
          "equipmentIdentifier": "-1F1",
          "circuitTotalPower": 4.2,
          "deviceRows": [
            {
              "id": "row_1",
              "displayName": "Office lights",
              "rowTotalPower": 1.8
            }
          ]
        }
      ]
    }
  ]
}

Circuit CRUD

  • POST /projects/:projectId/circuit-lists/:circuitListId/circuits
    • create circuit in list/section
  • POST /projects/:projectId/circuit-lists/:circuitListId/circuits-with-device-rows
    • atomically create one circuit and one or more initial device rows
  • PATCH /circuits/:circuitId
    • update circuit-level fields (BMK, protection, cable, reserve flag, etc.)
  • DELETE /circuits/:circuitId
    • delete circuit (and related rows via DB relations)
  • GET /circuit-sections/:sectionId/next-identifier
    • preview next identifier for section (prefix + maxSuffix + 1)

Request sketch (POST .../circuits):

{
  "sectionId": "sec_1",
  "equipmentIdentifier": "-2F14",
  "displayName": "Sockets East",
  "sortOrder": 140
}

Device Row CRUD

  • POST /circuits/:circuitId/device-rows
    • create row inside a circuit
  • PATCH /circuit-device-rows/:rowId
    • update row values
  • DELETE /circuit-device-rows/:rowId
    • delete row

Move Device Row

  • PATCH /circuit-device-rows/:rowId/move
  • Purpose: move one row to existing circuit or to newly created circuit in target section.

Request sketch:

{
  "targetCircuitId": "cir_target"
}

or

{
  "targetSectionId": "sec_target",
  "createNewCircuit": true
}

Bulk Move Device Rows

  • PATCH /circuit-device-rows/move-bulk
  • Purpose: move multiple rows in one command flow.

Request sketch:

{
  "rowIds": ["row_1", "row_2"],
  "targetCircuitId": "cir_target"
}

Reorder Circuits

  • PATCH /circuit-sections/:sectionId/circuits/reorder
  • Purpose: persist explicit circuit order within one section.

Request sketch:

{
  "orderedCircuitIds": ["cir_2", "cir_1", "cir_3"]
}

Renumber Section

  • POST /circuit-sections/:sectionId/renumber
  • Purpose: explicit renumbering by section prefix; never implicit on move/sort.

Safe Equipment Identifier Update

  • PATCH /circuit-sections/:sectionId/equipment-identifiers
  • Purpose: apply explicit per-circuit identifiers safely even with unique constraints.

Request sketch:

{
  "identifiers": [
    { "circuitId": "cir_1", "equipmentIdentifier": "-2F1" },
    { "circuitId": "cir_2", "equipmentIdentifier": "-2F2" }
  ]
}

Removed Legacy Endpoints

The former /consumers read/write endpoints were removed after every retained consumer had a verified Circuit-First migration mapping. Database upgrade tooling reads retained legacy rows directly; application features must use the Circuit-First endpoints above.

Linked Project Device Review

  • GET /project-devices/projects/:projectId/:projectDeviceId/links
    • returns all linked circuit device rows with distribution-board/circuit context and field differences
  • POST /project-devices/projects/:projectId/:projectDeviceId/synchronize
    • applies only explicitly selected fields to explicitly selected linked rows
  • POST /project-devices/projects/:projectId/:projectDeviceId/restore
    • restores the captured pre-sync values for session-local undo
  • POST /project-devices/projects/:projectId/:projectDeviceId/disconnect
    • disconnects explicitly selected rows without changing their local values
  • POST /project-devices/projects/:projectId/:projectDeviceId/reconnect
    • restores a disconnected link if the row has not been linked elsewhere meanwhile

displayName is included in the comparison but is not selected by default in the UI. Updating a project device never triggers synchronization implicitly.