Update print view and documentation for logical feeds
This commit is contained in:
parent
042a050876
commit
b74b495fab
10 changed files with 278 additions and 158 deletions
51
AGENTS.md
51
AGENTS.md
|
|
@ -52,7 +52,8 @@ See `docs/current-architecture.md` for the complete module and request flow.
|
||||||
- Device rows inside a circuit do not have their own equipment identifiers.
|
- Device rows inside a circuit do not have their own equipment identifiers.
|
||||||
- Protection and cable data belong to the circuit, not to individual devices.
|
- 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.
|
- 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.
|
- 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.
|
||||||
- Existing equipment identifiers must never be changed automatically.
|
- Existing equipment identifiers must never be changed automatically.
|
||||||
- Renumbering is always an explicit user action.
|
- Renumbering is always an explicit user action.
|
||||||
|
|
||||||
|
|
@ -284,16 +285,19 @@ not change the project revision or undo/redo stacks. Restoring a server-stored
|
||||||
snapshot verifies its checksum and the current-state hash, replaces supported
|
snapshot verifies its checksum and the current-state hash, replaces supported
|
||||||
project data atomically and records a new `restore` revision with a complete
|
project data atomically and records a new `restore` revision with a complete
|
||||||
inverse command. Restore can therefore be undone and redone after a restart.
|
inverse command. Restore can therefore be undone and redone after a restart.
|
||||||
Current snapshot schema version 8 additionally stores the optional net room
|
Current snapshot schema version 9 additionally stores
|
||||||
area `rooms[].areaM2` (see `docs/building-structure.md`); versions 1 to 7 are
|
`feedingDistributionBoardId` and `feedingCircuitId` on distribution boards;
|
||||||
upgraded with `areaM2 = null`. Version 7 stores the project buildings and
|
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
|
||||||
`floors[].buildingId`; versions 1 to 6 are upgraded with no buildings and
|
`floors[].buildingId`; versions 1 to 6 are upgraded with no buildings and
|
||||||
`buildingId = null`. Version 6 stores the
|
`buildingId = null`. Version 6 stores the
|
||||||
distribution-board info fields `boardNumber`, `location`, `fedFrom` and
|
distribution-board info fields `boardNumber`, `location`, `fedFrom` and
|
||||||
`remark` and the project cover fields `siteAddress`, `workStage`,
|
`remark` and the project cover fields `siteAddress`, `workStage`,
|
||||||
`projectLead` and `projectEditor`; all older supported versions are upgraded
|
`projectLead` and `projectEditor`; all older supported versions are upgraded
|
||||||
with `null` for these fields.
|
with `null` for these fields.
|
||||||
Stored named and automatic snapshots of every supported version (1 to 8)
|
Stored named and automatic snapshots of every supported version (1 to 9)
|
||||||
remain restorable; they are upgraded while parsing. Version 5 stores
|
remain restorable; they are upgraded while parsing. Version 5 stores
|
||||||
the manual quantity share of every CircuitDeviceRow. Version 4 snapshots are upgraded with
|
the manual quantity share of every CircuitDeviceRow. Version 4 snapshots are upgraded with
|
||||||
`manualQuantity = quantity`; they already contain the complete confirmed
|
`manualQuantity = quantity`; they already contain the complete confirmed
|
||||||
|
|
@ -309,7 +313,7 @@ project graph.
|
||||||
Project-wide Revit CSV configuration uses
|
Project-wide Revit CSV configuration uses
|
||||||
`external-csv-configuration.update`. Its complete expected/target snapshot is
|
`external-csv-configuration.update`. Its complete expected/target snapshot is
|
||||||
persisted through the shared project-command transaction, so create, update,
|
persisted through the shared project-command transaction, so create, update,
|
||||||
delete, Undo and Redo are revision-safe. Snapshot schema 5-8, restore and portable
|
delete, Undo and Redo are revision-safe. Snapshot schema 5-9, restore and portable
|
||||||
project duplication preserve the configuration; duplication remaps its internal
|
project duplication preserve the configuration; duplication remaps its internal
|
||||||
id and project link.
|
id and project link.
|
||||||
The project page exposes the Revit CSV modal. It edits the complete
|
The project page exposes the Revit CSV modal. It edits the complete
|
||||||
|
|
@ -323,7 +327,7 @@ immutable import batches with original bytes plus the classified cell matrix,
|
||||||
normalized source-room mappings and external objects unique by
|
normalized source-room mappings and external objects unique by
|
||||||
`(sourceId, ifcGuid)`. Source values, planning values and overrides are separate.
|
`(sourceId, ifcGuid)`. Source values, planning values and overrides are separate.
|
||||||
The general external-model repository is read-only; runtime writes belong only
|
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-8
|
to typed command adapters. Snapshot/restore and portable project transfer schema versions 5-9
|
||||||
capture the complete external state and remap every internal UUID and link while
|
capture the complete external state and remap every internal UUID and link while
|
||||||
preserving IFCGUIDs, source values, original bytes and the classified matrix.
|
preserving IFCGUIDs, source values, original bytes and the classified matrix.
|
||||||
Import batches additionally store the monotonic project CSV configuration
|
Import batches additionally store the monotonic project CSV configuration
|
||||||
|
|
@ -463,22 +467,34 @@ snapshot contains the group, optional component protections, complete circuits,
|
||||||
circuit protections and all device-row link/override metadata. Delete
|
circuit protections and all device-row link/override metadata. Delete
|
||||||
re-captures and compares the subtree before cascading; restore preserves every
|
re-captures and compares the subtree before cascading; restore preserves every
|
||||||
UUID in foreign-key-safe order.
|
UUID in foreign-key-safe order.
|
||||||
The circuit-tree read model exposes fixed `headerComponents`, group-owned
|
The project-wide circuit-tree read model exposes fixed `headerComponents`, group-owned
|
||||||
`components`, `footerComponents`, optional group category/number and optional
|
`components`, `footerComponents`, optional group category/number and optional
|
||||||
one-to-one protection DTOs for circuits and group components. These are
|
one-to-one protection DTOs for circuits and group components. These are
|
||||||
the only supported circuit-protection source; no flat compatibility fields remain.
|
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 floor assignment and a project-enabled supply type use
|
||||||
`distribution-board.update`; both values are snapshot/export fields and one
|
`distribution-board.update`; both values are snapshot/export fields and one
|
||||||
persistent undo step.
|
persistent undo step.
|
||||||
The same command (still schema version 1, additively extended) carries the
|
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
|
||||||
board info-card fields `boardNumber`, `location`, `fedFrom` and `remark`
|
board info-card fields `boardNumber`, `location`, `fedFrom` and `remark`
|
||||||
(migration `0007`). They are nullable, trimmed planner values; the PUT route
|
(migration `0007`). They are nullable, trimmed planner values; the PUT route
|
||||||
only includes submitted info fields in the command and its inverse. An empty
|
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
|
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
|
it is rejected like a changed simultaneity factor. Populated-subtree commands
|
||||||
stored before these fields existed are normalized to `null` values
|
stored before these fields existed are normalized to `null` values
|
||||||
(`normalizeDistributionBoardSubtreeSnapshot`). `fedFrom` is deliberately free
|
(`normalizeDistributionBoardSubtreeSnapshot`). `fedFrom` remains free text for
|
||||||
text: the logical link to the feeding board/circuit is an open design decision.
|
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.
|
||||||
These fields are intended to be prefillable from Revit parameters later, but
|
These fields are intended to be prefillable from Revit parameters later, but
|
||||||
only through the Phase 14 rules (preview, explicit confirmation, no silent
|
only through the Phase 14 rules (preview, explicit confirmation, no silent
|
||||||
overwrite).
|
overwrite).
|
||||||
|
|
@ -521,9 +537,10 @@ Required operations:
|
||||||
`<thead>` so it repeats on printed pages. PDF export uses the browser print
|
`<thead>` 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
|
dialog; page size, margins and "Seite x von y" come from injected named
|
||||||
`@page` rules (`lb-portrait`, `lb-landscape`).
|
`@page` rules (`lb-portrait`, `lb-landscape`).
|
||||||
- The board hierarchy and "Σ inkl. unterlagert" in the overview are
|
- The board hierarchy follows logical board ids. The overview shows power
|
||||||
PRELIMINARY (text match of `fedFrom` against board number/name, plain sum,
|
before board GZF and demand including linked downstream boards; it does not
|
||||||
no upstream simultaneity factor). Replace this once the feed link is modeled.
|
add a second cumulative column. Board plans render downstream-board loads as
|
||||||
|
read-only rows. `fedFrom` groups only external root feeds.
|
||||||
- Colors/fonts follow the kbp corporate design (`--pd-*` variables in
|
- Colors/fonts follow the kbp corporate design (`--pd-*` variables in
|
||||||
`print-document.css`). Empty cover values are shown as
|
`print-document.css`). Empty cover values are shown as
|
||||||
"[ergänzen]"; the Planstände editor column shows the project's current
|
"[ergänzen]"; the Planstände editor column shows the project's current
|
||||||
|
|
@ -701,9 +718,9 @@ for the early-phase power balance by DIN 276 cost group (see
|
||||||
- full electrical sizing
|
- full electrical sizing
|
||||||
- multi-user/PostgreSQL operation
|
- multi-user/PostgreSQL operation
|
||||||
- supported production deployment
|
- supported production deployment
|
||||||
- logical feed link between distribution boards (`fedFrom` stays free text
|
- second/replacement board feeds, including changeover semantics for SV/SiBe
|
||||||
until the project owner decides whether it references a board, a feeding
|
- Revit prefill proposals for logical board feeds through the Phase 14 preview
|
||||||
circuit or both) and the resulting hierarchical power summation
|
and explicit-confirmation workflow
|
||||||
|
|
||||||
Do not implement these while working on an unrelated phase.
|
Do not implement these while working on an unrelated phase.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,13 @@ Prüfsumme; Ersetzen übernimmt die Grobbilanzen atomar und mit Undo/Redo,
|
||||||
Duplizieren vergibt neue IDs. Transferformat 1 bleibt ohne Grobbilanzen
|
Duplizieren vergibt neue IDs. Transferformat 1 bleibt ohne Grobbilanzen
|
||||||
importierbar.
|
importierbar.
|
||||||
|
|
||||||
Das aktuelle Projekt-Snapshot-Schema 8 enthält außerdem die optionale
|
Das aktuelle Projekt-Snapshot-Schema 9 enthält die logischen Einspeisungsverweise
|
||||||
Netto-Raumfläche `rooms[].areaM2`. Snapshot-Versionen 1–7 werden dafür mit
|
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
|
||||||
`areaM2 = null` hochgestuft. Restore, Export und Import erhalten vorhandene
|
`areaM2 = null` hochgestuft. Restore, Export und Import erhalten vorhandene
|
||||||
Flächen; eine eigene Transferformat-Version ist nicht nötig, weil Format 2 den
|
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
|
||||||
versionierten Projektsnapshot einbettet.
|
versionierten Projektsnapshot einbettet.
|
||||||
|
|
||||||
The public dispatcher supports the Circuit and CircuitDeviceRow commands
|
The public dispatcher supports the Circuit and CircuitDeviceRow commands
|
||||||
|
|
@ -254,23 +257,30 @@ commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
|
||||||
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
|
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
|
||||||
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
|
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
|
||||||
- body:
|
- body:
|
||||||
`{ "floorId": null, "supplyType": "AV", "simultaneityFactor": 1, "expectedRevision": 13 }`
|
`{ "floorId": null, "supplyType": "AV", "simultaneityFactor": 1, "feedingDistributionBoardId": "board_hv", "feedingCircuitId": "circuit_out", "expectedRevision": 13 }`
|
||||||
- optional info-card fields: `boardNumber` (max. 50), `location`,
|
- optional info-card fields: `boardNumber` (max. 50), `location`,
|
||||||
`fedFrom` (max. 200 each) and `remark` (max. 2000); strings are trimmed
|
`fedFrom` (max. 200 each) and `remark` (max. 2000); strings are trimmed
|
||||||
and an empty string is stored as `null`
|
and an empty string is stored as `null`
|
||||||
- executes `distribution-board.update`; all submitted values, including the
|
- `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
|
||||||
submitted info fields, are restored together by persistent Undo/Redo
|
submitted info fields, are restored together by persistent Undo/Redo
|
||||||
- `fedFrom` is free text until the logical feed link is modeled
|
- `fedFrom` remains free text for external feeds such as HAK, metering,
|
||||||
|
transformer or generator and for supplementary notes
|
||||||
- `POST /projects/:projectId/distribution-boards/:distributionBoardId/copy`
|
- `POST /projects/:projectId/distribution-boards/:distributionBoardId/copy`
|
||||||
- body: `{ "name": "UV-02 Kopie", "expectedRevision": 14 }`
|
- body: `{ "name": "UV-02 Kopie", "expectedRevision": 14 }`
|
||||||
- duplicates the complete distribution-board subtree with new owning UUIDs
|
- duplicates the complete distribution-board subtree with new owning UUIDs
|
||||||
while preserving project-device and room links
|
while preserving project-device, room and incoming feeding links
|
||||||
- response includes the new `distributionBoard`, its `circuitList`, the
|
- response includes the new `distributionBoard`, its `circuitList`, the
|
||||||
revision and current history state
|
revision and current history state
|
||||||
- `DELETE /projects/:projectId/distribution-boards/:distributionBoardId`
|
- `DELETE /projects/:projectId/distribution-boards/:distributionBoardId`
|
||||||
- body: `{ "expectedRevision": 15 }`
|
- body: `{ "expectedRevision": 15 }`
|
||||||
- removes the complete, exactly captured distribution-board subtree in one
|
- removes the complete, exactly captured distribution-board subtree in one
|
||||||
transaction
|
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
|
- response includes `distributionBoardId`, the revision and current history
|
||||||
state
|
state
|
||||||
- Copy and deletion use `distribution-board.insert-subtree` and
|
- Copy and deletion use `distribution-board.insert-subtree` and
|
||||||
|
|
@ -327,12 +337,17 @@ commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
|
||||||
|
|
||||||
### Tree Endpoint
|
### 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`
|
- `GET /projects/:projectId/circuit-lists/:circuitListId/tree`
|
||||||
- Purpose: returns fixed header components, grouped sections with optional
|
- Purpose: returns fixed header components, grouped sections with optional
|
||||||
protection components, complete circuit/device-row blocks and auxiliary
|
protection components, complete circuit/device-row blocks and auxiliary
|
||||||
footer components. Circuit and component protection devices use their
|
footer components. Circuit and component protection devices use their
|
||||||
separate one-to-one DTOs. Calculated row, circuit, section and board totals
|
separate one-to-one DTOs. Calculated row, circuit, section and board totals
|
||||||
remain included.
|
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.
|
||||||
|
|
||||||
Response sketch:
|
Response sketch:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,13 @@
|
||||||
- Protection and cable values are planner-owned manual selections. The later
|
- Protection and cable values are planner-owned manual selections. The later
|
||||||
sizing/warning engine does not yet validate them against load, length or
|
sizing/warning engine does not yet validate them against load, length or
|
||||||
voltage-drop rules.
|
voltage-drop rules.
|
||||||
- Distribution-board `fedFrom` is free text. The print overview derives its
|
- One logical incoming feed per distribution board is supported. A second or
|
||||||
supply structure and "Σ inkl. unterlagert" only from exact text matches with
|
replacement feed with changeover semantics (for example SV/SiBe) is still a
|
||||||
board numbers/names and adds values without an upstream simultaneity factor
|
follow-up. `fedFrom` remains the free-text field for external sources and
|
||||||
(see `docs/print-view.md`).
|
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`).
|
||||||
- The print view uses a kbp corporate design derived from logo and
|
- The print view uses a kbp corporate design derived from logo and
|
||||||
presentation material. Snapshots do not record an editor, so the cover's
|
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
|
Planstände list shows the project's current editor for every entry. On screen, long
|
||||||
|
|
|
||||||
|
|
@ -468,8 +468,22 @@ Derselbe Befehl versioniert die Felder der Verteiler-Infokarte
|
||||||
(`boardNumber`, `location`, `fedFrom`, `remark`, Migration `0007`). Die
|
(`boardNumber`, `location`, `fedFrom`, `remark`, Migration `0007`). Die
|
||||||
Infokarte liegt im Bearbeiten-Dialog der Verteilung; die Werte sind lokale
|
Infokarte liegt im Bearbeiten-Dialog der Verteilung; die Werte sind lokale
|
||||||
Planungswerte und sollen später über den Revit-Abgleich nur nach Vorschau und
|
Planungswerte und sollen später über den Revit-Abgleich nur nach Vorschau und
|
||||||
ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` ist vorerst Freitext.
|
ausdrücklicher Bestätigung vorbelegt werden. `fedFrom` bleibt Freitext für
|
||||||
Snapshot-Schema 8 enthält zusätzlich die optionale Netto-Raumfläche
|
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
|
||||||
`rooms[].areaM2`; Versionen 1–7 werden dafür mit `null` hochgestuft.
|
`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;
|
Snapshot-Schema 7 enthält die Gebäude und die Gebäudezuordnung der Etagen;
|
||||||
ältere Versionen werden ohne Gebäude hochgestuft.
|
ältere Versionen werden ohne Gebäude hochgestuft.
|
||||||
|
|
@ -580,7 +594,7 @@ Oberfläche: `/projects/:projectId/grobbilanz`. Details:
|
||||||
`toLowerCase()` gegen das vollständige Register der Stromkreisliste und wird
|
`toLowerCase()` gegen das vollständige Register der Stromkreisliste und wird
|
||||||
von jedem Anlage-/Umbenennungspfad für Stromkreise und Verteilerkomponenten
|
von jedem Anlage-/Umbenennungspfad für Stromkreise und Verteilerkomponenten
|
||||||
aufgerufen, auch dort, wo zuvor kein Vorab-Check existierte. Snapshot- und
|
aufgerufen, auch dort, wo zuvor kein Vorab-Check existierte. Snapshot- und
|
||||||
Transfer-Integration verwenden aktuell Snapshot-Schema 8. Persistente
|
Transfer-Integration verwenden aktuell Snapshot-Schema 9. Persistente
|
||||||
Insert/Delete/Update-Commands für veränderliche Verteilerkomponenten,
|
Insert/Delete/Update-Commands für veränderliche Verteilerkomponenten,
|
||||||
Gruppen einschließlich befüllter Unterbäume sowie vollständige
|
Gruppen einschließlich befüllter Unterbäume sowie vollständige
|
||||||
Gruppensortierung sind integriert. Der Editor zeigt die geschützte Struktur
|
Gruppensortierung sind integriert. Der Editor zeigt die geschützte Struktur
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ Die Aktionen für Büroangaben und Logo speichern dagegen globale Einstellungen.
|
||||||
Projekt- und Verteilerangaben werden in den jeweiligen Bearbeitungsdialogen
|
Projekt- und Verteilerangaben werden in den jeweiligen Bearbeitungsdialogen
|
||||||
über Projektbefehle mit Undo/Redo gepflegt.
|
über Projektbefehle mit Undo/Redo gepflegt.
|
||||||
|
|
||||||
Beim Laden werden die Projektrevisionen aller Stromkreislisten und ein
|
Beim Laden werden alle Stromkreislisten in einer projektweiten Abfrage gelesen;
|
||||||
abschließend erneut gelesener Projektstand verglichen. Ändert sich das Projekt
|
deren Projektrevision und ein abschließend erneut gelesener Projektstand werden
|
||||||
|
verglichen. Ändert sich das Projekt
|
||||||
währenddessen, wird statt eines gemischten Datenstands eine Aufforderung zum
|
währenddessen, wird statt eines gemischten Datenstands eine Aufforderung zum
|
||||||
Neuladen angezeigt. Eine bereits geladene Seitenansicht aktualisiert sich nicht
|
Neuladen angezeigt. Eine bereits geladene Seitenansicht aktualisiert sich nicht
|
||||||
automatisch bei späteren Änderungen in einem anderen Tab.
|
automatisch bei späteren Änderungen in einem anderen Tab.
|
||||||
|
|
@ -31,12 +32,14 @@ automatisch bei späteren Änderungen in einem anderen Tab.
|
||||||
erscheinen als orangefarbenes „[ergänzen]“.
|
erscheinen als orangefarbenes „[ergänzen]“.
|
||||||
2. **Verteilerübersicht (quer)** – Versorgungsstruktur und Tabelle je
|
2. **Verteilerübersicht (quer)** – Versorgungsstruktur und Tabelle je
|
||||||
Verteiler mit Stromkreisanzahl, P<sub>inst</sub> (Anzahl × Leistung je
|
Verteiler mit Stromkreisanzahl, P<sub>inst</sub> (Anzahl × Leistung je
|
||||||
Stück), P<sub>Geräte</sub> (mit Geräte-GZF), Verteiler-GZF,
|
Stück, nur lokale Geräte), Leistung vor Verteiler-GZF einschließlich
|
||||||
P<sub>gl</sub>, I<sub>B</sub> und „Σ inkl. unterlagert“.
|
gespeister Unterverteilungen, Verteiler-GZF, P<sub>gl</sub> einschließlich
|
||||||
|
Unterverteilungen und I<sub>B</sub>. Die Hierarchie folgt den logischen IDs.
|
||||||
3. **Verteilerpläne (quer, je Verteiler)** – Titelleiste mit
|
3. **Verteilerpläne (quer, je Verteiler)** – Titelleiste mit
|
||||||
Verteilernummer, Bezeichnung, Örtlichkeit, Netzart, „Gespeist von“ und
|
Verteilernummer, Bezeichnung, Örtlichkeit, Netzart, „Gespeist von“ und
|
||||||
P<sub>gl</sub>; darunter Kopfkomponenten, Gruppen, Stromkreise
|
P<sub>gl</sub>; darunter Kopfkomponenten, Gruppen, Stromkreise
|
||||||
(mehrzeilige mit Sammelzeile und eingerückten Gerätezeilen),
|
(mehrzeilige mit Sammelzeile, eingerückten Gerätezeilen und
|
||||||
|
schreibgeschützten Unterverteilungszeilen),
|
||||||
Gruppensummen und Verteilersumme. Fehlende Kabelangaben sind als „offen“
|
Gruppensummen und Verteilersumme. Fehlende Kabelangaben sind als „offen“
|
||||||
markiert. Die Titelleiste steht im Tabellenkopf und wiederholt sich beim
|
markiert. Die Titelleiste steht im Tabellenkopf und wiederholt sich beim
|
||||||
Drucken auf Folgeseiten.
|
Drucken auf Folgeseiten.
|
||||||
|
|
@ -46,19 +49,25 @@ 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
|
einfügt. Die Bildschirmansicht zeigt Blätter in Papiergröße; die exakte
|
||||||
Seitenaufteilung langer Verteilerpläne entsteht erst beim Drucken.
|
Seitenaufteilung langer Verteilerpläne entsteht erst beim Drucken.
|
||||||
|
|
||||||
## Vorläufige Punkte
|
## Berechnung und offene Punkte
|
||||||
|
|
||||||
- **Einspeisung / Hierarchie:** `fedFrom` ist Freitext. Die Übersicht ordnet
|
- **Einspeisung / Hierarchie:** `feedingDistributionBoardId` bestimmt die
|
||||||
einen Verteiler nur dann unter, wenn der Text eindeutig der Nummer oder
|
Hierarchie. Ist zusätzlich `feedingCircuitId` gesetzt, fließt die Leistung
|
||||||
Bezeichnung eines anderen Verteilers entspricht (ohne Beachtung von
|
der Unterverteilung nach deren eigenem Verteiler-GZF als virtuelle Last in
|
||||||
Groß-/Kleinschreibung und äußeren Leerzeichen). Mehrdeutige Treffer bleiben
|
genau diesen Abgang ein. Ketten werden rekursiv berechnet; mehrere
|
||||||
ohne Zuordnung; „Σ inkl. unterlagert“ ist
|
Unterverteilungen an einem Abgang sind erlaubt. Ohne Abgang bleibt es bei der
|
||||||
eine einfache Summe ohne übergeordneten GZF. Die fachliche Verknüpfung
|
Hierarchie ohne Leistungsaufschlag. `fedFrom` bezeichnet nur externe Quellen
|
||||||
(Verteiler und/oder speisender Stromkreis) und die Summierungslogik sind
|
oder ergänzende Hinweise.
|
||||||
offen.
|
- **Keine Doppelzählung:** Die Abgangs- und Verteilersummen enthalten die
|
||||||
- **I<sub>B</sub>** ist ein symmetrischer Drehstromwert
|
Unterverteilungsleistung bereits. Deshalb gibt es keine zusätzliche Spalte
|
||||||
S / (√3 · U<sub>3~</sub>) mit S aus P / cos φ je Gerätezeile
|
„Σ inkl. unterlagert“. Die Summenzeile verwendet für hierarchische Leistungen
|
||||||
(fehlendes cos φ = 1) und Verteiler-GZF.
|
nur Wurzelverteilungen; P<sub>inst</sub> summiert weiterhin die lokalen Geräte.
|
||||||
|
- **I<sub>B</sub>:** 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 / U<sub>1~</sub>, sonst
|
||||||
|
I = S / (√3 · U<sub>3~</sub>). Für externe oder nur bis zur Verteilung
|
||||||
|
bekannte Einspeisungen bleibt die bisherige Darstellung als symmetrischer
|
||||||
|
Drehstromwert bestehen.
|
||||||
- **Corporate Design:** Farben (`--pd-*` in
|
- **Corporate Design:** Farben (`--pd-*` in
|
||||||
`src/frontend/print/print-document.css`) und Schrift (Arial) sind aus Logo
|
`src/frontend/print/print-document.css`) und Schrift (Arial) sind aus Logo
|
||||||
und kbp-Präsentationsmaterial abgeleitet und werden durch die offizielle
|
und kbp-Präsentationsmaterial abgeleitet und werden durch die offizielle
|
||||||
|
|
@ -118,10 +127,11 @@ ihren Wert.
|
||||||
|
|
||||||
## Bestehende Datenbanken
|
## Bestehende Datenbanken
|
||||||
|
|
||||||
Vor dem Anwenden der additiven Migrationen `0007` und `0008` eine verifizierte
|
Vor dem Anwenden der additiven Migrationen einschließlich `0012` eine verifizierte
|
||||||
Datenbanksicherung mit `npm run db:backup` erstellen. Die neuen Projekt- und
|
Datenbanksicherung mit `npm run db:backup` erstellen. Die neuen Projekt- und
|
||||||
Verteilerfelder bleiben bei bestehenden Datensätzen zunächst leer (`null`).
|
Verteilerfelder bleiben bei bestehenden Datensätzen zunächst leer (`null`).
|
||||||
Snapshot-Schema 6 enthält beide Feldgruppen; die unterstützten Versionen 1–5
|
Snapshot-Schema 9 enthält zusätzlich die logischen Einspeisungsverweise; die
|
||||||
werden beim Lesen hochgestuft. Restore und Projektkopie erhalten die neuen
|
unterstützten Versionen 1–8 werden mit leeren Verweisen hochgestuft. Restore und
|
||||||
Angaben. Globale Büroangaben und Logo gehören zur Datenbanksicherung, nicht
|
Projektkopie erhalten beziehungsweise remappen die neuen Angaben. Globale
|
||||||
|
Büroangaben und Logo gehören zur Datenbanksicherung, nicht
|
||||||
zum logischen Projektsnapshot oder Projekttransfer.
|
zum logischen Projektsnapshot oder Projekttransfer.
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,10 @@ The pre-release cleanup established the following compatibility boundary:
|
||||||
- [x] Rough power balance for LPH 2/3 by cost group, including the comparison
|
- [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
|
with the detailed balance and the explicit copy of one row as a
|
||||||
ProjectDevice.
|
ProjectDevice.
|
||||||
|
- [x] Logical distribution-board feed link with an optional feeding circuit,
|
||||||
|
recursive load rollup and deletion guards (#3).
|
||||||
|
|
||||||
Open follow-ups (Gitea issues): logical feed link between distribution boards
|
Open follow-ups (Gitea issues): bill of quantities export (#5), area-based loads in W/m² for
|
||||||
(#3), bill of quantities export (#5), area-based loads in W/m² for
|
|
||||||
ProjectDevices and device rows (#20).
|
ProjectDevices and device rows (#20).
|
||||||
|
|
||||||
## Recommended Sequence
|
## Recommended Sequence
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ import "../../../../frontend/print/print-document.css";
|
||||||
import {
|
import {
|
||||||
deleteDocumentLogo,
|
deleteDocumentLogo,
|
||||||
documentLogoUrl,
|
documentLogoUrl,
|
||||||
getCircuitTree,
|
getProjectCircuitTrees,
|
||||||
getDocumentBranding,
|
getDocumentBranding,
|
||||||
getProject,
|
getProject,
|
||||||
listCircuitLists,
|
|
||||||
listDistributionBoards,
|
listDistributionBoards,
|
||||||
listFloors,
|
listFloors,
|
||||||
listProjectSnapshots,
|
listProjectSnapshots,
|
||||||
|
|
@ -31,7 +30,7 @@ import {
|
||||||
import { PrintDocument } from "../../../../frontend/print/print-document";
|
import { PrintDocument } from "../../../../frontend/print/print-document";
|
||||||
import {
|
import {
|
||||||
assertPrintRevisionConsistency,
|
assertPrintRevisionConsistency,
|
||||||
buildPreliminaryBoardHierarchy,
|
buildBoardHierarchy,
|
||||||
summarizePrintBoard,
|
summarizePrintBoard,
|
||||||
type PrintBoardSummary,
|
type PrintBoardSummary,
|
||||||
} from "../../../../frontend/print/print-document-model";
|
} from "../../../../frontend/print/print-document-model";
|
||||||
|
|
@ -89,17 +88,20 @@ export default function ProjectPrintPage() {
|
||||||
}
|
}
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
async function load() {
|
async function load() {
|
||||||
const [project, boards, circuitLists, floors, snapshots, branding] =
|
const [project, boards, projectTrees, floors, snapshots, branding] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getProject(projectId),
|
getProject(projectId),
|
||||||
listDistributionBoards(projectId),
|
listDistributionBoards(projectId),
|
||||||
listCircuitLists(projectId),
|
getProjectCircuitTrees(projectId),
|
||||||
listFloors(projectId),
|
listFloors(projectId),
|
||||||
listProjectSnapshots(projectId),
|
listProjectSnapshots(projectId),
|
||||||
getDocumentBranding(),
|
getDocumentBranding(),
|
||||||
]);
|
]);
|
||||||
const floorNameById = new Map(floors.map((floor) => [floor.id, floor.name]));
|
const floorNameById = new Map(floors.map((floor) => [floor.id, floor.name]));
|
||||||
const summaries = await Promise.all(
|
const treeByBoardId = new Map(
|
||||||
|
projectTrees.trees.map((tree) => [tree.distributionBoardId, tree])
|
||||||
|
);
|
||||||
|
const summaries =
|
||||||
[...boards]
|
[...boards]
|
||||||
.sort(
|
.sort(
|
||||||
(left, right) =>
|
(left, right) =>
|
||||||
|
|
@ -107,20 +109,17 @@ export default function ProjectPrintPage() {
|
||||||
boardSortCollator.compare(left.name, right.name)
|
boardSortCollator.compare(left.name, right.name)
|
||||||
)
|
)
|
||||||
.flatMap((board) => {
|
.flatMap((board) => {
|
||||||
const circuitList = circuitLists.find(
|
const tree = treeByBoardId.get(board.id);
|
||||||
(list) => list.distributionBoardId === board.id
|
return tree ? [{ board, tree }] : [];
|
||||||
);
|
|
||||||
return circuitList ? [{ board, circuitList }] : [];
|
|
||||||
})
|
})
|
||||||
.map(async ({ board, circuitList }) =>
|
.map(({ board, tree }) =>
|
||||||
summarizePrintBoard({
|
summarizePrintBoard({
|
||||||
board,
|
board,
|
||||||
circuitListId: circuitList.id,
|
circuitListId: tree.circuitListId,
|
||||||
tree: await getCircuitTree(projectId, circuitList.id),
|
tree,
|
||||||
floorName: board.floorId ? floorNameById.get(board.floorId) ?? null : null,
|
floorName: board.floorId ? floorNameById.get(board.floorId) ?? null : null,
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
);
|
|
||||||
const finalProject = await getProject(projectId);
|
const finalProject = await getProject(projectId);
|
||||||
assertPrintRevisionConsistency(project.currentRevision, summaries, finalProject.currentRevision);
|
assertPrintRevisionConsistency(project.currentRevision, summaries, finalProject.currentRevision);
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
|
|
@ -148,7 +147,7 @@ export default function ProjectPrintPage() {
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
const entries = useMemo(
|
const entries = useMemo(
|
||||||
() => (data ? buildPreliminaryBoardHierarchy(data.summaries) : []),
|
() => (data ? buildBoardHierarchy(data.summaries) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const stand = useMemo(
|
const stand = useMemo(
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ export interface PrintBoardSummary {
|
||||||
circuitCount: number;
|
circuitCount: number;
|
||||||
/** Σ quantity × power per unit, without any simultaneity factor. */
|
/** Σ quantity × power per unit, without any simultaneity factor. */
|
||||||
installedPowerKw: number;
|
installedPowerKw: number;
|
||||||
/** Σ row totals, i.e. with the device-row simultaneity factors. */
|
/** Device-row demand plus downstream-board demand before this board's GZF. */
|
||||||
devicePowerKw: number;
|
powerBeforeBoardSimultaneityKw: number;
|
||||||
simultaneityFactor: number;
|
simultaneityFactor: number;
|
||||||
/** Device power with the board simultaneity factor applied. */
|
/** Total active power with this board's simultaneity factor applied. */
|
||||||
demandPowerKw: number;
|
demandPowerKw: number;
|
||||||
/**
|
/**
|
||||||
* Symmetrical three-phase design current from the apparent power
|
* Design current from the apparent power including downstream boards.
|
||||||
* S = Σ(row total / cos φ) × board GZF. A missing cos φ counts as 1.
|
* The selected feeder circuit determines whether one or three phases apply.
|
||||||
*/
|
*/
|
||||||
designCurrentA: number;
|
designCurrentA: number;
|
||||||
}
|
}
|
||||||
|
|
@ -46,8 +46,13 @@ export function summarizePrintBoard(input: {
|
||||||
circuitCount += 1;
|
circuitCount += 1;
|
||||||
for (const row of circuit.deviceRows) {
|
for (const row of circuit.deviceRows) {
|
||||||
installedPowerKw += row.quantity * row.powerPerUnit;
|
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;
|
const simultaneityFactor = tree.distributionBoardSimultaneityFactor;
|
||||||
|
|
@ -55,12 +60,13 @@ export function summarizePrintBoard(input: {
|
||||||
...input,
|
...input,
|
||||||
circuitCount,
|
circuitCount,
|
||||||
installedPowerKw,
|
installedPowerKw,
|
||||||
devicePowerKw: tree.distributionBoardTotalPower,
|
powerBeforeBoardSimultaneityKw: tree.distributionBoardTotalPower,
|
||||||
simultaneityFactor,
|
simultaneityFactor,
|
||||||
demandPowerKw: tree.distributionBoardTotalPowerWithSimultaneityFactor,
|
demandPowerKw: tree.distributionBoardTotalPowerWithSimultaneityFactor,
|
||||||
designCurrentA:
|
designCurrentA: calculateDesignCurrent(
|
||||||
(apparentPowerKva * simultaneityFactor * 1000) /
|
apparentPowerKva * simultaneityFactor,
|
||||||
(Math.sqrt(3) * tree.threePhaseVoltageV),
|
tree
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +74,6 @@ export interface PrintBoardHierarchyEntry {
|
||||||
summary: PrintBoardSummary;
|
summary: PrintBoardSummary;
|
||||||
depth: number;
|
depth: number;
|
||||||
parentBoardId: string | null;
|
parentBoardId: string | null;
|
||||||
/** Own demand plus the plain sum of all resolved downstream boards. */
|
|
||||||
cumulatedDemandPowerKw: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assertPrintRevisionConsistency(
|
export function assertPrintRevisionConsistency(
|
||||||
|
|
@ -82,31 +86,21 @@ 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[]
|
summaries: PrintBoardSummary[]
|
||||||
): PrintBoardHierarchyEntry[] {
|
): PrintBoardHierarchyEntry[] {
|
||||||
const normalize = (value: string | null) =>
|
const summaryById = new Map(
|
||||||
value?.trim().toLocaleLowerCase("de") || null;
|
summaries.map((summary) => [summary.board.id, summary])
|
||||||
|
);
|
||||||
const parentById = new Map<string, string | null>();
|
const parentById = new Map<string, string | null>();
|
||||||
for (const summary of summaries) {
|
for (const summary of summaries) {
|
||||||
const fedFrom = normalize(summary.board.fedFrom);
|
const parentId = summary.board.feedingDistributionBoardId;
|
||||||
const candidates = fedFrom
|
parentById.set(
|
||||||
? summaries.filter(
|
summary.board.id,
|
||||||
(candidate) =>
|
parentId && parentId !== summary.board.id && summaryById.has(parentId)
|
||||||
candidate !== summary &&
|
? parentId
|
||||||
(normalize(candidate.board.boardNumber) === fedFrom ||
|
: null
|
||||||
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) {
|
for (const summary of summaries) {
|
||||||
const visited = new Set<string>([summary.board.id]);
|
const visited = new Set<string>([summary.board.id]);
|
||||||
|
|
@ -125,20 +119,12 @@ export function buildPreliminaryBoardHierarchy(
|
||||||
summaries.filter(
|
summaries.filter(
|
||||||
(summary) => parentById.get(summary.board.id) === boardId
|
(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 entries: PrintBoardHierarchyEntry[] = [];
|
||||||
const walk = (summary: PrintBoardSummary, depth: number) => {
|
const walk = (summary: PrintBoardSummary, depth: number) => {
|
||||||
entries.push({
|
entries.push({
|
||||||
summary,
|
summary,
|
||||||
depth,
|
depth,
|
||||||
parentBoardId: parentById.get(summary.board.id) ?? null,
|
parentBoardId: parentById.get(summary.board.id) ?? null,
|
||||||
cumulatedDemandPowerKw: cumulated(summary),
|
|
||||||
});
|
});
|
||||||
childrenOf(summary.board.id).forEach((child) => walk(child, depth + 1));
|
childrenOf(summary.board.id).forEach((child) => walk(child, depth + 1));
|
||||||
};
|
};
|
||||||
|
|
@ -146,6 +132,25 @@ export function buildPreliminaryBoardHierarchy(
|
||||||
return entries;
|
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", {
|
const kilowattFormat = new Intl.NumberFormat("de-DE", {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2,
|
maximumFractionDigits: 2,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
formatCircuitCable,
|
formatCircuitCable,
|
||||||
formatCurrent,
|
formatCurrent,
|
||||||
formatFactor,
|
formatFactor,
|
||||||
|
formatFeedingReference,
|
||||||
formatKilowatt,
|
formatKilowatt,
|
||||||
formatProtectionDevice,
|
formatProtectionDevice,
|
||||||
formatUnitPower,
|
formatUnitPower,
|
||||||
|
|
@ -269,8 +270,8 @@ function OverviewSheet({
|
||||||
stand: string;
|
stand: string;
|
||||||
}) {
|
}) {
|
||||||
const roots = entries.filter((entry) => entry.parentBoardId === null);
|
const roots = entries.filter((entry) => entry.parentBoardId === null);
|
||||||
// Top-level boards whose "fed from" text names no other board are grouped
|
// Root boards may still carry an external feed description such as
|
||||||
// below that text as an external feed (e.g. "HAK / Zähleranlage").
|
// "HAK / Zähleranlage"; logical board links determine the actual tree.
|
||||||
const feedGroups = new Map<string, PrintBoardHierarchyEntry[]>();
|
const feedGroups = new Map<string, PrintBoardHierarchyEntry[]>();
|
||||||
for (const root of roots) {
|
for (const root of roots) {
|
||||||
const feed = root.summary.board.fedFrom ?? "";
|
const feed = root.summary.board.fedFrom ?? "";
|
||||||
|
|
@ -320,11 +321,10 @@ function OverviewSheet({
|
||||||
<th>Nr.</th><th>Bezeichnung</th><th>Örtlichkeit</th><th>Etage</th><th>Netz</th><th>Gespeist von</th>
|
<th>Nr.</th><th>Bezeichnung</th><th>Örtlichkeit</th><th>Etage</th><th>Netz</th><th>Gespeist von</th>
|
||||||
<th className="pd-r">SK</th>
|
<th className="pd-r">SK</th>
|
||||||
<th className="pd-r">P<sub>inst</sub><br />[kW]</th>
|
<th className="pd-r">P<sub>inst</sub><br />[kW]</th>
|
||||||
<th className="pd-r">P<sub>Geräte</sub><br />[kW]</th>
|
<th className="pd-r">P vor Vert.-GZF<br />[kW]</th>
|
||||||
<th className="pd-r">GZF<br />Vert.</th>
|
<th className="pd-r">GZF<br />Vert.</th>
|
||||||
<th className="pd-r">P<sub>gl</sub><br />[kW]</th>
|
<th className="pd-r">P<sub>gl</sub> inkl. UV<br />[kW]</th>
|
||||||
<th className="pd-r">I<sub>B</sub><br />[A]</th>
|
<th className="pd-r">I<sub>B</sub><br />[A]</th>
|
||||||
<th className="pd-r">Σ inkl.<br />unterlagert [kW]</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -340,41 +340,45 @@ function OverviewSheet({
|
||||||
<td>{board.location ?? "–"}</td>
|
<td>{board.location ?? "–"}</td>
|
||||||
<td>{entry.summary.floorName ?? "–"}</td>
|
<td>{entry.summary.floorName ?? "–"}</td>
|
||||||
<td>{board.supplyType ?? "–"}</td>
|
<td>{board.supplyType ?? "–"}</td>
|
||||||
<td>{board.fedFrom ?? "–"}</td>
|
<td>{formatFeedingReference(entry.summary)}</td>
|
||||||
<td className="pd-r">{entry.summary.circuitCount}</td>
|
<td className="pd-r">{entry.summary.circuitCount}</td>
|
||||||
<td className="pd-r">{formatKilowatt(entry.summary.installedPowerKw)}</td>
|
<td className="pd-r">{formatKilowatt(entry.summary.installedPowerKw)}</td>
|
||||||
<td className="pd-r">{formatKilowatt(entry.summary.devicePowerKw)}</td>
|
<td className="pd-r">{formatKilowatt(entry.summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||||
<td className="pd-r">{formatFactor(entry.summary.simultaneityFactor)}</td>
|
<td className="pd-r">{formatFactor(entry.summary.simultaneityFactor)}</td>
|
||||||
<td className="pd-r"><strong>{formatKilowatt(entry.summary.demandPowerKw)}</strong></td>
|
<td className="pd-r"><strong>{formatKilowatt(entry.summary.demandPowerKw)}</strong></td>
|
||||||
<td className="pd-r">{formatCurrent(entry.summary.designCurrentA)}</td>
|
<td className="pd-r">{formatCurrent(entry.summary.designCurrentA)}</td>
|
||||||
<td className="pd-r">{formatKilowatt(entry.cumulatedDemandPowerKw)}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7}>Summe aller Verteiler (ohne übergeordneten GZF)</td>
|
<td colSpan={7}>Summe der Einspeisungen</td>
|
||||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}</td>
|
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}</td>
|
||||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.devicePowerKw))}</td>
|
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||||
<td />
|
entry.parentBoardId === null
|
||||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}</td>
|
? entry.summary.powerBeforeBoardSimultaneityKw
|
||||||
|
: 0
|
||||||
|
))}</td>
|
||||||
<td />
|
<td />
|
||||||
|
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||||
|
entry.parentBoardId === null ? entry.summary.demandPowerKw : 0
|
||||||
|
))}</td>
|
||||||
<td />
|
<td />
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
<p className="pd-note">
|
<p className="pd-note">
|
||||||
<strong>Vorläufig:</strong> „Gespeist von“ ist noch ein Freitextfeld. Struktur und
|
Die Struktur folgt den logischen Verknüpfungen. Eine Unterverteilung
|
||||||
„Σ inkl. unterlagert“ werden nur über eindeutig passende Verteilernummern bzw.
|
wird im gewählten Abgang mit ihrer Leistung nach eigenem
|
||||||
-bezeichnungen zugeordnet und einfach addiert (kein übergeordneter GZF).
|
Verteiler-GZF berücksichtigt. Ohne gewählten Abgang erscheint sie
|
||||||
Mehrdeutige Angaben bleiben ohne Zuordnung. Die fachliche
|
nur in der Hierarchie.
|
||||||
Verknüpfung und Summierung folgt.
|
|
||||||
</p>
|
</p>
|
||||||
<p className="pd-legend">
|
<p className="pd-legend">
|
||||||
P<sub>inst</sub> = Anzahl × Leistung je Stück · P<sub>Geräte</sub> = mit Geräte-GZF ·
|
P<sub>inst</sub> = lokale Geräte ohne GZF · P vor Vert.-GZF =
|
||||||
P<sub>gl</sub> = mit Verteiler-GZF · I<sub>B</sub> = S / (√3 · {project.threePhaseVoltageV} V),
|
Geräte-GZF einschließlich gespeister Unterverteilungen · P<sub>gl</sub> =
|
||||||
S aus cos φ je Gerätezeile
|
zusätzlich mit Verteiler-GZF · I<sub>B</sub> aus S und der Phasenart
|
||||||
|
des speisenden Abgangs
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -404,6 +408,7 @@ function ComponentRow({ component }: { component: CircuitTreeComponentDto }) {
|
||||||
|
|
||||||
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||||
const rows = circuit.deviceRows;
|
const rows = circuit.deviceRows;
|
||||||
|
const downstreamBoards = circuit.downstreamBoardLoads ?? [];
|
||||||
const circuitCells = (
|
const circuitCells = (
|
||||||
<>
|
<>
|
||||||
<td>{formatProtectionDevice(circuit.protectionDevice) ?? <Open />}</td>
|
<td>{formatProtectionDevice(circuit.protectionDevice) ?? <Open />}</td>
|
||||||
|
|
@ -414,7 +419,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||||
<td>{circuit.remark ?? ""}</td>
|
<td>{circuit.remark ?? ""}</td>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0 && downstreamBoards.length === 0) {
|
||||||
return (
|
return (
|
||||||
<tr className="pd-circuit pd-reserve">
|
<tr className="pd-circuit pd-reserve">
|
||||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||||
|
|
@ -426,7 +431,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (rows.length === 1) {
|
if (rows.length === 1 && downstreamBoards.length === 0) {
|
||||||
const row = rows[0];
|
const row = rows[0];
|
||||||
const title = circuit.displayName ?? row.displayName;
|
const title = circuit.displayName ?? row.displayName;
|
||||||
return (
|
return (
|
||||||
|
|
@ -468,6 +473,16 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||||
<td colSpan={4} />
|
<td colSpan={4} />
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
{downstreamBoards.map((load) => (
|
||||||
|
<tr className="pd-device" key={`downstream:${load.distributionBoardId}`}>
|
||||||
|
<td />
|
||||||
|
<td>Unterverteilung {load.boardNumber ?? load.name}</td>
|
||||||
|
<td>–</td>
|
||||||
|
<td /><td /><td />
|
||||||
|
<td className="pd-r">{formatKilowatt(load.activePowerKw)}</td>
|
||||||
|
<td colSpan={4}>Automatisch aus der verknüpften Verteilung</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -501,7 +516,7 @@ function BoardPlanSheet({
|
||||||
rows={[
|
rows={[
|
||||||
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
|
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
|
||||||
["Netzart", board.supplyType ?? "–"],
|
["Netzart", board.supplyType ?? "–"],
|
||||||
["Gespeist von", board.fedFrom ?? "–"],
|
["Gespeist von", formatFeedingReference(summary)],
|
||||||
[
|
[
|
||||||
<>P<sub>gl</sub></>,
|
<>P<sub>gl</sub></>,
|
||||||
<>
|
<>
|
||||||
|
|
@ -542,7 +557,7 @@ function BoardPlanSheet({
|
||||||
<td colSpan={5}>
|
<td colSpan={5}>
|
||||||
Summe Verteiler (P<sub>inst</sub> {formatKilowatt(summary.installedPowerKw)} kW)
|
Summe Verteiler (P<sub>inst</sub> {formatKilowatt(summary.installedPowerKw)} kW)
|
||||||
</td>
|
</td>
|
||||||
<td className="pd-r">{formatKilowatt(summary.devicePowerKw)}</td>
|
<td className="pd-r">{formatKilowatt(summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||||
<td colSpan={4} />
|
<td colSpan={4} />
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import assert from "node:assert/strict";
|
||||||
import { describe, it } from "node:test";
|
import { describe, it } from "node:test";
|
||||||
import {
|
import {
|
||||||
assertPrintRevisionConsistency,
|
assertPrintRevisionConsistency,
|
||||||
buildPreliminaryBoardHierarchy,
|
buildBoardHierarchy,
|
||||||
|
formatFeedingReference,
|
||||||
formatCircuitCable,
|
formatCircuitCable,
|
||||||
formatProtectionDevice,
|
formatProtectionDevice,
|
||||||
summarizePrintBoard,
|
summarizePrintBoard,
|
||||||
|
|
@ -27,6 +28,8 @@ function board(
|
||||||
location: null,
|
location: null,
|
||||||
fedFrom: null,
|
fedFrom: null,
|
||||||
remark: null,
|
remark: null,
|
||||||
|
feedingDistributionBoardId: null,
|
||||||
|
feedingCircuitId: null,
|
||||||
...info,
|
...info,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +55,8 @@ function tree(
|
||||||
const total = deviceRows.reduce((sum, row) => sum + row.rowTotalPower, 0);
|
const total = deviceRows.reduce((sum, row) => sum + row.rowTotalPower, 0);
|
||||||
return {
|
return {
|
||||||
circuitListId: "list",
|
circuitListId: "list",
|
||||||
|
distributionBoardId: "board",
|
||||||
|
distributionBoardName: "Board",
|
||||||
currentRevision: 1,
|
currentRevision: 1,
|
||||||
singlePhaseVoltageV: 230,
|
singlePhaseVoltageV: 230,
|
||||||
threePhaseVoltageV: 400,
|
threePhaseVoltageV: 400,
|
||||||
|
|
@ -79,7 +84,13 @@ function tree(
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
isReserve: false,
|
isReserve: false,
|
||||||
circuitTotalPower: total,
|
circuitTotalPower: total,
|
||||||
|
circuitApparentPower: deviceRows.reduce(
|
||||||
|
(sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1),
|
||||||
|
0
|
||||||
|
),
|
||||||
|
circuitCosPhi: 1,
|
||||||
deviceRows,
|
deviceRows,
|
||||||
|
downstreamBoardLoads: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -117,16 +128,15 @@ describe("print document model", () => {
|
||||||
assert.equal(formatCircuitCable(circuit), null);
|
assert.equal(formatCircuitCable(circuit), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not guess a parent when feed text matches more than one board", () => {
|
it("does not derive hierarchy from the external free text", () => {
|
||||||
const summaries = [
|
const summaries = [
|
||||||
summary("a", { boardNumber: "HV" }, 2),
|
summary("a", { boardNumber: "HV" }, 2),
|
||||||
summary("b", { name: "HV" }, 3),
|
summary("b", { name: "HV" }, 3),
|
||||||
summary("uv", { fedFrom: " hv " }, 1),
|
summary("uv", { fedFrom: " hv " }, 1),
|
||||||
];
|
];
|
||||||
for (const ordered of [summaries, [...summaries].reverse()]) {
|
for (const ordered of [summaries, [...summaries].reverse()]) {
|
||||||
const entries = buildPreliminaryBoardHierarchy(ordered);
|
const entries = buildBoardHierarchy(ordered);
|
||||||
assert.ok(entries.every((entry) => entry.parentBoardId === null));
|
assert.ok(entries.every((entry) => entry.parentBoardId === null));
|
||||||
assert.equal(entries.reduce((sum, entry) => sum + entry.cumulatedDemandPowerKw, 0), 6);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -145,17 +155,39 @@ describe("print document model", () => {
|
||||||
});
|
});
|
||||||
assert.equal(result.circuitCount, 1);
|
assert.equal(result.circuitCount, 1);
|
||||||
assert.equal(result.installedPowerKw, 14);
|
assert.equal(result.installedPowerKw, 14);
|
||||||
assert.equal(result.devicePowerKw, 10);
|
assert.equal(result.powerBeforeBoardSimultaneityKw, 10);
|
||||||
assert.equal(result.demandPowerKw, 5);
|
assert.equal(result.demandPowerKw, 5);
|
||||||
// S = (4 / 0.8 + 6 / 1) × 0.5 = 5.5 kVA → I = 5500 / (√3 · 400)
|
// 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);
|
assert.ok(Math.abs(result.designCurrentA - 5500 / (Math.sqrt(3) * 400)) < 1e-9);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nests boards by matching free-text feed and sums downstream demand", () => {
|
it("uses the selected one-phase feeder circuit for the design current", () => {
|
||||||
const entries = buildPreliminaryBoardHierarchy([
|
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([
|
||||||
summary("hv", { boardNumber: "HV 0.1", fedFrom: "HAK" }, 10),
|
summary("hv", { boardNumber: "HV 0.1", fedFrom: "HAK" }, 10),
|
||||||
summary("uv-a", { boardNumber: "UV 1.1", fedFrom: " hv 0.1 " }, 4),
|
summary("uv-a", { boardNumber: "UV 1.1", feedingDistributionBoardId: "hv" }, 4),
|
||||||
summary("uv-b", { boardNumber: "UV 1.2", fedFrom: "UV 1.1" }, 2),
|
summary("uv-b", { boardNumber: "UV 1.2", feedingDistributionBoardId: "uv-a" }, 2),
|
||||||
summary("uv-c", { name: "UV-C", fedFrom: "unbekannt" }, 1),
|
summary("uv-c", { name: "UV-C", fedFrom: "unbekannt" }, 1),
|
||||||
]);
|
]);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
|
|
@ -163,28 +195,37 @@ describe("print document model", () => {
|
||||||
entry.summary.board.id,
|
entry.summary.board.id,
|
||||||
entry.depth,
|
entry.depth,
|
||||||
entry.parentBoardId,
|
entry.parentBoardId,
|
||||||
entry.cumulatedDemandPowerKw,
|
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
["hv", 0, null, 16],
|
["hv", 0, null],
|
||||||
["uv-a", 1, "hv", 6],
|
["uv-a", 1, "hv"],
|
||||||
["uv-b", 2, "uv-a", 2],
|
["uv-b", 2, "uv-a"],
|
||||||
["uv-c", 0, null, 1],
|
["uv-c", 0, null],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps boards with cyclic feed texts at the top level", () => {
|
it("keeps malformed cyclic logical links visible at the top level", () => {
|
||||||
const entries = buildPreliminaryBoardHierarchy([
|
const entries = buildBoardHierarchy([
|
||||||
summary("a", { boardNumber: "A", fedFrom: "B" }, 1),
|
summary("a", { boardNumber: "A", feedingDistributionBoardId: "b" }, 1),
|
||||||
summary("b", { boardNumber: "B", fedFrom: "A" }, 1),
|
summary("b", { boardNumber: "B", feedingDistributionBoardId: "a" }, 1),
|
||||||
]);
|
]);
|
||||||
assert.equal(entries.length, 2);
|
assert.equal(entries.length, 2);
|
||||||
assert.ok(entries.some((entry) => entry.parentBoardId === null));
|
assert.ok(entries.some((entry) => entry.parentBoardId === null));
|
||||||
assert.equal(
|
});
|
||||||
entries.reduce((sum, entry) => sum + (entry.depth === 0 ? entry.cumulatedDemandPowerKw : 0), 0),
|
|
||||||
2
|
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");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("formats protection devices for the plan table", () => {
|
it("formats protection devices for the plan table", () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue