diff --git a/AGENTS.md b/AGENTS.md
index 9ef5b87..8312d55 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -52,8 +52,7 @@ See `docs/current-architecture.md` for the complete module and request flow.
- Device rows inside a circuit do not have their own equipment identifiers.
- Protection and cable data belong to the circuit, not to individual devices.
- Device-level values include quantity, power per unit, simultaneity factor, cosPhi, room data, cost group and category.
-- Circuit total power is the sum of all device rows in that circuit plus the
- read-only loads of logically linked downstream boards on that feeder circuit.
+- Circuit total power is the sum of all device rows in that circuit.
- Existing equipment identifiers must never be changed automatically.
- Renumbering is always an explicit user action.
@@ -285,19 +284,16 @@ not change the project revision or undo/redo stacks. Restoring a server-stored
snapshot verifies its checksum and the current-state hash, replaces supported
project data atomically and records a new `restore` revision with a complete
inverse command. Restore can therefore be undone and redone after a restart.
-Current snapshot schema version 9 additionally stores
-`feedingDistributionBoardId` and `feedingCircuitId` on distribution boards;
-versions 1 to 8 are upgraded with both references set to `null`. Version 8
-stores the optional net room area `rooms[].areaM2` (see
-`docs/building-structure.md`); versions 1 to 7 are upgraded with
-`areaM2 = null`. Version 7 stores the project buildings and
+Current snapshot schema version 8 additionally stores the optional net room
+area `rooms[].areaM2` (see `docs/building-structure.md`); versions 1 to 7 are
+upgraded with `areaM2 = null`. Version 7 stores the project buildings and
`floors[].buildingId`; versions 1 to 6 are upgraded with no buildings and
`buildingId = null`. Version 6 stores the
distribution-board info fields `boardNumber`, `location`, `fedFrom` and
`remark` and the project cover fields `siteAddress`, `workStage`,
`projectLead` and `projectEditor`; all older supported versions are upgraded
with `null` for these fields.
-Stored named and automatic snapshots of every supported version (1 to 9)
+Stored named and automatic snapshots of every supported version (1 to 8)
remain restorable; they are upgraded while parsing. Version 5 stores
the manual quantity share of every CircuitDeviceRow. Version 4 snapshots are upgraded with
`manualQuantity = quantity`; they already contain the complete confirmed
@@ -313,7 +309,7 @@ project graph.
Project-wide Revit CSV configuration uses
`external-csv-configuration.update`. Its complete expected/target snapshot is
persisted through the shared project-command transaction, so create, update,
-delete, Undo and Redo are revision-safe. Snapshot schema 5-9, restore and portable
+delete, Undo and Redo are revision-safe. Snapshot schema 5-8, restore and portable
project duplication preserve the configuration; duplication remaps its internal
id and project link.
The project page exposes the Revit CSV modal. It edits the complete
@@ -327,7 +323,7 @@ immutable import batches with original bytes plus the classified cell matrix,
normalized source-room mappings and external objects unique by
`(sourceId, ifcGuid)`. Source values, planning values and overrides are separate.
The general external-model repository is read-only; runtime writes belong only
-to typed command adapters. Snapshot/restore and portable project transfer schema versions 5-9
+to typed command adapters. Snapshot/restore and portable project transfer schema versions 5-8
capture the complete external state and remap every internal UUID and link while
preserving IFCGUIDs, source values, original bytes and the classified matrix.
Import batches additionally store the monotonic project CSV configuration
@@ -467,34 +463,22 @@ snapshot contains the group, optional component protections, complete circuits,
circuit protections and all device-row link/override metadata. Delete
re-captures and compares the subtree before cascading; restore preserves every
UUID in foreign-key-safe order.
-The project-wide circuit-tree read model exposes fixed `headerComponents`, group-owned
+The circuit-tree read model exposes fixed `headerComponents`, group-owned
`components`, `footerComponents`, optional group category/number and optional
one-to-one protection DTOs for circuits and group components. These are
the only supported circuit-protection source; no flat compatibility fields remain.
-It resolves logical board feeds once for the complete project and adds each
-downstream board's demand after its own board simultaneity factor to the chosen
-feeding circuit as a read-only virtual row. Recursive chains and several boards
-on one circuit are supported. A board-only link affects hierarchy but adds no
-load until a feeding circuit is selected.
Distribution-board floor assignment and a project-enabled supply type use
`distribution-board.update`; both values are snapshot/export fields and one
persistent undo step.
-The same command uses schema version 2 for the logical
-`feedingDistributionBoardId` and optional matching `feedingCircuitId` together
-with the existing fields. Version-1 history commands remain executable and do
-not change logical feed references. The command rejects self-links, cycles and
-circuits from another board. The
+The same command (still schema version 1, additively extended) carries the
board info-card fields `boardNumber`, `location`, `fedFrom` and `remark`
(migration `0007`). They are nullable, trimmed planner values; the PUT route
only includes submitted info fields in the command and its inverse. An empty
board with info values is no longer an unchanged setup, so history deletion of
it is rejected like a changed simultaneity factor. Populated-subtree commands
stored before these fields existed are normalized to `null` values
-(`normalizeDistributionBoardSubtreeSnapshot`). `fedFrom` remains free text for
-external feeds and notes; project-board feeds use the two UUID references.
-Deleting a referenced board or feeding circuit is blocked in every direct,
-subtree and history-only removal path. Board copies retain their incoming feed,
-and project duplication remaps both references.
+(`normalizeDistributionBoardSubtreeSnapshot`). `fedFrom` is deliberately free
+text: the logical link to the feeding board/circuit is an open design decision.
These fields are intended to be prefillable from Revit parameters later, but
only through the Phase 14 rules (preview, explicit confirmation, no silent
overwrite).
@@ -537,10 +521,9 @@ Required operations:
`` so it repeats on printed pages. PDF export uses the browser print
dialog; page size, margins and "Seite x von y" come from injected named
`@page` rules (`lb-portrait`, `lb-landscape`).
-- The board hierarchy follows logical board ids. The overview shows power
- before board GZF and demand including linked downstream boards; it does not
- add a second cumulative column. Board plans render downstream-board loads as
- read-only rows. `fedFrom` groups only external root feeds.
+- The board hierarchy and "Σ inkl. unterlagert" in the overview are
+ PRELIMINARY (text match of `fedFrom` against board number/name, plain sum,
+ no upstream simultaneity factor). Replace this once the feed link is modeled.
- Colors/fonts follow the kbp corporate design (`--pd-*` variables in
`print-document.css`). Empty cover values are shown as
"[ergänzen]"; the Planstände editor column shows the project's current
@@ -718,9 +701,9 @@ for the early-phase power balance by DIN 276 cost group (see
- full electrical sizing
- multi-user/PostgreSQL operation
- supported production deployment
-- second/replacement board feeds, including changeover semantics for SV/SiBe
-- Revit prefill proposals for logical board feeds through the Phase 14 preview
- and explicit-confirmation workflow
+- logical feed link between distribution boards (`fedFrom` stays free text
+ until the project owner decides whether it references a board, a feeding
+ circuit or both) and the resulting hierarchical power summation
Do not implement these while working on an unrelated phase.
diff --git a/docs/circuit-list-editor-api.md b/docs/circuit-list-editor-api.md
index a8173f5..c9cd2b7 100644
--- a/docs/circuit-list-editor-api.md
+++ b/docs/circuit-list-editor-api.md
@@ -67,13 +67,10 @@ Prüfsumme; Ersetzen übernimmt die Grobbilanzen atomar und mit Undo/Redo,
Duplizieren vergibt neue IDs. Transferformat 1 bleibt ohne Grobbilanzen
importierbar.
-Das aktuelle Projekt-Snapshot-Schema 9 enthält die logischen Einspeisungsverweise
-der Verteilungen; Versionen 1–8 werden mit leeren Verweisen hochgestuft. Schema 8
-enthält außerdem die optionale Netto-Raumfläche `rooms[].areaM2`.
-Snapshot-Versionen 1–7 werden dafür mit
+Das aktuelle Projekt-Snapshot-Schema 8 enthält außerdem die optionale
+Netto-Raumfläche `rooms[].areaM2`. Snapshot-Versionen 1–7 werden dafür mit
`areaM2 = null` hochgestuft. Restore, Export und Import erhalten vorhandene
-Flächen und Einspeisungsverweise; beim Duplizieren werden Board- und Circuit-IDs
-neu zugeordnet. Eine eigene Transferformat-Version ist nicht nötig, weil Format 2 den
+Flächen; eine eigene Transferformat-Version ist nicht nötig, weil Format 2 den
versionierten Projektsnapshot einbettet.
The public dispatcher supports the Circuit and CircuitDeviceRow commands
@@ -257,30 +254,23 @@ commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
- body:
- `{ "floorId": null, "supplyType": "AV", "simultaneityFactor": 1, "feedingDistributionBoardId": "board_hv", "feedingCircuitId": "circuit_out", "expectedRevision": 13 }`
+ `{ "floorId": null, "supplyType": "AV", "simultaneityFactor": 1, "expectedRevision": 13 }`
- optional info-card fields: `boardNumber` (max. 50), `location`,
`fedFrom` (max. 200 each) and `remark` (max. 2000); strings are trimmed
and an empty string is stored as `null`
- - `feedingDistributionBoardId` and `feedingCircuitId` are optional nullable
- fields. A feeding circuit requires a feeding board and must belong to it;
- self-links and cycles are rejected. A board-only link creates hierarchy
- without assigning its load to an upstream circuit.
- - executes `distribution-board.update` schema version 2; all submitted values, including the
+ - executes `distribution-board.update`; all submitted values, including the
submitted info fields, are restored together by persistent Undo/Redo
- - `fedFrom` remains free text for external feeds such as HAK, metering,
- transformer or generator and for supplementary notes
+ - `fedFrom` is free text until the logical feed link is modeled
- `POST /projects/:projectId/distribution-boards/:distributionBoardId/copy`
- body: `{ "name": "UV-02 Kopie", "expectedRevision": 14 }`
- duplicates the complete distribution-board subtree with new owning UUIDs
- while preserving project-device, room and incoming feeding links
+ while preserving project-device and room links
- response includes the new `distributionBoard`, its `circuitList`, the
revision and current history state
- `DELETE /projects/:projectId/distribution-boards/:distributionBoardId`
- body: `{ "expectedRevision": 15 }`
- removes the complete, exactly captured distribution-board subtree in one
transaction
- - rejects deletion while another board references this board or one of its
- circuits as a feeder; circuit and populated-group deletion use the same guard
- response includes `distributionBoardId`, the revision and current history
state
- Copy and deletion use `distribution-board.insert-subtree` and
@@ -337,17 +327,12 @@ commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
### Tree Endpoint
-- `GET /projects/:projectId/circuit-trees`
- - returns all circuit trees for one project from one project-wide read and is
- used by the print view and feeding-board selection
- `GET /projects/:projectId/circuit-lists/:circuitListId/tree`
- Purpose: returns fixed header components, grouped sections with optional
protection components, complete circuit/device-row blocks and auxiliary
footer components. Circuit and component protection devices use their
separate one-to-one DTOs. Calculated row, circuit, section and board totals
- remain included. Circuit totals contain the demand of linked downstream
- boards. `downstreamBoardLoads` exposes those contributions as read-only rows;
- `feedingReference` resolves the current board/circuit labels by id.
+ remain included.
Response sketch:
diff --git a/docs/circuit-list-editor-known-limitations.md b/docs/circuit-list-editor-known-limitations.md
index fdc463a..78bb44e 100644
--- a/docs/circuit-list-editor-known-limitations.md
+++ b/docs/circuit-list-editor-known-limitations.md
@@ -33,13 +33,10 @@
- Protection and cable values are planner-owned manual selections. The later
sizing/warning engine does not yet validate them against load, length or
voltage-drop rules.
-- One logical incoming feed per distribution board is supported. A second or
- replacement feed with changeover semantics (for example SV/SiBe) is still a
- follow-up. `fedFrom` remains the free-text field for external sources and
- supplementary notes. Revit does not yet propose logical feed links.
-- A logical link without a selected feeding circuit appears in the hierarchy,
- but deliberately contributes no load to an upstream circuit until the planner
- selects that circuit (see `docs/print-view.md`).
+- Distribution-board `fedFrom` is free text. The print overview derives its
+ supply structure and "Σ inkl. unterlagert" only from exact text matches with
+ board numbers/names and adds values without an upstream simultaneity factor
+ (see `docs/print-view.md`).
- The print view uses a kbp corporate design derived from logo and
presentation material. Snapshots do not record an editor, so the cover's
Planstände list shows the project's current editor for every entry. On screen, long
diff --git a/docs/current-architecture.md b/docs/current-architecture.md
index 920002d..332b6a9 100644
--- a/docs/current-architecture.md
+++ b/docs/current-architecture.md
@@ -468,22 +468,8 @@ Derselbe Befehl versioniert die Felder der Verteiler-Infokarte
(`boardNumber`, `location`, `fedFrom`, `remark`, Migration `0007`). Die
Infokarte liegt im Bearbeiten-Dialog der Verteilung; die Werte sind lokale
Planungswerte und sollen später über den Revit-Abgleich nur nach Vorschau und
-ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` bleibt Freitext für
-externe Einspeisungen und Hinweise.
-`distribution-board.update` verwendet Schema-Version 2 für die optionalen
-logischen Verweise `feedingDistributionBoardId` und `feedingCircuitId`.
-Version-1-Befehle aus der Historie bleiben ausführbar und verändern diese
-Verweise nicht. Der speisende Stromkreis muss zur speisenden Verteilung gehören;
-Selbstverweise und Zyklen werden abgelehnt. Löschbefehle für referenzierte
-Verteilungen und Stromkreise, einschließlich Unterbaum- und Undo-Pfaden, werden
-vor der Datenbankänderung fachlich gesperrt.
-Die projektweite Circuit-Tree-Projektion berechnet rekursiv die Lasten aller
-Verteilungen. Die Leistung einer Unterverteilung nach ihrem eigenen
-Verteiler-GZF wird als virtuelle, schreibgeschützte Zeile genau dem gewählten
-Abgang zugerechnet. Eine Verknüpfung ohne Stromkreis bildet nur die Hierarchie.
-Snapshot-Schema 9 enthält die beiden Einspeisungsverweise; Versionen 1–8 werden
-mit `null` hochgestuft. Restore und Projektduplizierung erhalten beziehungsweise
-remappen die Verweise. Snapshot-Schema 8 enthält zusätzlich die optionale Netto-Raumfläche
+ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` ist vorerst Freitext.
+Snapshot-Schema 8 enthält zusätzlich die optionale Netto-Raumfläche
`rooms[].areaM2`; Versionen 1–7 werden dafür mit `null` hochgestuft.
Snapshot-Schema 7 enthält die Gebäude und die Gebäudezuordnung der Etagen;
ältere Versionen werden ohne Gebäude hochgestuft.
@@ -594,7 +580,7 @@ Oberfläche: `/projects/:projectId/grobbilanz`. Details:
`toLowerCase()` gegen das vollständige Register der Stromkreisliste und wird
von jedem Anlage-/Umbenennungspfad für Stromkreise und Verteilerkomponenten
aufgerufen, auch dort, wo zuvor kein Vorab-Check existierte. Snapshot- und
- Transfer-Integration verwenden aktuell Snapshot-Schema 9. Persistente
+ Transfer-Integration verwenden aktuell Snapshot-Schema 8. Persistente
Insert/Delete/Update-Commands für veränderliche Verteilerkomponenten,
Gruppen einschließlich befüllter Unterbäume sowie vollständige
Gruppensortierung sind integriert. Der Editor zeigt die geschützte Struktur
diff --git a/docs/print-view.md b/docs/print-view.md
index a843030..eba91ed 100644
--- a/docs/print-view.md
+++ b/docs/print-view.md
@@ -5,9 +5,8 @@ Die Aktionen für Büroangaben und Logo speichern dagegen globale Einstellungen.
Projekt- und Verteilerangaben werden in den jeweiligen Bearbeitungsdialogen
über Projektbefehle mit Undo/Redo gepflegt.
-Beim Laden werden alle Stromkreislisten in einer projektweiten Abfrage gelesen;
-deren Projektrevision und ein abschließend erneut gelesener Projektstand werden
-verglichen. Ändert sich das Projekt
+Beim Laden werden die Projektrevisionen aller Stromkreislisten und ein
+abschließend erneut gelesener Projektstand verglichen. Ändert sich das Projekt
währenddessen, wird statt eines gemischten Datenstands eine Aufforderung zum
Neuladen angezeigt. Eine bereits geladene Seitenansicht aktualisiert sich nicht
automatisch bei späteren Änderungen in einem anderen Tab.
@@ -32,14 +31,12 @@ automatisch bei späteren Änderungen in einem anderen Tab.
erscheinen als orangefarbenes „[ergänzen]“.
2. **Verteilerübersicht (quer)** – Versorgungsstruktur und Tabelle je
Verteiler mit Stromkreisanzahl, Pinst (Anzahl × Leistung je
- Stück, nur lokale Geräte), Leistung vor Verteiler-GZF einschließlich
- gespeister Unterverteilungen, Verteiler-GZF, Pgl einschließlich
- Unterverteilungen und IB . Die Hierarchie folgt den logischen IDs.
+ Stück), PGeräte (mit Geräte-GZF), Verteiler-GZF,
+ Pgl , IB und „Σ inkl. unterlagert“.
3. **Verteilerpläne (quer, je Verteiler)** – Titelleiste mit
Verteilernummer, Bezeichnung, Örtlichkeit, Netzart, „Gespeist von“ und
Pgl ; darunter Kopfkomponenten, Gruppen, Stromkreise
- (mehrzeilige mit Sammelzeile, eingerückten Gerätezeilen und
- schreibgeschützten Unterverteilungszeilen),
+ (mehrzeilige mit Sammelzeile und eingerückten Gerätezeilen),
Gruppensummen und Verteilersumme. Fehlende Kabelangaben sind als „offen“
markiert. Die Titelleiste steht im Tabellenkopf und wiederholt sich beim
Drucken auf Folgeseiten.
@@ -49,25 +46,19 @@ Seitenformat, Ränder und „Seite x von y“ kommen beim Drucken aus benannten
einfügt. Die Bildschirmansicht zeigt Blätter in Papiergröße; die exakte
Seitenaufteilung langer Verteilerpläne entsteht erst beim Drucken.
-## Berechnung und offene Punkte
+## Vorläufige Punkte
-- **Einspeisung / Hierarchie:** `feedingDistributionBoardId` bestimmt die
- Hierarchie. Ist zusätzlich `feedingCircuitId` gesetzt, fließt die Leistung
- der Unterverteilung nach deren eigenem Verteiler-GZF als virtuelle Last in
- genau diesen Abgang ein. Ketten werden rekursiv berechnet; mehrere
- Unterverteilungen an einem Abgang sind erlaubt. Ohne Abgang bleibt es bei der
- Hierarchie ohne Leistungsaufschlag. `fedFrom` bezeichnet nur externe Quellen
- oder ergänzende Hinweise.
-- **Keine Doppelzählung:** Die Abgangs- und Verteilersummen enthalten die
- Unterverteilungsleistung bereits. Deshalb gibt es keine zusätzliche Spalte
- „Σ inkl. unterlagert“. Die Summenzeile verwendet für hierarchische Leistungen
- nur Wurzelverteilungen; Pinst summiert weiterhin die lokalen Geräte.
-- **IB :** S entsteht aus Σ(P / cos φ) für Geräte und den wirksamen
- cos φ der Unterverteilungen, jeweils mit dem fachlich zugehörigen GZF. Bei
- einem 1-phasigen speisenden Abgang gilt I = S / U1~ , sonst
- I = S / (√3 · U3~ ). Für externe oder nur bis zur Verteilung
- bekannte Einspeisungen bleibt die bisherige Darstellung als symmetrischer
- Drehstromwert bestehen.
+- **Einspeisung / Hierarchie:** `fedFrom` ist Freitext. Die Übersicht ordnet
+ einen Verteiler nur dann unter, wenn der Text eindeutig der Nummer oder
+ Bezeichnung eines anderen Verteilers entspricht (ohne Beachtung von
+ Groß-/Kleinschreibung und äußeren Leerzeichen). Mehrdeutige Treffer bleiben
+ ohne Zuordnung; „Σ inkl. unterlagert“ ist
+ eine einfache Summe ohne übergeordneten GZF. Die fachliche Verknüpfung
+ (Verteiler und/oder speisender Stromkreis) und die Summierungslogik sind
+ offen.
+- **IB ** ist ein symmetrischer Drehstromwert
+ S / (√3 · U3~ ) mit S aus P / cos φ je Gerätezeile
+ (fehlendes cos φ = 1) und Verteiler-GZF.
- **Corporate Design:** Farben (`--pd-*` in
`src/frontend/print/print-document.css`) und Schrift (Arial) sind aus Logo
und kbp-Präsentationsmaterial abgeleitet und werden durch die offizielle
@@ -127,11 +118,10 @@ ihren Wert.
## Bestehende Datenbanken
-Vor dem Anwenden der additiven Migrationen einschließlich `0012` eine verifizierte
+Vor dem Anwenden der additiven Migrationen `0007` und `0008` eine verifizierte
Datenbanksicherung mit `npm run db:backup` erstellen. Die neuen Projekt- und
Verteilerfelder bleiben bei bestehenden Datensätzen zunächst leer (`null`).
-Snapshot-Schema 9 enthält zusätzlich die logischen Einspeisungsverweise; die
-unterstützten Versionen 1–8 werden mit leeren Verweisen hochgestuft. Restore und
-Projektkopie erhalten beziehungsweise remappen die neuen Angaben. Globale
-Büroangaben und Logo gehören zur Datenbanksicherung, nicht
+Snapshot-Schema 6 enthält beide Feldgruppen; die unterstützten Versionen 1–5
+werden beim Lesen hochgestuft. Restore und Projektkopie erhalten die neuen
+Angaben. Globale Büroangaben und Logo gehören zur Datenbanksicherung, nicht
zum logischen Projektsnapshot oder Projekttransfer.
diff --git a/docs/spec/08-current-product-backlog.md b/docs/spec/08-current-product-backlog.md
index c0a2720..d31eecc 100644
--- a/docs/spec/08-current-product-backlog.md
+++ b/docs/spec/08-current-product-backlog.md
@@ -149,10 +149,9 @@ The pre-release cleanup established the following compatibility boundary:
- [x] Rough power balance for LPH 2/3 by cost group, including the comparison
with the detailed balance and the explicit copy of one row as a
ProjectDevice.
-- [x] Logical distribution-board feed link with an optional feeding circuit,
- recursive load rollup and deletion guards (#3).
-Open follow-ups (Gitea issues): bill of quantities export (#5), area-based loads in W/m² for
+Open follow-ups (Gitea issues): logical feed link between distribution boards
+(#3), bill of quantities export (#5), area-based loads in W/m² for
ProjectDevices and device rows (#20).
## Recommended Sequence
diff --git a/src/app/globals.css b/src/app/globals.css
index d14431b..de46d37 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1541,16 +1541,6 @@ a.kpi:hover {
color: var(--text-faint);
}
-.tree-grid .downstream-board-load-row td {
- background: var(--panel-bg-subtle);
- color: var(--text-muted);
- font-style: italic;
-}
-
-.tree-grid .downstream-board-load-row td:nth-child(2) {
- padding-left: 1.5rem;
-}
-
.tree-grid .empty-circuit-row td {
background: var(--panel-bg-subtle);
}
diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx
index 4a99cf0..ed03cd9 100644
--- a/src/app/projects/[projectId]/page.tsx
+++ b/src/app/projects/[projectId]/page.tsx
@@ -56,7 +56,6 @@ import {
disconnectProjectDeviceRows,
exportProjectTransfer,
getProject,
- getProjectCircuitTrees,
getProjectDeviceSyncPreview,
listBuildings,
listCircuitLists,
@@ -74,7 +73,6 @@ import {
import type {
BuildingDto,
CircuitListDto,
- CircuitTreeResponseDto,
CreateRoomInput,
CreateProjectDeviceInput,
DistributionBoardDto,
@@ -153,7 +151,6 @@ export default function ProjectDetailPage() {
const [project, setProject] = useState(null);
const [boards, setBoards] = useState([]);
const [circuitLists, setCircuitLists] = useState([]);
- const [circuitTrees, setCircuitTrees] = useState([]);
const [buildings, setBuildings] = useState([]);
const [floors, setFloors] = useState([]);
const [rooms, setRooms] = useState([]);
@@ -173,8 +170,6 @@ export default function ProjectDetailPage() {
setEditingBoardSimultaneityFactor,
] = useState("1");
const [editingBoardCopyName, setEditingBoardCopyName] = useState("");
- const [editingFeedingBoardId, setEditingFeedingBoardId] = useState("");
- const [editingFeedingCircuitId, setEditingFeedingCircuitId] = useState("");
const [editingBoardInfo, setEditingBoardInfo] =
useState(
toDistributionBoardInfoDraft({
@@ -214,7 +209,6 @@ export default function ProjectDetailPage() {
getProject(projectId),
listDistributionBoards(projectId),
listCircuitLists(projectId),
- getProjectCircuitTrees(projectId),
listBuildings(projectId),
listFloors(projectId),
listRooms(projectId),
@@ -225,7 +219,6 @@ export default function ProjectDetailPage() {
currentProject,
distributionBoards,
loadedCircuitLists,
- loadedCircuitTrees,
loadedBuildings,
loadedFloors,
loadedRooms,
@@ -235,7 +228,6 @@ export default function ProjectDetailPage() {
setProject(currentProject);
setBoards(distributionBoards);
setCircuitLists(loadedCircuitLists);
- setCircuitTrees(loadedCircuitTrees.trees);
setBuildings(loadedBuildings);
setFloors(loadedFloors);
setRooms(loadedRooms);
@@ -318,48 +310,6 @@ export default function ProjectDetailPage() {
() => new Map(circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList])),
[circuitLists]
);
- const circuitTreeByBoardId = useMemo(
- () => new Map(circuitTrees.map((tree) => [tree.distributionBoardId, tree])),
- [circuitTrees]
- );
- const feedingBoardOptions = useMemo(() => {
- if (!editingBoard) return [];
- const boardById = new Map(boards.map((board) => [board.id, board]));
- return boards.filter((candidate) => {
- if (candidate.id === editingBoard.id) return false;
- const visited = new Set();
- let current: DistributionBoardDto | undefined = candidate;
- while (current && current.feedingDistributionBoardId && !visited.has(current.id)) {
- if (current.feedingDistributionBoardId === editingBoard.id) return false;
- visited.add(current.id);
- current = boardById.get(current.feedingDistributionBoardId);
- }
- return true;
- });
- }, [boards, editingBoard]);
- const selectedFeedingTree = editingFeedingBoardId
- ? circuitTreeByBoardId.get(editingFeedingBoardId)
- : undefined;
- const selectedFeedingCircuits = useMemo(
- () =>
- selectedFeedingTree?.sections.flatMap((section) =>
- section.circuits.map((circuit) => ({ circuit, section }))
- ) ?? [],
- [selectedFeedingTree]
- );
- const selectedFeedingCircuit = selectedFeedingCircuits.find(
- ({ circuit }) => circuit.id === editingFeedingCircuitId
- );
- const selectedFeedingBoard = boards.find(
- (board) => board.id === editingFeedingBoardId
- );
- const selectedFeedingCircuitUseCount = editingFeedingCircuitId
- ? boards.filter(
- (board) =>
- board.id !== editingBoard?.id &&
- board.feedingCircuitId === editingFeedingCircuitId
- ).length
- : 0;
const enabledBoardSupplyTypes =
project?.enabledDistributionBoardSupplyTypes ?? [];
const usedBoardSupplyTypes = useMemo(
@@ -379,21 +329,6 @@ export default function ProjectDetailPage() {
[boards]
);
- function formatBoardFeedingReference(board: DistributionBoardDto) {
- if (!board.feedingDistributionBoardId) return board.fedFrom ?? "–";
- const feedingBoard = boards.find(
- (candidate) => candidate.id === board.feedingDistributionBoardId
- );
- if (!feedingBoard) return "Unbekannte Verteilung";
- const boardLabel = feedingBoard.boardNumber ?? feedingBoard.name;
- if (!board.feedingCircuitId) return boardLabel;
- const circuit = circuitTreeByBoardId
- .get(feedingBoard.id)
- ?.sections.flatMap((section) => section.circuits)
- .find((candidate) => candidate.id === board.feedingCircuitId);
- return `${boardLabel}${circuit?.equipmentIdentifier ?? ""}`;
- }
-
function applyProjectRevision(currentRevision: number) {
setProject((current) =>
current ? { ...current, currentRevision } : current
@@ -423,7 +358,6 @@ export default function ProjectDetailPage() {
]);
setCircuitLists(await listCircuitLists(projectId));
applyProjectRevision(result.history.currentRevision);
- setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
setBoardName("");
setBoardFloorId("");
setBoardSupplyType(
@@ -554,8 +488,6 @@ export default function ProjectDetailPage() {
);
setEditingBoardCopyName(`${board.name} Kopie`);
setEditingBoardInfo(toDistributionBoardInfoDraft(board));
- setEditingFeedingBoardId(board.feedingDistributionBoardId ?? "");
- setEditingFeedingCircuitId(board.feedingCircuitId ?? "");
}
function openBoardCreator() {
@@ -591,8 +523,6 @@ export default function ProjectDetailPage() {
floorId: editingBoardFloorId || null,
supplyType: editingBoardSupplyType,
simultaneityFactor,
- feedingDistributionBoardId: editingFeedingBoardId || null,
- feedingCircuitId: editingFeedingCircuitId || null,
...fromDistributionBoardInfoDraft(editingBoardInfo),
},
project.currentRevision
@@ -605,7 +535,6 @@ export default function ProjectDetailPage() {
)
);
applyProjectRevision(result.history.currentRevision);
- setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
setEditingBoard(null);
} catch (err) {
setError(
@@ -661,7 +590,6 @@ export default function ProjectDetailPage() {
result.circuitList,
]);
applyProjectRevision(result.history.currentRevision);
- setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
setEditingBoard(null);
} catch (err) {
setError(
@@ -705,7 +633,6 @@ export default function ProjectDetailPage() {
)
);
applyProjectRevision(result.history.currentRevision);
- setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
setEditingBoard(null);
} catch (err) {
setError(
@@ -1062,7 +989,6 @@ export default function ProjectDetailPage() {
Örtlichkeit
Etage
Netzart
- Gespeist von
GZF
Aktionen
@@ -1089,7 +1015,6 @@ export default function ProjectDetailPage() {
]
: "Nicht festgelegt"}
- {formatBoardFeedingReference(board)}
{board.simultaneityFactor.toLocaleString(
"de-DE",
@@ -1467,7 +1392,7 @@ export default function ProjectDetailPage() {
{editingBoard ? (
setEditingBoard(null)}
onSubmit={handleUpdateBoard}
@@ -1554,91 +1479,6 @@ export default function ProjectDetailPage() {
-
-
- Logische Einspeisung
-
-
-
-
- Speisende Verteilung
-
- {
- setEditingFeedingBoardId(event.target.value);
- setEditingFeedingCircuitId("");
- }}
- value={editingFeedingBoardId}
- >
- Keine Projektverteilung
- {feedingBoardOptions.map((board) => (
-
- {board.boardNumber ?? board.name}
-
- ))}
-
-
-
-
- Speisender Abgang
-
- setEditingFeedingCircuitId(event.target.value)}
- value={editingFeedingCircuitId}
- >
- Nur Verteilung bekannt
- {selectedFeedingCircuits.map(({ circuit }) => (
-
- {circuit.equipmentIdentifier}
- {circuit.displayName ? ` · ${circuit.displayName}` : ""}
-
- ))}
-
-
- {editingFeedingBoardId && !editingFeedingCircuitId ? (
-
-
- Ohne speisenden Abgang wird die Hierarchie angezeigt, die
- Leistung dieser Verteilung aber keinem Stromkreis zugerechnet.
-
-
- ) : null}
- {selectedFeedingBoard?.supplyType &&
- editingBoardSupplyType !== selectedFeedingBoard.supplyType ? (
-
-
- Die Netzarten weichen ab: {selectedFeedingBoard.supplyType}
- {" → "}{editingBoardSupplyType}. Bitte Umschaltung und
- Versorgungskonzept prüfen.
-
-
- ) : null}
- {selectedFeedingCircuit &&
- selectedFeedingCircuit.section.category !== "three_phase" ? (
-
-
- Der gewählte Abgang ist 1-phasig. Bitte prüfen, ob diese
- Einspeisung für die Unterverteilung geeignet ist.
-
-
- ) : null}
- {selectedFeedingCircuitUseCount > 0 ? (
-
-
- Dieser Abgang speist bereits {selectedFeedingCircuitUseCount}
- {selectedFeedingCircuitUseCount === 1
- ? " weitere Verteilung."
- : " weitere Verteilungen."}
-
-
- ) : null}
-
-
[floor.id, floor.name]));
- const treeByBoardId = new Map(
- projectTrees.trees.map((tree) => [tree.distributionBoardId, tree])
- );
- const summaries =
+ const summaries = await Promise.all(
[...boards]
.sort(
(left, right) =>
@@ -109,17 +107,20 @@ export default function ProjectPrintPage() {
boardSortCollator.compare(left.name, right.name)
)
.flatMap((board) => {
- const tree = treeByBoardId.get(board.id);
- return tree ? [{ board, tree }] : [];
+ const circuitList = circuitLists.find(
+ (list) => list.distributionBoardId === board.id
+ );
+ return circuitList ? [{ board, circuitList }] : [];
})
- .map(({ board, tree }) =>
+ .map(async ({ board, circuitList }) =>
summarizePrintBoard({
board,
- circuitListId: tree.circuitListId,
- tree,
+ circuitListId: circuitList.id,
+ tree: await getCircuitTree(projectId, circuitList.id),
floorName: board.floorId ? floorNameById.get(board.floorId) ?? null : null,
})
- );
+ )
+ );
const finalProject = await getProject(projectId);
assertPrintRevisionConsistency(project.currentRevision, summaries, finalProject.currentRevision);
if (cancelled) {
@@ -147,7 +148,7 @@ export default function ProjectPrintPage() {
}, [projectId]);
const entries = useMemo(
- () => (data ? buildBoardHierarchy(data.summaries) : []),
+ () => (data ? buildPreliminaryBoardHierarchy(data.summaries) : []),
[data]
);
const stand = useMemo(
diff --git a/src/db/migrations/0012_good_gateway.sql b/src/db/migrations/0012_good_gateway.sql
deleted file mode 100644
index 57ad9dc..0000000
--- a/src/db/migrations/0012_good_gateway.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-ALTER TABLE `distribution_boards` ADD `feeding_distribution_board_id` text REFERENCES distribution_boards(id);--> statement-breakpoint
-ALTER TABLE `distribution_boards` ADD `feeding_circuit_id` text REFERENCES circuits(id);--> statement-breakpoint
-CREATE INDEX `distribution_boards_feeding_board_idx` ON `distribution_boards` (`feeding_distribution_board_id`);--> statement-breakpoint
-CREATE INDEX `distribution_boards_feeding_circuit_idx` ON `distribution_boards` (`feeding_circuit_id`);
\ No newline at end of file
diff --git a/src/db/migrations/meta/0012_snapshot.json b/src/db/migrations/meta/0012_snapshot.json
deleted file mode 100644
index 832cf1a..0000000
--- a/src/db/migrations/meta/0012_snapshot.json
+++ /dev/null
@@ -1,2986 +0,0 @@
-{
- "version": "6",
- "dialect": "sqlite",
- "id": "8029e6ab-56de-4345-8d38-0ca7462640de",
- "prevId": "63f27b3a-7378-462d-8119-b67d67903dbb",
- "tables": {
- "buildings": {
- "name": "buildings",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {
- "buildings_project_id_projects_id_fk": {
- "name": "buildings_project_id_projects_id_fk",
- "tableFrom": "buildings",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuit_device_rows": {
- "name": "circuit_device_rows",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "circuit_id": {
- "name": "circuit_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "linked_project_device_id": {
- "name": "linked_project_device_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "display_name": {
- "name": "display_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "phase_type": {
- "name": "phase_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "connection_kind": {
- "name": "connection_kind",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "cost_group": {
- "name": "cost_group",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "category": {
- "name": "category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "level": {
- "name": "level",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "room_id": {
- "name": "room_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "room_number_snapshot": {
- "name": "room_number_snapshot",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "room_name_snapshot": {
- "name": "room_name_snapshot",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "quantity": {
- "name": "quantity",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "manual_quantity": {
- "name": "manual_quantity",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "power_per_unit": {
- "name": "power_per_unit",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "simultaneity_factor": {
- "name": "simultaneity_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "cos_phi": {
- "name": "cos_phi",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "remark": {
- "name": "remark",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "overridden_fields": {
- "name": "overridden_fields",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "circuit_device_rows_circuit_id_idx": {
- "name": "circuit_device_rows_circuit_id_idx",
- "columns": [
- "circuit_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "circuit_device_rows_circuit_id_circuits_id_fk": {
- "name": "circuit_device_rows_circuit_id_circuits_id_fk",
- "tableFrom": "circuit_device_rows",
- "tableTo": "circuits",
- "columnsFrom": [
- "circuit_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "circuit_device_rows_linked_project_device_id_project_devices_id_fk": {
- "name": "circuit_device_rows_linked_project_device_id_project_devices_id_fk",
- "tableFrom": "circuit_device_rows",
- "tableTo": "project_devices",
- "columnsFrom": [
- "linked_project_device_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "circuit_device_rows_room_id_rooms_id_fk": {
- "name": "circuit_device_rows_room_id_rooms_id_fk",
- "tableFrom": "circuit_device_rows",
- "tableTo": "rooms",
- "columnsFrom": [
- "room_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuit_list_equipment_identifiers": {
- "name": "circuit_list_equipment_identifiers",
- "columns": {
- "owner_type": {
- "name": "owner_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "owner_id": {
- "name": "owner_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "circuit_list_id": {
- "name": "circuit_list_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "equipment_identifier": {
- "name": "equipment_identifier",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "circuit_list_equipment_identifiers_owner_unique": {
- "name": "circuit_list_equipment_identifiers_owner_unique",
- "columns": [
- "owner_type",
- "owner_id"
- ],
- "isUnique": true
- },
- "circuit_list_equipment_identifiers_list_identifier_unique": {
- "name": "circuit_list_equipment_identifiers_list_identifier_unique",
- "columns": [
- "circuit_list_id",
- "equipment_identifier"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk": {
- "name": "circuit_list_equipment_identifiers_circuit_list_id_circuit_lists_id_fk",
- "tableFrom": "circuit_list_equipment_identifiers",
- "tableTo": "circuit_lists",
- "columnsFrom": [
- "circuit_list_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuit_lists": {
- "name": "circuit_lists",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "distribution_board_id": {
- "name": "distribution_board_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "circuit_lists_distribution_board_id_unique": {
- "name": "circuit_lists_distribution_board_id_unique",
- "columns": [
- "distribution_board_id"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "circuit_lists_project_id_projects_id_fk": {
- "name": "circuit_lists_project_id_projects_id_fk",
- "tableFrom": "circuit_lists",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "circuit_lists_distribution_board_id_distribution_boards_id_fk": {
- "name": "circuit_lists_distribution_board_id_distribution_boards_id_fk",
- "tableFrom": "circuit_lists",
- "tableTo": "distribution_boards",
- "columnsFrom": [
- "distribution_board_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuit_protection_devices": {
- "name": "circuit_protection_devices",
- "columns": {
- "circuit_id": {
- "name": "circuit_id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "rated_current_a": {
- "name": "rated_current_a",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "fuse_utilization_category": {
- "name": "fuse_utilization_category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "trip_characteristic": {
- "name": "trip_characteristic",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "rcd_type": {
- "name": "rcd_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "rated_residual_current_ma": {
- "name": "rated_residual_current_ma",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "circuit_protection_devices_circuit_id_circuits_id_fk": {
- "name": "circuit_protection_devices_circuit_id_circuits_id_fk",
- "tableFrom": "circuit_protection_devices",
- "tableTo": "circuits",
- "columnsFrom": [
- "circuit_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuit_sections": {
- "name": "circuit_sections",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "circuit_list_id": {
- "name": "circuit_list_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "key": {
- "name": "key",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "display_name": {
- "name": "display_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "prefix": {
- "name": "prefix",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "category": {
- "name": "category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "group_number": {
- "name": "group_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "circuit_sections_list_key_unique": {
- "name": "circuit_sections_list_key_unique",
- "columns": [
- "circuit_list_id",
- "key"
- ],
- "isUnique": true
- },
- "circuit_sections_list_prefix_unique": {
- "name": "circuit_sections_list_prefix_unique",
- "columns": [
- "circuit_list_id",
- "prefix"
- ],
- "isUnique": true
- },
- "circuit_sections_list_category_group_unique": {
- "name": "circuit_sections_list_category_group_unique",
- "columns": [
- "circuit_list_id",
- "category",
- "group_number"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "circuit_sections_circuit_list_id_circuit_lists_id_fk": {
- "name": "circuit_sections_circuit_list_id_circuit_lists_id_fk",
- "tableFrom": "circuit_sections",
- "tableTo": "circuit_lists",
- "columnsFrom": [
- "circuit_list_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "circuits": {
- "name": "circuits",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "circuit_list_id": {
- "name": "circuit_list_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "section_id": {
- "name": "section_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "equipment_identifier": {
- "name": "equipment_identifier",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "display_name": {
- "name": "display_name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "cable_type": {
- "name": "cable_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "cable_cross_section": {
- "name": "cable_cross_section",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "cable_length": {
- "name": "cable_length",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "rcd_assignment": {
- "name": "rcd_assignment",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "terminal_designation": {
- "name": "terminal_designation",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "voltage": {
- "name": "voltage",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "control_requirement": {
- "name": "control_requirement",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "is_reserve": {
- "name": "is_reserve",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "remark": {
- "name": "remark",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "circuits_section_id_idx": {
- "name": "circuits_section_id_idx",
- "columns": [
- "section_id"
- ],
- "isUnique": false
- },
- "circuits_list_equipment_identifier_unique": {
- "name": "circuits_list_equipment_identifier_unique",
- "columns": [
- "circuit_list_id",
- "equipment_identifier"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "circuits_circuit_list_id_circuit_lists_id_fk": {
- "name": "circuits_circuit_list_id_circuit_lists_id_fk",
- "tableFrom": "circuits",
- "tableTo": "circuit_lists",
- "columnsFrom": [
- "circuit_list_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "circuits_section_id_circuit_sections_id_fk": {
- "name": "circuits_section_id_circuit_sections_id_fk",
- "tableFrom": "circuits",
- "tableTo": "circuit_sections",
- "columnsFrom": [
- "section_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "distribution_board_component_protection_devices": {
- "name": "distribution_board_component_protection_devices",
- "columns": {
- "component_id": {
- "name": "component_id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "rated_current_a": {
- "name": "rated_current_a",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "fuse_utilization_category": {
- "name": "fuse_utilization_category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "trip_characteristic": {
- "name": "trip_characteristic",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "rcd_type": {
- "name": "rcd_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "rated_residual_current_ma": {
- "name": "rated_residual_current_ma",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk": {
- "name": "distribution_board_component_protection_devices_component_id_distribution_board_components_id_fk",
- "tableFrom": "distribution_board_component_protection_devices",
- "tableTo": "distribution_board_components",
- "columnsFrom": [
- "component_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "distribution_board_components": {
- "name": "distribution_board_components",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "circuit_list_id": {
- "name": "circuit_list_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "section_id": {
- "name": "section_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "equipment_identifier": {
- "name": "equipment_identifier",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "placement": {
- "name": "placement",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {
- "distribution_board_components_list_identifier_unique": {
- "name": "distribution_board_components_list_identifier_unique",
- "columns": [
- "circuit_list_id",
- "equipment_identifier"
- ],
- "isUnique": true
- },
- "distribution_board_components_section_role_unique": {
- "name": "distribution_board_components_section_role_unique",
- "columns": [
- "section_id",
- "role"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "distribution_board_components_circuit_list_id_circuit_lists_id_fk": {
- "name": "distribution_board_components_circuit_list_id_circuit_lists_id_fk",
- "tableFrom": "distribution_board_components",
- "tableTo": "circuit_lists",
- "columnsFrom": [
- "circuit_list_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "distribution_board_components_section_id_circuit_sections_id_fk": {
- "name": "distribution_board_components_section_id_circuit_sections_id_fk",
- "tableFrom": "distribution_board_components",
- "tableTo": "circuit_sections",
- "columnsFrom": [
- "section_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "distribution_boards": {
- "name": "distribution_boards",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "floor_id": {
- "name": "floor_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "supply_type": {
- "name": "supply_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "simultaneity_factor": {
- "name": "simultaneity_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 1
- },
- "board_number": {
- "name": "board_number",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "location": {
- "name": "location",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "fed_from": {
- "name": "fed_from",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "remark": {
- "name": "remark",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "feeding_distribution_board_id": {
- "name": "feeding_distribution_board_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "feeding_circuit_id": {
- "name": "feeding_circuit_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "distribution_boards_feeding_board_idx": {
- "name": "distribution_boards_feeding_board_idx",
- "columns": [
- "feeding_distribution_board_id"
- ],
- "isUnique": false
- },
- "distribution_boards_feeding_circuit_idx": {
- "name": "distribution_boards_feeding_circuit_idx",
- "columns": [
- "feeding_circuit_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "distribution_boards_project_id_projects_id_fk": {
- "name": "distribution_boards_project_id_projects_id_fk",
- "tableFrom": "distribution_boards",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "distribution_boards_floor_id_floors_id_fk": {
- "name": "distribution_boards_floor_id_floors_id_fk",
- "tableFrom": "distribution_boards",
- "tableTo": "floors",
- "columnsFrom": [
- "floor_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "distribution_boards_feeding_distribution_board_id_distribution_boards_id_fk": {
- "name": "distribution_boards_feeding_distribution_board_id_distribution_boards_id_fk",
- "tableFrom": "distribution_boards",
- "tableTo": "distribution_boards",
- "columnsFrom": [
- "feeding_distribution_board_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "restrict",
- "onUpdate": "no action"
- },
- "distribution_boards_feeding_circuit_id_circuits_id_fk": {
- "name": "distribution_boards_feeding_circuit_id_circuits_id_fk",
- "tableFrom": "distribution_boards",
- "tableTo": "circuits",
- "columnsFrom": [
- "feeding_circuit_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "restrict",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "document_branding_assets": {
- "name": "document_branding_assets",
- "columns": {
- "key": {
- "name": "key",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "mime_type": {
- "name": "mime_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "file_name": {
- "name": "file_name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "data": {
- "name": "data",
- "type": "blob",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "updated_at": {
- "name": "updated_at",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "(unixepoch())"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "document_branding_settings": {
- "name": "document_branding_settings",
- "columns": {
- "key": {
- "name": "key",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "value": {
- "name": "value",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "updated_at": {
- "name": "updated_at",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "(unixepoch())"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "estimate_balances": {
- "name": "estimate_balances",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "work_stage": {
- "name": "work_stage",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "date_iso": {
- "name": "date_iso",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "reserve_av_percent": {
- "name": "reserve_av_percent",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "reserve_sv_percent": {
- "name": "reserve_sv_percent",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {
- "estimate_balances_project_id_idx": {
- "name": "estimate_balances_project_id_idx",
- "columns": [
- "project_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "estimate_balances_project_id_projects_id_fk": {
- "name": "estimate_balances_project_id_projects_id_fk",
- "tableFrom": "estimate_balances",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "estimate_groups": {
- "name": "estimate_groups",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "balance_id": {
- "name": "balance_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "cost_group": {
- "name": "cost_group",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "''"
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "editor": {
- "name": "editor",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {
- "estimate_groups_balance_id_idx": {
- "name": "estimate_groups_balance_id_idx",
- "columns": [
- "balance_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "estimate_groups_balance_id_estimate_balances_id_fk": {
- "name": "estimate_groups_balance_id_estimate_balances_id_fk",
- "tableFrom": "estimate_groups",
- "tableTo": "estimate_balances",
- "columnsFrom": [
- "balance_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "estimate_rows": {
- "name": "estimate_rows",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "group_id": {
- "name": "group_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "supply": {
- "name": "supply",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "''"
- },
- "quantity": {
- "name": "quantity",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "unit": {
- "name": "unit",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'piece'"
- },
- "connection": {
- "name": "connection",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "power_per_unit_w": {
- "name": "power_per_unit_w",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "simultaneity_factor": {
- "name": "simultaneity_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 1
- },
- "cos_phi": {
- "name": "cos_phi",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 1
- },
- "safety_supply": {
- "name": "safety_supply",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": false
- },
- "remark": {
- "name": "remark",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "estimate_rows_group_id_idx": {
- "name": "estimate_rows_group_id_idx",
- "columns": [
- "group_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "estimate_rows_group_id_estimate_groups_id_fk": {
- "name": "estimate_rows_group_id_estimate_groups_id_fk",
- "tableFrom": "estimate_rows",
- "tableTo": "estimate_groups",
- "columnsFrom": [
- "group_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "external_csv_configurations": {
- "name": "external_csv_configurations",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "configuration_version": {
- "name": "configuration_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "configuration": {
- "name": "configuration",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "external_csv_configurations_project_id_unique": {
- "name": "external_csv_configurations_project_id_unique",
- "columns": [
- "project_id"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "external_csv_configurations_project_id_projects_id_fk": {
- "name": "external_csv_configurations_project_id_projects_id_fk",
- "tableFrom": "external_csv_configurations",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "external_import_batches": {
- "name": "external_import_batches",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_id": {
- "name": "source_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "import_kind": {
- "name": "import_kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "imported_at_iso": {
- "name": "imported_at_iso",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "file_name": {
- "name": "file_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sha256": {
- "name": "sha256",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "applied_project_revision": {
- "name": "applied_project_revision",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "configuration_version": {
- "name": "configuration_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "configuration_snapshot": {
- "name": "configuration_snapshot",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "original_bytes": {
- "name": "original_bytes",
- "type": "blob",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "document": {
- "name": "document",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "external_import_batches_project_revision_idx": {
- "name": "external_import_batches_project_revision_idx",
- "columns": [
- "project_id",
- "applied_project_revision"
- ],
- "isUnique": false
- },
- "external_import_batches_source_imported_idx": {
- "name": "external_import_batches_source_imported_idx",
- "columns": [
- "source_id",
- "imported_at_iso"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "external_import_batches_project_id_projects_id_fk": {
- "name": "external_import_batches_project_id_projects_id_fk",
- "tableFrom": "external_import_batches",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "external_import_batches_source_id_external_model_sources_id_fk": {
- "name": "external_import_batches_source_id_external_model_sources_id_fk",
- "tableFrom": "external_import_batches",
- "tableTo": "external_model_sources",
- "columnsFrom": [
- "source_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "external_model_objects": {
- "name": "external_model_objects",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_id": {
- "name": "source_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "ifc_guid": {
- "name": "ifc_guid",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "last_seen_import_batch_id": {
- "name": "last_seen_import_batch_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "last_accepted_import_batch_id": {
- "name": "last_accepted_import_batch_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "accepted_source_values": {
- "name": "accepted_source_values",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "planning_values": {
- "name": "planning_values",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "overridden_fields": {
- "name": "overridden_fields",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "external_room_mapping_id": {
- "name": "external_room_mapping_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "distribution_board_id": {
- "name": "distribution_board_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "linked_project_device_id": {
- "name": "linked_project_device_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "circuit_device_row_id": {
- "name": "circuit_device_row_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "presence_status": {
- "name": "presence_status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'present'"
- }
- },
- "indexes": {
- "external_model_objects_source_ifc_guid_unique": {
- "name": "external_model_objects_source_ifc_guid_unique",
- "columns": [
- "source_id",
- "ifc_guid"
- ],
- "isUnique": true
- },
- "external_model_objects_project_presence_idx": {
- "name": "external_model_objects_project_presence_idx",
- "columns": [
- "project_id",
- "presence_status"
- ],
- "isUnique": false
- },
- "external_model_objects_distribution_board_idx": {
- "name": "external_model_objects_distribution_board_idx",
- "columns": [
- "distribution_board_id"
- ],
- "isUnique": false
- },
- "external_model_objects_circuit_device_row_idx": {
- "name": "external_model_objects_circuit_device_row_idx",
- "columns": [
- "circuit_device_row_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "external_model_objects_project_id_projects_id_fk": {
- "name": "external_model_objects_project_id_projects_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "external_model_objects_source_id_external_model_sources_id_fk": {
- "name": "external_model_objects_source_id_external_model_sources_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "external_model_sources",
- "columnsFrom": [
- "source_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk": {
- "name": "external_model_objects_last_seen_import_batch_id_external_import_batches_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "external_import_batches",
- "columnsFrom": [
- "last_seen_import_batch_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "restrict",
- "onUpdate": "no action"
- },
- "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk": {
- "name": "external_model_objects_last_accepted_import_batch_id_external_import_batches_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "external_import_batches",
- "columnsFrom": [
- "last_accepted_import_batch_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "restrict",
- "onUpdate": "no action"
- },
- "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk": {
- "name": "external_model_objects_external_room_mapping_id_external_room_mappings_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "external_room_mappings",
- "columnsFrom": [
- "external_room_mapping_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "external_model_objects_distribution_board_id_distribution_boards_id_fk": {
- "name": "external_model_objects_distribution_board_id_distribution_boards_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "distribution_boards",
- "columnsFrom": [
- "distribution_board_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "external_model_objects_linked_project_device_id_project_devices_id_fk": {
- "name": "external_model_objects_linked_project_device_id_project_devices_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "project_devices",
- "columnsFrom": [
- "linked_project_device_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk": {
- "name": "external_model_objects_circuit_device_row_id_circuit_device_rows_id_fk",
- "tableFrom": "external_model_objects",
- "tableTo": "circuit_device_rows",
- "columnsFrom": [
- "circuit_device_row_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "external_model_sources": {
- "name": "external_model_sources",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_type": {
- "name": "source_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'revit_csv'"
- }
- },
- "indexes": {
- "external_model_sources_project_type_unique": {
- "name": "external_model_sources_project_type_unique",
- "columns": [
- "project_id",
- "source_type"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "external_model_sources_project_id_projects_id_fk": {
- "name": "external_model_sources_project_id_projects_id_fk",
- "tableFrom": "external_model_sources",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "external_room_mappings": {
- "name": "external_room_mappings",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_id": {
- "name": "source_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "normalized_source_room_key": {
- "name": "normalized_source_room_key",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_floor_name": {
- "name": "source_floor_name",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "source_room_number": {
- "name": "source_room_number",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_room_name": {
- "name": "source_room_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "room_id": {
- "name": "room_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "default_distribution_board_id": {
- "name": "default_distribution_board_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "external_room_mappings_source_key_unique": {
- "name": "external_room_mappings_source_key_unique",
- "columns": [
- "source_id",
- "normalized_source_room_key"
- ],
- "isUnique": true
- },
- "external_room_mappings_project_room_idx": {
- "name": "external_room_mappings_project_room_idx",
- "columns": [
- "project_id",
- "room_id"
- ],
- "isUnique": false
- }
- },
- "foreignKeys": {
- "external_room_mappings_project_id_projects_id_fk": {
- "name": "external_room_mappings_project_id_projects_id_fk",
- "tableFrom": "external_room_mappings",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "external_room_mappings_source_id_external_model_sources_id_fk": {
- "name": "external_room_mappings_source_id_external_model_sources_id_fk",
- "tableFrom": "external_room_mappings",
- "tableTo": "external_model_sources",
- "columnsFrom": [
- "source_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "external_room_mappings_room_id_rooms_id_fk": {
- "name": "external_room_mappings_room_id_rooms_id_fk",
- "tableFrom": "external_room_mappings",
- "tableTo": "rooms",
- "columnsFrom": [
- "room_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- },
- "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk": {
- "name": "external_room_mappings_default_distribution_board_id_distribution_boards_id_fk",
- "tableFrom": "external_room_mappings",
- "tableTo": "distribution_boards",
- "columnsFrom": [
- "default_distribution_board_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "floors": {
- "name": "floors",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "building_id": {
- "name": "building_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "floors_project_id_projects_id_fk": {
- "name": "floors_project_id_projects_id_fk",
- "tableFrom": "floors",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "floors_building_id_buildings_id_fk": {
- "name": "floors_building_id_buildings_id_fk",
- "tableFrom": "floors",
- "tableTo": "buildings",
- "columnsFrom": [
- "building_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "global_devices": {
- "name": "global_devices",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "display_name": {
- "name": "display_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "category": {
- "name": "category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "quantity": {
- "name": "quantity",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "installed_power_per_unit_kw": {
- "name": "installed_power_per_unit_kw",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "demand_factor": {
- "name": "demand_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "voltage_v": {
- "name": "voltage_v",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "phase_count": {
- "name": "phase_count",
- "type": "integer",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "power_factor": {
- "name": "power_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "note": {
- "name": "note",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "project_change_sets": {
- "name": "project_change_sets",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_revision_id": {
- "name": "project_revision_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "command_type": {
- "name": "command_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "payload_schema_version": {
- "name": "payload_schema_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "forward_payload_json": {
- "name": "forward_payload_json",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "inverse_payload_json": {
- "name": "inverse_payload_json",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "project_change_sets_revision_unique": {
- "name": "project_change_sets_revision_unique",
- "columns": [
- "project_revision_id"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "project_change_sets_project_revision_id_project_revisions_id_fk": {
- "name": "project_change_sets_project_revision_id_project_revisions_id_fk",
- "tableFrom": "project_change_sets",
- "tableTo": "project_revisions",
- "columnsFrom": [
- "project_revision_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "project_devices": {
- "name": "project_devices",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "display_name": {
- "name": "display_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "phase_type": {
- "name": "phase_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'single_phase'"
- },
- "connection_kind": {
- "name": "connection_kind",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "cost_group": {
- "name": "cost_group",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "category": {
- "name": "category",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "quantity": {
- "name": "quantity",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "power_per_unit": {
- "name": "power_per_unit",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- },
- "simultaneity_factor": {
- "name": "simultaneity_factor",
- "type": "real",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 1
- },
- "cos_phi": {
- "name": "cos_phi",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "remark": {
- "name": "remark",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "voltage_v": {
- "name": "voltage_v",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "project_devices_project_id_projects_id_fk": {
- "name": "project_devices_project_id_projects_id_fk",
- "tableFrom": "project_devices",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "project_history_stack_entries": {
- "name": "project_history_stack_entries",
- "columns": {
- "change_set_id": {
- "name": "change_set_id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "stack": {
- "name": "stack",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "position": {
- "name": "position",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- }
- },
- "indexes": {
- "project_history_stack_entries_position_unique": {
- "name": "project_history_stack_entries_position_unique",
- "columns": [
- "project_id",
- "stack",
- "position"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "project_history_stack_entries_change_set_id_project_change_sets_id_fk": {
- "name": "project_history_stack_entries_change_set_id_project_change_sets_id_fk",
- "tableFrom": "project_history_stack_entries",
- "tableTo": "project_change_sets",
- "columnsFrom": [
- "change_set_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "project_history_stack_entries_project_id_projects_id_fk": {
- "name": "project_history_stack_entries_project_id_projects_id_fk",
- "tableFrom": "project_history_stack_entries",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {
- "project_history_stack_entries_stack_check": {
- "name": "project_history_stack_entries_stack_check",
- "value": "\"project_history_stack_entries\".\"stack\" in ('undo', 'redo')"
- }
- }
- },
- "project_revisions": {
- "name": "project_revisions",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "revision_number": {
- "name": "revision_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "created_at_iso": {
- "name": "created_at_iso",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "actor_id": {
- "name": "actor_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "source": {
- "name": "source",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "project_revisions_project_number_unique": {
- "name": "project_revisions_project_number_unique",
- "columns": [
- "project_id",
- "revision_number"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "project_revisions_project_id_projects_id_fk": {
- "name": "project_revisions_project_id_projects_id_fk",
- "tableFrom": "project_revisions",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "project_snapshots": {
- "name": "project_snapshots",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "source_revision": {
- "name": "source_revision",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "schema_version": {
- "name": "schema_version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "kind": {
- "name": "kind",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'named'"
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "payload_json": {
- "name": "payload_json",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "payload_sha256": {
- "name": "payload_sha256",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "created_at_iso": {
- "name": "created_at_iso",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "created_by_actor_id": {
- "name": "created_by_actor_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {
- "project_snapshots_project_created_idx": {
- "name": "project_snapshots_project_created_idx",
- "columns": [
- "project_id",
- "created_at_iso"
- ],
- "isUnique": false
- },
- "project_snapshots_project_kind_revision_idx": {
- "name": "project_snapshots_project_kind_revision_idx",
- "columns": [
- "project_id",
- "kind",
- "source_revision"
- ],
- "isUnique": false
- },
- "project_snapshots_project_name_unique": {
- "name": "project_snapshots_project_name_unique",
- "columns": [
- "project_id",
- "name"
- ],
- "isUnique": true
- }
- },
- "foreignKeys": {
- "project_snapshots_project_id_projects_id_fk": {
- "name": "project_snapshots_project_id_projects_id_fk",
- "tableFrom": "project_snapshots",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "projects": {
- "name": "projects",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "internal_project_number": {
- "name": "internal_project_number",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "external_project_number": {
- "name": "external_project_number",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "building_owner": {
- "name": "building_owner",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "is_public_building": {
- "name": "is_public_building",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": false
- },
- "single_phase_voltage_v": {
- "name": "single_phase_voltage_v",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 230
- },
- "three_phase_voltage_v": {
- "name": "three_phase_voltage_v",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 400
- },
- "enabled_distribution_board_supply_types": {
- "name": "enabled_distribution_board_supply_types",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": "'[\"AV\",\"SV\",\"EV\",\"USV\",\"MSR\",\"SiBe\"]'"
- },
- "site_address": {
- "name": "site_address",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "work_stage": {
- "name": "work_stage",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "project_lead": {
- "name": "project_lead",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "project_editor": {
- "name": "project_editor",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "current_revision": {
- "name": "current_revision",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false,
- "default": 0
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- },
- "rooms": {
- "name": "rooms",
- "columns": {
- "id": {
- "name": "id",
- "type": "text",
- "primaryKey": true,
- "notNull": true,
- "autoincrement": false
- },
- "project_id": {
- "name": "project_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "floor_id": {
- "name": "floor_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- },
- "room_number": {
- "name": "room_number",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "room_name": {
- "name": "room_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "autoincrement": false
- },
- "area_m2": {
- "name": "area_m2",
- "type": "real",
- "primaryKey": false,
- "notNull": false,
- "autoincrement": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "rooms_project_id_projects_id_fk": {
- "name": "rooms_project_id_projects_id_fk",
- "tableFrom": "rooms",
- "tableTo": "projects",
- "columnsFrom": [
- "project_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "rooms_floor_id_floors_id_fk": {
- "name": "rooms_floor_id_floors_id_fk",
- "tableFrom": "rooms",
- "tableTo": "floors",
- "columnsFrom": [
- "floor_id"
- ],
- "columnsTo": [
- "id"
- ],
- "onDelete": "set null",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "checkConstraints": {}
- }
- },
- "views": {},
- "enums": {},
- "_meta": {
- "schemas": {},
- "tables": {},
- "columns": {}
- },
- "internal": {
- "indexes": {}
- }
-}
\ No newline at end of file
diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json
index 548734d..90c3bd0 100644
--- a/src/db/migrations/meta/_journal.json
+++ b/src/db/migrations/meta/_journal.json
@@ -85,13 +85,6 @@
"when": 1790531351492,
"tag": "0011_glossy_norman_osborn",
"breakpoints": true
- },
- {
- "idx": 12,
- "version": "6",
- "when": 1790540142918,
- "tag": "0012_good_gateway",
- "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/db/repositories/circuit-device-row-move-project-command.repository.ts b/src/db/repositories/circuit-device-row-move-project-command.repository.ts
index e1e3f5a..2c5bfc9 100644
--- a/src/db/repositories/circuit-device-row-move-project-command.repository.ts
+++ b/src/db/repositories/circuit-device-row-move-project-command.repository.ts
@@ -23,7 +23,6 @@ import { circuits } from "../schema/circuits.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
import { updateDerivedCircuitVoltage } from "./project-voltage.persistence.js";
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
-import { assertCircuitsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
interface PersistedMoveRow {
id: string;
@@ -172,9 +171,6 @@ export class CircuitDeviceRowMoveProjectCommandRepository
projectId,
destinationCircuitIds
);
- assertCircuitsAreNotReferencedAsFeeders(database, projectId, [
- targetCircuit.id,
- ]);
const deleted = database
.delete(circuits)
.where(
diff --git a/src/db/repositories/circuit-group-subtree-project-command.repository.ts b/src/db/repositories/circuit-group-subtree-project-command.repository.ts
index 9bf27a0..448cf99 100644
--- a/src/db/repositories/circuit-group-subtree-project-command.repository.ts
+++ b/src/db/repositories/circuit-group-subtree-project-command.repository.ts
@@ -26,7 +26,6 @@ import { distributionBoardComponentProtectionDevices } from "../schema/distribut
import { distributionBoardComponents } from "../schema/distribution-board-components.js";
import { toCircuitDeviceRowInsertValues } from "./circuit-device-row-structure.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
-import { assertCircuitsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
export class CircuitGroupSubtreeProjectCommandRepository
implements CircuitGroupSubtreeProjectCommandStore
@@ -66,11 +65,6 @@ export class CircuitGroupSubtreeProjectCommandRepository
"Circuit-group subtree changed before deletion."
);
}
- assertCircuitsAreNotReferencedAsFeeders(
- database,
- projectId,
- current.circuits.map((entry) => entry.circuit.id)
- );
const deleted = database
.delete(circuitSections)
.where(eq(circuitSections.id, current.group.id))
diff --git a/src/db/repositories/circuit-section.repository.ts b/src/db/repositories/circuit-section.repository.ts
index 79a3704..d7817ec 100644
--- a/src/db/repositories/circuit-section.repository.ts
+++ b/src/db/repositories/circuit-section.repository.ts
@@ -1,5 +1,5 @@
import crypto from "node:crypto";
-import { and, asc, eq, inArray } from "drizzle-orm";
+import { and, asc, eq } from "drizzle-orm";
import { defaultCircuitSectionDefinitions } from "../../domain/models/distribution-board-structure-project-command.model.js";
import type { AppDatabase } from "../database-context.js";
import { circuitSections } from "../schema/circuit-sections.js";
@@ -30,15 +30,6 @@ export class CircuitSectionRepository {
.orderBy(asc(circuitSections.sortOrder));
}
- async listByCircuitLists(circuitListIds: string[]) {
- if (circuitListIds.length === 0) return [];
- return this.database
- .select()
- .from(circuitSections)
- .where(inArray(circuitSections.circuitListId, circuitListIds))
- .orderBy(asc(circuitSections.sortOrder), asc(circuitSections.id));
- }
-
async createDefaults(circuitListId: string) {
const db = this.database;
for (const entry of createDefaultCircuitSectionValues(circuitListId)) {
diff --git a/src/db/repositories/circuit-structure-project-command.repository.ts b/src/db/repositories/circuit-structure-project-command.repository.ts
index e7cfcb2..9b8941d 100644
--- a/src/db/repositories/circuit-structure-project-command.repository.ts
+++ b/src/db/repositories/circuit-structure-project-command.repository.ts
@@ -26,7 +26,6 @@ import {
toCircuitDeviceRowSnapshot,
} from "./circuit-device-row-structure.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
-import { assertCircuitsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
import { resolveCircuitVoltage } from "./project-voltage.persistence.js";
import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js";
@@ -237,8 +236,6 @@ export class CircuitStructureProjectCommandRepository
protectionDevice: protectionDevice ?? null,
});
- assertCircuitsAreNotReferencedAsFeeders(database, projectId, [circuit.id]);
-
const result = database
.delete(circuits)
.where(
diff --git a/src/db/repositories/circuit.repository.ts b/src/db/repositories/circuit.repository.ts
index 7d40fa1..ecbaba0 100644
--- a/src/db/repositories/circuit.repository.ts
+++ b/src/db/repositories/circuit.repository.ts
@@ -1,4 +1,4 @@
-import { asc, eq, inArray } from "drizzle-orm";
+import { asc, eq } from "drizzle-orm";
import type { AppDatabase } from "../database-context.js";
import { circuits } from "../schema/circuits.js";
@@ -14,15 +14,6 @@ export class CircuitRepository {
.orderBy(asc(circuits.sortOrder), asc(circuits.equipmentIdentifier));
}
- async listByCircuitLists(circuitListIds: string[]) {
- if (circuitListIds.length === 0) return [];
- return this.database
- .select()
- .from(circuits)
- .where(inArray(circuits.circuitListId, circuitListIds))
- .orderBy(asc(circuits.sortOrder), asc(circuits.equipmentIdentifier));
- }
-
async listBySection(sectionId: string) {
const db = this.database;
return db
diff --git a/src/db/repositories/distribution-board-component.repository.ts b/src/db/repositories/distribution-board-component.repository.ts
index eedb4af..ab60603 100644
--- a/src/db/repositories/distribution-board-component.repository.ts
+++ b/src/db/repositories/distribution-board-component.repository.ts
@@ -19,18 +19,6 @@ export class DistributionBoardComponentRepository {
);
}
- async listByCircuitLists(circuitListIds: string[]) {
- if (circuitListIds.length === 0) return [];
- return this.database
- .select()
- .from(distributionBoardComponents)
- .where(inArray(distributionBoardComponents.circuitListId, circuitListIds))
- .orderBy(
- asc(distributionBoardComponents.sortOrder),
- asc(distributionBoardComponents.id)
- );
- }
-
async listProtectionByComponentIds(componentIds: string[]) {
if (componentIds.length === 0) {
return [];
diff --git a/src/db/repositories/distribution-board-feeding-deletion-guard.persistence.ts b/src/db/repositories/distribution-board-feeding-deletion-guard.persistence.ts
deleted file mode 100644
index 679f31e..0000000
--- a/src/db/repositories/distribution-board-feeding-deletion-guard.persistence.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { and, asc, eq, inArray } from "drizzle-orm";
-import type { AppDatabase } from "../database-context.js";
-import { distributionBoards } from "../schema/distribution-boards.js";
-
-export function assertBoardsAreNotReferencedAsFeeders(
- database: AppDatabase,
- projectId: string,
- distributionBoardIds: string[]
-) {
- if (distributionBoardIds.length === 0) return;
- const dependent = database
- .select({
- name: distributionBoards.name,
- boardNumber: distributionBoards.boardNumber,
- })
- .from(distributionBoards)
- .where(
- and(
- eq(distributionBoards.projectId, projectId),
- inArray(
- distributionBoards.feedingDistributionBoardId,
- [...new Set(distributionBoardIds)]
- )
- )
- )
- .orderBy(asc(distributionBoards.boardNumber), asc(distributionBoards.name))
- .limit(1)
- .get();
- if (dependent) throwFeedingReferenceError(dependent);
-}
-
-export function assertCircuitsAreNotReferencedAsFeeders(
- database: AppDatabase,
- projectId: string,
- circuitIds: string[]
-) {
- if (circuitIds.length === 0) return;
- const dependent = database
- .select({
- name: distributionBoards.name,
- boardNumber: distributionBoards.boardNumber,
- })
- .from(distributionBoards)
- .where(
- and(
- eq(distributionBoards.projectId, projectId),
- inArray(
- distributionBoards.feedingCircuitId,
- [...new Set(circuitIds)]
- )
- )
- )
- .orderBy(asc(distributionBoards.boardNumber), asc(distributionBoards.name))
- .limit(1)
- .get();
- if (dependent) throwFeedingReferenceError(dependent);
-}
-
-function throwFeedingReferenceError(dependent: {
- name: string;
- boardNumber: string | null;
-}): never {
- const label = dependent.boardNumber ?? dependent.name;
- throw new Error(`Speist ${label} – erst Einspeisung ändern.`);
-}
diff --git a/src/db/repositories/distribution-board-structure-project-command.repository.ts b/src/db/repositories/distribution-board-structure-project-command.repository.ts
index 9346df7..db2cede 100644
--- a/src/db/repositories/distribution-board-structure-project-command.repository.ts
+++ b/src/db/repositories/distribution-board-structure-project-command.repository.ts
@@ -30,7 +30,6 @@ import { distributionBoards } from "../schema/distribution-boards.js";
import { floors } from "../schema/floors.js";
import { projects } from "../schema/projects.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
-import { assertBoardsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
export class DistributionBoardStructureProjectCommandRepository
implements DistributionBoardStructureProjectCommandStore
@@ -249,8 +248,6 @@ export class DistributionBoardStructureProjectCommandRepository
);
}
- assertBoardsAreNotReferencedAsFeeders(database, projectId, [board.id]);
-
const deleted = database
.delete(distributionBoards)
.where(
@@ -328,16 +325,6 @@ export class DistributionBoardStructureProjectCommandRepository
patch.supplyType
);
}
- this.assertFeedingReference(database, input.projectId, current.id, {
- feedingDistributionBoardId:
- patch.feedingDistributionBoardId === undefined
- ? current.feedingDistributionBoardId
- : patch.feedingDistributionBoardId,
- feedingCircuitId:
- patch.feedingCircuitId === undefined
- ? current.feedingCircuitId
- : patch.feedingCircuitId,
- });
const inversePatch = Object.fromEntries(
input.command.payload.changes.map((change) => [
change.field,
@@ -346,8 +333,7 @@ export class DistributionBoardStructureProjectCommandRepository
) as DistributionBoardUpdatePatch;
const inverse = createDistributionBoardUpdateProjectCommand(
current.id,
- inversePatch,
- input.command.schemaVersion
+ inversePatch
);
const updated = database
.update(distributionBoards)
@@ -366,77 +352,6 @@ export class DistributionBoardStructureProjectCommandRepository
}
return inverse;
}
-
- private assertFeedingReference(
- database: AppDatabase,
- projectId: string,
- distributionBoardId: string,
- reference: {
- feedingDistributionBoardId: string | null;
- feedingCircuitId: string | null;
- }
- ) {
- const { feedingDistributionBoardId, feedingCircuitId } = reference;
- if (feedingDistributionBoardId === null) {
- if (feedingCircuitId !== null) {
- throw new Error(
- "Ein speisender Stromkreis erfordert eine speisende Verteilung."
- );
- }
- return;
- }
- if (feedingDistributionBoardId === distributionBoardId) {
- throw new Error("Eine Verteilung kann sich nicht selbst speisen.");
- }
- const projectBoards = database
- .select({
- id: distributionBoards.id,
- feedingDistributionBoardId:
- distributionBoards.feedingDistributionBoardId,
- })
- .from(distributionBoards)
- .where(eq(distributionBoards.projectId, projectId))
- .all();
- const boardById = new Map(projectBoards.map((board) => [board.id, board]));
- if (!boardById.has(feedingDistributionBoardId)) {
- throw new Error(
- "Die speisende Verteilung gehört nicht zum Projekt."
- );
- }
- const visited = new Set([distributionBoardId]);
- let currentId: string | null = feedingDistributionBoardId;
- while (currentId !== null) {
- if (visited.has(currentId)) {
- throw new Error("Die Einspeisungsverknüpfung würde einen Zyklus erzeugen.");
- }
- visited.add(currentId);
- currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
- }
- if (feedingCircuitId === null) {
- return;
- }
- const feedingCircuit = database
- .select({
- projectId: circuitLists.projectId,
- distributionBoardId: circuitLists.distributionBoardId,
- })
- .from(circuits)
- .innerJoin(
- circuitLists,
- eq(circuitLists.id, circuits.circuitListId)
- )
- .where(eq(circuits.id, feedingCircuitId))
- .get();
- if (
- !feedingCircuit ||
- feedingCircuit.projectId !== projectId ||
- feedingCircuit.distributionBoardId !== feedingDistributionBoardId
- ) {
- throw new Error(
- "Der speisende Stromkreis gehört nicht zur gewählten Verteilung."
- );
- }
- }
}
function assertSupplyTypeEnabled(
@@ -477,20 +392,11 @@ function structureMatches(
location,
fedFrom,
remark,
- feedingDistributionBoardId,
- feedingCircuitId,
...actualDistributionBoard
} = actual.board;
if (
simultaneityFactor !== 1 ||
- [
- boardNumber,
- location,
- fedFrom,
- remark,
- feedingDistributionBoardId,
- feedingCircuitId,
- ].some((value) => value !== null) ||
+ [boardNumber, location, fedFrom, remark].some((value) => value !== null) ||
!sameRecord(
expected.distributionBoard,
actualDistributionBoard
diff --git a/src/db/repositories/distribution-board-subtree-project-command.repository.ts b/src/db/repositories/distribution-board-subtree-project-command.repository.ts
index 969c287..6788dcc 100644
--- a/src/db/repositories/distribution-board-subtree-project-command.repository.ts
+++ b/src/db/repositories/distribution-board-subtree-project-command.repository.ts
@@ -32,7 +32,6 @@ import { projects } from "../schema/projects.js";
import { rooms } from "../schema/rooms.js";
import { toCircuitDeviceRowInsertValues } from "./circuit-device-row-structure.persistence.js";
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
-import { assertBoardsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
export class DistributionBoardSubtreeProjectCommandRepository
implements DistributionBoardSubtreeProjectCommandStore
@@ -83,9 +82,6 @@ export class DistributionBoardSubtreeProjectCommandRepository
"Distribution-board subtree changed before deletion."
);
}
- assertBoardsAreNotReferencedAsFeeders(database, projectId, [
- current.distributionBoard.id,
- ]);
const deleted = database
.delete(distributionBoards)
.where(
@@ -304,69 +300,6 @@ export class DistributionBoardSubtreeProjectCommandRepository
);
}
}
- const feedingDistributionBoardId =
- snapshot.distributionBoard.feedingDistributionBoardId;
- const feedingCircuitId = snapshot.distributionBoard.feedingCircuitId;
- if (feedingDistributionBoardId === null) {
- if (feedingCircuitId !== null) {
- throw new Error(
- "Ein speisender Stromkreis erfordert eine speisende Verteilung."
- );
- }
- } else {
- if (feedingDistributionBoardId === snapshot.distributionBoard.id) {
- throw new Error("Eine Verteilung kann sich nicht selbst speisen.");
- }
- const projectBoards = database
- .select({
- id: distributionBoards.id,
- feedingDistributionBoardId:
- distributionBoards.feedingDistributionBoardId,
- })
- .from(distributionBoards)
- .where(eq(distributionBoards.projectId, projectId))
- .all();
- const boardById = new Map(projectBoards.map((board) => [board.id, board]));
- if (!boardById.has(feedingDistributionBoardId)) {
- throw new Error(
- "Die speisende Verteilung gehört nicht zum Projekt."
- );
- }
- const visited = new Set([snapshot.distributionBoard.id]);
- let currentId: string | null = feedingDistributionBoardId;
- while (currentId !== null) {
- if (visited.has(currentId)) {
- throw new Error(
- "Die Einspeisungsverknüpfung würde einen Zyklus erzeugen."
- );
- }
- visited.add(currentId);
- currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
- }
- if (feedingCircuitId !== null) {
- const feedingCircuit = database
- .select({
- projectId: circuitLists.projectId,
- distributionBoardId: circuitLists.distributionBoardId,
- })
- .from(circuits)
- .innerJoin(
- circuitLists,
- eq(circuitLists.id, circuits.circuitListId)
- )
- .where(eq(circuits.id, feedingCircuitId))
- .get();
- if (
- !feedingCircuit ||
- feedingCircuit.projectId !== projectId ||
- feedingCircuit.distributionBoardId !== feedingDistributionBoardId
- ) {
- throw new Error(
- "Der speisende Stromkreis gehört nicht zur gewählten Verteilung."
- );
- }
- }
- }
const linkedProjectDeviceIds = uniqueStrings(
snapshot.circuits.flatMap((circuit) =>
circuit.deviceRows.flatMap((row) =>
diff --git a/src/db/repositories/external-object-new-circuit-project-command.repository.ts b/src/db/repositories/external-object-new-circuit-project-command.repository.ts
index 7667a28..bea64ff 100644
--- a/src/db/repositories/external-object-new-circuit-project-command.repository.ts
+++ b/src/db/repositories/external-object-new-circuit-project-command.repository.ts
@@ -33,7 +33,6 @@ import {
snapshotsEqual,
} from "./external-object-assignment.persistence.js";
import { assertEquipmentIdentifierAvailable } from "./equipment-identifier-uniqueness.persistence.js";
-import { assertCircuitsAreNotReferencedAsFeeders } from "./distribution-board-feeding-deletion-guard.persistence.js";
export class ExternalObjectNewCircuitProjectCommandRepository
implements ExternalObjectNewCircuitProjectCommandStore
@@ -163,9 +162,6 @@ export class ExternalObjectNewCircuitProjectCommandRepository
objects,
"External object changed during new-circuit assignment."
);
- assertCircuitsAreNotReferencedAsFeeders(database, projectId, [
- expectedCircuit.id,
- ]);
const deleted = database.delete(circuits).where(and(
eq(circuits.id, expectedCircuit.id),
eq(circuits.circuitListId, expectedCircuit.circuitListId)
diff --git a/src/db/repositories/project-state-restore-command.repository.ts b/src/db/repositories/project-state-restore-command.repository.ts
index 386d805..97cfe17 100644
--- a/src/db/repositories/project-state-restore-command.repository.ts
+++ b/src/db/repositories/project-state-restore-command.repository.ts
@@ -270,14 +270,6 @@ function replaceProjectState(
database: AppDatabase,
state: ProjectStateSnapshot
) {
- database
- .update(distributionBoards)
- .set({
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
- })
- .where(eq(distributionBoards.projectId, state.project.id))
- .run();
database
.delete(externalModelSources)
.where(eq(externalModelSources.projectId, state.project.id))
@@ -338,15 +330,7 @@ function replaceProjectState(
}
insertMany(database, rooms, state.rooms);
insertMany(database, projectDevices, state.projectDevices);
- insertMany(
- database,
- distributionBoards,
- state.distributionBoards.map((board) => ({
- ...board,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
- }))
- );
+ insertMany(database, distributionBoards, state.distributionBoards);
insertMany(database, circuitLists, state.circuitLists);
insertMany(database, circuitSections, state.circuitSections);
insertMany(
@@ -357,7 +341,6 @@ function replaceProjectState(
isReserve: circuit.isReserve ? 1 : 0,
}))
);
- restoreDistributionBoardFeedingReferences(database, state);
insertMany(
database,
distributionBoardComponents,
@@ -395,31 +378,6 @@ function replaceProjectState(
insertMany(database, externalModelObjects, state.externalModel.objects);
}
-function restoreDistributionBoardFeedingReferences(
- database: AppDatabase,
- state: ProjectStateSnapshot
-) {
- for (const board of state.distributionBoards) {
- if (
- board.feedingDistributionBoardId === null &&
- board.feedingCircuitId === null
- ) {
- continue;
- }
- const updated = database
- .update(distributionBoards)
- .set({
- feedingDistributionBoardId: board.feedingDistributionBoardId,
- feedingCircuitId: board.feedingCircuitId,
- })
- .where(eq(distributionBoards.id, board.id))
- .run();
- if (updated.changes !== 1) {
- throw new Error("Distribution-board feeding reference could not be restored.");
- }
- }
-}
-
function insertMany[0]>(
database: AppDatabase,
table: TTable,
diff --git a/src/db/repositories/project-transfer.repository.ts b/src/db/repositories/project-transfer.repository.ts
index 1d93afd..21647ff 100644
--- a/src/db/repositories/project-transfer.repository.ts
+++ b/src/db/repositories/project-transfer.repository.ts
@@ -173,15 +173,7 @@ function insertProjectState(
insertMany(database, floors, state.floors);
insertMany(database, rooms, state.rooms);
insertMany(database, projectDevices, state.projectDevices);
- insertMany(
- database,
- distributionBoards,
- state.distributionBoards.map((board) => ({
- ...board,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
- }))
- );
+ insertMany(database, distributionBoards, state.distributionBoards);
insertMany(database, circuitLists, state.circuitLists);
insertMany(database, circuitSections, state.circuitSections);
insertMany(
@@ -192,25 +184,6 @@ function insertProjectState(
isReserve: circuit.isReserve ? 1 : 0,
}))
);
- for (const board of state.distributionBoards) {
- if (
- board.feedingDistributionBoardId === null &&
- board.feedingCircuitId === null
- ) {
- continue;
- }
- const updated = database
- .update(distributionBoards)
- .set({
- feedingDistributionBoardId: board.feedingDistributionBoardId,
- feedingCircuitId: board.feedingCircuitId,
- })
- .where(eq(distributionBoards.id, board.id))
- .run();
- if (updated.changes !== 1) {
- throw new Error("Distribution-board feeding reference could not be imported.");
- }
- }
insertMany(
database,
distributionBoardComponents,
diff --git a/src/db/schema/circuit-lists.ts b/src/db/schema/circuit-lists.ts
index 2283aeb..0af5138 100644
--- a/src/db/schema/circuit-lists.ts
+++ b/src/db/schema/circuit-lists.ts
@@ -1,4 +1,4 @@
-import { sqliteTable, text, unique, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
+import { sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
import { distributionBoards } from "./distribution-boards.js";
import { projects } from "./projects.js";
@@ -11,7 +11,7 @@ export const circuitLists = sqliteTable(
.references(() => projects.id, { onDelete: "cascade" }),
distributionBoardId: text("distribution_board_id")
.notNull()
- .references((): AnySQLiteColumn => distributionBoards.id, { onDelete: "cascade" }),
+ .references(() => distributionBoards.id, { onDelete: "cascade" }),
name: text("name").notNull(),
},
(table) => [unique("circuit_lists_distribution_board_id_unique").on(table.distributionBoardId)]
diff --git a/src/db/schema/circuits.ts b/src/db/schema/circuits.ts
index 15ef0e3..6e3ed3e 100644
--- a/src/db/schema/circuits.ts
+++ b/src/db/schema/circuits.ts
@@ -1,4 +1,4 @@
-import { index, integer, real, sqliteTable, text, unique, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
+import { index, integer, real, sqliteTable, text, unique } from "drizzle-orm/sqlite-core";
import { circuitLists } from "./circuit-lists.js";
import { circuitSections } from "./circuit-sections.js";
@@ -8,7 +8,7 @@ export const circuits = sqliteTable(
id: text("id").primaryKey(),
circuitListId: text("circuit_list_id")
.notNull()
- .references((): AnySQLiteColumn => circuitLists.id, { onDelete: "cascade" }),
+ .references(() => circuitLists.id, { onDelete: "cascade" }),
sectionId: text("section_id")
.notNull()
.references(() => circuitSections.id, { onDelete: "cascade" }),
diff --git a/src/db/schema/distribution-boards.ts b/src/db/schema/distribution-boards.ts
index 60bbe4d..eb63486 100644
--- a/src/db/schema/distribution-boards.ts
+++ b/src/db/schema/distribution-boards.ts
@@ -1,44 +1,24 @@
-import { index, real, sqliteTable, text, type AnySQLiteColumn } from "drizzle-orm/sqlite-core";
-import { circuits } from "./circuits.js";
+import { real, sqliteTable, text } from "drizzle-orm/sqlite-core";
import { floors } from "./floors.js";
import { projects } from "./projects.js";
import type { DistributionBoardSupplyType } from "../../shared/constants/distribution-board.js";
-export const distributionBoards = sqliteTable(
- "distribution_boards",
- {
- id: text("id").primaryKey(),
- projectId: text("project_id")
- .notNull()
- .references(() => projects.id, { onDelete: "cascade" }),
- name: text("name").notNull(),
- floorId: text("floor_id").references(() => floors.id, {
- onDelete: "set null",
- }),
- supplyType: text("supply_type").$type(),
- simultaneityFactor: real("simultaneity_factor")
- .notNull()
- .default(1),
- boardNumber: text("board_number"),
- location: text("location"),
- fedFrom: text("fed_from"),
- remark: text("remark"),
- feedingDistributionBoardId: text("feeding_distribution_board_id").references(
- (): AnySQLiteColumn => distributionBoards.id,
- { onDelete: "restrict" }
- ),
- feedingCircuitId: text("feeding_circuit_id").references(
- (): AnySQLiteColumn => circuits.id,
- { onDelete: "restrict" }
- ),
- },
- (table) => [
- index("distribution_boards_feeding_board_idx").on(
- table.feedingDistributionBoardId
- ),
- index("distribution_boards_feeding_circuit_idx").on(
- table.feedingCircuitId
- ),
- ]
-);
+export const distributionBoards = sqliteTable("distribution_boards", {
+ id: text("id").primaryKey(),
+ projectId: text("project_id")
+ .notNull()
+ .references(() => projects.id, { onDelete: "cascade" }),
+ name: text("name").notNull(),
+ floorId: text("floor_id").references(() => floors.id, {
+ onDelete: "set null",
+ }),
+ supplyType: text("supply_type").$type(),
+ simultaneityFactor: real("simultaneity_factor")
+ .notNull()
+ .default(1),
+ boardNumber: text("board_number"),
+ location: text("location"),
+ fedFrom: text("fed_from"),
+ remark: text("remark"),
+});
diff --git a/src/domain/calculations/distribution-board-load-calculation.ts b/src/domain/calculations/distribution-board-load-calculation.ts
deleted file mode 100644
index 4c7bad6..0000000
--- a/src/domain/calculations/distribution-board-load-calculation.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-export interface DistributionBoardLoadBoardInput {
- id: string;
- name: string;
- boardNumber: string | null;
- simultaneityFactor: number;
- feedingDistributionBoardId: string | null;
- feedingCircuitId: string | null;
-}
-
-export interface DistributionBoardLoadCircuitInput {
- id: string;
- distributionBoardId: string;
- rows: Array<{
- activePowerKw: number;
- cosPhi: number | null;
- }>;
-}
-
-export interface DownstreamDistributionBoardLoad {
- distributionBoardId: string;
- name: string;
- boardNumber: string | null;
- activePowerKw: number;
- apparentPowerKva: number;
- cosPhi: number;
-}
-
-export interface CalculatedCircuitLoad {
- circuitId: string;
- deviceActivePowerKw: number;
- deviceApparentPowerKva: number;
- activePowerKw: number;
- apparentPowerKva: number;
- cosPhi: number;
- downstreamBoards: DownstreamDistributionBoardLoad[];
-}
-
-export interface CalculatedDistributionBoardLoad {
- distributionBoardId: string;
- activePowerBeforeSimultaneityKw: number;
- apparentPowerBeforeSimultaneityKva: number;
- demandActivePowerKw: number;
- demandApparentPowerKva: number;
- cosPhi: number;
-}
-
-export interface DistributionBoardLoadCalculation {
- boards: Map;
- circuits: Map;
-}
-
-/**
- * Calculates one project graph. A child board enters exactly one feeder
- * circuit with its demand after the child's own board simultaneity factor.
- * Board-only relations form a hierarchy but intentionally add no load.
- */
-export function calculateDistributionBoardLoads(input: {
- boards: DistributionBoardLoadBoardInput[];
- circuits: DistributionBoardLoadCircuitInput[];
-}): DistributionBoardLoadCalculation {
- const boardById = new Map(input.boards.map((board) => [board.id, board]));
- const circuitById = new Map(input.circuits.map((circuit) => [circuit.id, circuit]));
- const circuitsByBoardId = groupBy(
- input.circuits,
- (circuit) => circuit.distributionBoardId
- );
- const childrenByCircuitId = new Map();
- for (const board of input.boards) {
- assertBoardInput(board, boardById, circuitById);
- if (board.feedingCircuitId === null) continue;
- const children = childrenByCircuitId.get(board.feedingCircuitId) ?? [];
- children.push(board);
- childrenByCircuitId.set(board.feedingCircuitId, children);
- }
-
- const boards = new Map();
- const circuits = new Map();
- const activePath = new Set();
-
- const calculateBoard = (boardId: string): CalculatedDistributionBoardLoad => {
- const cached = boards.get(boardId);
- if (cached) return cached;
- if (activePath.has(boardId)) {
- throw new Error("Distribution-board feeding graph contains a cycle.");
- }
- const board = boardById.get(boardId);
- if (!board) throw new Error("Distribution board is missing from load graph.");
- activePath.add(boardId);
-
- let activePowerBeforeSimultaneityKw = 0;
- let apparentPowerBeforeSimultaneityKva = 0;
- for (const circuit of circuitsByBoardId.get(boardId) ?? []) {
- let deviceActivePowerKw = 0;
- let deviceApparentPowerKva = 0;
- for (const row of circuit.rows) {
- assertNonNegativeFinite(row.activePowerKw, "row active power");
- const cosPhi = row.cosPhi ?? 1;
- if (!Number.isFinite(cosPhi) || cosPhi <= 0 || cosPhi > 1) {
- throw new Error("Row cos phi must be greater than zero and at most one.");
- }
- deviceActivePowerKw += row.activePowerKw;
- deviceApparentPowerKva += row.activePowerKw / cosPhi;
- }
- const downstreamBoards = (childrenByCircuitId.get(circuit.id) ?? [])
- .map((child) => {
- const childLoad = calculateBoard(child.id);
- return {
- distributionBoardId: child.id,
- name: child.name,
- boardNumber: child.boardNumber,
- activePowerKw: childLoad.demandActivePowerKw,
- apparentPowerKva: childLoad.demandApparentPowerKva,
- cosPhi: effectiveCosPhi(
- childLoad.demandActivePowerKw,
- childLoad.demandApparentPowerKva
- ),
- };
- });
- const activePowerKw =
- deviceActivePowerKw +
- downstreamBoards.reduce((sum, child) => sum + child.activePowerKw, 0);
- const apparentPowerKva =
- deviceApparentPowerKva +
- downstreamBoards.reduce((sum, child) => sum + child.apparentPowerKva, 0);
- circuits.set(circuit.id, {
- circuitId: circuit.id,
- deviceActivePowerKw,
- deviceApparentPowerKva,
- activePowerKw,
- apparentPowerKva,
- cosPhi: effectiveCosPhi(activePowerKw, apparentPowerKva),
- downstreamBoards,
- });
- activePowerBeforeSimultaneityKw += activePowerKw;
- apparentPowerBeforeSimultaneityKva += apparentPowerKva;
- }
-
- activePath.delete(boardId);
- const result = {
- distributionBoardId: boardId,
- activePowerBeforeSimultaneityKw,
- apparentPowerBeforeSimultaneityKva,
- demandActivePowerKw:
- activePowerBeforeSimultaneityKw * board.simultaneityFactor,
- demandApparentPowerKva:
- apparentPowerBeforeSimultaneityKva * board.simultaneityFactor,
- cosPhi: effectiveCosPhi(
- activePowerBeforeSimultaneityKw,
- apparentPowerBeforeSimultaneityKva
- ),
- };
- boards.set(boardId, result);
- return result;
- };
-
- for (const board of input.boards) calculateBoard(board.id);
- return { boards, circuits };
-}
-
-function assertBoardInput(
- board: DistributionBoardLoadBoardInput,
- boardById: Map,
- circuitById: Map
-) {
- if (
- !Number.isFinite(board.simultaneityFactor) ||
- board.simultaneityFactor < 0 ||
- board.simultaneityFactor > 1
- ) {
- throw new Error("Board simultaneity factor must be between zero and one.");
- }
- if (board.feedingDistributionBoardId === board.id) {
- throw new Error("Distribution board cannot feed itself.");
- }
- if (
- board.feedingDistributionBoardId !== null &&
- !boardById.has(board.feedingDistributionBoardId)
- ) {
- throw new Error("Feeding distribution board is missing from load graph.");
- }
- if (
- board.feedingCircuitId !== null &&
- board.feedingDistributionBoardId === null
- ) {
- throw new Error("Feeding circuit requires a feeding distribution board.");
- }
- if (
- board.feedingCircuitId !== null &&
- circuitById.get(board.feedingCircuitId)?.distributionBoardId !==
- board.feedingDistributionBoardId
- ) {
- throw new Error("Feeding circuit belongs to another distribution board.");
- }
-}
-
-function effectiveCosPhi(activePowerKw: number, apparentPowerKva: number) {
- return activePowerKw > 0 && apparentPowerKva > 0
- ? activePowerKw / apparentPowerKva
- : 1;
-}
-
-function assertNonNegativeFinite(value: number, label: string) {
- if (!Number.isFinite(value) || value < 0) {
- throw new Error(`${label} must be a non-negative finite number.`);
- }
-}
-
-function groupBy(values: T[], keyOf: (value: T) => string) {
- const groups = new Map();
- for (const value of values) {
- const key = keyOf(value);
- const group = groups.get(key) ?? [];
- group.push(value);
- groups.set(key, group);
- }
- return groups;
-}
diff --git a/src/domain/models/circuit-tree.model.ts b/src/domain/models/circuit-tree.model.ts
index 00a0c2d..71fb743 100644
--- a/src/domain/models/circuit-tree.model.ts
+++ b/src/domain/models/circuit-tree.model.ts
@@ -40,10 +40,7 @@ export interface CircuitTreeCircuit {
isReserve: boolean;
remark?: string;
circuitTotalPower: number;
- circuitApparentPower: number;
- circuitCosPhi: number;
deviceRows: CircuitTreeDeviceRow[];
- downstreamBoardLoads: CircuitTreeDownstreamBoardLoad[];
protectionDevice?: CircuitTreeProtectionDevice;
}
@@ -62,11 +59,6 @@ export interface CircuitTreeSectionBlock {
export interface CircuitTreeResponse {
circuitListId: string;
- distributionBoardId: string;
- distributionBoardName: string;
- distributionBoardNumber?: string;
- distributionBoardSupplyType?: DistributionBoardSupplyType;
- feedingReference?: CircuitTreeFeedingReference;
currentRevision: number;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
@@ -78,36 +70,7 @@ export interface CircuitTreeResponse {
footerComponents: CircuitTreeComponent[];
}
-export interface CircuitTreeDownstreamBoardLoad {
- distributionBoardId: string;
- name: string;
- boardNumber?: string;
- supplyType?: DistributionBoardSupplyType;
- activePowerKw: number;
- apparentPowerKva: number;
- cosPhi: number;
-}
-
-export interface CircuitTreeFeedingReference {
- distributionBoardId: string;
- circuitListId: string;
- distributionBoardName: string;
- distributionBoardNumber?: string;
- circuitId?: string;
- circuitEquipmentIdentifier?: string;
- circuitDisplayName?: string;
- circuitCategory?: CircuitGroupCategory;
-}
-
-export interface ProjectCircuitTreesResponse {
- currentRevision: number;
- trees: CircuitTreeResponse[];
-}
-
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group.js";
-import type {
- DistributionBoardSupplyType,
-} from "../../shared/constants/distribution-board.js";
import type {
DistributionBoardComponentPlacement,
DistributionBoardComponentRole,
diff --git a/src/domain/models/distribution-board-project-command.model.ts b/src/domain/models/distribution-board-project-command.model.ts
index 961cf53..2e917d0 100644
--- a/src/domain/models/distribution-board-project-command.model.ts
+++ b/src/domain/models/distribution-board-project-command.model.ts
@@ -9,18 +9,16 @@ import type { SerializedProjectCommand } from "./project-command.model.js";
export const distributionBoardUpdateCommandType =
"distribution-board.update" as const;
-// Version 2 adds logical feeding references. Version-1 commands remain
-// executable and can only change the fields they originally knew.
-export const distributionBoardUpdateCommandSchemaVersion = 2 as const;
-const legacyDistributionBoardUpdateCommandSchemaVersion = 1 as const;
+// The info-card fields were added additively to schema version 1: every
+// previously stored command remains valid, and the inverse is still derived
+// from the persisted row for exactly the changed fields.
+export const distributionBoardUpdateCommandSchemaVersion = 1 as const;
export interface DistributionBoardUpdateValues
extends Record {
floorId: string | null;
supplyType: DistributionBoardSupplyType | null;
simultaneityFactor: number;
- feedingDistributionBoardId: string | null;
- feedingCircuitId: string | null;
}
export type DistributionBoardUpdateField =
@@ -43,20 +41,16 @@ export interface DistributionBoardUpdateCommandPayload {
export interface DistributionBoardUpdateProjectCommand
extends SerializedProjectCommand {
- schemaVersion:
- | typeof legacyDistributionBoardUpdateCommandSchemaVersion
- | typeof distributionBoardUpdateCommandSchemaVersion;
+ schemaVersion: typeof distributionBoardUpdateCommandSchemaVersion;
type: typeof distributionBoardUpdateCommandType;
}
export function createDistributionBoardUpdateProjectCommand(
distributionBoardId: string,
- patch: DistributionBoardUpdatePatch,
- schemaVersion: DistributionBoardUpdateProjectCommand["schemaVersion"] =
- distributionBoardUpdateCommandSchemaVersion
+ patch: DistributionBoardUpdatePatch
): DistributionBoardUpdateProjectCommand {
const command: DistributionBoardUpdateProjectCommand = {
- schemaVersion,
+ schemaVersion: distributionBoardUpdateCommandSchemaVersion,
type: distributionBoardUpdateCommandType,
payload: {
distributionBoardId,
@@ -74,8 +68,7 @@ export function assertDistributionBoardUpdateProjectCommand(
command: SerializedProjectCommand
): asserts command is DistributionBoardUpdateProjectCommand {
if (
- (command.schemaVersion !== legacyDistributionBoardUpdateCommandSchemaVersion &&
- command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion) ||
+ command.schemaVersion !== distributionBoardUpdateCommandSchemaVersion ||
command.type !== distributionBoardUpdateCommandType ||
!isPlainObject(command.payload)
) {
@@ -100,12 +93,7 @@ export function assertDistributionBoardUpdateProjectCommand(
(change.field !== "floorId" &&
change.field !== "supplyType" &&
change.field !== "simultaneityFactor" &&
- change.field !== "feedingDistributionBoardId" &&
- change.field !== "feedingCircuitId" &&
!isInfoField(change.field)) ||
- (command.schemaVersion === legacyDistributionBoardUpdateCommandSchemaVersion &&
- (change.field === "feedingDistributionBoardId" ||
- change.field === "feedingCircuitId")) ||
seen.has(change.field)
) {
throw new Error(
@@ -117,9 +105,7 @@ export function assertDistributionBoardUpdateProjectCommand(
} else if (change.field === "floorId") {
if (
change.value !== null &&
- (typeof change.value !== "string" ||
- !change.value.trim() ||
- change.value !== change.value.trim())
+ (typeof change.value !== "string" || !change.value.trim())
) {
throw new Error("floorId must be a non-empty string or null.");
}
@@ -132,16 +118,6 @@ export function assertDistributionBoardUpdateProjectCommand(
) {
throw new Error("supplyType is invalid.");
}
- } else if (
- change.field === "feedingDistributionBoardId" ||
- change.field === "feedingCircuitId"
- ) {
- if (
- change.value !== null &&
- (typeof change.value !== "string" || !change.value.trim())
- ) {
- throw new Error(`${change.field} must be a non-empty string or null.`);
- }
} else if (
typeof change.value !== "number" ||
!Number.isFinite(change.value) ||
diff --git a/src/domain/models/distribution-board-subtree-snapshot.model.ts b/src/domain/models/distribution-board-subtree-snapshot.model.ts
index 700a52b..a0269fc 100644
--- a/src/domain/models/distribution-board-subtree-snapshot.model.ts
+++ b/src/domain/models/distribution-board-subtree-snapshot.model.ts
@@ -74,8 +74,6 @@ export interface DistributionBoardSubtreeSnapshot {
floorId: string | null;
supplyType: DistributionBoardSupplyType | null;
simultaneityFactor: number;
- feedingDistributionBoardId: string | null;
- feedingCircuitId: string | null;
} & Record;
circuitList: {
id: string;
@@ -186,9 +184,6 @@ export function normalizeDistributionBoardSubtreeSnapshot(
for (const field of distributionBoardInfoFields) {
board[field] = board[field] ?? null;
}
- board.feedingDistributionBoardId =
- board.feedingDistributionBoardId ?? null;
- board.feedingCircuitId = board.feedingCircuitId ?? null;
return { ...snapshot, distributionBoard: board };
}
@@ -232,18 +227,6 @@ export function assertDistributionBoardSubtreeSnapshot(
assertNullableString(board[field], `distributionBoard.${field}`);
}
}
- if (board.feedingDistributionBoardId !== undefined) {
- assertNullableString(
- board.feedingDistributionBoardId,
- "distributionBoard.feedingDistributionBoardId"
- );
- }
- if (board.feedingCircuitId !== undefined) {
- assertNullableString(
- board.feedingCircuitId,
- "distributionBoard.feedingCircuitId"
- );
- }
for (const field of [
"id",
"projectId",
diff --git a/src/domain/models/project-state-snapshot.model.ts b/src/domain/models/project-state-snapshot.model.ts
index ea3aba7..b03bfbd 100644
--- a/src/domain/models/project-state-snapshot.model.ts
+++ b/src/domain/models/project-state-snapshot.model.ts
@@ -35,8 +35,7 @@ import {
type ExternalModelStateSnapshot,
} from "../../external-model/domain/external-model-contracts.js";
-export const projectStateSnapshotSchemaVersion = 9 as const;
-const roomAreaProjectStateSnapshotSchemaVersion = 8 as const;
+export const projectStateSnapshotSchemaVersion = 8 as const;
const buildingProjectStateSnapshotSchemaVersion = 7 as const;
const coverProjectStateSnapshotSchemaVersion = 6 as const;
const boardInfoProjectStateSnapshotSchemaVersion = 5 as const;
@@ -113,7 +112,7 @@ const distributionBoardInfoValueSchema = (field: DistributionBoardInfoField) =>
.max(distributionBoardInfoFieldMaxLengths[field])
.nullable();
-const distributionBoardWithoutFeedingSchema = legacyDistributionBoardSchema
+const distributionBoardSchema = legacyDistributionBoardSchema
.extend({
boardNumber: distributionBoardInfoValueSchema("boardNumber"),
location: distributionBoardInfoValueSchema("location"),
@@ -122,13 +121,6 @@ const distributionBoardWithoutFeedingSchema = legacyDistributionBoardSchema
})
.strict();
-const distributionBoardSchema = distributionBoardWithoutFeedingSchema
- .extend({
- feedingDistributionBoardId: idSchema.nullable(),
- feedingCircuitId: idSchema.nullable(),
- })
- .strict();
-
function withEmptyDistributionBoardInfo(
boards: Array>
) {
@@ -141,16 +133,6 @@ function withEmptyDistributionBoardInfo(
}));
}
-function withEmptyDistributionBoardFeeding(
- boards: Array>
-) {
- return boards.map((board) => ({
- ...board,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
- }));
-}
-
const circuitListSchema = z
.object({
id: idSchema,
@@ -571,22 +553,11 @@ export const projectStateSnapshotSchema = z
})
.strict();
-const roomAreaProjectStateSnapshotSchema = z
- .object({
- schemaVersion: z.literal(roomAreaProjectStateSnapshotSchemaVersion),
- project: projectSchema,
- distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
- externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
- externalModel: externalModelStateSchema,
- ...projectStateSnapshotContents,
- })
- .strict();
-
const buildingProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(buildingProjectStateSnapshotSchemaVersion),
project: projectSchema,
- distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
+ distributionBoards: z.array(distributionBoardSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...buildingProjectStateSnapshotContents,
@@ -597,7 +568,7 @@ const coverProjectStateSnapshotSchema = z
.object({
schemaVersion: z.literal(coverProjectStateSnapshotSchemaVersion),
project: projectSchema,
- distributionBoards: z.array(distributionBoardWithoutFeedingSchema),
+ distributionBoards: z.array(distributionBoardSchema),
externalCsvConfiguration: externalCsvConfigurationSnapshotSchema.nullable(),
externalModel: externalModelStateSchema,
...previousProjectStateSnapshotContents,
@@ -684,27 +655,6 @@ export function parseProjectStateSnapshot(
}
function upgradePreviousProjectStateSnapshot(value: unknown): unknown {
- const roomArea = upgradeToRoomAreaProjectStateSnapshot(value);
- if (
- roomArea !== null &&
- typeof roomArea === "object" &&
- !Array.isArray(roomArea) &&
- (roomArea as { schemaVersion?: unknown }).schemaVersion ===
- roomAreaProjectStateSnapshotSchemaVersion
- ) {
- const previous = roomAreaProjectStateSnapshotSchema.parse(roomArea);
- return {
- ...previous,
- schemaVersion: projectStateSnapshotSchemaVersion,
- distributionBoards: withEmptyDistributionBoardFeeding(
- previous.distributionBoards
- ),
- };
- }
- return roomArea;
-}
-
-function upgradeToRoomAreaProjectStateSnapshot(value: unknown): unknown {
const cover = upgradeToCoverProjectStateSnapshot(value);
if (
cover !== null &&
@@ -716,7 +666,7 @@ function upgradeToRoomAreaProjectStateSnapshot(value: unknown): unknown {
const previous = buildingProjectStateSnapshotSchema.parse(cover);
return {
...previous,
- schemaVersion: roomAreaProjectStateSnapshotSchemaVersion,
+ schemaVersion: projectStateSnapshotSchemaVersion,
rooms: previous.rooms.map((room) => ({ ...room, areaM2: null })),
};
}
@@ -733,7 +683,7 @@ function upgradeToRoomAreaProjectStateSnapshot(value: unknown): unknown {
// building. Build version 7 first so the room-area upgrade above remains a
// single exact transition.
const previous = coverProjectStateSnapshotSchema.parse(cover);
- return upgradeToRoomAreaProjectStateSnapshot({
+ return upgradePreviousProjectStateSnapshot({
...previous,
schemaVersion: buildingProjectStateSnapshotSchemaVersion,
buildings: [],
@@ -923,15 +873,6 @@ function assertProjectStateSnapshotRelations(
const sectionById = new Map(
snapshot.circuitSections.map((entry) => [entry.id, entry])
);
- const boardById = new Map(
- snapshot.distributionBoards.map((entry) => [entry.id, entry])
- );
- const circuitListById = new Map(
- snapshot.circuitLists.map((entry) => [entry.id, entry])
- );
- const circuitById = new Map(
- snapshot.circuits.map((entry) => [entry.id, entry])
- );
const componentById = new Map(
snapshot.distributionBoardComponents.map((entry) => [
entry.id,
@@ -960,47 +901,6 @@ function assertProjectStateSnapshotRelations(
"Snapshot distribution board uses a disabled supply type."
);
}
- if (board.feedingDistributionBoardId === board.id) {
- throw new Error("Snapshot distribution board cannot feed itself.");
- }
- if (board.feedingDistributionBoardId !== null) {
- assertReference(
- boardIds,
- board.feedingDistributionBoardId,
- "feeding distribution board"
- );
- }
- if (board.feedingCircuitId !== null) {
- if (board.feedingDistributionBoardId === null) {
- throw new Error(
- "Snapshot feeding circuit requires a feeding distribution board."
- );
- }
- assertReference(circuitIds, board.feedingCircuitId, "feeding circuit");
- const feedingCircuit = circuitById.get(board.feedingCircuitId);
- const feedingList = feedingCircuit
- ? circuitListById.get(feedingCircuit.circuitListId)
- : undefined;
- if (
- !feedingList ||
- feedingList.distributionBoardId !== board.feedingDistributionBoardId
- ) {
- throw new Error(
- "Snapshot feeding circuit belongs to another distribution board."
- );
- }
- }
- }
- for (const board of snapshot.distributionBoards) {
- const visited = new Set([board.id]);
- let currentId = board.feedingDistributionBoardId;
- while (currentId !== null) {
- if (visited.has(currentId)) {
- throw new Error("Snapshot distribution-board feeding graph contains a cycle.");
- }
- visited.add(currentId);
- currentId = boardById.get(currentId)?.feedingDistributionBoardId ?? null;
- }
}
for (const list of snapshot.circuitLists) {
assertProjectOwnership(list.projectId, projectId, "circuit list");
diff --git a/src/domain/models/project-transfer.model.ts b/src/domain/models/project-transfer.model.ts
index d68b29e..80b9d59 100644
--- a/src/domain/models/project-transfer.model.ts
+++ b/src/domain/models/project-transfer.model.ts
@@ -223,14 +223,6 @@ export function remapProjectState(
...board,
id: requiredId(boardIds, board.id),
projectId: targetProjectId,
- feedingDistributionBoardId:
- board.feedingDistributionBoardId === null
- ? null
- : requiredId(boardIds, board.feedingDistributionBoardId),
- feedingCircuitId:
- board.feedingCircuitId === null
- ? null
- : requiredId(circuitIds, board.feedingCircuitId),
floorId:
board.floorId === null
? null
diff --git a/src/frontend/components/cable-sizing-defaults.ts b/src/frontend/components/cable-sizing-defaults.ts
index 069c575..ae4c7a4 100644
--- a/src/frontend/components/cable-sizing-defaults.ts
+++ b/src/frontend/components/cable-sizing-defaults.ts
@@ -22,9 +22,6 @@ export function deriveCircuitPhase(
* Rows without cos φ count as 1; a circuit without load yields 1.
*/
export function deriveCircuitCosPhi(circuit: CircuitTreeCircuitDto): number {
- if (Number.isFinite(circuit.circuitCosPhi) && circuit.circuitCosPhi > 0) {
- return Math.round(circuit.circuitCosPhi * 100) / 100;
- }
let activePowerKw = 0;
let apparentPowerKva = 0;
for (const row of circuit.deviceRows) {
diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx
index bac08ce..e8a2f23 100644
--- a/src/frontend/components/circuit-tree-editor.tsx
+++ b/src/frontend/components/circuit-tree-editor.tsx
@@ -1,6 +1,5 @@
"use client";
-import Link from "next/link";
import { DragEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
import {
ArrowDown,
@@ -3294,23 +3293,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
threePhaseVoltageV={data.threePhaseVoltageV}
/>
) : null}
- {data.feedingReference ? (
-
- Gespeist von{" "}
-
- {data.feedingReference.distributionBoardNumber ??
- data.feedingReference.distributionBoardName}
- {data.feedingReference.circuitEquipmentIdentifier ?? ""}
-
- {data.feedingReference.circuitId
- ? null
- : " (speisender Stromkreis noch nicht festgelegt)"}
-
- ) : null}
- {row.circuit &&
- (row.rowType === "circuitCompact" ||
- row.rowType === "circuitSummary" ||
- row.rowType === "reserveCircuit") ? (
+ {row.circuit && row.rowType !== "deviceRow" ? (
<>
= {
boardNumber: "z. B. UV 1.1",
location: "z. B. Raum 003 Technik",
- fedFrom: "z. B. HAK, Zähleranlage, Trafo oder NEA",
+ fedFrom: "z. B. HV 0.1 oder HAK / Zähleranlage",
remark: "",
};
@@ -98,8 +98,8 @@ export function DistributionBoardInfoCard({
)}
{field === "fedFrom" ? (
- Für externe Einspeisungen oder ergänzende Hinweise. Eine
- speisende Projektverteilung wird oben logisch verknüpft.
+ Vorerst Freitext. Die logische Verknüpfung mit dem
+ speisenden Verteiler folgt.
) : null}
diff --git a/src/frontend/print/print-document-model.ts b/src/frontend/print/print-document-model.ts
index d3e4724..abe08fe 100644
--- a/src/frontend/print/print-document-model.ts
+++ b/src/frontend/print/print-document-model.ts
@@ -19,14 +19,14 @@ export interface PrintBoardSummary {
circuitCount: number;
/** Σ quantity × power per unit, without any simultaneity factor. */
installedPowerKw: number;
- /** Device-row demand plus downstream-board demand before this board's GZF. */
- powerBeforeBoardSimultaneityKw: number;
+ /** Σ row totals, i.e. with the device-row simultaneity factors. */
+ devicePowerKw: number;
simultaneityFactor: number;
- /** Total active power with this board's simultaneity factor applied. */
+ /** Device power with the board simultaneity factor applied. */
demandPowerKw: number;
/**
- * Design current from the apparent power including downstream boards.
- * The selected feeder circuit determines whether one or three phases apply.
+ * Symmetrical three-phase design current from the apparent power
+ * S = Σ(row total / cos φ) × board GZF. A missing cos φ counts as 1.
*/
designCurrentA: number;
}
@@ -46,13 +46,8 @@ export function summarizePrintBoard(input: {
circuitCount += 1;
for (const row of circuit.deviceRows) {
installedPowerKw += row.quantity * row.powerPerUnit;
+ apparentPowerKva += row.rowTotalPower / (row.cosPhi || 1);
}
- apparentPowerKva +=
- circuit.circuitApparentPower ??
- circuit.deviceRows.reduce(
- (sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1),
- 0
- );
}
}
const simultaneityFactor = tree.distributionBoardSimultaneityFactor;
@@ -60,13 +55,12 @@ export function summarizePrintBoard(input: {
...input,
circuitCount,
installedPowerKw,
- powerBeforeBoardSimultaneityKw: tree.distributionBoardTotalPower,
+ devicePowerKw: tree.distributionBoardTotalPower,
simultaneityFactor,
demandPowerKw: tree.distributionBoardTotalPowerWithSimultaneityFactor,
- designCurrentA: calculateDesignCurrent(
- apparentPowerKva * simultaneityFactor,
- tree
- ),
+ designCurrentA:
+ (apparentPowerKva * simultaneityFactor * 1000) /
+ (Math.sqrt(3) * tree.threePhaseVoltageV),
};
}
@@ -74,6 +68,8 @@ export interface PrintBoardHierarchyEntry {
summary: PrintBoardSummary;
depth: number;
parentBoardId: string | null;
+ /** Own demand plus the plain sum of all resolved downstream boards. */
+ cumulatedDemandPowerKw: number;
}
export function assertPrintRevisionConsistency(
@@ -86,21 +82,31 @@ export function assertPrintRevisionConsistency(
}
}
-export function buildBoardHierarchy(
+/**
+ * PRELIMINARY: `fedFrom` is free text until the logical feed link is
+ * decided. A board is shown below another board only when its `fedFrom`
+ * text equals that board's number or name (case-insensitive). The
+ * cumulated value is a plain sum without an upstream simultaneity factor.
+ * Unresolved texts and cycles keep a board at the top level.
+ */
+export function buildPreliminaryBoardHierarchy(
summaries: PrintBoardSummary[]
): PrintBoardHierarchyEntry[] {
- const summaryById = new Map(
- summaries.map((summary) => [summary.board.id, summary])
- );
+ const normalize = (value: string | null) =>
+ value?.trim().toLocaleLowerCase("de") || null;
const parentById = new Map();
for (const summary of summaries) {
- const parentId = summary.board.feedingDistributionBoardId;
- parentById.set(
- summary.board.id,
- parentId && parentId !== summary.board.id && summaryById.has(parentId)
- ? parentId
- : null
- );
+ const fedFrom = normalize(summary.board.fedFrom);
+ const candidates = fedFrom
+ ? summaries.filter(
+ (candidate) =>
+ candidate !== summary &&
+ (normalize(candidate.board.boardNumber) === fedFrom ||
+ normalize(candidate.board.name) === fedFrom)
+ )
+ : [];
+ const parent = candidates.length === 1 ? candidates[0] : undefined;
+ parentById.set(summary.board.id, parent?.board.id ?? null);
}
for (const summary of summaries) {
const visited = new Set([summary.board.id]);
@@ -119,12 +125,20 @@ export function buildBoardHierarchy(
summaries.filter(
(summary) => parentById.get(summary.board.id) === boardId
);
+ const cumulated = (summary: PrintBoardSummary): number =>
+ summary.demandPowerKw +
+ childrenOf(summary.board.id).reduce(
+ (sum, child) => sum + cumulated(child),
+ 0
+ );
+
const entries: PrintBoardHierarchyEntry[] = [];
const walk = (summary: PrintBoardSummary, depth: number) => {
entries.push({
summary,
depth,
parentBoardId: parentById.get(summary.board.id) ?? null,
+ cumulatedDemandPowerKw: cumulated(summary),
});
childrenOf(summary.board.id).forEach((child) => walk(child, depth + 1));
};
@@ -132,25 +146,6 @@ export function buildBoardHierarchy(
return entries;
}
-export function formatFeedingReference(summary: PrintBoardSummary): string {
- const reference = summary.tree.feedingReference;
- if (!reference) return summary.board.fedFrom ?? "–";
- return `${
- reference.distributionBoardNumber ?? reference.distributionBoardName
- }${reference.circuitEquipmentIdentifier ?? ""}`;
-}
-
-function calculateDesignCurrent(
- apparentPowerKva: number,
- tree: CircuitTreeResponseDto
-) {
- const category = tree.feedingReference?.circuitCategory;
- return category === "lighting" || category === "single_phase"
- ? (apparentPowerKva * 1000) / tree.singlePhaseVoltageV
- : (apparentPowerKva * 1000) /
- (Math.sqrt(3) * tree.threePhaseVoltageV);
-}
-
const kilowattFormat = new Intl.NumberFormat("de-DE", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
diff --git a/src/frontend/print/print-document.tsx b/src/frontend/print/print-document.tsx
index 5a0152b..c39b4ba 100644
--- a/src/frontend/print/print-document.tsx
+++ b/src/frontend/print/print-document.tsx
@@ -13,7 +13,6 @@ import {
formatCircuitCable,
formatCurrent,
formatFactor,
- formatFeedingReference,
formatKilowatt,
formatProtectionDevice,
formatUnitPower,
@@ -270,8 +269,8 @@ function OverviewSheet({
stand: string;
}) {
const roots = entries.filter((entry) => entry.parentBoardId === null);
- // Root boards may still carry an external feed description such as
- // "HAK / Zähleranlage"; logical board links determine the actual tree.
+ // Top-level boards whose "fed from" text names no other board are grouped
+ // below that text as an external feed (e.g. "HAK / Zähleranlage").
const feedGroups = new Map();
for (const root of roots) {
const feed = root.summary.board.fedFrom ?? "";
@@ -321,10 +320,11 @@ function OverviewSheet({
Nr. Bezeichnung Örtlichkeit Etage Netz Gespeist von
SK
Pinst [kW]
- P vor Vert.-GZF [kW]
+ PGeräte [kW]
GZF Vert.
- Pgl inkl. UV [kW]
+ Pgl [kW]
IB [A]
+ Σ inkl. unterlagert [kW]
@@ -340,45 +340,41 @@ function OverviewSheet({
{board.location ?? "–"}
{entry.summary.floorName ?? "–"}
{board.supplyType ?? "–"}
- {formatFeedingReference(entry.summary)}
+ {board.fedFrom ?? "–"}
{entry.summary.circuitCount}
{formatKilowatt(entry.summary.installedPowerKw)}
- {formatKilowatt(entry.summary.powerBeforeBoardSimultaneityKw)}
+ {formatKilowatt(entry.summary.devicePowerKw)}
{formatFactor(entry.summary.simultaneityFactor)}
{formatKilowatt(entry.summary.demandPowerKw)}
{formatCurrent(entry.summary.designCurrentA)}
+ {formatKilowatt(entry.cumulatedDemandPowerKw)}
);
})}
- Summe der Einspeisungen
+ Summe aller Verteiler (ohne übergeordneten GZF)
{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}
- {formatKilowatt(sum((entry) =>
- entry.parentBoardId === null
- ? entry.summary.powerBeforeBoardSimultaneityKw
- : 0
- ))}
+ {formatKilowatt(sum((entry) => entry.summary.devicePowerKw))}
+
+ {formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}
- {formatKilowatt(sum((entry) =>
- entry.parentBoardId === null ? entry.summary.demandPowerKw : 0
- ))}
- Die Struktur folgt den logischen Verknüpfungen. Eine Unterverteilung
- wird im gewählten Abgang mit ihrer Leistung nach eigenem
- Verteiler-GZF berücksichtigt. Ohne gewählten Abgang erscheint sie
- nur in der Hierarchie.
+ Vorläufig: „Gespeist von“ ist noch ein Freitextfeld. Struktur und
+ „Σ inkl. unterlagert“ werden nur über eindeutig passende Verteilernummern bzw.
+ -bezeichnungen zugeordnet und einfach addiert (kein übergeordneter GZF).
+ Mehrdeutige Angaben bleiben ohne Zuordnung. Die fachliche
+ Verknüpfung und Summierung folgt.
- Pinst = lokale Geräte ohne GZF · P vor Vert.-GZF =
- Geräte-GZF einschließlich gespeister Unterverteilungen · Pgl =
- zusätzlich mit Verteiler-GZF · IB aus S und der Phasenart
- des speisenden Abgangs
+ Pinst = Anzahl × Leistung je Stück · PGeräte = mit Geräte-GZF ·
+ Pgl = mit Verteiler-GZF · IB = S / (√3 · {project.threePhaseVoltageV} V),
+ S aus cos φ je Gerätezeile
@@ -408,7 +404,6 @@ function ComponentRow({ component }: { component: CircuitTreeComponentDto }) {
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
const rows = circuit.deviceRows;
- const downstreamBoards = circuit.downstreamBoardLoads ?? [];
const circuitCells = (
<>
{formatProtectionDevice(circuit.protectionDevice) ?? }
@@ -419,7 +414,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
{circuit.remark ?? ""}
>
);
- if (rows.length === 0 && downstreamBoards.length === 0) {
+ if (rows.length === 0) {
return (
{circuit.equipmentIdentifier}
@@ -431,7 +426,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
);
}
- if (rows.length === 1 && downstreamBoards.length === 0) {
+ if (rows.length === 1) {
const row = rows[0];
const title = circuit.displayName ?? row.displayName;
return (
@@ -473,16 +468,6 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
))}
- {downstreamBoards.map((load) => (
-
-
- Unterverteilung {load.boardNumber ?? load.name}
- –
-
- {formatKilowatt(load.activePowerKw)}
- Automatisch aus der verknüpften Verteilung
-
- ))}
>
);
}
@@ -516,7 +501,7 @@ function BoardPlanSheet({
rows={[
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
["Netzart", board.supplyType ?? "–"],
- ["Gespeist von", formatFeedingReference(summary)],
+ ["Gespeist von", board.fedFrom ?? "–"],
[
<>Pgl >,
<>
@@ -557,7 +542,7 @@ function BoardPlanSheet({
Summe Verteiler (Pinst {formatKilowatt(summary.installedPowerKw)} kW)
- {formatKilowatt(summary.powerBeforeBoardSimultaneityKw)}
+ {formatKilowatt(summary.devicePowerKw)}
diff --git a/src/frontend/types.ts b/src/frontend/types.ts
index 572182b..e8a2289 100644
--- a/src/frontend/types.ts
+++ b/src/frontend/types.ts
@@ -195,8 +195,6 @@ export interface DistributionBoardDto extends DistributionBoardInfoDto {
floorId: string | null;
supplyType: DistributionBoardSupplyType | null;
simultaneityFactor: number;
- feedingDistributionBoardId: string | null;
- feedingCircuitId: string | null;
}
export interface DocumentLogoMetadataDto {
@@ -444,10 +442,7 @@ export interface CircuitTreeCircuitDto {
isReserve: boolean;
remark?: string;
circuitTotalPower: number;
- circuitApparentPower: number;
- circuitCosPhi: number;
deviceRows: CircuitTreeDeviceRowDto[];
- downstreamBoardLoads: CircuitTreeDownstreamBoardLoadDto[];
protectionDevice?: CircuitTreeProtectionDeviceDto;
}
@@ -466,11 +461,6 @@ export interface CircuitTreeSectionDto {
export interface CircuitTreeResponseDto {
circuitListId: string;
- distributionBoardId: string;
- distributionBoardName: string;
- distributionBoardNumber?: string;
- distributionBoardSupplyType?: DistributionBoardSupplyType;
- feedingReference?: CircuitTreeFeedingReferenceDto;
currentRevision: number;
singlePhaseVoltageV: number;
threePhaseVoltageV: number;
@@ -482,32 +472,6 @@ export interface CircuitTreeResponseDto {
footerComponents: CircuitTreeComponentDto[];
}
-export interface CircuitTreeDownstreamBoardLoadDto {
- distributionBoardId: string;
- name: string;
- boardNumber?: string;
- supplyType?: DistributionBoardSupplyType;
- activePowerKw: number;
- apparentPowerKva: number;
- cosPhi: number;
-}
-
-export interface CircuitTreeFeedingReferenceDto {
- distributionBoardId: string;
- circuitListId: string;
- distributionBoardName: string;
- distributionBoardNumber?: string;
- circuitId?: string;
- circuitEquipmentIdentifier?: string;
- circuitDisplayName?: string;
- circuitCategory?: CircuitGroupCategory;
-}
-
-export interface ProjectCircuitTreesResponseDto {
- currentRevision: number;
- trees: CircuitTreeResponseDto[];
-}
-
export interface ExternalCircuitListObjectDto {
id: string;
ifcGuid: string;
diff --git a/src/frontend/utils/api.ts b/src/frontend/utils/api.ts
index 8473bfa..501ed01 100644
--- a/src/frontend/utils/api.ts
+++ b/src/frontend/utils/api.ts
@@ -33,7 +33,6 @@ import type {
ProjectDto,
RoomDto,
CircuitTreeResponseDto,
- ProjectCircuitTreesResponseDto,
ExternalCsvConfigurationSnapshotDto,
ExternalCsvPreviewDto,
ExternalInitialImportPlanDto,
@@ -373,13 +372,7 @@ export function updateDistributionBoard(
floorId: string | null;
supplyType: NonNullable;
simultaneityFactor: number;
- } & Partial &
- Partial<
- Pick<
- DistributionBoardDto,
- "feedingDistributionBoardId" | "feedingCircuitId"
- >
- >,
+ } & Partial,
expectedRevision: number
) {
return request(
@@ -530,12 +523,6 @@ export function getCircuitTree(projectId: string, circuitListId: string) {
return request(`/api/projects/${projectId}/circuit-lists/${circuitListId}/tree`);
}
-export function getProjectCircuitTrees(projectId: string) {
- return request(
- `/api/projects/${projectId}/circuit-trees`
- );
-}
-
export function insertDistributionBoardComponentCommand(
projectId: string,
expectedRevision: number,
diff --git a/src/frontend/utils/circuit-grid-model.ts b/src/frontend/utils/circuit-grid-model.ts
index faa4a11..ba4be98 100644
--- a/src/frontend/utils/circuit-grid-model.ts
+++ b/src/frontend/utils/circuit-grid-model.ts
@@ -44,7 +44,6 @@ export type RowType =
| "circuitCompact"
| "circuitSummary"
| "deviceRow"
- | "downstreamBoardLoad"
| "reserveCircuit"
| "placeholder"
| "footerComponent";
@@ -615,7 +614,6 @@ export function getCellKind(rowType: RowType, key: CellKey): CellKind {
return "readonly";
}
if (rowType === "deviceRow") return deviceFieldKeys.has(key) ? "deviceField" : "readonly";
- if (rowType === "downstreamBoardLoad") return "readonly";
if (rowType === "circuitSummary") return circuitFieldKeys.has(key) ? "circuitField" : "readonly";
if (rowType === "reserveCircuit" || rowType === "circuitCompact") {
if (deviceFieldKeys.has(key)) return "deviceField";
diff --git a/src/frontend/utils/circuit-grid-projection.ts b/src/frontend/utils/circuit-grid-projection.ts
index a9b11de..fb2a846 100644
--- a/src/frontend/utils/circuit-grid-projection.ts
+++ b/src/frontend/utils/circuit-grid-projection.ts
@@ -2,7 +2,6 @@ import type {
CircuitTreeCircuitDto,
CircuitTreeComponentDto,
CircuitTreeDeviceRowDto,
- CircuitTreeDownstreamBoardLoadDto,
CircuitTreeSectionDto,
} from "../types.js";
import { buildCircuitStructureProjection } from "./circuit-structure-projection";
@@ -46,7 +45,6 @@ export interface VisibleGridRow {
circuit?: CircuitTreeCircuitDto;
device?: CircuitTreeDeviceRowDto;
component?: CircuitTreeComponentDto;
- downstreamBoardLoad?: CircuitTreeDownstreamBoardLoadDto;
cells: VisibleGridCell[];
}
@@ -128,16 +126,13 @@ function makeVisibleGridRow(
rowType: RowType,
sectionId: string,
circuit?: CircuitTreeCircuitDto,
- device?: CircuitTreeDeviceRowDto,
- downstreamBoardLoad?: CircuitTreeDownstreamBoardLoadDto
+ device?: CircuitTreeDeviceRowDto
): VisibleGridRow {
const rowKey =
rowType === "placeholder"
? `placeholder:${sectionId}`
: rowType === "deviceRow" && device
? `device:${device.id}`
- : rowType === "downstreamBoardLoad" && downstreamBoardLoad
- ? `downstream-board:${circuit?.id}:${downstreamBoardLoad.distributionBoardId}`
: `${rowType}:${circuit?.id ?? sectionId}`;
const cells = allColumns.map((column): VisibleGridCell => {
@@ -145,17 +140,7 @@ function makeVisibleGridRow(
const editable = kind === "circuitField" || kind === "deviceField";
let value: GridValue;
- if (rowType === "downstreamBoardLoad" && downstreamBoardLoad) {
- value = column.key === "displayName"
- ? `Unterverteilung ${downstreamBoardLoad.boardNumber ?? downstreamBoardLoad.name}`
- : column.key === "rowTotalPower"
- ? downstreamBoardLoad.activePowerKw
- : column.key === "cosPhi"
- ? downstreamBoardLoad.cosPhi
- : column.key === "remark"
- ? "Automatisch aus der verknüpften Verteilung"
- : undefined;
- } else if (rowType === "placeholder") {
+ if (rowType === "placeholder") {
value = column.key === "equipmentIdentifier" ? "-frei-" : undefined;
} else if (kind === "deviceField" && device) {
value = getDeviceValue(device, column.key);
@@ -181,15 +166,7 @@ function makeVisibleGridRow(
return { cellKey: column.key, editable, kind, value };
});
- return {
- rowKey,
- rowType,
- sectionId,
- circuit,
- device,
- downstreamBoardLoad,
- cells,
- };
+ return { rowKey, rowType, sectionId, circuit, device, cells };
}
export function buildVisibleGridRowsWithStructure(
@@ -244,27 +221,15 @@ export function buildVisibleGridRowsWithStructure(
}
if (row.rowType === "circuitBlock") {
const circuit = row.circuit;
- const downstreamLoads = circuit.downstreamBoardLoads ?? [];
- if (circuit.deviceRows.length === 0 && downstreamLoads.length === 0) {
+ if (circuit.deviceRows.length === 0) {
rows.push(makeVisibleGridRow("reserveCircuit", section.id, circuit));
- } else if (circuit.deviceRows.length === 1 && downstreamLoads.length === 0) {
+ } else if (circuit.deviceRows.length === 1) {
rows.push(makeVisibleGridRow("circuitCompact", section.id, circuit, circuit.deviceRows[0]));
} else {
rows.push(makeVisibleGridRow("circuitSummary", section.id, circuit));
for (const device of circuit.deviceRows) {
rows.push(makeVisibleGridRow("deviceRow", section.id, circuit, device));
}
- for (const load of downstreamLoads) {
- rows.push(
- makeVisibleGridRow(
- "downstreamBoardLoad",
- section.id,
- circuit,
- undefined,
- load
- )
- );
- }
}
continue;
}
diff --git a/src/server/controllers/circuit-tree.controller.ts b/src/server/controllers/circuit-tree.controller.ts
index 17ac134..b7c86bf 100644
--- a/src/server/controllers/circuit-tree.controller.ts
+++ b/src/server/controllers/circuit-tree.controller.ts
@@ -1,11 +1,13 @@
import type { Request, Response } from "express";
-import { calculateDistributionBoardLoads } from "../../domain/calculations/distribution-board-load-calculation.js";
-import { calculateRowTotalPower } from "../../domain/calculations/circuit-power-calculation.js";
-import type {
- CircuitTreeProtectionDevice,
- CircuitTreeResponse,
- ProjectCircuitTreesResponse,
-} from "../../domain/models/circuit-tree.model.js";
+import {
+ applyDistributionBoardSimultaneityFactor,
+ calculateCircuitTotalPower,
+ calculateDistributionBoardTotalPower,
+ calculateRowTotalPower,
+ calculateSectionTotalPower,
+} from "../../domain/calculations/circuit-power-calculation.js";
+import type { CircuitTreeResponse } from "../../domain/models/circuit-tree.model.js";
+import type { CircuitTreeProtectionDevice } from "../../domain/models/circuit-tree.model.js";
import {
circuitDeviceRowRepository,
circuitListRepository,
@@ -18,7 +20,9 @@ import {
} from "../composition/application-repositories.js";
export function isMissingCircuitTreeSchemaError(error: unknown): boolean {
- if (!(error instanceof Error)) return false;
+ if (!(error instanceof Error)) {
+ return false;
+ }
return (
error.message.includes("no such table: circuit_sections") ||
error.message.includes("no such table: circuits") ||
@@ -33,147 +37,70 @@ export async function getCircuitTree(req: Request, res: Response) {
if (typeof projectId !== "string" || typeof circuitListId !== "string") {
return res.status(400).json({ error: "Invalid parameters" });
}
+
const list = await circuitListRepository.findById(projectId, circuitListId);
- if (!list) return res.status(404).json({ error: "Circuit list not found" });
-
- try {
- const result = await buildProjectCircuitTrees(projectId);
- if (!result) return res.status(404).json({ error: "Project not found" });
- const tree = result.trees.find((entry) => entry.circuitListId === circuitListId);
- if (!tree) {
- return res.status(404).json({ error: "Distribution board not found" });
- }
- return res.json(tree);
- } catch (error) {
- if (isMissingCircuitTreeSchemaError(error)) {
- const project = await projectRepository.findById(projectId);
- const board = await distributionBoardRepository.findById(
- projectId,
- list.distributionBoardId
- );
- if (!project || !board) {
- return res.status(404).json({ error: "Project structure not found" });
- }
- return res.json(emptyCircuitTree(project, board, circuitListId));
- }
- throw error;
+ if (!list) {
+ return res.status(404).json({ error: "Circuit list not found" });
}
-}
-
-export async function getProjectCircuitTrees(req: Request, res: Response) {
- const { projectId } = req.params;
- if (typeof projectId !== "string") {
- return res.status(400).json({ error: "Invalid parameters" });
- }
- try {
- const result = await buildProjectCircuitTrees(projectId);
- if (!result) return res.status(404).json({ error: "Project not found" });
- return res.json(result);
- } catch (error) {
- if (isMissingCircuitTreeSchemaError(error)) {
- const project = await projectRepository.findById(projectId);
- if (!project) return res.status(404).json({ error: "Project not found" });
- return res.json({ currentRevision: project.currentRevision, trees: [] });
- }
- throw error;
- }
-}
-
-export async function buildProjectCircuitTrees(
- projectId: string
-): Promise {
const project = await projectRepository.findById(projectId);
- if (!project) return null;
- const [lists, boards] = await Promise.all([
- circuitListRepository.listByProject(projectId),
- distributionBoardRepository.listByProject(projectId),
- ]);
- const listIds = lists.map((entry) => entry.id);
- const [sections, circuits, components] = await Promise.all([
- circuitSectionRepository.listByCircuitLists(listIds),
- circuitRepository.listByCircuitLists(listIds),
- distributionBoardComponentRepository.listByCircuitLists(listIds),
- ]);
- const [rows, circuitProtections, componentProtections] = await Promise.all([
- circuitDeviceRowRepository.listByCircuitList(circuits.map((entry) => entry.id)),
- circuitProtectionDeviceRepository.listByCircuitIds(circuits.map((entry) => entry.id)),
- distributionBoardComponentRepository.listProtectionByComponentIds(
- components.map((entry) => entry.id)
- ),
- ]);
+ if (!project) {
+ return res.status(404).json({ error: "Project not found" });
+ }
+ const distributionBoard =
+ await distributionBoardRepository.findById(
+ projectId,
+ list.distributionBoardId
+ );
+ if (!distributionBoard) {
+ return res.status(404).json({
+ error: "Distribution board not found",
+ });
+ }
- const boardById = new Map(boards.map((board) => [board.id, board]));
- const listById = new Map(lists.map((list) => [list.id, list]));
- const listByBoardId = new Map(
- lists.map((list) => [list.distributionBoardId, list])
- );
- const sectionById = new Map(sections.map((section) => [section.id, section]));
- const circuitById = new Map(circuits.map((circuit) => [circuit.id, circuit]));
- const rowsByCircuitId = groupBy(rows, (row) => row.circuitId);
- const sectionsByListId = groupBy(sections, (section) => section.circuitListId);
- const circuitsByListId = groupBy(circuits, (circuit) => circuit.circuitListId);
- const componentsByListId = groupBy(components, (component) => component.circuitListId);
- const circuitProtectionById = new Map(
- circuitProtections.map((entry) => [entry.circuitId, entry])
- );
- const componentProtectionById = new Map(
- componentProtections.map((entry) => [entry.componentId, entry])
- );
+ try {
+ const sections = await circuitSectionRepository.listByCircuitList(circuitListId);
+ const circuits = await circuitRepository.listByCircuitList(circuitListId);
+ const rows = await circuitDeviceRowRepository.listByCircuitList(circuits.map((entry) => entry.id));
+ const components =
+ await distributionBoardComponentRepository.listByCircuitList(
+ circuitListId
+ );
+ const [circuitProtections, componentProtections] =
+ await Promise.all([
+ circuitProtectionDeviceRepository.listByCircuitIds(
+ circuits.map((entry) => entry.id)
+ ),
+ distributionBoardComponentRepository.listProtectionByComponentIds(
+ components.map((entry) => entry.id)
+ ),
+ ]);
- const loads = calculateDistributionBoardLoads({
- boards: boards.map((board) => ({
- id: board.id,
- name: board.name,
- boardNumber: board.boardNumber,
- simultaneityFactor: board.simultaneityFactor,
- feedingDistributionBoardId: board.feedingDistributionBoardId,
- feedingCircuitId: board.feedingCircuitId,
- })),
- circuits: circuits.map((circuit) => {
- const list = listById.get(circuit.circuitListId);
- if (!list) throw new Error("Circuit list is missing from project tree.");
- return {
- id: circuit.id,
- distributionBoardId: list.distributionBoardId,
- rows: (rowsByCircuitId.get(circuit.id) ?? []).map((row) => ({
- activePowerKw: calculateRowTotalPower(
- row.quantity,
- row.powerPerUnit,
- row.simultaneityFactor
- ),
- cosPhi: row.cosPhi,
- })),
- };
- }),
- });
+ const sectionById = new Map(sections.map((section) => [section.id, section]));
+ const rowsByCircuitId = new Map();
+ const circuitProtectionById = new Map(
+ circuitProtections.map((entry) => [entry.circuitId, entry])
+ );
+ const componentProtectionById = new Map(
+ componentProtections.map((entry) => [entry.componentId, entry])
+ );
+ for (const row of rows) {
+ if (!rowsByCircuitId.has(row.circuitId)) {
+ rowsByCircuitId.set(row.circuitId, []);
+ }
+ rowsByCircuitId.get(row.circuitId)!.push(row);
+ }
- const trees = lists.map((list): CircuitTreeResponse => {
- const board = boardById.get(list.distributionBoardId);
- if (!board) throw new Error("Distribution board is missing from project tree.");
- const boardLoad = loads.boards.get(board.id);
- if (!boardLoad) throw new Error("Distribution-board load is missing.");
const tree: CircuitTreeResponse = {
- circuitListId: list.id,
- distributionBoardId: board.id,
- distributionBoardName: board.name,
- distributionBoardNumber: board.boardNumber ?? undefined,
- distributionBoardSupplyType: board.supplyType ?? undefined,
- feedingReference: resolveFeedingReference(
- board,
- boardById,
- listByBoardId,
- circuitById,
- sectionById
- ),
+ circuitListId,
currentRevision: project.currentRevision,
singlePhaseVoltageV: project.singlePhaseVoltageV,
threePhaseVoltageV: project.threePhaseVoltageV,
- distributionBoardSimultaneityFactor: board.simultaneityFactor,
- distributionBoardTotalPower: boardLoad.activePowerBeforeSimultaneityKw,
- distributionBoardTotalPowerWithSimultaneityFactor:
- boardLoad.demandActivePowerKw,
+ distributionBoardSimultaneityFactor:
+ distributionBoard.simultaneityFactor,
+ distributionBoardTotalPower: 0,
+ distributionBoardTotalPowerWithSimultaneityFactor: 0,
headerComponents: [],
- sections: (sectionsByListId.get(list.id) ?? []).map((section) => ({
+ sections: sections.map((section) => ({
id: section.id,
key: section.key,
displayName: section.displayName,
@@ -188,8 +115,9 @@ export async function buildProjectCircuitTrees(
footerComponents: [],
};
const sectionBlocks = new Map(tree.sections.map((section) => [section.id, section]));
- for (const component of componentsByListId.get(list.id) ?? []) {
- const protection = componentProtectionById.get(component.id);
+ for (const component of components) {
+ const protection =
+ componentProtectionById.get(component.id);
const dto = {
id: component.id,
circuitListId: component.circuitListId,
@@ -199,18 +127,24 @@ export async function buildProjectCircuitTrees(
role: component.role,
placement: component.placement,
sortOrder: component.sortOrder,
- protectionDevice: protection ? toProtectionDeviceDto(protection) : undefined,
+ protectionDevice: protection
+ ? toProtectionDeviceDto(protection)
+ : undefined,
};
- if (component.placement === "header") tree.headerComponents.push(dto);
- else if (component.placement === "footer") tree.footerComponents.push(dto);
- else if (component.sectionId !== null) {
+ if (component.placement === "header") {
+ tree.headerComponents.push(dto);
+ } else if (component.placement === "footer") {
+ tree.footerComponents.push(dto);
+ } else if (component.sectionId !== null) {
sectionBlocks.get(component.sectionId)?.components.push(dto);
}
}
- for (const circuit of circuitsByListId.get(list.id) ?? []) {
- const block = sectionBlocks.get(circuit.sectionId);
- if (!block) continue;
+ for (const circuit of circuits) {
+ const section = sectionById.get(circuit.sectionId);
+ if (!section || section.circuitListId !== circuit.circuitListId) {
+ continue;
+ }
const deviceRows = (rowsByCircuitId.get(circuit.id) ?? []).map((row) => ({
id: row.id,
linkedProjectDeviceId: row.linkedProjectDeviceId ?? undefined,
@@ -232,16 +166,12 @@ export async function buildProjectCircuitTrees(
cosPhi: row.cosPhi ?? undefined,
remark: row.remark ?? undefined,
overriddenFields: row.overriddenFields ?? undefined,
- rowTotalPower: calculateRowTotalPower(
- row.quantity,
- row.powerPerUnit,
- row.simultaneityFactor
- ),
+ rowTotalPower: calculateRowTotalPower(row.quantity, row.powerPerUnit, row.simultaneityFactor),
}));
- const circuitLoad = loads.circuits.get(circuit.id);
- if (!circuitLoad) throw new Error("Circuit load is missing.");
+ const circuitTotalPower = calculateCircuitTotalPower(deviceRows);
const protection = circuitProtectionById.get(circuit.id);
- block.circuits.push({
+
+ sectionBlocks.get(section.id)?.circuits.push({
id: circuit.id,
circuitListId: circuit.circuitListId,
sectionId: circuit.sectionId,
@@ -256,97 +186,50 @@ export async function buildProjectCircuitTrees(
voltage: circuit.voltage ?? undefined,
controlRequirement: circuit.controlRequirement ?? undefined,
status: circuit.status ?? undefined,
- isReserve:
- Boolean(circuit.isReserve) &&
- circuitLoad.downstreamBoards.length === 0,
+ isReserve: Boolean(circuit.isReserve),
remark: circuit.remark ?? undefined,
- circuitTotalPower: circuitLoad.activePowerKw,
- circuitApparentPower: circuitLoad.apparentPowerKva,
- circuitCosPhi: circuitLoad.cosPhi,
+ circuitTotalPower,
deviceRows,
- downstreamBoardLoads: circuitLoad.downstreamBoards
- .map((load) => ({
- distributionBoardId: load.distributionBoardId,
- name: load.name,
- boardNumber: load.boardNumber ?? undefined,
- supplyType: boardById.get(load.distributionBoardId)?.supplyType ?? undefined,
- activePowerKw: load.activePowerKw,
- apparentPowerKva: load.apparentPowerKva,
- cosPhi: load.cosPhi,
- }))
- .sort((left, right) =>
- (left.boardNumber ?? left.name).localeCompare(
- right.boardNumber ?? right.name,
- "de"
- )
- ),
- protectionDevice: protection ? toProtectionDeviceDto(protection) : undefined,
+ protectionDevice: protection
+ ? toProtectionDeviceDto(protection)
+ : undefined,
});
}
+
for (const section of tree.sections) {
- section.sectionTotalPower = section.circuits.reduce(
- (sum, circuit) => sum + circuit.circuitTotalPower,
- 0
+ section.sectionTotalPower = calculateSectionTotalPower(
+ section.circuits
);
}
- return tree;
- });
+ tree.distributionBoardTotalPower =
+ calculateDistributionBoardTotalPower(tree.sections);
+ tree.distributionBoardTotalPowerWithSimultaneityFactor =
+ applyDistributionBoardSimultaneityFactor(
+ tree.distributionBoardTotalPower,
+ tree.distributionBoardSimultaneityFactor
+ );
- return { currentRevision: project.currentRevision, trees };
-}
-
-function resolveFeedingReference(
- board: Awaited>[number],
- boardById: Map>[number]>,
- listByBoardId: Map>[number]>,
- circuitById: Map>[number]>,
- sectionById: Map>[number]>
-) {
- if (board.feedingDistributionBoardId === null) return undefined;
- const feedingBoard = boardById.get(board.feedingDistributionBoardId);
- if (!feedingBoard) throw new Error("Feeding distribution board is missing.");
- const feedingList = listByBoardId.get(feedingBoard.id);
- if (!feedingList) throw new Error("Feeding circuit list is missing.");
- const feedingCircuit = board.feedingCircuitId === null
- ? undefined
- : circuitById.get(board.feedingCircuitId);
- return {
- distributionBoardId: feedingBoard.id,
- circuitListId: feedingList.id,
- distributionBoardName: feedingBoard.name,
- distributionBoardNumber: feedingBoard.boardNumber ?? undefined,
- circuitId: feedingCircuit?.id,
- circuitEquipmentIdentifier: feedingCircuit?.equipmentIdentifier,
- circuitDisplayName: feedingCircuit?.displayName ?? undefined,
- circuitCategory: feedingCircuit
- ? sectionById.get(feedingCircuit.sectionId)?.category ?? undefined
- : undefined,
- };
-}
-
-function emptyCircuitTree(
- project: NonNullable>>,
- board: NonNullable>>,
- circuitListId: string
-): CircuitTreeResponse & { warning: string } {
- return {
- circuitListId,
- distributionBoardId: board.id,
- distributionBoardName: board.name,
- distributionBoardNumber: board.boardNumber ?? undefined,
- distributionBoardSupplyType: board.supplyType ?? undefined,
- currentRevision: project.currentRevision,
- singlePhaseVoltageV: project.singlePhaseVoltageV,
- threePhaseVoltageV: project.threePhaseVoltageV,
- distributionBoardSimultaneityFactor: board.simultaneityFactor,
- distributionBoardTotalPower: 0,
- distributionBoardTotalPowerWithSimultaneityFactor: 0,
- headerComponents: [],
- sections: [],
- footerComponents: [],
- warning:
- "Circuit-first tables are not available yet. Run database migrations (including 0008_circuit_first_model).",
- };
+ return res.json(tree);
+ } catch (error) {
+ if (isMissingCircuitTreeSchemaError(error)) {
+ return res.json({
+ circuitListId,
+ currentRevision: project.currentRevision,
+ singlePhaseVoltageV: project.singlePhaseVoltageV,
+ threePhaseVoltageV: project.threePhaseVoltageV,
+ distributionBoardSimultaneityFactor:
+ distributionBoard.simultaneityFactor,
+ distributionBoardTotalPower: 0,
+ distributionBoardTotalPowerWithSimultaneityFactor: 0,
+ headerComponents: [],
+ sections: [],
+ footerComponents: [],
+ warning:
+ "Circuit-first tables are not available yet. Run database migrations (including 0008_circuit_first_model).",
+ });
+ }
+ throw error;
+ }
}
function toProtectionDeviceDto(value: {
@@ -360,20 +243,11 @@ function toProtectionDeviceDto(value: {
return {
type: value.type,
ratedCurrentA: value.ratedCurrentA,
- fuseUtilizationCategory: value.fuseUtilizationCategory ?? undefined,
+ fuseUtilizationCategory:
+ value.fuseUtilizationCategory ?? undefined,
tripCharacteristic: value.tripCharacteristic ?? undefined,
rcdType: value.rcdType ?? undefined,
- ratedResidualCurrentMa: value.ratedResidualCurrentMa ?? undefined,
+ ratedResidualCurrentMa:
+ value.ratedResidualCurrentMa ?? undefined,
};
}
-
-function groupBy(values: T[], keyOf: (value: T) => string) {
- const groups = new Map();
- for (const value of values) {
- const key = keyOf(value);
- const group = groups.get(key) ?? [];
- group.push(value);
- groups.set(key, group);
- }
- return groups;
-}
diff --git a/src/server/controllers/distribution-board.controller.ts b/src/server/controllers/distribution-board.controller.ts
index d2e9bb9..9ea713a 100644
--- a/src/server/controllers/distribution-board.controller.ts
+++ b/src/server/controllers/distribution-board.controller.ts
@@ -69,8 +69,6 @@ export async function createDistributionBoard(req: Request, res: Response) {
location: null,
fedFrom: null,
remark: null,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
},
});
} catch (error) {
@@ -100,17 +98,6 @@ export async function updateDistributionBoard(
.filter((field) => parsed.data[field] !== undefined)
.map((field) => [field, parsed.data[field]])
);
- const feedingPatch = {
- ...(parsed.data.feedingDistributionBoardId === undefined
- ? {}
- : {
- feedingDistributionBoardId:
- parsed.data.feedingDistributionBoardId,
- }),
- ...(parsed.data.feedingCircuitId === undefined
- ? {}
- : { feedingCircuitId: parsed.data.feedingCircuitId }),
- };
try {
const result = projectCommandService.executeUser({
projectId,
@@ -123,7 +110,6 @@ export async function updateDistributionBoard(
supplyType: parsed.data.supplyType,
simultaneityFactor: parsed.data.simultaneityFactor,
...infoPatch,
- ...feedingPatch,
}
),
});
diff --git a/src/server/routes/project.routes.ts b/src/server/routes/project.routes.ts
index 927223e..529def2 100644
--- a/src/server/routes/project.routes.ts
+++ b/src/server/routes/project.routes.ts
@@ -29,10 +29,7 @@ import {
listEstimateBalances,
updateEstimateBalance,
} from "../controllers/estimate-balance.controller.js";
-import {
- getCircuitTree,
- getProjectCircuitTrees,
-} from "../controllers/circuit-tree.controller.js";
+import { getCircuitTree } from "../controllers/circuit-tree.controller.js";
import { getNextCircuitIdentifier } from "../controllers/circuit.controller.js";
import {
getProjectHistory,
@@ -112,7 +109,6 @@ projectRouter.delete(
deleteDistributionBoard
);
projectRouter.get("/:projectId/circuit-lists", listCircuitListsByProject);
-projectRouter.get("/:projectId/circuit-trees", getProjectCircuitTrees);
projectRouter.get("/:projectId/circuit-lists/:circuitListId/tree", getCircuitTree);
projectRouter.get(
"/:projectId/circuit-sections/:sectionId/next-identifier",
diff --git a/src/shared/validation/project-structure.schemas.ts b/src/shared/validation/project-structure.schemas.ts
index ede5152..b8655ba 100644
--- a/src/shared/validation/project-structure.schemas.ts
+++ b/src/shared/validation/project-structure.schemas.ts
@@ -86,8 +86,6 @@ export const updateDistributionBoardSchema = z
location: distributionBoardInfoValueSchema("location"),
fedFrom: distributionBoardInfoValueSchema("fedFrom"),
remark: distributionBoardInfoValueSchema("remark"),
- feedingDistributionBoardId: z.string().trim().min(1).nullable().optional(),
- feedingCircuitId: z.string().trim().min(1).nullable().optional(),
})
.strict();
diff --git a/tests/cable-sizing-calculation.test.ts b/tests/cable-sizing-calculation.test.ts
index 53d81a1..39cfbb1 100644
--- a/tests/cable-sizing-calculation.test.ts
+++ b/tests/cable-sizing-calculation.test.ts
@@ -267,15 +267,6 @@ describe("cable sizing defaults from the circuit", () => {
sortOrder: 1,
isReserve: rows.length === 0,
circuitTotalPower: rows.reduce((sum, row) => sum + row.power, 0),
- circuitApparentPower: rows.reduce(
- (sum, row) => sum + row.power / (row.cosPhi ?? 1),
- 0
- ),
- circuitCosPhi: rows.length
- ? rows.reduce((sum, row) => sum + row.power, 0) /
- rows.reduce((sum, row) => sum + row.power / (row.cosPhi ?? 1), 0)
- : 1,
- downstreamBoardLoads: [],
deviceRows: rows.map((row, index) => ({
id: `row-${index}`,
sortOrder: index,
@@ -297,19 +288,6 @@ describe("cable sizing defaults from the circuit", () => {
assert.equal(deriveCircuitCosPhi(circuit([])), 1);
});
- it("uses the projected cos φ including downstream boards", () => {
- const feeder = circuit([{ power: 1, cosPhi: 1 }]);
- feeder.circuitCosPhi = 0.67;
- feeder.downstreamBoardLoads = [{
- distributionBoardId: "uv-2",
- name: "UV 2",
- activePowerKw: 3,
- apparentPowerKva: 5,
- cosPhi: 0.6,
- }];
- assert.equal(deriveCircuitCosPhi(feeder), 0.67);
- });
-
it("derives the phase from the group category or three-phase rows", () => {
assert.equal(deriveCircuitPhase(circuit([{ power: 1, phaseType: "single_phase" }]), "single_phase"), 1);
assert.equal(deriveCircuitPhase(circuit([{ power: 1, phaseType: "three_phase" }]), "single_phase"), 3);
diff --git a/tests/circuit-device-row-move-project-command.repository.test.ts b/tests/circuit-device-row-move-project-command.repository.test.ts
index 8975368..0569099 100644
--- a/tests/circuit-device-row-move-project-command.repository.test.ts
+++ b/tests/circuit-device-row-move-project-command.repository.test.ts
@@ -14,7 +14,6 @@ import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
-import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
import {
@@ -612,62 +611,6 @@ describe("circuit device-row move project-command repository", () => {
}
});
- it("blocks undo from removing a generated circuit used as a feeder", () => {
- const fixture = createTestDatabase();
- try {
- const store = new CircuitDeviceRowMoveProjectCommandRepository(
- fixture.context.db
- );
- const targetCircuit = createMoveTargetSnapshot(fixture);
- const command = createCircuitDeviceRowMoveWithNewCircuitProjectCommand(
- "create",
- targetCircuit,
- [{
- rowId: "row-1",
- expectedCircuitId: "circuit-1",
- expectedSortOrder: 10,
- targetCircuitId: targetCircuit.id,
- targetSortOrder: 10,
- }]
- );
- const moved = store.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command,
- });
- const downstream = new DistributionBoardFixtureRepository(
- fixture.context.db
- ).createWithCircuitListAndDefaultSections("project-1", "UV-02");
- fixture.context.db
- .update(distributionBoards)
- .set({
- feedingDistributionBoardId: fixture.circuitListId,
- feedingCircuitId: targetCircuit.id,
- })
- .where(eq(distributionBoards.id, downstream.id))
- .run();
-
- assert.throws(
- () => store.execute({
- projectId: "project-1",
- expectedRevision: 1,
- source: "undo",
- historyTargetChangeSetId: moved.revision.changeSetId,
- command: moved.inverse,
- }),
- /Speist UV-02 – erst Einspeisung ändern\./
- );
- assert.equal(getRow(fixture.context, "row-1")?.circuitId, targetCircuit.id);
- assert.ok(
- fixture.context.db.select().from(circuits)
- .where(eq(circuits.id, targetCircuit.id)).get()
- );
- } finally {
- fixture.context.close();
- }
- });
-
it("rejects deletion when the created target changed outside history", () => {
const fixture = createTestDatabase();
try {
diff --git a/tests/circuit-grid-projection.test.ts b/tests/circuit-grid-projection.test.ts
index 1a16913..952e3d6 100644
--- a/tests/circuit-grid-projection.test.ts
+++ b/tests/circuit-grid-projection.test.ts
@@ -47,10 +47,7 @@ function makeCircuit(
sortOrder,
isReserve: deviceRows.length === 0,
circuitTotalPower: deviceRows.reduce((total, row) => total + row.rowTotalPower, 0),
- circuitApparentPower: deviceRows.reduce((total, row) => total + row.rowTotalPower, 0),
- circuitCosPhi: 1,
deviceRows,
- downstreamBoardLoads: [],
};
}
@@ -179,44 +176,6 @@ describe("circuit grid projection", () => {
);
});
- it("renders linked downstream boards as separate read-only rows", () => {
- const feeder = {
- ...reserveCircuit,
- isReserve: false,
- circuitTotalPower: 4,
- circuitApparentPower: 5,
- circuitCosPhi: 0.8,
- downstreamBoardLoads: [{
- distributionBoardId: "board-child",
- name: "Unterverteilung",
- boardNumber: "UV-02",
- activePowerKw: 4,
- apparentPowerKva: 5,
- cosPhi: 0.8,
- }],
- };
- const rows = buildVisibleGridRowsWithStructure(
- [{ ...sections[0], circuits: [feeder] }],
- { headerComponents: [], footerComponents: [] }
- );
- assert.deepEqual(rows.map((row) => row.rowType), [
- "section",
- "circuitSummary",
- "downstreamBoardLoad",
- "placeholder",
- ]);
- const loadRow = rows[2];
- assert.equal(
- loadRow.cells.find((cell) => cell.cellKey === "displayName")?.value,
- "Unterverteilung UV-02"
- );
- assert.equal(
- loadRow.cells.find((cell) => cell.cellKey === "rowTotalPower")?.value,
- 4
- );
- assert.equal(loadRow.cells.every((cell) => !cell.editable), true);
- });
-
it("shows circuit protection once on each visible circuit row", () => {
const protectedSections = sections.map((section) => ({
...section,
diff --git a/tests/circuit-group-subtree-project-command.repository.test.ts b/tests/circuit-group-subtree-project-command.repository.test.ts
index 8494798..05d7dcc 100644
--- a/tests/circuit-group-subtree-project-command.repository.test.ts
+++ b/tests/circuit-group-subtree-project-command.repository.test.ts
@@ -16,7 +16,6 @@ import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
import { distributionBoardComponentProtectionDevices } from "../src/db/schema/distribution-board-component-protection-devices.js";
import { distributionBoardComponents } from "../src/db/schema/distribution-board-components.js";
-import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
import { createCircuitGroupDeleteSubtreeProjectCommand } from "../src/domain/models/circuit-group-subtree-project-command.model.js";
@@ -229,45 +228,6 @@ describe("circuit-group subtree project command", () => {
}
});
- it("blocks deleting a group containing a feeding circuit", () => {
- const context = createTestDatabase();
- try {
- const list = context.db.select().from(circuitLists).get()!;
- const downstream = new DistributionBoardFixtureRepository(
- context.db
- ).createWithCircuitListAndDefaultSections("project-1", "Unterverteiler");
- context.db
- .update(distributionBoards)
- .set({
- feedingDistributionBoardId: list.distributionBoardId,
- feedingCircuitId: "subtree-circuit",
- })
- .where(eq(distributionBoards.id, downstream.id))
- .run();
- const repository =
- new CircuitGroupSubtreeProjectCommandRepository(context.db);
- const groupId = context.db
- .select()
- .from(circuits)
- .where(eq(circuits.id, "subtree-circuit"))
- .get()!.sectionId;
- const snapshot = repository.capture("project-1", groupId);
-
- assert.throws(
- () => repository.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command: createCircuitGroupDeleteSubtreeProjectCommand(snapshot),
- }),
- /Speist Unterverteiler – erst Einspeisung ändern\./
- );
- assert.deepEqual(repository.capture("project-1", groupId), snapshot);
- } finally {
- context.close();
- }
- });
-
it("rolls back a cascaded deletion when history persistence fails", () => {
const context = createTestDatabase();
try {
diff --git a/tests/circuit-structure-project-command.repository.test.ts b/tests/circuit-structure-project-command.repository.test.ts
index 8985ad0..4c05554 100644
--- a/tests/circuit-structure-project-command.repository.test.ts
+++ b/tests/circuit-structure-project-command.repository.test.ts
@@ -14,7 +14,6 @@ import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
import { circuitProtectionDevices } from "../src/db/schema/circuit-protection-devices.js";
import { circuitSections } from "../src/db/schema/circuit-sections.js";
import { circuits } from "../src/db/schema/circuits.js";
-import { distributionBoards } from "../src/db/schema/distribution-boards.js";
import { projectDevices } from "../src/db/schema/project-devices.js";
import { projectRevisions } from "../src/db/schema/project-revisions.js";
import { projects } from "../src/db/schema/projects.js";
@@ -443,48 +442,6 @@ describe("circuit structure project-command repository", () => {
}
});
- it("blocks deleting a circuit that feeds a distribution board", () => {
- const fixture = createTestDatabase();
- try {
- const downstream = new DistributionBoardFixtureRepository(
- fixture.context.db
- ).createWithCircuitListAndDefaultSections("project-1", "UV Unterverteiler");
- fixture.context.db
- .update(distributionBoards)
- .set({
- boardNumber: "UV-02",
- feedingDistributionBoardId: fixture.circuitListId,
- feedingCircuitId: "circuit-existing",
- })
- .where(eq(distributionBoards.id, downstream.id))
- .run();
-
- const store = new CircuitStructureProjectCommandRepository(
- fixture.context.db
- );
- assert.throws(
- () =>
- store.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command: createCircuitDeleteProjectCommand(
- "circuit-existing",
- fixture.circuitListId
- ),
- }),
- /Speist UV-02 – erst Einspeisung ändern\./
- );
- assert.ok(getCircuit(fixture.context, "circuit-existing"));
- assert.equal(
- fixture.context.db.select().from(projectRevisions).all().length,
- 0
- );
- } finally {
- fixture.context.close();
- }
- });
-
it("rolls back insert and delete for late history failures", () => {
const fixture = createTestDatabase();
try {
diff --git a/tests/circuit-tree.controller.test.ts b/tests/circuit-tree.controller.test.ts
index ea20da2..7fcf884 100644
--- a/tests/circuit-tree.controller.test.ts
+++ b/tests/circuit-tree.controller.test.ts
@@ -38,15 +38,13 @@ describe("circuit tree controller", () => {
it("returns header, grouped and footer components with both protection layers", async () => {
const originals = {
findList: circuitListRepository.findById,
- listLists: circuitListRepository.listByProject,
findProject: projectRepository.findById,
findBoard: distributionBoardRepository.findById,
- listBoards: distributionBoardRepository.listByProject,
- listSections: circuitSectionRepository.listByCircuitLists,
- listCircuits: circuitRepository.listByCircuitLists,
+ listSections: circuitSectionRepository.listByCircuitList,
+ listCircuits: circuitRepository.listByCircuitList,
listRows: circuitDeviceRowRepository.listByCircuitList,
listComponents:
- distributionBoardComponentRepository.listByCircuitLists,
+ distributionBoardComponentRepository.listByCircuitList,
listComponentProtection:
distributionBoardComponentRepository.listProtectionByComponentIds,
listCircuitProtection:
@@ -59,13 +57,6 @@ describe("circuit tree controller", () => {
distributionBoardId: "board-1",
name: "UV-01",
}) as never;
- circuitListRepository.listByProject = async () =>
- [{
- id: "list-1",
- projectId: "project-1",
- distributionBoardId: "board-1",
- name: "UV-01",
- }] as never;
projectRepository.findById = async () =>
({
id: "project-1",
@@ -75,19 +66,8 @@ describe("circuit tree controller", () => {
threePhaseVoltageV: 400,
}) as never;
distributionBoardRepository.findById = async () =>
- ({ id: "board-1", name: "UV-01", simultaneityFactor: 0.8 }) as never;
- distributionBoardRepository.listByProject = async () =>
- [{
- id: "board-1",
- projectId: "project-1",
- name: "UV-01",
- boardNumber: "UV-01",
- supplyType: "AV",
- simultaneityFactor: 0.8,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
- }] as never;
- circuitSectionRepository.listByCircuitLists = async () =>
+ ({ id: "board-1", simultaneityFactor: 0.8 }) as never;
+ circuitSectionRepository.listByCircuitList = async () =>
[
{
id: "group-1",
@@ -100,7 +80,7 @@ describe("circuit tree controller", () => {
groupNumber: 1,
},
] as never;
- circuitRepository.listByCircuitLists = async () =>
+ circuitRepository.listByCircuitList = async () =>
[
{
id: "circuit-1",
@@ -110,19 +90,10 @@ describe("circuit tree controller", () => {
displayName: "Licht",
sortOrder: 10,
isReserve: 0,
- cableType: null,
- cableCrossSection: null,
- cableLength: null,
- rcdAssignment: null,
- terminalDesignation: null,
- voltage: 230,
- controlRequirement: null,
- status: null,
- remark: null,
},
] as never;
circuitDeviceRowRepository.listByCircuitList = async () => [];
- distributionBoardComponentRepository.listByCircuitLists =
+ distributionBoardComponentRepository.listByCircuitList =
async () =>
[
{
@@ -199,15 +170,13 @@ describe("circuit tree controller", () => {
);
} finally {
circuitListRepository.findById = originals.findList;
- circuitListRepository.listByProject = originals.listLists;
projectRepository.findById = originals.findProject;
distributionBoardRepository.findById = originals.findBoard;
- distributionBoardRepository.listByProject = originals.listBoards;
- circuitSectionRepository.listByCircuitLists =
+ circuitSectionRepository.listByCircuitList =
originals.listSections;
- circuitRepository.listByCircuitLists = originals.listCircuits;
+ circuitRepository.listByCircuitList = originals.listCircuits;
circuitDeviceRowRepository.listByCircuitList = originals.listRows;
- distributionBoardComponentRepository.listByCircuitLists =
+ distributionBoardComponentRepository.listByCircuitList =
originals.listComponents;
distributionBoardComponentRepository.listProtectionByComponentIds =
originals.listComponentProtection;
diff --git a/tests/distribution-board-load-calculation.test.ts b/tests/distribution-board-load-calculation.test.ts
deleted file mode 100644
index f6c9a95..0000000
--- a/tests/distribution-board-load-calculation.test.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import assert from "node:assert/strict";
-import { describe, it } from "node:test";
-import { calculateDistributionBoardLoads } from "../src/domain/calculations/distribution-board-load-calculation.js";
-
-const board = (
- id: string,
- simultaneityFactor: number,
- feedingDistributionBoardId: string | null = null,
- feedingCircuitId: string | null = null
-) => ({
- id,
- name: id,
- boardNumber: id.toUpperCase(),
- simultaneityFactor,
- feedingDistributionBoardId,
- feedingCircuitId,
-});
-
-describe("distribution-board load calculation", () => {
- it("rolls recursive demand into the selected feeder circuit exactly once", () => {
- const result = calculateDistributionBoardLoads({
- boards: [
- board("nshv", 0.8),
- board("uv1", 0.5, "nshv", "nshv-out"),
- board("uv2", 0.25, "uv1", "uv1-out"),
- ],
- circuits: [
- { id: "nshv-out", distributionBoardId: "nshv", rows: [{ activePowerKw: 2, cosPhi: 1 }] },
- { id: "uv1-out", distributionBoardId: "uv1", rows: [{ activePowerKw: 4, cosPhi: 0.8 }] },
- { id: "uv2-load", distributionBoardId: "uv2", rows: [{ activePowerKw: 8, cosPhi: 0.8 }] },
- ],
- });
-
- assert.deepEqual(result.boards.get("uv2"), {
- distributionBoardId: "uv2",
- activePowerBeforeSimultaneityKw: 8,
- apparentPowerBeforeSimultaneityKva: 10,
- demandActivePowerKw: 2,
- demandApparentPowerKva: 2.5,
- cosPhi: 0.8,
- });
- assert.equal(result.circuits.get("uv1-out")?.activePowerKw, 6);
- assert.equal(result.boards.get("uv1")?.demandActivePowerKw, 3);
- assert.equal(result.circuits.get("nshv-out")?.activePowerKw, 5);
- assert.equal(result.boards.get("nshv")?.demandActivePowerKw, 4);
- assert.equal(result.circuits.get("nshv-out")?.downstreamBoards.length, 1);
- });
-
- it("combines apparent power for multiple child boards on one circuit", () => {
- const result = calculateDistributionBoardLoads({
- boards: [board("main", 1), board("a", 1, "main", "out"), board("b", 1, "main", "out")],
- circuits: [
- { id: "out", distributionBoardId: "main", rows: [] },
- { id: "a-load", distributionBoardId: "a", rows: [{ activePowerKw: 2, cosPhi: 0.5 }] },
- { id: "b-load", distributionBoardId: "b", rows: [{ activePowerKw: 2, cosPhi: 1 }] },
- ],
- });
- const feeder = result.circuits.get("out")!;
- assert.equal(feeder.activePowerKw, 4);
- assert.equal(feeder.apparentPowerKva, 6);
- assert.equal(feeder.cosPhi, 2 / 3);
- assert.deepEqual(feeder.downstreamBoards.map((entry) => entry.distributionBoardId), ["a", "b"]);
- });
-
- it("keeps a board-only hierarchy relation out of load rollup", () => {
- const result = calculateDistributionBoardLoads({
- boards: [board("main", 1), board("child", 1, "main", null)],
- circuits: [
- { id: "main-load", distributionBoardId: "main", rows: [{ activePowerKw: 1, cosPhi: 1 }] },
- { id: "child-load", distributionBoardId: "child", rows: [{ activePowerKw: 5, cosPhi: 1 }] },
- ],
- });
- assert.equal(result.boards.get("main")?.demandActivePowerKw, 1);
- assert.equal(result.boards.get("child")?.demandActivePowerKw, 5);
- });
-
- it("rejects cycles defensively", () => {
- assert.throws(
- () => calculateDistributionBoardLoads({
- boards: [board("a", 1, "b", "b-out"), board("b", 1, "a", "a-out")],
- circuits: [
- { id: "a-out", distributionBoardId: "a", rows: [] },
- { id: "b-out", distributionBoardId: "b", rows: [] },
- ],
- }),
- /cycle/
- );
- });
-});
diff --git a/tests/distribution-board-structure-project-command.repository.test.ts b/tests/distribution-board-structure-project-command.repository.test.ts
index a02f005..f829edf 100644
--- a/tests/distribution-board-structure-project-command.repository.test.ts
+++ b/tests/distribution-board-structure-project-command.repository.test.ts
@@ -219,54 +219,6 @@ describe("distribution-board structure project command", () => {
}
});
- it("blocks undo from removing an empty board used as a feeder", () => {
- const context = createTestDatabase();
- try {
- const repository =
- new DistributionBoardStructureProjectCommandRepository(context.db);
- const upstream = createDistributionBoardStructureSnapshot(
- "project-1",
- "NSHV"
- );
- const inserted = repository.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command: createDistributionBoardInsertProjectCommand(upstream),
- });
- const downstream = createDistributionBoardStructureSnapshot(
- "project-1",
- "UV-01"
- );
- context.db.insert(distributionBoards).values({
- ...downstream.distributionBoard,
- boardNumber: "UV-01",
- feedingDistributionBoardId: upstream.distributionBoard.id,
- }).run();
- context.db.insert(circuitLists).values(downstream.circuitList).run();
- context.db.insert(circuitSections).values(downstream.sections).run();
- context.db.insert(distributionBoardComponents)
- .values(downstream.components).run();
-
- assert.throws(
- () => repository.execute({
- projectId: "project-1",
- expectedRevision: 1,
- source: "undo",
- historyTargetChangeSetId: inserted.revision.changeSetId,
- command: inserted.inverse,
- }),
- /Speist UV-01 – erst Einspeisung ändern\./
- );
- assert.ok(
- context.db.select().from(distributionBoards)
- .where(eq(distributionBoards.id, upstream.distributionBoard.id)).get()
- );
- } finally {
- context.close();
- }
- });
-
it("allows only supply types enabled in the project", () => {
const context = createTestDatabase();
try {
@@ -539,159 +491,6 @@ describe("distribution-board structure project command", () => {
}
});
- it("links a feeding board and circuit while preserving version-1 updates", () => {
- const context = createTestDatabase();
- try {
- const repository =
- new DistributionBoardStructureProjectCommandRepository(context.db);
- const upstream = createDistributionBoardStructureSnapshot(
- "project-1",
- "NSHV",
- { supplyType: "AV" }
- );
- const downstream = createDistributionBoardStructureSnapshot(
- "project-1",
- "UV EG",
- { supplyType: "AV" }
- );
- repository.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command: createDistributionBoardInsertProjectCommand(upstream),
- });
- repository.execute({
- projectId: "project-1",
- expectedRevision: 1,
- source: "user",
- command: createDistributionBoardInsertProjectCommand(downstream),
- });
- context.db.insert(circuits).values([
- {
- id: "feed-circuit",
- circuitListId: upstream.circuitList.id,
- sectionId: upstream.sections[2]!.id,
- equipmentIdentifier: "-3F1.1",
- sortOrder: 10,
- },
- {
- id: "downstream-circuit",
- circuitListId: downstream.circuitList.id,
- sectionId: downstream.sections[2]!.id,
- equipmentIdentifier: "-3F1.1",
- sortOrder: 10,
- },
- ]).run();
-
- const linked = repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 2,
- source: "user",
- command: createDistributionBoardUpdateProjectCommand(
- downstream.distributionBoard.id,
- {
- feedingDistributionBoardId: upstream.distributionBoard.id,
- feedingCircuitId: "feed-circuit",
- }
- ),
- });
- assert.deepEqual(
- context.db
- .select({
- feedingDistributionBoardId:
- distributionBoards.feedingDistributionBoardId,
- feedingCircuitId: distributionBoards.feedingCircuitId,
- })
- .from(distributionBoards)
- .where(eq(distributionBoards.id, downstream.distributionBoard.id))
- .get(),
- {
- feedingDistributionBoardId: upstream.distributionBoard.id,
- feedingCircuitId: "feed-circuit",
- }
- );
-
- repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 3,
- source: "undo",
- historyTargetChangeSetId: new ProjectHistoryRepository(
- context.db
- ).getNextCommand("project-1", "undo")?.changeSetId,
- command: linked.inverse,
- });
- repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 4,
- source: "user",
- command: createDistributionBoardUpdateProjectCommand(
- downstream.distributionBoard.id,
- {
- feedingDistributionBoardId: upstream.distributionBoard.id,
- feedingCircuitId: null,
- }
- ),
- });
-
- assert.throws(
- () => repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 5,
- source: "user",
- command: createDistributionBoardUpdateProjectCommand(
- upstream.distributionBoard.id,
- {
- feedingDistributionBoardId: downstream.distributionBoard.id,
- feedingCircuitId: null,
- }
- ),
- }),
- /Zyklus/
- );
- assert.throws(
- () => repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 5,
- source: "user",
- command: createDistributionBoardUpdateProjectCommand(
- downstream.distributionBoard.id,
- {
- feedingDistributionBoardId: upstream.distributionBoard.id,
- feedingCircuitId: "downstream-circuit",
- }
- ),
- }),
- /gehört nicht zur gewählten Verteilung/
- );
-
- repository.executeUpdate({
- projectId: "project-1",
- expectedRevision: 5,
- source: "user",
- command: {
- schemaVersion: 1,
- type: "distribution-board.update",
- payload: {
- distributionBoardId: downstream.distributionBoard.id,
- changes: [{ field: "boardNumber", value: "UV-01" }],
- },
- },
- });
- const afterLegacyUpdate = context.db
- .select()
- .from(distributionBoards)
- .where(eq(distributionBoards.id, downstream.distributionBoard.id))
- .get();
- assert.equal(afterLegacyUpdate?.boardNumber, "UV-01");
- assert.equal(
- afterLegacyUpdate?.feedingDistributionBoardId,
- upstream.distributionBoard.id
- );
- } finally {
- context.close();
- }
- });
-
it("sends distribution-board writes to the revision-safe API", async () => {
const requests: Array<{ url: string; method: string; body: unknown }> = [];
const originalFetch = globalThis.fetch;
diff --git a/tests/distribution-board-subtree-project-command.repository.test.ts b/tests/distribution-board-subtree-project-command.repository.test.ts
index 6508b9b..7975f2b 100644
--- a/tests/distribution-board-subtree-project-command.repository.test.ts
+++ b/tests/distribution-board-subtree-project-command.repository.test.ts
@@ -291,43 +291,6 @@ describe("distribution-board subtree project command", () => {
}
});
- it("blocks deleting a board subtree that feeds another board", () => {
- const context = createTestDatabase();
- try {
- const upstream = createPopulatedBoard(context);
- const downstream = new DistributionBoardFixtureRepository(
- context.db
- ).createWithCircuitListAndDefaultSections("project-1", "Unterverteiler");
- context.db
- .update(distributionBoards)
- .set({
- boardNumber: "UV-03",
- feedingDistributionBoardId: upstream.id,
- })
- .where(eq(distributionBoards.id, downstream.id))
- .run();
- const repository =
- new DistributionBoardSubtreeProjectCommandRepository(context.db);
- const snapshot = repository.capture("project-1", upstream.id);
-
- assert.throws(
- () =>
- repository.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command:
- createDistributionBoardDeleteSubtreeProjectCommand(snapshot),
- }),
- /Speist UV-03 – erst Einspeisung ändern\./
- );
- assert.deepEqual(repository.capture("project-1", upstream.id), snapshot);
- assert.equal(context.db.select().from(projectRevisions).all().length, 0);
- } finally {
- context.close();
- }
- });
-
it("rolls back a cascaded deletion when history persistence fails", () => {
const context = createTestDatabase();
try {
diff --git a/tests/distribution-board-subtree.test.ts b/tests/distribution-board-subtree.test.ts
index 6e48294..33d5318 100644
--- a/tests/distribution-board-subtree.test.ts
+++ b/tests/distribution-board-subtree.test.ts
@@ -19,8 +19,6 @@ function createSnapshot(): DistributionBoardSubtreeSnapshot {
floorId: "floor-1",
supplyType: "AV",
simultaneityFactor: 0.8,
- feedingDistributionBoardId: "board-upstream",
- feedingCircuitId: "circuit-upstream",
},
circuitList: {
id: "list-1",
@@ -159,11 +157,6 @@ describe("distribution-board subtree snapshot", () => {
assert.equal(clone.distributionBoard.id, "new-1");
assert.equal(clone.circuitList.id, "new-1");
assert.equal(clone.distributionBoard.name, "UV 1 Kopie");
- assert.equal(
- clone.distributionBoard.feedingDistributionBoardId,
- "board-upstream"
- );
- assert.equal(clone.distributionBoard.feedingCircuitId, "circuit-upstream");
assert.equal(clone.circuitList.name, "UV 1 Kopie Stromkreisliste");
assert.equal(clone.sections[0].id, "new-2");
assert.equal(clone.circuits[0].id, "new-3");
diff --git a/tests/external-object-row-assignment-project-command.repository.test.ts b/tests/external-object-row-assignment-project-command.repository.test.ts
index 35579eb..da8551e 100644
--- a/tests/external-object-row-assignment-project-command.repository.test.ts
+++ b/tests/external-object-row-assignment-project-command.repository.test.ts
@@ -533,61 +533,6 @@ describe("external object new-circuit project command", () => {
}
});
- it("blocks history removal when the generated circuit became a feeder", () => {
- const context = createFixture(null);
- try {
- const object = objectSnapshot(null);
- const circuit = newExternalCircuit(context.db);
- const command = createExternalObjectNewCircuitProjectCommand(
- externalObjectAssignToNewCircuitCommandType,
- {
- circuit,
- objects: [{
- expected: object,
- target: { ...object, circuitDeviceRowId: "row-new" },
- }],
- confirmedConflictObjectIds: [],
- }
- );
- const repository = new ExternalObjectNewCircuitProjectCommandRepository(context.db);
- const inserted = repository.execute({
- projectId: "project-1",
- expectedRevision: 0,
- source: "user",
- command,
- });
- context.db.insert(distributionBoards).values({
- id: "board-2",
- projectId: "project-1",
- name: "Unterverteiler",
- boardNumber: "UV-02",
- feedingDistributionBoardId: "board-1",
- feedingCircuitId: circuit.id,
- }).run();
-
- assert.throws(
- () => repository.execute({
- projectId: "project-1",
- expectedRevision: 1,
- source: "undo",
- historyTargetChangeSetId: inserted.revision.changeSetId,
- command: inserted.inverse,
- }),
- /Speist UV-02 – erst Einspeisung ändern\./
- );
- assert.ok(
- context.db.select().from(circuits)
- .where(eq(circuits.id, circuit.id)).get()
- );
- assert.equal(
- context.db.select().from(externalModelObjects).get()!.circuitDeviceRowId,
- "row-new"
- );
- } finally {
- context.close();
- }
- });
-
it("rolls back the complete circuit subtree when history persistence fails", () => {
const context = createFixture(null);
try {
diff --git a/tests/print-document-model.test.ts b/tests/print-document-model.test.ts
index ba91c3e..80a5200 100644
--- a/tests/print-document-model.test.ts
+++ b/tests/print-document-model.test.ts
@@ -2,8 +2,7 @@ import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
assertPrintRevisionConsistency,
- buildBoardHierarchy,
- formatFeedingReference,
+ buildPreliminaryBoardHierarchy,
formatCircuitCable,
formatProtectionDevice,
summarizePrintBoard,
@@ -28,8 +27,6 @@ function board(
location: null,
fedFrom: null,
remark: null,
- feedingDistributionBoardId: null,
- feedingCircuitId: null,
...info,
};
}
@@ -55,8 +52,6 @@ function tree(
const total = deviceRows.reduce((sum, row) => sum + row.rowTotalPower, 0);
return {
circuitListId: "list",
- distributionBoardId: "board",
- distributionBoardName: "Board",
currentRevision: 1,
singlePhaseVoltageV: 230,
threePhaseVoltageV: 400,
@@ -84,13 +79,7 @@ function tree(
sortOrder: 1,
isReserve: false,
circuitTotalPower: total,
- circuitApparentPower: deviceRows.reduce(
- (sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1),
- 0
- ),
- circuitCosPhi: 1,
deviceRows,
- downstreamBoardLoads: [],
},
],
},
@@ -128,15 +117,16 @@ describe("print document model", () => {
assert.equal(formatCircuitCable(circuit), null);
});
- it("does not derive hierarchy from the external free text", () => {
+ it("does not guess a parent when feed text matches more than one board", () => {
const summaries = [
summary("a", { boardNumber: "HV" }, 2),
summary("b", { name: "HV" }, 3),
summary("uv", { fedFrom: " hv " }, 1),
];
for (const ordered of [summaries, [...summaries].reverse()]) {
- const entries = buildBoardHierarchy(ordered);
+ const entries = buildPreliminaryBoardHierarchy(ordered);
assert.ok(entries.every((entry) => entry.parentBoardId === null));
+ assert.equal(entries.reduce((sum, entry) => sum + entry.cumulatedDemandPowerKw, 0), 6);
}
});
@@ -155,39 +145,17 @@ describe("print document model", () => {
});
assert.equal(result.circuitCount, 1);
assert.equal(result.installedPowerKw, 14);
- assert.equal(result.powerBeforeBoardSimultaneityKw, 10);
+ assert.equal(result.devicePowerKw, 10);
assert.equal(result.demandPowerKw, 5);
// S = (4 / 0.8 + 6 / 1) × 0.5 = 5.5 kVA → I = 5500 / (√3 · 400)
assert.ok(Math.abs(result.designCurrentA - 5500 / (Math.sqrt(3) * 400)) < 1e-9);
});
- it("uses the selected one-phase feeder circuit for the design current", () => {
- const inputTree = tree([
- { quantity: 1, powerPerUnit: 2.3, simultaneityFactor: 1, cosPhi: 1 },
- ]);
- inputTree.feedingReference = {
- distributionBoardId: "hv",
- circuitListId: "hv-list",
- distributionBoardName: "Hauptverteilung",
- distributionBoardNumber: "NSHV",
- circuitId: "out",
- circuitEquipmentIdentifier: "-2F1.1",
- circuitCategory: "single_phase",
- };
- const result = summarizePrintBoard({
- board: board("uv"),
- circuitListId: "uv",
- floorName: null,
- tree: inputTree,
- });
- assert.ok(Math.abs(result.designCurrentA - 10) < 1e-9);
- });
-
- it("nests boards by their logical feeding references", () => {
- const entries = buildBoardHierarchy([
+ it("nests boards by matching free-text feed and sums downstream demand", () => {
+ const entries = buildPreliminaryBoardHierarchy([
summary("hv", { boardNumber: "HV 0.1", fedFrom: "HAK" }, 10),
- summary("uv-a", { boardNumber: "UV 1.1", feedingDistributionBoardId: "hv" }, 4),
- summary("uv-b", { boardNumber: "UV 1.2", feedingDistributionBoardId: "uv-a" }, 2),
+ summary("uv-a", { boardNumber: "UV 1.1", fedFrom: " hv 0.1 " }, 4),
+ summary("uv-b", { boardNumber: "UV 1.2", fedFrom: "UV 1.1" }, 2),
summary("uv-c", { name: "UV-C", fedFrom: "unbekannt" }, 1),
]);
assert.deepEqual(
@@ -195,37 +163,28 @@ describe("print document model", () => {
entry.summary.board.id,
entry.depth,
entry.parentBoardId,
+ entry.cumulatedDemandPowerKw,
]),
[
- ["hv", 0, null],
- ["uv-a", 1, "hv"],
- ["uv-b", 2, "uv-a"],
- ["uv-c", 0, null],
+ ["hv", 0, null, 16],
+ ["uv-a", 1, "hv", 6],
+ ["uv-b", 2, "uv-a", 2],
+ ["uv-c", 0, null, 1],
]
);
});
- it("keeps malformed cyclic logical links visible at the top level", () => {
- const entries = buildBoardHierarchy([
- summary("a", { boardNumber: "A", feedingDistributionBoardId: "b" }, 1),
- summary("b", { boardNumber: "B", feedingDistributionBoardId: "a" }, 1),
+ it("keeps boards with cyclic feed texts at the top level", () => {
+ const entries = buildPreliminaryBoardHierarchy([
+ summary("a", { boardNumber: "A", fedFrom: "B" }, 1),
+ summary("b", { boardNumber: "B", fedFrom: "A" }, 1),
]);
assert.equal(entries.length, 2);
assert.ok(entries.some((entry) => entry.parentBoardId === null));
- });
-
- it("formats the resolved feeding board and circuit instead of free text", () => {
- const value = summary("uv", { fedFrom: "Alttext" }, 1);
- value.tree.feedingReference = {
- distributionBoardId: "hv",
- circuitListId: "hv-list",
- distributionBoardName: "Hauptverteilung",
- distributionBoardNumber: "NSHV",
- circuitId: "out",
- circuitEquipmentIdentifier: "-3F1.1",
- circuitCategory: "three_phase",
- };
- assert.equal(formatFeedingReference(value), "NSHV-3F1.1");
+ assert.equal(
+ entries.reduce((sum, entry) => sum + (entry.depth === 0 ? entry.cumulatedDemandPowerKw : 0), 0),
+ 2
+ );
});
it("formats protection devices for the plan table", () => {
diff --git a/tests/print-view-migration.test.ts b/tests/print-view-migration.test.ts
index 343de70..19e4dcc 100644
--- a/tests/print-view-migration.test.ts
+++ b/tests/print-view-migration.test.ts
@@ -50,8 +50,6 @@ it("upgrades a populated pre-print database through the current schema without c
for (const [key, value] of Object.entries(oldRoom)) assert.deepEqual(room[key], value);
for (const field of ["site_address", "work_stage", "project_lead", "project_editor"]) assert.equal(project[field], null);
for (const field of ["board_number", "location", "fed_from", "remark"]) assert.equal(board[field], null);
- assert.equal(board.feeding_distribution_board_id, null);
- assert.equal(board.feeding_circuit_id, null);
assert.equal(floor.building_id, null);
assert.equal(room.area_m2, null);
assert.equal(context.sqlite.prepare("SELECT count(*) AS count FROM buildings").get()!.count, 0);
diff --git a/tests/project-snapshot.repository.test.ts b/tests/project-snapshot.repository.test.ts
index bc4cd12..7a02fec 100644
--- a/tests/project-snapshot.repository.test.ts
+++ b/tests/project-snapshot.repository.test.ts
@@ -476,14 +476,7 @@ describe("project snapshot repository", () => {
delete payload.project[field];
}
for (const board of payload.distributionBoards) {
- for (const field of [
- "boardNumber",
- "location",
- "fedFrom",
- "remark",
- "feedingDistributionBoardId",
- "feedingCircuitId",
- ]) {
+ for (const field of ["boardNumber", "location", "fedFrom", "remark"]) {
delete board[field];
}
}
diff --git a/tests/project-state-snapshot.test.ts b/tests/project-state-snapshot.test.ts
index aa2371b..c067c19 100644
--- a/tests/project-state-snapshot.test.ts
+++ b/tests/project-state-snapshot.test.ts
@@ -6,25 +6,12 @@ import {
projectStateSnapshotSchemaVersion,
serializeProjectStateSnapshot,
} from "../src/domain/models/project-state-snapshot.model.js";
-import { remapProjectState } from "../src/domain/models/project-transfer.model.js";
import { createNamedProjectSnapshotSchema } from "../src/shared/validation/project-snapshot.schemas.js";
-/** Removes the fields introduced by snapshot schema version 9. */
-function withoutVersionNineFields<
- T extends { distributionBoards: object[] },
->(snapshot: T): T {
- for (const board of snapshot.distributionBoards) {
- delete (board as Record).feedingDistributionBoardId;
- delete (board as Record).feedingCircuitId;
- }
- return snapshot;
-}
-
/** Removes the fields introduced by snapshot schema version 7. */
function withoutVersionSevenFields<
- T extends { floors: object[]; distributionBoards: object[] },
+ T extends { floors: object[] },
>(snapshot: T): T {
- withoutVersionNineFields(snapshot);
delete (snapshot as Record).buildings;
for (const floor of snapshot.floors) {
delete (floor as Record).buildingId;
@@ -130,8 +117,6 @@ function protectedSnapshot() {
location: "Raum 003 Technik" as string | null,
fedFrom: "HV 0.1" as string | null,
remark: null as string | null,
- feedingDistributionBoardId: null as string | null,
- feedingCircuitId: null as string | null,
},
],
circuitLists: [
@@ -221,96 +206,6 @@ describe("project state snapshot model", () => {
);
});
- it("upgrades snapshot version 8 with empty feeding references", () => {
- const previous = withoutVersionNineFields(protectedSnapshot());
- previous.schemaVersion = 8 as typeof previous.schemaVersion;
-
- const upgraded = parseProjectStateSnapshot(previous);
-
- assert.equal(upgraded.schemaVersion, projectStateSnapshotSchemaVersion);
- assert.equal(
- upgraded.distributionBoards[0]?.feedingDistributionBoardId,
- null
- );
- assert.equal(upgraded.distributionBoards[0]?.feedingCircuitId, null);
- });
-
- it("validates logical feeding references and cycles", () => {
- const linked = protectedSnapshot();
- linked.distributionBoards.push({
- id: "board-2",
- projectId: "project-1",
- name: "UV 2",
- floorId: null,
- supplyType: null,
- simultaneityFactor: 1,
- boardNumber: "UV 2.1",
- location: null,
- fedFrom: null,
- remark: null,
- feedingDistributionBoardId: "board-1",
- feedingCircuitId: "circuit-1",
- });
- linked.circuitLists.push({
- id: "list-2",
- projectId: "project-1",
- distributionBoardId: "board-2",
- name: "UV 2 Stromkreisliste",
- });
- linked.circuitSections.push({
- id: "section-2",
- circuitListId: "list-2",
- key: "three_phase",
- displayName: "Dreiphasig 1",
- prefix: "-3F1.",
- sortOrder: 10,
- category: "three_phase",
- groupNumber: 1,
- });
- const parsed = parseProjectStateSnapshot(linked);
- assert.equal(parsed.distributionBoards[1]?.feedingCircuitId, "circuit-1");
- let nextId = 0;
- const remapped = remapProjectState(
- parsed,
- "project-copy",
- () => `copy-${++nextId}`
- );
- const remappedUpstream = remapped.distributionBoards.find(
- (board) => board.name === "UV 1"
- );
- const remappedDownstream = remapped.distributionBoards.find(
- (board) => board.name === "UV 2"
- );
- assert.ok(remappedUpstream);
- assert.ok(remappedDownstream);
- assert.equal(
- remappedDownstream.feedingDistributionBoardId,
- remappedUpstream.id
- );
- assert.notEqual(remappedDownstream.feedingCircuitId, "circuit-1");
- assert.ok(
- remapped.circuits.some(
- (circuit) => circuit.id === remappedDownstream.feedingCircuitId
- )
- );
-
- const missingBoard = structuredClone(linked);
- missingBoard.distributionBoards[1]!.feedingDistributionBoardId = null;
- assert.throws(
- () => parseProjectStateSnapshot(missingBoard),
- /requires a feeding distribution board/
- );
-
- const cycle = structuredClone(linked);
- cycle.distributionBoards[0]!.feedingDistributionBoardId = "board-2";
- cycle.distributionBoards[0]!.feedingCircuitId = null;
- cycle.distributionBoards[1]!.feedingCircuitId = null;
- assert.throws(
- () => parseProjectStateSnapshot(cycle),
- /contains a cycle/
- );
- });
-
it("upgrades the supported baseline snapshot with a safe building default", () => {
const baseline = withoutVersionSixFields(structuredClone(minimalSnapshot()));
baseline.schemaVersion = 1 as typeof baseline.schemaVersion;