Documentation
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# Circuit List Editor API
|
||||
|
||||
## Scope
|
||||
|
||||
The circuit-first editor uses tree + circuit + row endpoints. Legacy consumer endpoints still exist for compatibility and migration, but should not be extended for new circuit-list behavior.
|
||||
|
||||
## Circuit-First Endpoints
|
||||
|
||||
### Tree Endpoint
|
||||
|
||||
- `GET /projects/:projectId/circuit-lists/:circuitListId/tree`
|
||||
- Purpose: returns section/circuit/device-row tree with calculated row and circuit totals.
|
||||
|
||||
Response sketch:
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
- `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`):
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"targetCircuitId": "cir_target"
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"identifiers": [
|
||||
{ "circuitId": "cir_1", "equipmentIdentifier": "-2F1" },
|
||||
{ "circuitId": "cir_2", "equipmentIdentifier": "-2F2" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Legacy Endpoints (Temporary)
|
||||
|
||||
- `GET /consumers/projects/:projectId`
|
||||
- `POST /consumers`
|
||||
- `PUT /consumers/:consumerId`
|
||||
- `DELETE /consumers/:consumerId`
|
||||
|
||||
These remain for migration/legacy views. New circuit-list interactions must use circuit-first endpoints above.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Circuit List Editor Architecture
|
||||
|
||||
## Purpose
|
||||
|
||||
The circuit-list editor is a circuit-first planning workspace for distribution-board design. It is optimized for spreadsheet-like editing while preserving electrical domain boundaries: circuit-level data stays on circuits, and load rows stay inside circuits.
|
||||
|
||||
## Domain Model Overview
|
||||
|
||||
- `CircuitSection`
|
||||
- Groups circuits by planning section (for example lighting, single-phase, three-phase).
|
||||
- Owns section metadata (`key`, `displayName`, `prefix`, ordering).
|
||||
- `Circuit`
|
||||
- Core electrical unit in the list.
|
||||
- Owns circuit-level identifiers and technical data:
|
||||
- `equipmentIdentifier` (BMK)
|
||||
- protection data
|
||||
- cable data
|
||||
- reserve state
|
||||
- circuit-level remark/status
|
||||
- `CircuitDeviceRow`
|
||||
- Load/device line inside a circuit.
|
||||
- Owns row-level load and context values:
|
||||
- `quantity`
|
||||
- `powerPerUnit`
|
||||
- `simultaneityFactor`
|
||||
- `cosPhi`
|
||||
- room snapshots
|
||||
- category/cost group and related row attributes
|
||||
- `ProjectDevice`
|
||||
- Reusable device template entity at project level.
|
||||
- Can be linked to `CircuitDeviceRow` entries, with copied display values on insert.
|
||||
- Legacy `Consumer`
|
||||
- Old row-first model still present for compatibility/migration.
|
||||
- Should not be extended for new circuit-list behavior.
|
||||
|
||||
## Why A Circuit Is Not One Row
|
||||
|
||||
A circuit can contain zero, one, or many device rows. Treating a circuit as a single row breaks:
|
||||
|
||||
- BMK ownership (belongs to circuit, not each device row)
|
||||
- circuit-level protection/cable fields
|
||||
- grouped calculations and circuit move/reorder semantics
|
||||
|
||||
The tree model keeps circuit identity stable while allowing row-level load composition.
|
||||
|
||||
## Rendering Model: Single vs Multi Device
|
||||
|
||||
- Single-device circuit:
|
||||
- Rendered as compact combined row (`circuitCompact`) for fast editing.
|
||||
- Multi-device circuit:
|
||||
- Rendered as one circuit summary row (`circuitSummary`) plus indented `deviceRow` entries.
|
||||
|
||||
This keeps visual density high without losing ownership boundaries.
|
||||
|
||||
## Reserve / Empty Circuits
|
||||
|
||||
Circuits with no device rows are rendered as reserve rows (`reserveCircuit`).
|
||||
Section-level `-frei-` placeholder rows represent insertion targets for creating a new circuit in that section.
|
||||
|
||||
## BMK Ownership (`equipmentIdentifier`)
|
||||
|
||||
`equipmentIdentifier` / BMK is circuit-owned (`Circuit.equipmentIdentifier`).
|
||||
|
||||
- Device rows do not have their own BMK.
|
||||
- Existing identifiers must stay stable unless explicitly changed by user action.
|
||||
- Renumbering is explicit, not implicit on move/sort/delete.
|
||||
|
||||
## Data Ownership Split: Circuit vs Device Row
|
||||
|
||||
Circuit-level fields on `Circuit`:
|
||||
|
||||
- protection type/rating/characteristic
|
||||
- cable type/cross-section/length
|
||||
- RCD/terminal/status
|
||||
|
||||
Device-level load fields on `CircuitDeviceRow`:
|
||||
|
||||
- quantity, power per unit, simultaneity, cosPhi
|
||||
- row naming/categorization and room snapshots
|
||||
|
||||
This split matches execution-design workflows where many loads share one protective path.
|
||||
|
||||
## Calculated Totals
|
||||
|
||||
- `rowTotalPower` is calculated per `CircuitDeviceRow`.
|
||||
- `circuitTotalPower` is calculated as sum of all row totals in one circuit.
|
||||
|
||||
Totals are exposed by the tree response and shown in computed read-only cells.
|
||||
|
||||
## Frontend Route
|
||||
|
||||
Primary circuit-first editor route:
|
||||
|
||||
- `/projects/:projectId/circuit-lists/:circuitListId/tree-edit`
|
||||
|
||||
Legacy route remains separate (read-only/preview/migration transition path) and is intentionally not merged into the editable tree editor.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Circuit List Editor Interactions
|
||||
|
||||
## Editing Model
|
||||
|
||||
Inline cells are static text by default. A cell enters edit mode by:
|
||||
|
||||
- double-click
|
||||
- `Enter`
|
||||
- `F2`
|
||||
- typing a printable character (type-to-edit)
|
||||
|
||||
`Enter` confirms changes. `Escape` cancels current edit draft. `Tab` and `Shift+Tab` confirm and move to next/previous editable cell.
|
||||
|
||||
## `selectedCell` vs `editingCell`
|
||||
|
||||
- `selectedCell` tracks spreadsheet navigation focus.
|
||||
- `editingCell` tracks active input draft (`draft`, edit mode, focus token).
|
||||
|
||||
The editor can have a selected cell without an active editor input. `editingCell` is only set while editing.
|
||||
|
||||
## Normalized Visible Grid
|
||||
|
||||
The UI works from a normalized `visibleRows` model built from filtered/sorted sections:
|
||||
|
||||
- section header rows
|
||||
- circuit rows (`circuitCompact`, `circuitSummary`, `reserveCircuit`)
|
||||
- device rows (`deviceRow`)
|
||||
- section placeholder rows (`placeholder`)
|
||||
|
||||
Selection, keyboard movement, and editability checks run against this normalized grid, not directly against nested API JSON.
|
||||
|
||||
## Keyboard Behavior
|
||||
|
||||
- `Enter`: start edit (when not editing) or commit (when editing input)
|
||||
- `F2`: start edit on selected cell
|
||||
- `Escape`: cancel edit draft, or clear multi-row selection when not editing
|
||||
- `Tab` / `Shift+Tab`: move between editable cells (commits active edit)
|
||||
- arrow keys: move selected cell in grid when not editing
|
||||
- type-to-edit: printable keys open edit mode and replace current display value with typed character
|
||||
|
||||
## `-frei-` Placeholder Behavior
|
||||
|
||||
Each section has a trailing placeholder row showing `-frei-` in BMK column:
|
||||
|
||||
- serves as drop target for creating new circuits from project devices or moved rows
|
||||
- editable placeholder cells can create a new circuit + first row through the same edit command flow
|
||||
|
||||
## Add Circuit Behavior
|
||||
|
||||
Add-circuit actions create reserve circuits in the active section using the section prefix and next numeric suffix (`max + 1`).
|
||||
No automatic gap-filling or global renumbering is performed.
|
||||
|
||||
## Drag-and-Drop Behavior
|
||||
|
||||
Intent is separated by drag source type:
|
||||
|
||||
- project device drag:
|
||||
- drop to section/placeholder -> create new circuit with linked row
|
||||
- drop to existing circuit row -> append row to that circuit
|
||||
- device row drag:
|
||||
- drop to existing circuit -> move row(s) into that circuit
|
||||
- drop to placeholder -> create new target circuit and move row(s)
|
||||
- circuit drag (BMK handle):
|
||||
- reorder circuits inside same section only
|
||||
- cross-section reorder is rejected
|
||||
- bulk device row move:
|
||||
- supported via multi-selection + drag
|
||||
- multi-circuit move:
|
||||
- supported for same-section selected circuit blocks
|
||||
|
||||
## Filtering and Sorting
|
||||
|
||||
- Per-column filtering works on normalized displayed values.
|
||||
- Sorting is view-level first and treats circuits as blocks.
|
||||
- Multi-device circuits are not split during sort.
|
||||
- Sorting alone does not persist order.
|
||||
|
||||
## Apply Sorted Order
|
||||
|
||||
`Apply sorted order` persists current sorted block order to backend by section.
|
||||
|
||||
- disabled while filters are active
|
||||
- undoable via command history
|
||||
- until applied, sort remains view-only
|
||||
|
||||
## Column Configuration
|
||||
|
||||
- Column visibility and order are configurable.
|
||||
- BMK column (`equipmentIdentifier`) is locked as first column.
|
||||
- Layout is saved in local storage (`circuitTreeEditor.columnLayout.v1`).
|
||||
|
||||
## Undo/Redo
|
||||
|
||||
Undo/redo wraps editor operations as command objects with async `redo`/`undo` and reloads tree after each command.
|
||||
|
||||
Covered operations include:
|
||||
|
||||
- insert/delete circuit
|
||||
- insert/delete row
|
||||
- edit cell values
|
||||
- moves (single/bulk rows, circuit reorder)
|
||||
- renumber and identifier update flows
|
||||
- apply sorted order
|
||||
|
||||
Current limitations:
|
||||
|
||||
- session-local only
|
||||
- no persisted history across browser reload
|
||||
- some multi-step backend flows are not fully transaction-hardened end-to-end
|
||||
@@ -0,0 +1,12 @@
|
||||
# Circuit List Editor Known Limitations
|
||||
|
||||
- Undo/redo history is session-local only.
|
||||
- Undo/redo history is not persisted across reloads or between users.
|
||||
- No linked project-device sync review dialog is implemented yet.
|
||||
- No global cross-project device library workflow is implemented yet.
|
||||
- Final electrical sizing logic is not implemented yet.
|
||||
- No full norm-compliant voltage-drop and protection-dimensioning calculation flow yet.
|
||||
- Bulk device-row move flow is command-based but not fully transaction-hardened end-to-end across all affected circuits.
|
||||
- Sorting is view-only until users explicitly apply sorted order.
|
||||
- Cross-section circuit drag-reorder is intentionally blocked.
|
||||
- Legacy consumer and circuit-first paths coexist; migration is transitional and still requires operational discipline.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Circuit List Editor Migration
|
||||
|
||||
## Goal
|
||||
|
||||
Migrate legacy row-first consumers into the circuit-first model without deleting legacy data.
|
||||
|
||||
## Legacy Mapping
|
||||
|
||||
Legacy `Consumer` rows map into:
|
||||
|
||||
- `Circuit` for shared circuit identity and circuit-level technical fields
|
||||
- `CircuitDeviceRow` for per-device load rows
|
||||
|
||||
Multiple legacy consumers can map into one circuit when they share normalized circuit identity.
|
||||
|
||||
## Grouping Strategy (`circuitNumber`)
|
||||
|
||||
Migration groups legacy rows by normalized `circuitNumber`:
|
||||
|
||||
- valid/normalizable values become one target circuit per normalized value
|
||||
- duplicates are grouped under that circuit (multiple `CircuitDeviceRow`s)
|
||||
- missing/invalid values trigger generated identifiers and may fall back to `unassigned` section
|
||||
|
||||
## Default Section Backfill
|
||||
|
||||
Before migration, default sections are created/backfilled per circuit list.
|
||||
This guarantees a valid target section space, including `unassigned` when no section can be inferred.
|
||||
|
||||
## Migration Commands
|
||||
|
||||
Run in this order for local database workflows:
|
||||
|
||||
1. Backup:
|
||||
- `npm run db:backup`
|
||||
2. Migrate schema:
|
||||
- `npm run db:migrate`
|
||||
3. Verify circuit schema:
|
||||
- `npm run db:verify:circuit-schema`
|
||||
4. Backfill missing sections:
|
||||
- `npm run db:backfill:sections`
|
||||
5. Migrate legacy consumers:
|
||||
- `npm run db:migrate:legacy-consumers`
|
||||
|
||||
## Validation Checks
|
||||
|
||||
After migration, verify:
|
||||
|
||||
- tree endpoint returns sections/circuits/rows
|
||||
- grouped duplicate circuit numbers are reported
|
||||
- generated identifiers are reported where expected
|
||||
- migrated rows include `legacyConsumerId` traceability
|
||||
- no duplicate BMKs exist inside one circuit list
|
||||
|
||||
## If Tree Endpoint Returns Empty Sections
|
||||
|
||||
Likely causes:
|
||||
|
||||
- circuit-first tables missing (migration not run)
|
||||
- sections not backfilled yet
|
||||
- migrated dataset genuinely empty for selected list
|
||||
|
||||
Actions:
|
||||
|
||||
1. Run schema migration and verification commands.
|
||||
2. Run section backfill command.
|
||||
3. Run legacy-consumer migration command.
|
||||
4. Retry tree endpoint.
|
||||
|
||||
## Legacy Data Retention
|
||||
|
||||
Do not delete legacy consumers yet.
|
||||
|
||||
- legacy endpoints/views may still depend on them
|
||||
- migration trace tables reference old/new mapping
|
||||
- keeping legacy rows allows comparison and rollback validation during transition
|
||||
Reference in New Issue
Block a user