Merge rough estimate balance

This commit is contained in:
Julian Appel 2026-09-27 12:38:32 +02:00
commit 060a203425
45 changed files with 9378 additions and 58 deletions

View file

@ -16,6 +16,8 @@ ausdrücklich getrennt und dürfen nicht als bereits implementiert verstanden we
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

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`,
@ -214,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

View file

@ -290,15 +290,20 @@ 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 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-,
`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

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.