Files
leistungsbilanz-ts/docs/current-architecture.md

22 KiB
Raw Permalink Blame History

Aktuelle Systemarchitektur

Status und unterstützter Pfad

Der unterstützte Editor ist Circuit-First:

Project → DistributionBoard → CircuitList → CircuitSection → Circuit → CircuitDeviceRow

Ein Stromkreis ist nicht dasselbe wie eine Gerätezeile. BMK, Schutz- und Kabeldaten gehören zum Stromkreis; Last-, Raum- und Kategoriedaten gehören zur Gerätezeile.

Die frühere Consumer-Oberfläche und ihre API sind entfernt. Die Tabelle consumers sowie Mappings und Reports bleiben ausschließlich erhalten, damit ältere Datenbanken über den expliziten Upgrade-Befehl migriert und geprüft werden können.

Laufzeit

Browser :3001
    │
    ▼
Next.js App Router ── /api/* Rewrite ──▶ Express API :3000
                                              │
                                              ▼
                                    Repository / Transaktion
                                              │
                                              ▼
                              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.

Wichtige Einstiegspunkte

  • src/app/projects/page.tsx Projektliste und globale Gerätebibliothek
  • src/app/projects/[projectId]/page.tsx Projektstammdaten, Verteilungen, Räume und Projektgeräte
  • src/app/projects/[projectId]/circuit-lists/[circuitListId]/tree-edit/page.tsx unterstützte Editorroute
  • src/frontend/components/circuit-tree-editor.tsx Editorzustand, Befehlsausführung, Drag-and-drop und der schrittweise Historien-Cutover
  • src/frontend/components/circuit-grid-*.ts reine Grid-Projektion, Zellbesitz, Einfügen und Sicherheitsregeln
  • src/frontend/utils/api.ts typisierte Frontend-API-Aufrufe
  • src/server/index.ts und src/server/routes/ API-Komposition
  • src/domain/services/ fachliche Command- und Synchronisierungsregeln
  • src/server/composition/ Verdrahtung fachlicher Services mit konkreten SQLite-Repositories
  • src/db/repositories/ Abfragen, Persistenzmapper und Transaktionsadapter
  • src/db/schema/ und src/db/migrations/ SQLite-Schema und Migrationen

Daten- und Befehlsfluss

  1. Das Grid projiziert den geladenen Circuit-Tree in sichtbare Zeilen.
  2. Eine Benutzeraktion wird im Frontend validiert und als API-Befehl gesendet.
  3. Controller validieren Requestdaten mit Zod.
  4. Domain-Services prüfen fachliche Regeln wie BMK-Eindeutigkeit, Abschnittszuordnung und Reserveverhalten.
  5. Repositories schreiben Daten. Kritische Mehrfachschreibvorgänge besitzen explizite SQLite-Transaktionsadapter mit Commit-/Rollback-Integrationstests.
  6. Das Frontend lädt den Circuit-Tree neu und stellt Auswahl beziehungsweise Viewport soweit möglich wieder her.

Der Editor besitzt keinen sitzungslokalen Undo-/Redo-Stapel mehr. Beim initialen Laden und nach jedem Tree-Reload liest er den persistenten History-Status und gleicht dessen Revision mit currentRevision des Trees ab. Das Datenmodell besitzt einen projektbezogenen Revisionszähler sowie getrennte Revision-/Change-Set- Tabellen. Eine direkt getestete Persistence-Funktion schreibt diese Historienmetadaten innerhalb der zentralen Command-Transaktion optimistisch und atomar fort. Vorwärts- und Rückwärtskommandos besitzen einen versionierten, JSON-sicheren Umschlag; Typ und Payload können dadurch nach einem Neustart verlustfrei rekonstruiert werden. Alle aktuell unterstützten Circuit-, Gerätezeilen-, Projektgeräte-, Projektstruktur- und Projekteinstellungsänderungen verwenden typisierte Command-Stores, die Fachänderung, automatisch erzeugtes inverses Kommando, Revision und Historienstapel gemeinsam committen beziehungsweise zurückrollen. Gerätezeilen-Kommandos bewahren dabei auch lokale ProjectDevice-Overrides und prüfen Projektzugehörigkeit von Verknüpfungen und Räumen. Projektweite, persistente Undo-/Redo-Stacks verwalten die zulässige LIFO-Reihenfolge und verwerfen den Redo-Zweig bei einem neuen Benutzerkommando. Ihr Status ist über GET /api/projects/:projectId/history lesbar. Die unveränderliche, absteigend paginierte Revisions-Timeline ist ohne Befehls-Payloads über GET /api/projects/:projectId/history/revisions lesbar. Ein zentraler Dispatcher führt die nachfolgend beschriebenen typisierten Kommandos über öffentliche Command-, Undo- und Redo-Endpunkte aus. Alle unterstützten Runtime-Project-Command-Stores einschließlich der vollständigen Snapshot-Wiederherstellung verwenden dabei project-command-transaction.persistence.ts als gemeinsame äußere Transaktionsgrenze. Sie führt Fachänderung, Revisions-Append und History-Transition in fester Reihenfolge innerhalb derselben SQLite-Transaktion aus. Für CircuitDeviceRow-Feldänderungen übernimmt eine typisierte Variante den während der Fachänderung um Override-Metadaten ergänzten Forward-Command. Die fachlichen Validierungs-, Forward- und Inversenregeln bleiben im jeweiligen Store. Die Low-Level-Funktion appendProjectRevision bleibt ein internes Detail dieser Persistenzgrenze und wird direkt mit einer realen SQLite-Transaktion getestet. Ein eigenständiges Runtime-Revisions-Repository existiert nicht. ProjectDeviceSyncService und CircuitNumberingService kennen nur schmale, fachlich benannte Reader-Interfaces. Ihre SQLite-Repositories werden ausschließlich in src/server/composition/ erzeugt und injiziert. Auch die allgemeinen Projekt-, Geräte-, Raum-, Geschoss- und Circuit- Repositories verlangen einen expliziten AppDatabase-Kontext. Ihre Anwendungsinstanzen werden zentral in src/server/composition/application-repositories.ts erzeugt; Controller importieren weder den globalen SQLite-Client noch konkrete Repository-Klassen. circuit-device-row.insert und circuit-device-row.delete sind atomare Strukturkommandos. Beim Löschen wird die vollständige Zeile im inversen Kommando gesichert, sodass Undo dieselbe UUID und alle Fachwerte wiederherstellt. circuit.insert und circuit.delete behandeln einen Stromkreis mit null, einer oder mehreren Gerätezeilen als vollständigen Block. Undo bewahrt dabei sämtliche Circuit-/Row-UUIDs und ändert keine Betriebsmittelkennzeichen. Bestehende Circuit- und Gerätezeilen-Zelländerungen sowie eigenständiges Einfügen und Löschen verwendet das Grid bereits über die öffentliche Command-Grenze. Neue Circuits und Gerätezeilen erhalten ihre stabile UUID vor dem Command; Löschen und Undo bewahren diese Identität. Der Tree liefert dazu currentRevision; Undo/Redo für diese Aktionen läuft über die projektweite Serverhistorie. Direkte Feld-PATCH-, Struktur-POST-, Move-, Circuit- und Gerätezeilen-DELETE-Endpunkte sind entfernt. Gerätezeilen-Moves, Stromkreis-Reorders und die explizite Neunummerierung im Grid verwenden die persistenten Kommandos. Die Toolbar leitet ihre Undo-/Redo-Verfügbarkeit direkt aus den serverseitigen Stack-Tiefen ab, sodass ein Reload die Bedienbarkeit nicht unterbricht.

Benannte logische Projektstände werden in project_snapshots getrennt von Datenbank-Backups gespeichert. POST /api/projects/:projectId/snapshots erzeugt bei passender erwarteter Revision transaktional einen vollständigen, schema-versionierten Projektzustand mit SHA-256-Prüfwert. Enthalten sind Projekteinstellungen, Verteiler, Stromkreislisten, Bereiche, Stromkreise und Gerätezeilen sowie Projektgeräte, Geschosse und Räume. Globale Geräte, Legacy-Consumer und Migrationsberichte sind nicht Teil des Projekt-Snapshots. Create/List verändern weder Projektrevision noch Undo-/Redo-Stapel. kind unterscheidet benannte und automatische Stände. Die zentrale Revisionspersistenz erzeugt nach jeweils 25 weiteren Projektänderungen transaktional einen automatischen Stand. Pro Projekt bleiben die neuesten 12 automatischen Stände erhalten; ältere automatische Stände werden in derselben Transaktion entfernt. Benannte Stände und die unveränderliche Revisionshistorie sind von dieser Aufbewahrung ausdrücklich ausgeschlossen. POST /api/projects/:projectId/snapshots/:snapshotId/restore prüft Payload, Prüfsumme, erwartete Revision und den unmittelbar zuvor gelesenen Projektzustand. Der Restore ersetzt alle unterstützten Projektdaten in einer Transaktion und schreibt dabei eine neue Revision mit Quelle restore sowie ein vollständiges inverses Kommando. Undo und Redo können deshalb auch einen Restore nach einem Neustart exakt zurücknehmen oder wiederholen. Kompatible Upgrade-only-Verknüpfungen und Migrationsnachweise bleiben erhalten, obwohl sie nicht Bestandteil des logischen Snapshots sind. Die Projektseite bindet diese APIs in einem einklappbaren Bereich „Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte benennen, jeden aufgeführten benannten oder automatischen Stand nach expliziter Bestätigung wiederherstellen und die paginierte Revisions-Timeline mit deutschen Quellen- und Änderungsbezeichnungen lesen. Die Snapshot-Liste ordnet jede positive sourceRevision serverseitig ihren unveränderlichen Revisionsmetadaten zu. Dadurch zeigt auch ein älterer automatischer Stand die auslösende Änderung, ohne eine zweite frei formulierte Beschreibung zu speichern oder von den zuletzt paginiert geladenen Timeline-Einträgen abzuhängen. Revision null wird als Projektstart dargestellt. Projektweites Rückgängig/Wiederholen bleibt im Kopf dieses Bereichs auch im eingeklappten Zustand erreichbar. Die Verfügbarkeit stammt direkt aus den persistierten Server-Stacks; nach einer Historienaktion oder einem Restore lädt die Seite sämtliche Projektdaten neu. circuit-device-row.move verschiebt oder sortiert eine oder mehrere Zeilen zwischen vorhandenen Stromkreisen derselben Liste. Erwartete und neue Stromkreis-/Sortierpositionen machen Forward und Inverse deterministisch; Reservewerte aller beteiligten Stromkreise werden atomar neu abgeleitet. Das Kompositkommando circuit-device-row.move-with-new-circuit bildet auch das Verschieben auf einen freien Platz ab: Ein Zielstromkreis mit stabiler UUID und BMK wird zusammen mit allen Zeilenbewegungen erzeugt. Undo stellt die exakten Quellpositionen wieder her und löscht den erzeugten Stromkreis nur, wenn dessen Felder und vollständiger Zeilenbestand unverändert sind. Der Editor erzeugt die vollständigen Move-Zuweisungen aus dem geladenen Tree, vergibt für neue Ziele vor dem Kommando eine stabile UUID und führt das Toolbar-Undo/Redo über die projektweite Historie aus. Die Projektgerätepalette belegt keine permanente Layoutspalte mehr. Sie wird über die Editor-Toolbar als überlagernder Drawer geöffnet, während das Stromkreis-Grid standardmäßig die gesamte verfügbare Breite nutzt. Auswahl, Schnelleinfügen und die vorhandenen Drag-and-drop-Payloads bleiben im Drawer unverändert verfügbar; während eines aktiven Projektgeräte-Drags kann er nicht geschlossen werden. Der Drawer liegt am rechten Fensterrand und verdeckt damit nicht die führenden BMK- und Anzeigenamenspalten. Das Grid zeigt Geräte- und Stromkreisleistungen in einer gemeinsamen Spalte Gesamtsumme: Gerätezeilen verwenden rowTotalPower, Stromkreis-Sammelzeilen circuitTotalPower. Zahlen werden ausschließlich für die Anzeige deutsch und begrenzt formatiert; gespeicherte Werte und Bearbeitungsentwürfe behalten ihre volle Genauigkeit. Jeder Abschnitt liefert und zeigt seine aufsummierte Stromkreisleistung. Die Stromkreisliste zeigt außerdem die ungefilterte Gesamtleistung des Verteilers, den am Verteiler gespeicherten Gleichzeitigkeitsfaktor und die daraus abgeleitete Gesamtleistung unter Berücksichtigung dieses Faktors. Sortierung und Filter verändern diese fachlichen Summen nicht. Reihenfolge und Sichtbarkeit der Grid-Spalten sind reine UI-Präferenzen. Sie werden im Browser unter einem projektspezifischen Schlüssel gespeichert und deshalb beim Wechsel zwischen Verteilern desselben Projekts wiederverwendet, ohne Projektrevisionen oder fachliche Snapshots zu erzeugen. circuit.reorder-section speichert die erwartete und neue Sortierposition jedes Stromkreises eines vollständigen Abschnitts. Forward, Undo und Redo ändern ausschließlich sortOrder; Stromkreisblöcke, Gerätezeilen und BMKs bleiben unverändert. circuit.renumber-section bildet die getrennte, ausdrücklich ausgelöste Neunummerierung ab. Es speichert alle erwarteten und neuen BMKs des Abschnitts, löst Tauschkollisionen über temporäre Werte und ändert weder Sortierung noch Gerätezeilen. Die Bereichsaktion erzeugt die vollständigen erwarteten und neuen BMKs aus Präfix und aktueller Stromkreisreihenfolge und verwendet das persistente Kommando für Toolbar-Undo/Redo. Die früheren direkten Renumber- und Identifier-Restore-Endpunkte sowie ihr separater Transaktionsadapter sind entfernt. Drag-and-drop verwendet circuit.reorder-section. Die explizite Übernahme einer sortierten Ansicht verwendet circuit.reorder-sections, damit alle betroffenen Bereiche in einer Transaktion und als ein Undo-Schritt gespeichert werden. Direkte Reorder-Endpunkte existieren nicht mehr. project-device.sync-rows persistiert Synchronisierung, Trennen und erneutes Verknüpfen als atomaren Mehrzeilen-Command. Jede betroffene Zeile enthält den vollständigen erwarteten und neuen Stand aller synchronisierbaren Felder, einschließlich ProjectDevice-Verknüpfung und lokaler Override-Metadaten. Damit werden stille Überschreibungen veralteter Zeilen verhindert und Undo/Redo stellt exakt die vorherigen lokalen Werte wieder her. Die Synchronisieren- und Trennen-Endpunkte sowie die Projektseite verwenden diesen Command mit optimistischer Revisionsprüfung. Rückgängig/Wiederholen erfolgt dort einheitlich über die persistente projektweite Historie; der frühere sitzungslokale Spezial-Undo und separate direkte Restore-/Reconnect-Schreibwege existieren nicht mehr. project-device.update versioniert Änderungen an den kanonischen Projektgerätefeldern unabhängig davon. Der Store erzeugt die Inverse aus dem gespeicherten Gerät und schreibt Geräteänderung, Revision und Historienstapel atomar. Verknüpfte Stromkreiszeilen werden dabei bewusst nicht automatisch synchronisiert. Der ProjectDevice-PUT-Endpunkt und die Projektseite verwenden diesen Command mit optimistischer Revisionsprüfung. project-device.insert und project-device.delete versionieren außerdem den Lebenszyklus eines Projektgeräts mit stabiler UUID. Beim Löschen speichert das inverse Insert den vollständigen Gerätestand sowie vollständige, nach dem Löschen erwartete Snapshots aller zuvor verknüpften Gerätezeilen. Undo setzt die Links nur zurück, wenn diese Zeilen weiterhin zum Projekt gehören, unverknüpft und vollständig unverändert sind. Gerät, Linkänderungen, Revision 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, beide Standardspannungen sowie die im Projekt freigeschalteten Verteiler-Netzarten als eine atomare Änderung. Der Systemkatalog besteht aus AV, SV, EV, USV, MSR und SiBe; mindestens eine Netzart muss aktiv bleiben und eine bereits von einer Verteilung verwendete Netzart kann nicht deaktiviert werden. Die Projektseite bearbeitet diese Angaben in einem beschrifteten Einstellungsmodal statt in einer permanenten Formularkarte. Der Store leitet das inverse Kommando aus dem gespeicherten Projekt ab und schreibt 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-Versionen vor dieser Erweiterung bleiben les- und ausführbar; fehlende Metadaten werden dabei als null behandelt. 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, 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-, Struktur-, Geräte-, Raum-, Stromkreis- und Gerätezeilen-UUIDs vollständig neu zu und legt die Kopie mit Revision 0 in einer Transaktion an. Upgrade-only- Consumer-Verweise werden nicht in die Kopie übernommen; fachliche Verknüpfungen innerhalb des unterstützten Laufzeitmodells bleiben erhalten. Die Projektübersicht verwendet dafür den separaten Collection-Endpunkt POST /api/projects/import, der ausschließlich eine neue Kopie anlegt und deshalb weder eine bestehende Projekt-ID noch expectedRevision annimmt. Das Ersetzen eines Projekts bleibt auf dessen Einstellungsmodal und den projektgebundenen Endpunkt beschränkt. distribution-board.insert versioniert die Anlage einer Verteilung als einen vollständigen Block aus Verteilung, Stromkreisliste und vier Standardbereichen. Alle UUIDs entstehen vor dem Command und bleiben über Undo/Redo stabil. distribution-board.delete ist die persistierte Inverse und entfernt nur den vollständig unveränderten, weiterhin stromkreislosen Block. Controller und Projektseite übergeben die erwartete Projektrevision; der frühere direkte Controller-Schreibweg ist entfernt. Verteilungen besitzen eine optionale Etagenreferenz sowie eine Netzart aus dem Projektkatalog. Anlage und nachträgliche Bearbeitung prüfen die Projektzugehörigkeit der Etage und die Freigabe der Netzart in den Projekteinstellungen. distribution-board.update versioniert Etage, Netzart und den 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 6 und der portable Projekttransfer enthalten diesen Wert; Schema 5 und älter werden mit dem neutralen Faktor 1 hochgestuft. Snapshot-Schema 4 enthält bereits Etage und Netzart; Schema 1/2 sowie gespeicherte Version-1-Strukturcommands werden ohne erfundene Zuordnung hochgestuft. Snapshot-Schema 3 wird mit allen sechs Netzarten als Projektauswahl hochgestuft. project-floor.insert und project-room.insert versionieren die Anlage von Geschossen und Räumen mit stabilen UUIDs. Die vollständigen Datensätze bilden jeweils die persistierte Inverse für Undo/Redo. Ein Geschoss wird durch Undo nur entfernt, solange ihm weder ein Raum noch eine Verteilung zugeordnet wurde. Ein Raum wird nur entfernt, solange weder eine CircuitDeviceRow noch ein aufbewahrter Upgrade-Datensatz auf ihn verweist. Beide POST-Endpunkte verlangen expectedRevision, liefern den aktualisierten Historienstand und besitzen keinen direkten Create-Schreibweg mehr.

Projektgeräte

ProjectDevice verwendet ausschließlich die kanonischen Circuit-First-Felder: phaseType, powerPerUnit, simultaneityFactor, cosPhi, remark sowie optionale technische und kategorisierende Felder.

Beim Einfügen entsteht eine verknüpfte CircuitDeviceRow. Der Anzeigename wird kopiert, aber nicht still synchronisiert. Spätere Änderungen am Projektgerät werden als Diff angezeigt und nur für ausdrücklich gewählte Felder und Zeilen übernommen.

Die globale Gerätebibliothek ist ein einfacher, datenbankweiter Vorlagenbestand. Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät.

Persistenz und Migration

  • SQLite ist die aktuell unterstützte Datenbank.
  • Fremdschlüssel werden für jeden Datenbankkontext aktiviert.
  • npm run db:migrate wendet Drizzle-Migrationen an.
  • npm run db:verify:circuit-schema prüft erforderliche und entfernte Spalten.
  • npm run db:backup erzeugt ein konsistentes und verifiziertes Online-Backup.
  • npm run typecheck:scripts prüft alle TypeScript-Wartungs- und Upgrade-Skripte mit ihren Anwendungspfaden, ohne Code zu erzeugen.
  • Angewendete Migrationen werden niemals nachträglich verändert.
  • db:migrate:legacy-consumers ist Upgrade-Werkzeug, kein Anwendungspfad. Der fachliche Migrationsdienst kennt nur schmale Reader-/Store-Ports unter src/domain/ports; konkrete SQLite-Repositories werden ausschließlich im CLI-Skript zusammengesetzt.
  • Allgemeine Circuit-, Gerätezeilen-, CircuitList- und DistributionBoard- Repositories stellen im Anwendungspfad nur noch benötigte Leseabfragen bereit. Fachliche Schreibvorgänge liegen in den typisierten Command-Repositories; Upgrade-Schreibvorgänge bleiben in expliziten Migrationsadaptern.
  • Die vollständige Verteilungs-Testfixture liegt unter tests/support/distribution-board-fixture.ts und ist kein exportierter Produktions-Schreibweg.

PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen bei einem späteren Wechsel trotzdem einen eigenen PostgreSQL-Adapter.

Noch nicht unterstützt

  • Mehrbenutzerbetrieb und Konfliktauflösung
  • Revit-/CSV-/IFCGUID-Round-trip
  • vollständige elektrische Dimensionierung
  • Produktionsdeployment

Details: Bekannte Einschränkungen und Zukunftsarchitektur.