Merge remote-tracking branch 'origin/main' into feature/cable-sizing

This commit is contained in:
Julian Appel 2026-09-27 16:54:12 +02:00
commit 598003554a
136 changed files with 27182 additions and 1669 deletions

View file

@ -12,6 +12,12 @@ ausdrücklich getrennt und dürfen nicht als bereits implementiert verstanden we
fachliches Modell und Grid-Aufteilung
- [Editor-Interaktionen](circuit-list-editor-interactions.md) – Tastatur,
Drag-and-drop, Sortierung, Filterung und Undo/Redo
- [Seitenansicht und PDF-Export](print-view.md) – Deckblatt,
Verteilerübersicht, Verteilerpläne, Büro-Logo
- [Gebäudestruktur](building-structure.md) – Gebäude → Etagen → Räume,
Befehle, Snapshot-Schema 7, eigene Seite
- [Grobe Leistungsbilanz](estimate-balance.md) – LPH 2/3 nach Kostengruppen,
Rechnung, Vergleich mit der detaillierten Bilanz
- [Editor-API](circuit-list-editor-api.md) – unterstützte Circuit-First-Endpunkte
- [Bekannte Einschränkungen](circuit-list-editor-known-limitations.md) –
bewusst noch nicht implementierte oder begrenzte Funktionen
@ -27,7 +33,8 @@ ausdrücklich getrennt und dürfen nicht als bereits implementiert verstanden we
- [Projektversionen und externer Modellaustausch](project-history-and-external-model-architecture.md)
- [LLM-Kontext für die Revit-Anforderungsplanung](revit-requirements-llm-context.md)
- [Revit-CSV-Anforderungen](spec/revit-csv-integration-requirements.md)
- [Revit-CSV-Anforderungen](spec/revit-csv-integration-requirements.md) –
zurückgestellt; später über den CSV-Export/-Import des Linear-Plugins
- [Phase-14-Audit und Implementierungsplan](spec/revit-csv-phase-14-audit-and-plan.md)
- [Zukünftige Dimensionierung](spec/06-future-sizing-and-calculations.md)
- [Roadmap und Phasen](spec/07-implementation-phases-todo.md)
@ -50,3 +57,6 @@ Limitierungen und Roadmap.
- [Frühe Codex-Promptvorlagen](archive/initial-codex-prompts.md) – historischer
Arbeitsstand, keine aktuelle Implementierungsanweisung
- [Revit-/Dynamo-Anbindung](archive/revit-dynamo-leistungsbilanz-integration-konzept.md) –
nicht weiterverfolgte Vorüberlegung zur direkten Dynamo-/API-Anbindung;
Diagnoseskripte über den Git-Tag `archive/dynamo-vorueberlegungen`

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
# Gebäudestruktur (Gebäude → Etagen → Räume)
Projekte mit mehreren Gebäuden werden über eine eigene Ebene **Gebäude**
gegliedert. Etagen können einem Gebäude zugeordnet sein, Räume und Verteilungen
hängen wie bisher an der Etage. Das Gebäude eines Raums oder einer Verteilung
ergibt sich also aus der Etage; eine zweite, konkurrierende Zuordnung gibt es
nicht. Abgestimmt in Issue #6: Das Modell ist so bestätigt, und Gebäude mit
zugeordneten Etagen lassen sich nicht löschen.
## Datenmodell
| Tabelle | Feld | Bedeutung |
|---|---|---|
| `buildings` | `id`, `project_id` (FK, cascade), `name`, `sort_order` | Gebäude eines Projekts |
| `floors` | `building_id` (nullable FK → `buildings`, `ON DELETE set null`) | optionales übergeordnetes Gebäude |
Migration `0009_buildings`. Bestehende Etagen behalten `building_id = null` und
erscheinen in der Oberfläche als „Ohne Gebäude“. Es gibt keine Zwangsmigration
und kein automatisch angelegtes Standardgebäude. Vor dem Anwenden der Migration
auf eine bestehende Datenbank ist wie bei jeder Schemamigration eine separate
Datenbanksicherung anzulegen.
## Befehle, Undo/Redo
Alle Änderungen laufen über die bestehende Orts-Struktur
(`ProjectLocationStructureProjectCommandRepository`) mit Revision, Inverse und
persistentem Undo/Redo:
- `project-building.insert` / `project-building.delete` mit vollständigem
Gebäude-Snapshot (Schema-Version 1)
- `project-building.update`: ändert nur den Namen
- Löschen eines Gebäudes wird abgelehnt, solange Etagen zugeordnet sind
(„Das Gebäude kann nicht gelöscht werden, solange Etagen zugeordnet sind.“)
- Etagenbefehle `project-floor.insert/.update/.delete` haben seit
Schema-Version **2** das Feld `buildingId`. `project-floor.update` darf Name
und/oder Gebäude ändern, nicht aber `sortOrder`. Ein referenziertes Gebäude
muss zum Projekt gehören.
- Version-1-Etagenbefehle aus bestehender Historie bleiben ausführbar. Sie
stammen aus der Zeit vor den Gebäuden und werden vor der Ausführung mit
`buildingId = null` auf Version 2 gehoben; auch die daraus neu gespeicherten
Vorwärts- und Inversbefehle sind Version 2. Das ist bei linearer Historie
exakt: Wird ein Gebäude später zugeordnet, liegt dieser neuere Befehl im
Undo-Stapel vor dem alten Befehl und wird zuerst zurückgenommen.
- Raumbefehle bleiben unverändert (Version 1).
## Snapshots und Transfer
Projekt-Snapshot-Schema **7** enthält `buildings` und `floors[].buildingId`.
Alle älteren unterstützten Versionen (1–6) werden beim Einlesen mit leerer
Gebäudeliste und `buildingId = null` hochgestuft; gespeicherte Sicherungspunkte
bleiben wiederherstellbar. Die Relationen werden geprüft (Gebäude gehört zum
Projekt, `buildingId` verweist auf ein Gebäude des Snapshots). Restore löscht
und schreibt Gebäude zusammen mit den Etagen; der Projekt-Transfer vergibt neue
Gebäude-IDs und stellt die Etagenverweise um.
## API
- `GET /api/projects/:projectId/buildings` – Gebäude sortiert nach
`sortOrder`, Name
- `POST /api/projects/:projectId/buildings` – `{ "name": "Haus A", "expectedRevision": 3 }`
- `PUT /api/projects/:projectId/buildings/:buildingId` – `{ "name": "Haus B", "expectedRevision": 4 }`
- `DELETE /api/projects/:projectId/buildings/:buildingId` – `{ "expectedRevision": 5 }`
- `POST/PUT /api/projects/:projectId/floors[/:floorId]` akzeptieren zusätzlich
optional `buildingId` (`string | null`). Fehlt das Feld, bleibt beim Bearbeiten
das bisherige Gebäude erhalten; ältere Clients funktionieren unverändert.
## Oberfläche
Gebäude, Etagen und Räume werden auf der eigenen Seite
`/projects/:projectId/struktur` („Gebäudestruktur“) gepflegt, erreichbar über
die Seitenleiste und die Kennzahlen der Projektübersicht. Drei Spalten
Gebäude → Etagen → Räume filtern sich gegenseitig: Klick auf ein Gebäude zeigt
nur dessen Etagen, Klick auf eine Etage nur deren Räume, „Alle“ hebt den Filter
auf. Hinzufügen, Bearbeiten und Löschen laufen über Icon-Buttons wie in der
Stromkreisliste.
## Offen
- Revit: Eine Übernahme des Gebäudes aus Revit ist derzeit nicht geplant. Die
Revit-Arbeit ist insgesamt zurückgestellt und läuft später über die
Linear-CSV; das Modell steht einer späteren CSV-Zuordnung nicht im Weg.
- Sortierung von Gebäuden per Drag-and-drop ist noch nicht umgesetzt; neue
Gebäude werden hinten angehängt.

View file

@ -59,6 +59,14 @@ this value for optimistic concurrency checks.
- clients cannot submit arbitrary `project.restore-state` payloads through
the generic command endpoint
Grobbilanzen sind bewusst kein Teil dieser logischen Snapshots. Der portable
Projektexport unter `GET /projects/:projectId/export` verwendet dagegen
Transferformat 2 und enthält alle Grobbilanzen. Die beiden Importwege
`POST /projects/:projectId/import` und `POST /projects/import` prüfen ihre
Prüfsumme; Ersetzen übernimmt die Grobbilanzen atomar und mit Undo/Redo,
Duplizieren vergibt neue IDs. Transferformat 1 bleibt ohne Grobbilanzen
importierbar.
The public dispatcher supports the Circuit and CircuitDeviceRow commands
`circuit.update`, `circuit.insert`, `circuit.delete`,
`circuit-device-row.update`, `circuit-device-row.insert`,
@ -74,8 +82,9 @@ Protected-board structure uses
persisted inverse of a delete. The dispatcher also supports the documented
project-device, project settings, distribution-board and project-location
commands. The generic editor commands listed here use schema version `1`.
`project.update-settings` uses version `2` and still accepts supported baseline
version `1` commands without changing the newer building classification.
`project.update-settings` uses version `3` (including the print cover fields)
and still accepts version `2` and supported baseline version `1` commands
without changing the newer cover fields or building classification.
Pre-baseline formats remain unsupported. Unsupported types or schema versions
are rejected. Insert commands contain the complete entity or circuit block
with stable ids; delete commands include the expected parent identity. Circuit
@ -173,6 +182,9 @@ synchronizes linked circuit rows implicitly.
- `PUT /projects/:projectId`
- request:
`{ "expectedRevision": 12, "isPublicBuilding": true, "singlePhaseVoltageV": 230, "threePhaseVoltageV": 400, "enabledDistributionBoardSupplyTypes": ["AV", "MSR", "SiBe"], ...projectMetadata }`
- optional print cover fields `siteAddress` (max. 500), `workStage` (max.
100), `projectLead` and `projectEditor` (max. 200 each); omitted fields keep
their value, empty strings are stored as `null`
- executes `project.update-settings` as one atomic revision
- response:
`{ "project": { ... }, "revision": { ... }, "history": { ... } }`
@ -210,6 +222,12 @@ A stale `expectedRevision` returns HTTP `409` with
`PROJECT_REVISION_CONFLICT`. Undo or redo without an eligible stack entry
returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`.
## Estimate Balance (Grobe Leistungsbilanz)
Separate module under `/projects/:projectId/estimate-balances`; complete
documents are written through `estimate-balance.insert/.update/.delete` project
commands. Endpoints, payloads and calculation rules: `docs/estimate-balance.md`.
## Circuit-First Endpoints
### Distribution Board Setup
@ -230,9 +248,13 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`.
- stale revisions return `409 PROJECT_REVISION_CONFLICT`
- `PUT /projects/:projectId/distribution-boards/:distributionBoardId`
- body:
`{ "floorId": null, "supplyType": "AV", "expectedRevision": 13 }`
- executes `distribution-board.update`; floor and supply type are restored
together by persistent Undo/Redo
`{ "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`
- executes `distribution-board.update`; all submitted values, including the
submitted info fields, are restored together by persistent Undo/Redo
- `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
@ -249,15 +271,27 @@ returns HTTP `409` with `PROJECT_HISTORY_OPERATION_UNAVAILABLE`.
`distribution-board.delete-subtree`. Persistent Undo/Redo restores or
removes the same complete subtree, including protection devices.
### Project Floors and Rooms
### Project Buildings, Floors and Rooms
- `GET /projects/:projectId/buildings`
- `POST /projects/:projectId/buildings`
- body: `{ "name": "Haus A", "expectedRevision": 12 }`
- executes `project-building.insert` with a stable building id
- response: `{ "building": { ... }, "revision": { ... }, "history": { ... } }`
- `PUT /projects/:projectId/buildings/:buildingId`
- body: `{ "name": "Haus B", "expectedRevision": 13 }` (name only)
- `DELETE /projects/:projectId/buildings/:buildingId`
- body: `{ "expectedRevision": 14 }`
- rejects buildings with assigned floors
- `POST /projects/:projectId/floors`
- body: `{ "name": "EG", "expectedRevision": 13 }`
- executes `project-floor.insert` with a stable floor id
- body: `{ "name": "EG", "buildingId": "building_1", "expectedRevision": 13 }`
(`buildingId` optional, `null` = no building)
- executes `project-floor.insert` (command version 2) with a stable floor id
- response: `{ "floor": { ... }, "revision": { ... }, "history": { ... } }`
- `PUT /projects/:projectId/floors/:floorId`
- body: `{ "name": "1. OG", "expectedRevision": 14 }`
- executes an exact persistent floor update
- body: `{ "name": "1. OG", "buildingId": null, "expectedRevision": 14 }`
- executes an exact persistent floor update of name and/or building; a
missing `buildingId` keeps the current building
- `DELETE /projects/:projectId/floors/:floorId`
- body: `{ "expectedRevision": 15 }`
- rejects floors with assigned rooms or distribution boards
@ -447,3 +481,27 @@ Full request example and calculation rules: `docs/cable-sizing-module.md`.
Parser errors expose stable codes such as `header-not-found`,
`ambiguous-header`, `invalid-ifc-guid` and `duplicate-ifc-guid`. The confirmed
import uploads and parses the file again and verifies its hash.
## Document Branding (print view)
Office-wide branding for the print view. These endpoints are mounted at
`/api/document-branding`, outside `/api/projects`, because the data is global
and intentionally not part of project revisions, snapshots or transfer.
- `GET /document-branding`
- returns `{ "logo": { "mimeType", "fileName", "sizeBytes", "updatedAtIso" } | null, "office": { "officeName", "officeAddress" } }`
- `GET /document-branding/logo`
- returns the stored image bytes with its media type, or `404` without logo
- responses carry `X-Content-Type-Options: nosniff` and a restrictive
Content-Security-Policy so an SVG logo cannot execute scripts
- `PUT /document-branding/logo`
- request: `{ "fileName": "logo.png", "mimeType": "image/png", "contentBase64": "..." }`
- accepted types: `image/png`, `image/jpeg`, `image/svg+xml`; at most 2 MB
after decoding; the content signature must match the declared type
- replaces an existing logo; returns the logo metadata
- `DELETE /document-branding/logo`
- removes the logo; returns `204`
- `PUT /document-branding/office`
- request: `{ "officeName": "…" | null, "officeAddress": "…" | null }`
(at most 200 / 500 characters; empty text is stored as not set)
- returns `{ "office": { "officeName", "officeAddress" } }`

View file

@ -1,5 +1,8 @@
# Circuit List Editor Interactions
The back-arrow link beside the page title returns to the project's distribution
board overview without changing circuit-list data.
## Editing Model
Inline cells are static text by default. A cell enters edit mode by:
@ -44,6 +47,7 @@ Selection, keyboard movement, and editability checks run against this normalized
- arrow keys: move selected cell in grid when not editing
- type-to-edit: printable keys open edit mode and replace current display value with typed character
- `Ctrl+Plus` / `Ctrl+Shift+Plus`: insert below the selected circuit or device context
- `Alt+ArrowDown` on a GZF cell (selected or editing): open the AMEV simultaneity-factor table
- `Delete`: delete the selected circuit or device context after confirmation
For insertion, a circuit-level cell creates a reserve circuit directly after the selected circuit. A device-level cell creates a manual device row directly after the selected device. On the virtual `-frei-` row, or without a valid row selection, the shortcut creates a circuit at the end of the active section. Insertions use command history and never renumber existing equipment identifiers.
@ -52,6 +56,23 @@ For deletion, a circuit-level cell targets the complete circuit and a device-lev
Creating or deleting a device row updates the row and the circuit reserve state in one SQLite transaction.
## AMEV Simultaneity Factor
The selected GZF cell shows a small table-icon button; it and `Alt+ArrowDown` open
the shared `GzfPickerModal` (AMEV Planungshilfe, Tabelle 3), which the project
device dialog uses as well. "Übernehmen" commits the mean value of the chosen
consumer group through the same path as a typed edit (`commitEdit` with an
explicit target cell), so it is one undoable command and works on device rows,
compact circuits, reserve circuits and the `-frei-` row. A running inline edit
of the cell is discarded when the picker opens. The button only appears on the
selected cell; the grid keeps its static-text cells.
The dialog takes keyboard focus, keeps Tab/Shift+Tab within its actions and
closes on Escape without writing a value. Closing returns focus to the grid;
only "Übernehmen" saves the selected mean value. Opening the picker is disabled
while a write or history operation is running. Its cell button stays outside
the Tab order; `Alt+ArrowDown` provides keyboard access from the selected cell.
## Equipment Identifier Validation
Equipment identifiers are compared case-insensitively after trimming whitespace. A conflicting edit is highlighted before submission and cannot be committed. Existing conflicts are highlighted in the grid. Validation errors remain dismissible above the editor instead of replacing the complete workspace.
@ -127,11 +148,30 @@ Cross-section device moves show confirmation-required feedback before drop. The
## Filtering and Sorting
- Clicking a column label changes sorting; the funnel icon next to it opens
the value filter. An active filter is highlighted and shows the number of
selected values. Both controls retain German accessible names and tooltips.
- Header labels wrap between words; trailing units such as `[kW]` appear on
a separate line.
- Per-column filtering works on normalized displayed values.
- Sorting is view-level first and treats circuits as blocks.
- Multi-device circuits are not split during sort.
- Sorting alone does not persist order.
## Action Buttons
The editor toolbar uses icons with visible German labels. Group headers and
circuit/device rows use compact icon buttons with German accessible names and
tooltips. Group protection buttons additionally show `VS` or `FI` to distinguish
their roles. Dividers separate related actions; destructive actions use the
danger style. Circuit deletion uses a trash icon, while device-row deletion
uses a crossed-out list icon.
The shared `src/frontend/components/icon-button.tsx` component preserves
disabled states and expanded states for toggles. Row and group action buttons
retain `tabIndex={-1}` so the spreadsheet's existing cell navigation remains
unchanged. The existing command handlers and deletion confirmations are reused.
## Apply Sorted Order
`Apply sorted order` persists current sorted block order to backend by section.
@ -161,6 +201,11 @@ across multiple sections is atomic and occupies one history step. Explicit
renumbering uses a collision-safe persistent project command and one
project-history step.
The editor creates client-side UUIDs through `createClientId` in
`src/frontend/utils/client-id.ts`. It uses `crypto.randomUUID()` when available
and otherwise generates a UUID v4 with `crypto.getRandomValues()`, so insertion
also works over plain HTTP from another machine in the LAN.
Covered operations include:
- insert/delete circuit

View file

@ -9,6 +9,8 @@
- Named and automatic logical project snapshots can be listed and restored
through the project page with persistent Undo/Redo. Automatic snapshots are
created every 25 revisions and only their newest 12 entries are retained.
- Further Revit work is deferred; it will later use the CSV export/import of
the Linear plugin in Revit, not Dynamo.
- The Revit/CSV foundation and confirmed initial import are implemented.
Existing-row assignment, move and detach plus creation of an external-only
row in an existing or newly created circuit have atomic persistent commands.
@ -31,3 +33,12 @@
- 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.
- 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
board plans are not split into pages; pagination happens in the browser print
dialog.

View file

@ -27,9 +27,18 @@ Next.js App Router ── /api/* Rewrite ──▶ Express API :3000
data/leistungsbilanz.db (SQLite)
```
Im Docker-Entwicklungssetup laufen Frontend und API in getrennten Containern. Das
Frontend leitet `/api/*` über `API_INTERNAL_URL` an die API weiter. Die Datenbank
liegt über einen Host-Mount außerhalb des Containers.
In beiden Docker-Stacks laufen Frontend und API in getrennten Containern.
`compose.yaml` startet den gebauten Stand, `compose.dev.yaml` die Entwicklung
mit Hot Reload. Das Frontend leitet `/api/*` über `API_INTERNAL_URL` an die API
weiter; beim gebauten Stand wird das Ziel zur Build-Zeit festgelegt. Die
Datenbank liegt über einen Host-Mount außerhalb des Containers.
Neue IDs für Stromkreise, Gerätezeilen, Gruppen und Verteilerkomponenten erzeugt
der Editor über `src/frontend/utils/client-id.ts` (`createClientId`). Die
Hilfsfunktion nutzt `crypto.randomUUID()`, sofern verfügbar, andernfalls
`crypto.getRandomValues()` mit UUID-v4-Format. Damit funktioniert das Anlegen
auch beim HTTP-Zugriff im LAN ohne sicheren Browserkontext. Die IDs entstehen
vor dem Command und bleiben über Undo/Redo unverändert.
## Wichtige Einstiegspunkte
@ -232,8 +241,9 @@ und Historienstapel teilen dieselbe Transaktion. Create, Import aus der globalen
Gerätebibliothek und Delete laufen über dieselbe Command-Grenze; ihre Antworten
liefern Gerät und aktualisierten Historienstand an die Projektseite zurück.
`project.update-settings` versioniert Projektname, interne und externe
Projektnummer, Bauherr, Beschreibung, die Kennzeichnung als öffentliches
Gebäude, beide Standardspannungen sowie die im Projekt freigeschalteten
Projektnummer, Bauherr, Beschreibung, die Deckblattangaben (Anschrift,
Leistungsphase, Projektleitung, Bearbeitung; Schema-Version 3, Migration
`0008`), die Kennzeichnung als öffentliches Gebäude, beide Standardspannungen sowie die im Projekt freigeschalteten
Verteiler-Netzarten als eine atomare Änderung. Die Gebäudekennzeichnung ist
der spätere Eingang für die Auswahl halogenfreier Kabel und Leitungen. Der
Systemkatalog besteht aus `AV`, `SV`, `EV`, `USV`, `MSR` und `SiBe`; mindestens
@ -245,22 +255,55 @@ Werte, Revision und Historienstapel gemeinsam. `PUT /api/projects/:projectId`
verlangt deshalb `expectedRevision`, liefert Projekt plus aktualisierten
Historienstand und besitzt keinen separaten direkten Settings-Schreibweg mehr.
Kommando- und Snapshot-Payloads müssen dem aktuellen Baseline-Schema entsprechen.
Die Projektseite zeigt Verteilungen, Etagen und Räume als kompakte
Bestandsübersichten; ihre versionierten Erstellwege öffnen beschriftete Modals
statt dauerhafter Eingabezeilen. Projektgeräte werden als durchsuchbare,
Die Projektseite zeigt Verteilungen als kompakte Bestandsübersicht; ihre
versionierten Erstellwege öffnen beschriftete Modals statt dauerhafter
Eingabezeilen. Gebäude, Etagen und Räume werden auf der eigenen Seite
„Gebäudestruktur“ (`/projects/:projectId/struktur`) gepflegt; Etagen können
einem Gebäude zugeordnet sein (`docs/building-structure.md`). Projektgeräte werden als durchsuchbare,
fünfspaltige Übersicht dargestellt. Manuelle Anlage, Übernahme aus der globalen
Bibliothek und vollständige Bearbeitung erfolgen in einem gemeinsamen Modal.
Die anschließende Vorschau verknüpfter Stromkreiszeilen bleibt davon getrennt,
damit Änderungen weiterhin niemals still synchronisiert werden.
`GET /api/projects/:projectId/export` verpackt denselben vollständigen
Projektzustand in ein portables, format- und schema-versioniertes JSON-Dokument
mit SHA-256-Prüfsumme. `POST /api/projects/:projectId/import` prüft Format,
Snapshot-Relationen und Prüfsumme vor jedem Schreibzugriff. Der Modus `replace`
läuft über `project.restore-state` und ist dadurch eine atomare, dauerhaft
rückgängig machbare Projektrevision. Der Modus `duplicate` ordnet Projekt-,
Die Karten Verteilungen und Projektgeräte besitzen je eine einklappbare
Filterleiste im Stil der PM-App (Suche, Mehrfachauswahl für Etage/Netzart bzw.
Kategorie/Phasenzahl/Kostengruppe, „Zurücksetzen“, Anzeige „x von y“); die
Filterlogik liegt rein in `src/frontend/utils/overview-filters.ts`, der
Einklappzustand je Karte in `localStorage`. Aktionen verwenden wie der Editor
`IconButton`/`IconLink`.
Die allgemeinen Anwendungskarten verwenden in `src/app/globals.css` einen
gleichmäßigen Rahmen, 16-px-Rundungen und einen Schatten mit eigener
Dark-Mode-Abstimmung. Bootstrap-Karten mit `shadow-sm` nutzen denselben
Schatten; der Stromkreis-Grid bleibt eine eigenständige Komponente.
Die Verteilersuche berücksichtigt Name, Verteilernummer, Örtlichkeit und
Etagenname; die Gerätesuche
Name, Anzeigename, Kategorie einschließlich deutscher Bezeichnung, Anschlussart
und Kostengruppe. Der Gerätefilter „Anschluss“ wählt zwischen 1- und 3-phasig.
Mehrere Werte innerhalb eines Filters gelten alternativ, verschiedene Filter
und die Suche gemeinsam. Eine leere Auswahl bedeutet „Alle“; fehlende
Zuordnungen sind über „Ohne Etage“, „Nicht festgelegt“ beziehungsweise
„Ohne Kostengruppe“ auswählbar. Nicht mehr angebotene Auswahlwerte werden bei
der Filterung ignoriert. „Zurücksetzen“ leert die Filter der jeweiligen Karte.
Beim Einklappen bleiben aktive Filter wirksam und durch „Filter aktiv“ sichtbar.
Nur der Einklappzustand wird je Kartentyp im Browser gespeichert; Suchbegriffe
und Filterauswahl werden nicht dauerhaft gespeichert. Die Filter ändern keine
Projektdaten und erzeugen keine Revisionen. Das Löschen eines Projektgeräts
erfordert eine Bestätigung mit dessen Namen und verwendet weiterhin den
bestehenden persistenten Projektbefehl.
`GET /api/projects/:projectId/export` verpackt den vollständigen portablen
Projektzustand in ein format- und schema-versioniertes JSON-Dokument mit
SHA-256-Prüfsumme. Transferformat 2 enthält zusätzlich alle Grobbilanzen,
ohne sie in das logische Snapshot-Schema aufzunehmen; Format 1 bleibt ohne
Grobbilanzen importierbar. `POST /api/projects/:projectId/import` prüft Format,
Snapshot-Relationen, Grobbilanzen und Prüfsumme vor jedem Schreibzugriff. Der
Modus `replace` läuft über `project.restore-state` Version 2 und ersetzt den
Projektzustand und die Grobbilanzen atomar und dauerhaft rückgängig machbar.
Normale Snapshot-Restores verwenden weiterhin Version 1 und lassen
Grobbilanzen unverändert. Der Modus `duplicate` ordnet Projekt-,
Struktur-, Gruppen-, Verteilerkomponenten-, Raum-, Stromkreis- und
Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen vollständig neu zu und legt
die Kopie mit Revision `0` in einer Transaktion an. Fachliche Verknüpfungen
Gerätezeilen-UUIDs sowie Schutzgeräte-Referenzen und die IDs aller Grobbilanzen
vollständig neu zu und legt die Kopie mit Revision `0` in einer Transaktion an.
Fachliche Verknüpfungen
innerhalb des unterstützten Laufzeitmodells bleiben
erhalten. Die
Projektübersicht verwendet dafür den separaten Collection-Endpunkt
@ -416,7 +459,16 @@ wieder her.
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
ist für bestehende sowie neu angelegte Verteilungen standardmäßig `1`.
Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder
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` ist vorerst Freitext.
Snapshot-Schema 7 enthält zusätzlich die Gebäude und die Gebäudezuordnung
der Etagen; ältere Versionen werden ohne Gebäude hochgestuft.
Snapshot-Schema 6 enthält diese Felder und die Deckblattangaben des
Projekts; ältere Versionen werden mit `null`
hochgestuft. Snapshot-Schema 5 enthält zusätzlich den manuellen Mengenanteil jeder
Gerätezeile. Version 4 wird mit `manualQuantity = quantity` hochgestuft und
enthält bereits den vollständigen externen Modellzustand
aus Quelle, Batches, Raum-Mappings und Objekten. Es enthält außerdem die
@ -498,7 +550,7 @@ Revision und Undo/Redo. Details, Rechenweg und Prüfnachweis der Tabellen:
`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 5. Persistente
Transfer-Integration verwenden aktuell Snapshot-Schema 6. 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
@ -517,6 +569,10 @@ bei einem späteren Wechsel trotzdem einen eigenen PostgreSQL-Adapter.
## Revit-CSV-Grundlage
**Zurückgestellt:** Die weitere Revit-Arbeit wird später angegangen, dann
über den CSV-Export/-Import des Linear-Plugins in Revit statt über Dynamo.
Details: [Revit-CSV-Anforderungen, Abschnitt 1](spec/revit-csv-integration-requirements.md).
Der reine Adapter unter `src/external-model/csv` validiert die projektweite
Transport- und Mappingkonfiguration und parst UTF-8-CSV verlustarm in eine
klassifizierte Zellmatrix. Er erkennt Metadaten, Header, Passthrough-, Objekt-
@ -526,7 +582,7 @@ einschließlich BOM, CRLF und Quotierung bytegleich zurück. Der lokale Befehl
`external_csv_configurations` enthält höchstens eine versionierte Konfiguration
je Projekt. `external-csv-configuration.update` schreibt Anlage, Änderung oder
Entfernung zusammen mit Revision und persistentem Undo/Redo. Snapshot-Schema 5,
Entfernung zusammen mit Revision und persistentem Undo/Redo. Snapshot-Schema 6,
Restore und portabler Projekttransfer enthalten diesen Zustand; beim Duplizieren
werden Konfigurations-UUID und Projektlink remapped.

View file

@ -16,9 +16,10 @@ und macOS keine inotify-Events durchreichen; das kostet dauerhaft CPU, auch
ohne Benutzeraktivität. Dieser Stack gehört deshalb nur auf einen
Entwicklungsrechner.
Beides enthält weder TLS, Authentifizierung, Reverse Proxy, Prozesshärtung noch
ein zentral betriebenes Datenbanksystem. Der Stack darf deshalb nicht öffentlich
erreichbar gemacht werden.
Beide Stacks enthalten weder TLS, Authentifizierung, einen vorgeschalteten
Reverse Proxy noch ein zentral betriebenes Datenbanksystem. Die oben genannten
Container-Schutzmaßnahmen ersetzen diese Funktionen nicht. Der Stack darf
deshalb nicht öffentlich erreichbar gemacht werden.
## Topologie
@ -34,6 +35,11 @@ erreicht, kann jedes Projekt lesen und ändern. Erreichbar ist sie nur aus dem
Compose-Netz, praktisch also über den Next.js-Rewrite. Im Entwicklungs-Stack
hängen beide Ports am Loopback und sind nicht aus dem LAN erreichbar.
Beim HTTP-Zugriff auf den Server-Stack aus dem LAN erzeugt der Editor neue
UUIDs über `createClientId`, mit `crypto.getRandomValues()` als Ersatz für das
dort nicht verfügbare `crypto.randomUUID()`. HTTPS ist für diese
ID-Erzeugung nicht erforderlich.
### Speichergrenzen
Jeder Dienst hat ein `mem_limit`. Im Server-Stack ist zusätzlich
@ -140,14 +146,17 @@ Vor einer produktiven Installation werden mindestens benötigt:
- Entscheidung, ob SQLite für einen einzelnen Prozess genügt oder PostgreSQL für
Mehrbenutzerbetrieb erforderlich ist
Bis diese Punkte umgesetzt und getestet sind, besteht die „Installation“ aus dem
lokalen Entwicklungsstart in der README.
Der in der README beschriebene Server-Stack ermöglicht bereits den Betrieb des
gebauten Stands in einer geschützten Umgebung. Ein vollständig unterstütztes
Produktionssetup bleibt bis zur Umsetzung und Prüfung dieser Punkte offen.
## Backup und Wiederherstellung
`npm run db:backup` erzeugt unter `data/backups/` ein konsistentes SQLite-Backup,
öffnet es unabhängig und prüft Integrität sowie Fremdschlüssel. Diese
Dateisicherungen sind von zukünftigen logischen Projektversionen getrennt.
Dateisicherungen sind von den bereits vorhandenen benannten und automatischen
logischen Projektsicherungen getrennt. Projektsicherungen werden über die
Projektseite wiederhergestellt und unterstützen persistentes Undo/Redo.
Eine produktive Restore-Anweisung wird erst zusammen mit einem unterstützten
Produktionsbetrieb veröffentlicht. Lokale Wiederherstellung darf nur bei

View file

@ -13,6 +13,11 @@ Ein sauberer Clone enthält keine SQLite-Datenbank. Beim ersten Start werden
`data/leistungsbilanz.db`, alle Tabellen und Migrationseinträge automatisch
angelegt. Das Frontend ist unter <http://localhost:3001> erreichbar.
Dieser Standard-Stack verwendet gebauten Anwendungscode. Für die Entwicklung
mit Quellcode-Mounts und Hot Reload stattdessen
`docker compose -f compose.dev.yaml up --build --detach` verwenden; die Dienste
sind dabei nur über die Loopback-Ports des Entwicklungsrechners erreichbar.
Für direkte Node.js-Entwicklung:
```powershell
@ -86,7 +91,8 @@ Bei Docker- oder Laufzeitänderungen zusätzlich:
```powershell
docker compose up --build --detach
docker compose ps
Invoke-WebRequest http://localhost:3000/health
Invoke-WebRequest http://localhost:3001/health
Invoke-WebRequest http://localhost:3001/web-health
```
Bei sichtbaren Editoränderungen ist außerdem ein kurzer GUI-Test erforderlich.

119
docs/estimate-balance.md Normal file
View file

@ -0,0 +1,119 @@
# Grobe Leistungsbilanz (LPH 2/3)
Eigenes Modul für die Leistungsbilanz der Vor- und Entwurfsplanung, gegliedert
nach Kostengruppen (DIN 276, KG 400). Es gibt dort noch keine Verteilungen und
Stromkreise: Jedes Gewerk trägt unter seiner Kostengruppe Verbraucher als
Stückzahl mit Einzelleistung oder als Fläche mit W/m² ein. Aufbau und Rechnung
folgen der bisherigen Excel-Vorlage „Leistungsbilanz – KG400“. Abstimmung:
Issue #7.
Das Modul ist **nicht** mit der Stromkreisstruktur verknüpft. Es liest die
Gerätezeilen nur für den Vergleich „geschätzt ↔ detailliert“ und schreibt
nichts in Stromkreislisten, Verteilungen oder Projektgeräte.
## Aufbau
```
src/shared/estimate-balance/estimate-balance.ts Datenform, Rechnung, Vergleich (rein, ohne Imports)
src/estimate-balance/domain/estimate-balance-command.model.ts Snapshot-Schema (Zod), Befehle
src/estimate-balance/domain/estimate-balance-project-command.store.ts Store-Port
src/estimate-balance/api/estimate-balance-request.schemas.ts Request-Schemas
src/db/schema/estimate-balances.ts Tabellen (Migration 0010)
src/db/repositories/estimate-balance*.ts Lesen, Schreiben, Befehls-Store
src/server/controllers/estimate-balance.controller.ts API
src/app/projects/[projectId]/grobbilanz/ Übersicht der Stände und Editor
src/frontend/estimate-balance/ Editor-Komponenten und Helfer
```
Die gemeinsame Datei unter `src/shared/estimate-balance` hat keine Imports, weil
das Frontend sie direkt nutzt (Turbopack löst die `.js`-Suffixe der API nicht auf).
## Datenmodell
| Tabelle | Inhalt |
|---|---|
| `estimate_balances` | ein **Stand** je Projekt (z. B. Vorplanung, Entwurfsplanung): Name, LPH, Datum, Reserve AV/SV in %, Sortierung |
| `estimate_groups` | Kostengruppe (`"410"` … `"480"`, `""` für „Allgemein“), Titel, „bearbeitet von“, Sortierung |
| `estimate_rows` | Versorgung (Auswahl ELT/MSR oder Freitext), Verbraucher/Fläche, Anzahl bzw. Fläche, Einheit (`piece` = St., `area` = m²), Anschluss (fest/Steckdose), Einzelleistung in W bzw. W/m², GLF, cos φ, SV ja/nein, Anmerkung |
Neue Stände entstehen aus der Vorlage KG 400 (410 Sanitär, 420 Wärme, 430 Luft,
434 Kälte, 440 Starkstrom, 450 Schwachstrom, 460 Förder, 470 Nutzungsspezifisch,
480 GA, Allgemein; Reserve AV 10 %), leer oder als Kopie eines bestehenden
Stands mit neuen IDs. Stände bleiben bearbeitbar; ein zusätzlicher Status oder
eine Bearbeitungssperre ist derzeit nicht vorgesehen.
## Rechnung
- Zeile: P = Anzahl bzw. Fläche × Einzelleistung × GLF, S = P / cos φ.
- **AV** enthält alle Verbraucher (Normalversorgung). **SV** listet zusätzlich
die Zeilen mit „SV = ja“, die auch aus der Sicherheitsstromversorgung versorgt
werden müssen. Das ist die Lesart der Vorlage; die Regel liegt in
`calculateEstimateRow` und lässt sich dort zentral ändern.
- Summen je Kostengruppe, Gesamtsumme, Reserve in % (AV und SV getrennt),
Leistung inklusive Reserve und Maximalstrom I = S / (√3 · U) mit der
Drehstromspannung aus den Projekteinstellungen.
- Anzeige wie in der Tabellenkalkulation gerundet (`roundEstimateValue`,
40,175 → 40,18). Der Test `tests/estimate-balance.test.ts` rechnet das
Beispiel der Vorlage vollständig nach (143,24 kW, 148,48 kVA, mit 10 %
Reserve 157,56 kW / 163,33 kVA, 235,75 A).
## Vergleich geschätzt ↔ detailliert
`GET …/comparison` summiert alle Gerätezeilen aller Stromkreislisten des
Projekts (Anzahl × Leistung je Stück × Geräte-GZF, wie der Stromkreisbaum) und
ordnet sie über das Feld **Kostengruppe** der Gerätezeile zu. Maßgeblich ist das
längste passende Präfix: `434` → Gruppe 434, `431` → Gruppe 430, `444` → 440.
Zeilen ohne passende Kostengruppe erscheinen als „nicht zugeordnet“. Der
Verteiler-GZF wird auf keiner Seite angewendet; ein fehlendes cos φ zählt als 1.
Verglichen wird die AV-Leistung (kW) mit Differenz absolut und in %.
## Befehle, Undo/Redo
Alle Änderungen laufen über den bestehenden Projekt-Befehlsmechanismus mit
Revision, Inverse und persistentem Undo/Redo:
- `estimate-balance.insert` / `estimate-balance.delete` mit dem vollständigen
Stand (Gruppen und Zeilen)
- `estimate-balance.update` mit vollständigem `expected`/`target`-Dokument; der
Store prüft, dass der gespeicherte Stand genau `expected` entspricht, und
ersetzt ihn atomar. Die Inverse ist das vertauschte Paar.
Der Editor speichert das ganze Dokument verzögert (Autosave); jeder
Speichervorgang ist ein Undo-Schritt. Unveränderte Dokumente erzeugen keine
Revision (`{ unchanged: true }`).
## Projekttransfer und bewusste Abgrenzung
- Das reguläre SQLite-Datenbank-Backup sichert die Grobbilanz-Tabellen zusammen
mit allen übrigen Anwendungsdaten.
- Benannte und automatische Projektsnapshots enthalten keine Grobbilanzen; das
Snapshot-Schema bleibt Version 7. Snapshot-Restore und dessen Undo/Redo lassen
vorhandene Grobbilanzen unverändert.
- Der portable Projektexport enthält alle Grobbilanzen ab Transferformat 2.
Ersetzen übernimmt sie atomar und rückgängig machbar; Duplizieren vergibt neue
IDs für Bilanz, Gruppen und Zeilen. Alte Transferdateien in Version 1 bleiben
importierbar und enthalten definitionsgemäß keine Grobbilanzen.
- Keine Rechte je Gewerk; „bearbeitet von“ ist ein Freitext je Kostengruppe.
- Kostengruppen der Gerätezeilen sind nicht normiert; der Vergleich arbeitet
mit Präfixen.
- Der erste Vergleich zeigt die geschätzte und detaillierte AV-Wirkleistung in
kW. SV- und kVA-Vergleiche können später ergänzt werden.
- Eine Stromkreisnummer gehört nicht zur Grobbilanz, weil sie eine frühe
Abschätzung ohne Stromkreisplanung ist.
## API
- `GET /api/projects/:projectId/estimate-balances`
- `POST /api/projects/:projectId/estimate-balances` –
`{ "expectedRevision": 7, "name": "Entwurfsplanung", "workStage": "LPH 3", "dateIso": "2026-09-26", "template": "kg400" }`
(`template`: `kg400` | `empty` | `copy` mit `copyFromBalanceId`)
- `GET /api/projects/:projectId/estimate-balances/:balanceId`
- `PUT /api/projects/:projectId/estimate-balances/:balanceId` –
`{ "expectedRevision": 8, "balance": { …vollständiges Dokument… } }`
- `DELETE /api/projects/:projectId/estimate-balances/:balanceId` – `{ "expectedRevision": 9 }`
- `GET /api/projects/:projectId/estimate-balances/:balanceId/comparison`
Validierung: Name 1–200 Zeichen, getrimmte Texte, Anzahl/Fläche und
Einzelleistung ≥ 0, GLF 0–1, cos φ > 0 bis 1, Reserve 0–100 %, Datum
`YYYY-MM-DD`, eindeutige Gruppen- und Zeilen-IDs, unbekannte Felder werden
abgelehnt.

127
docs/print-view.md Normal file
View file

@ -0,0 +1,127 @@
# Seitenansicht und PDF-Export
Die Seitenansicht ist rein lesend und verändert keine Projektdaten.
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 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.
## Aufruf
- Projektseite → Button **Seitenansicht** öffnet
`/projects/:projectId/print` in einem neuen Tab.
- **Als PDF speichern / Drucken** nutzt den Druckdialog des Browsers
(Ziel „Als PDF speichern“). Der Dokumenttitel `Leistungsbilanz – <Projekt>`
wird als Dateiname vorgeschlagen.
- **Planformat** schaltet die Verteilerpläne zwischen A4 quer und A3 quer um.
Deckblatt bleibt A4 hoch.
- **Büroangaben** pflegt Büroname und Büroanschrift (Planverfasser, für alle
Projekte).
- **Logo hinterlegen / ersetzen / entfernen** verwaltet das Büro-Logo.
## Seiten
1. **Deckblatt (A4 hoch)** – tabellarisch: Projekt, Planverfasser, Dokument,
Planstände (aus benannten Sicherungspunkten) und Inhalt. Leere Angaben
erscheinen als orangefarbenes „[ergänzen]“.
2. **Verteilerübersicht (quer)** – Versorgungsstruktur und Tabelle je
Verteiler mit Stromkreisanzahl, P<sub>inst</sub> (Anzahl × Leistung je
Stück), P<sub>Geräte</sub> (mit Geräte-GZF), Verteiler-GZF,
P<sub>gl</sub>, I<sub>B</sub> und „Σ inkl. unterlagert“.
3. **Verteilerpläne (quer, je Verteiler)** – Titelleiste mit
Verteilernummer, Bezeichnung, Örtlichkeit, Netzart, „Gespeist von“ und
P<sub>gl</sub>; darunter Kopfkomponenten, Gruppen, Stromkreise
(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.
Seitenformat, Ränder und „Seite x von y“ kommen beim Drucken aus benannten
`@page`-Regeln (`lb-portrait`, `lb-landscape`), die die Seite zur Laufzeit
einfügt. Die Bildschirmansicht zeigt Blätter in Papiergröße; die exakte
Seitenaufteilung langer Verteilerpläne entsteht erst beim Drucken.
## Vorläufige Punkte
- **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.
- **I<sub>B</sub>** ist ein symmetrischer Drehstromwert
S / (√3 · U<sub>3~</sub>) 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
CD-Vorgabe ersetzt, falls diese abweicht.
- **Planstände:** Die Spalte „Bearbeitung“ zeigt für jeden Planstand die
aktuelle Bearbeitung aus den Projekteinstellungen, weil Sicherungspunkte
(noch) keinen Bearbeiter speichern.
## Datenquellen des Deckblatts
| Feld | Quelle |
|---|---|
| Bauvorhaben, Bauherr, Projektnummern, Beschreibung | Projekteinstellungen |
| Anschrift Bauvorhaben, Leistungsphase, Projektleitung, Bearbeitung | Projekteinstellungen → Abschnitt „Deckblatt“ (`siteAddress`, `workStage`, `projectLead`, `projectEditor`) |
| Büro, Anschrift Büro | Seitenansicht → „Büroangaben“ (global) |
| Stand, Datenstand, Anzahl Verteiler, Inhalt | automatisch |
| Planstände | benannte Sicherungspunkte |
Die vier Projektfelder laufen über `project.update-settings` (Schema-Version
3, Migration `0008`) und sind Teil von Snapshot-Schema 6 und Projektexport.
Im `PUT /api/projects/:projectId` sind sie optional; fehlende Felder behalten
ihren Wert.
## Büroangaben
- Tabelle `document_branding_settings` (Schlüssel/Wert, Migration `0008`),
global wie das Logo.
- `GET /api/document-branding` liefert zusätzlich
`office: { officeName, officeAddress }`.
- `PUT /api/document-branding/office` mit `{ "officeName", "officeAddress" }`
(max. 200 / 500 Zeichen, leerer Text = nicht gesetzt).
## Logo
- Globale Büro-Einstellung, nicht Teil von Projekten, Revisionen, Snapshots
oder Projektexport.
- Tabelle `document_branding_assets` (Migration `0007`).
- `GET /api/document-branding` → `{ logo: { mimeType, fileName, sizeBytes, updatedAtIso } | null }`
- `GET /api/document-branding/logo` → Bilddaten (`404` ohne Logo), mit
`X-Content-Type-Options: nosniff` und restriktiver CSP.
- `PUT /api/document-branding/logo` mit
`{ "fileName", "mimeType": "image/png" | "image/jpeg" | "image/svg+xml", "contentBase64" }`,
maximal 2 MB; der Dateiinhalt wird gegen das Format geprüft.
- `DELETE /api/document-branding/logo` → `204`.
## Code
- `src/app/projects/[projectId]/print/page.tsx` – Datenladen, Werkzeugleiste,
`@page`-Regeln
- `src/frontend/print/print-document-model.ts` – reine Berechnungen und
Formatierung (Tests: `tests/print-document-model.test.ts`)
- `src/frontend/print/print-document.tsx` – Blätter und Tabellen
- `src/frontend/print/print-document.css` – Layout, auf `.print-view`
begrenzt
- `src/frontend/components/distribution-board-info-card.tsx` – Infokarte im
Bearbeiten-Dialog der Verteilung
## Bestehende Datenbanken
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 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.

View file

@ -113,6 +113,10 @@ Implemented automatic-snapshot policy:
## Revit / CSV / IFCGUID Round-Trip Direction
**Deferred:** further Revit work will be tackled later, then through the
CSV export/import of the Linear plugin in Revit instead of Dynamo. Details:
[Revit-CSV requirements, section 1](spec/revit-csv-integration-requirements.md).
External model exchange should use explicit staging and link entities instead of writing imported rows directly into circuits.
Conceptual entities:
@ -275,6 +279,10 @@ Completed foundation:
- `project-floor.insert` and `project-room.insert` persist complete location
records with stable ids; their inverses reject changed or referenced records
so Undo never clears room assignments or device-row links implicitly
- `project-building.insert/.update/.delete` persist complete building records;
deleting a building with assigned floors is rejected. Floor commands carry
`buildingId` since command version 2; version 1 floor commands from older
history are upgraded with `buildingId = null` before execution
- the floor and room POST routes require the current expected revision and the
project page tracks the returned history state

View file

@ -1,5 +1,9 @@
# Kontext für die Revit-Anforderungsplanung
**Zurückgestellt:** Die weitere Revit-Arbeit wird später angegangen, dann
über den CSV-Export/-Import des Linear-Plugins in Revit statt über Dynamo.
Details: [Revit-CSV-Anforderungen, Abschnitt 1](spec/revit-csv-integration-requirements.md).
## Zweck dieser Datei
Diese Datei ist eine kompakte Übergabe an ein anderes LLM. Sie soll dabei

View file

@ -532,6 +532,10 @@ Acceptance criteria:
## Phase 14: External Model Round-Trip
**Zurückgestellt:** Die weitere Revit-Arbeit wird später angegangen, dann
über den CSV-Export/-Import des Linear-Plugins in Revit statt über Dynamo.
Details: [Revit-CSV-Anforderungen, Abschnitt 1](revit-csv-integration-requirements.md).
Status: Phase 14.2 (persistente externe Objekte und bestätigter Erstimport) ist
implementiert. Editor-Zuordnung, Rückexport und Folgeimport folgen. Siehe
[Phase-14-Audit und Implementierungsplan](revit-csv-phase-14-audit-and-plan.md)
@ -540,7 +544,7 @@ und die verbindlichen
Goal:
Exchange model objects and planning parameters with Revit or comparable tools through CSV or another agreed format.
Exchange model objects and planning parameters with Revit through CSV (Linear plugin export/import).
Tasks:

View file

@ -143,4 +143,5 @@ The pre-release cleanup established the following compatibility boundary:
The Revit/CSV/IFCGUID round-trip follows the distribution-board component
foundation as a separate jointly planned phase and must not be inferred from
these tasks.
these tasks. It is currently deferred and will later use the CSV
export/import of the Linear plugin in Revit, not Dynamo.

View file

@ -2,6 +2,19 @@
## 1. Status und Verbindlichkeit
> **Zurückgestellt (September 2026).** Die weitere Revit-Arbeit
> (Editor-Oberfläche zur Objektzuordnung, Folgeimport, Rückexport) wird später
> angegangen. Wenn sie wieder aufgenommen wird, bleibt der Austausch
> CSV-basiert: Export und Import laufen über das Linear-Plugin in Revit. Das
> entspricht der Vorgabe des internen Revit/CAD-Lenkungskreises, weil dabei
> nicht direkt per Dynamo im Modell gearbeitet wird. Eine Dynamo- oder
> API-Anbindung ist nicht vorgesehen; die früheren Vorüberlegungen liegen unter
> [docs/archive](../archive/revit-dynamo-leistungsbilanz-integration-konzept.md).
> Der Aufbau der Linear-CSV ist noch offen. Vor der Wiederaufnahme wird ein
> Musterexport gegen die bestehende projektweite CSV-Konfiguration und den
> Parser geprüft; erst danach wird entschieden, was anzupassen ist. Die bereits
> umgesetzten Schritte bleiben unverändert nutzbar.
Dieses Dokument ist die verbindliche fachliche und technische Arbeitsgrundlage für **Phase 14: External Model Round-Trip**.
Es unterscheidet ausdrücklich zwischen:
@ -1338,7 +1351,7 @@ Diese Punkte blockieren die Erstellung dieser Spezifikation nicht.
- automatische raumbezogene Stromkreisvorschläge;
- projektübergreifend wiederverwendbare Mappingprofile;
- Teilmodelle und mehrere externe Quellen;
- Dynamo/API/JSON-Transportadapter;
- Dynamo/API/JSON-Transportadapter (nicht vorgesehen, siehe Abschnitt 1);
- Rollen und Berechtigungen;
- Hintergrundjobs;
- PostgreSQL.

View file

@ -2,6 +2,10 @@
## Status
**Zurückgestellt:** Die weitere Revit-Arbeit wird später angegangen, dann
über den CSV-Export/-Import des Linear-Plugins in Revit statt über Dynamo.
Details: [Revit-CSV-Anforderungen, Abschnitt 1](revit-csv-integration-requirements.md).
Phase 14.0 ist abgeschlossen und freigegeben. Dieses Dokument beschreibt den
gegen den aktuellen Code geprüften Zielzuschnitt. Phase 14.1 ist abgeschlossen.
Phase 14.2 hat mit den externen Domänenverträgen, Identitätsregeln und dem