Harden Linear XLSX end to end

This commit is contained in:
Julian Appel 2026-09-30 21:02:20 +02:00
parent 2581beee3c
commit 4ff01b2ccd
26 changed files with 522 additions and 246 deletions

View file

@ -626,7 +626,7 @@ Oberfläche: `/projects/:projectId/grobbilanz`. Details:
`toLowerCase()` gegen das vollständige Register der Stromkreisliste und wird
von jedem Anlage-/Umbenennungspfad für Stromkreise und Verteilerkomponenten
aufgerufen, auch dort, wo zuvor kein Vorab-Check existierte. Snapshot- und
Transfer-Integration verwenden aktuell Snapshot-Schema 9. Persistente
Transfer-Integration verwenden aktuell Snapshot-Schema 10. 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
@ -643,7 +643,7 @@ 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.
## Revit-Anbindung: Linear-XLSX-Erstimport nach R5
## Revit-Anbindung: Linear-XLSX nach R10
R3 hat den Backend- und Persistenz-Cutover abgeschlossen. Migration `0014`
entfernt die alten externen CSV-Entwicklungsdaten, nachdem verknüpfte
@ -678,173 +678,32 @@ derselben Transaktion. Er erzeugt weder CircuitDeviceRows noch Circuits oder
BMKs. Seine vollständige Inverse macht Undo/Redo nach Neustart möglich, ohne
Workbookbytes erneut einzulesen oder zu parsen.
### Historische Ausgangslage vor R3
R6 bis R9 ergänzen die einmal geladene, clientseitig gefilterte und
virtualisierte Projektobjektliste, revisionierte Sammelplanung, atomare
Zuordnung im Stromkreislisteneditor, strukturtreue Rückgabe und den
vollständigen Folgeimport. Der Folgeimport trennt fachliche Quelländerungen
von noch nicht nach Revit übernommenen Rückgabewerten, aktualisiert akzeptierte
Mengen samt Row-Summe und markiert fehlende Objekte ohne ihren Link zu lösen.
Eine gesondert bestätigte Aktion löscht fehlende Objekte samt Mengenübergang;
Undo stellt Objekt, Link und Menge wieder her.
Der folgende Text beschreibt den Stand nach R1 und ist keine aktuelle
Laufzeitdokumentation mehr.
R10 weist den Ablauf über Rückgabe, Folgeimport, Snapshot-Restore,
Projektduplikat und Undo/Redo nach Datenbankneustart nach. Persistenzpfade für
vollständige externe Zustände schreiben große Mengen in begrenzten
SQLite-Blöcken. Der synthetische Abnahmetest verarbeitet 5.000 Objekte; Details
und Messwerte stehen in
[R10-Abnahme](spec/revit-excel-r10-validation.md).
Der nachfolgende Abschnitt beschreibt den am Ende von R1 noch vorhandenen
Legacy-Laufzeitpfad. Er verwendet CSV und IfcGUID und ist die geprüfte
Ausgangslage für den schrittweisen Cutover; er darf nicht erweitert werden.
Das verbindliche Ziel ist der XLSX-Austausch des Linear-Plugins mit exakter,
case-sensitiver Worksharing-ID. Details:
[Excel-Anforderungen](spec/revit-excel-integration-requirements.md) und
[R0–R10-Plan](spec/revit-excel-phase-14-plan.md).
## Historische CSV-Unterlagen
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-
und Verdachtszeilen, prüft IFCGUIDs und serialisiert unveränderte Revit-Exporte
einschließlich BOM, CRLF und Quotierung bytegleich zurück. Der lokale Befehl
`npm run revit:verify-reference` prüft die bereitgestellte Referenzdatei.
`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–8,
Restore und portabler Projekttransfer enthalten diesen Zustand; beim Duplizieren
werden Konfigurations-UUID und Projektlink remapped.
Migration `0003` ergänzt die relationale Grundlage für genau eine implizite
`revit_csv`-Quelle je Projekt, geordnete Importbatches, normalisierte
Quellraummappings und über `(sourceId, ifcGuid)` eindeutige externe Objekte.
Batches halten Konfigurationssnapshot, Originalbytes und die klassifizierte
Zellmatrix zusammen. Migration `0004` ergänzt die monotone projektweite
Konfigurationsversion am Batch, getrennt von der Schema-Version des
Konfigurationsformats. Quellwerte, lokale Planungswerte und Override-Metadaten
der Objekte sind getrennt; optionale Links auf Raum, Verteilung, ProjectDevice
und CircuitDeviceRow bleiben nullable. `ExternalModelStateRepository` liest den
vollständigen Zustand deterministisch und unterscheidet unbekannte von noch
nicht importierten Projekten. Der letzte bestätigte Batch wird aus der
Batchreihenfolge abgeleitet statt redundant an der Quelle gespeichert.
Snapshot/Transfer v5 erfasst zusätzlich `manualQuantity`; Version 4 wird mit
dem bisherigen Gesamtwert als manuellem Anteil hochgestuft. Originalbytes,
Matrix und alle internen Links werden weiterhin vollständig erfasst,
stellt sie in Fremdschlüsselreihenfolge wieder her und remappt beim
Projektduplikat jede interne UUID; IFCGUID und Quelltransport bleiben
unverändert. `external-import.apply-initial` ist die atomare Schreibgrenze für
einen bestätigten Erstimport. Vor dem Insert prüft sie den aktuellen
Konfigurationsstand, SHA-256 und Base64-Kanonizität, parst die Originalbytes
erneut, vergleicht Matrix, vollständige IFCGUID-Menge und Quellwerte und
validiert alle Raum-, Verteiler- und ProjectDevice-Links gegen das Projekt.
Nicht explizit als Override markierte Planungswerte müssen dem regelbasierten
Vorschlag entsprechen. Der Command erzeugt und verknüpft ausdrücklich keine
CircuitDeviceRow; Undo entfernt den vollständigen unveränderten Importzustand,
Redo verwendet dieselben UUIDs und Bytes. Bloße Vorschau und Importplanung
erzeugen weiterhin keine dieser Zeilen.
Migration `0005` trennt bei `CircuitDeviceRow` den manuellen Mengenanteil vom
weiterhin materialisierten Gesamtwert. Bestehende Daten werden mit
`manualQuantity = quantity` übernommen. Die Fachregel für spätere externe
Zuordnungs-Commands lautet `quantity = manualQuantity + Summe der
effectiveQuantity aller verknüpften ExternalModelObjects`; Link und beide
Mengenwerte müssen innerhalb desselben Projekt-Commands wechseln.
`external-object.update-row-assignment` ist diese atomare Schreibgrenze für
bestehende Gerätezeilen. Sie weist mehrere externe Einzelobjekte zu, verschiebt
sie zwischen vorhandenen Rows oder löst ihre Links. Vor dem Schreiben werden
die vollständigen erwarteten Row- und Objektsnapshots, Projektzugehörigkeit,
Verteilung, Gruppenkategorie, Raum und Selektionskennzeichen geprüft.
Abweichende lokale Planungswerte benötigen eine explizite Konfliktbestätigung;
unterschiedliche Räume, Kategorien oder Selektionskennzeichen werden nicht
still zusammengeführt. Alle betroffenen materialisierten Mengen werden aus dem
unveränderten manuellen Anteil und sämtlichen Ziel-Objektlinks validiert. Die
Gegenrichtung enthält dieselben vollständigen Snapshots und funktioniert nach
Neustart; beim Lösen des letzten Objekts bleibt die Row bestehen.
`external-object.assign-to-new-row` fügt unzugeordnete Objekte atomar als neue
Gerätezeile in einen vorhandenen Stromkreis ein. Der manuelle Anteil dieser Row
ist null; Gesamtmenge, Raum, Verteilung, Kategorie, Selektionskennzeichen und
Planungswerte werden vor dem Schreiben geprüft. Undo löst exakt diese Objekte
und löscht die erzeugte Row nur, wenn sie und ihre Linkmenge unverändert sind.
Der Löschbefehl ist ausschließlich für die Projekthistorie zugelassen; eine
normale spätere Trennung lässt die Row gemäß der allgemeinen Zuordnungsregel
bestehen.
`external-object.assign-to-new-circuit` erzeugt einen neuen Stromkreis mit
vorab festgeschriebenem BMK, abgeleiteter Projektspannung, genau einer
externen Row und verpflichtendem Standardschutz zusammen mit den Objektlinks.
Verteilungs-, Gruppen-, Raum-, Mengen- und Planungsregeln werden vor dem Insert
gegen den aktuellen Projektstand geprüft. Der historische Gegenbefehl ist kein
allgemeiner Löschpfad: Er entfernt den vollständigen Circuit-Teilbaum nur bei
unverändertem Snapshot und unveränderter Objektlinkmenge. Redo verwendet exakt
dieselben IDs, dasselbe BMK und denselben Schutzsnapshot.
Die drei Zuordnungsadapter teilen sich
`external-object-assignment.persistence.ts` für Snapshotvergleich,
Kompatibilitätsregeln und link-sichere Updates.
`GET /api/projects/:projectId/circuit-lists/:circuitListId/external-objects`
liefert die verteilungsbezogene Read-Projektion für den späteren Editor-Drawer.
Die CircuitList bestimmt den Verteiler serverseitig; die Antwort enthält nur
dessen externe Objekte, sortiert unzugeordnet vor zugeordnet, sowie kompakte
Quell-, Raum-, Planungs- und Linkangaben. Originalbytes und vollständige
Command-Snapshots werden nicht an diese Oberfläche gegeben. Ohne optionalen
Revit-Import ist die Projektion leer.
`GET` und `PUT /api/projects/:projectId/external-csv/configuration` lesen oder
ändern die Konfiguration; der PUT plant Identität und nächsten
Konfigurationsstand serverseitig und verwendet den typisierten Command.
`POST /api/projects/:projectId/external-csv/preview` nimmt Dateiname und
Base64-kodierte CSV bis 18 MB entgegen. Der zustandsfreie Service liefert Hash,
Dialekt, Header, Klassifikationszahlen, Verdachtszeilen und gemappte
Objektquellwerte. Er schreibt weder Entwurf noch Projektdaten.
`POST /api/projects/:projectId/external-csv/initial-import/plan` verwendet
denselben begrenzten Transport, setzt eine gespeicherte Konfiguration und einen
noch leeren externen Projektzustand voraus und bleibt ebenfalls zustandsfrei.
Die Antwort gruppiert Quellräume, schlägt bei genau einer identischen
Raumnummer den vorhandenen Raum vor, gruppiert exakte Familie-und-Typ-Werte,
liefert regelbasierte Objektplanungswerte und Konfliktzahlen und enthält die
vorhandenen Ebenen, Räume, Verteilungen und ProjectDevices als Auswahlkataloge.
Unbekannte Familien, ungültige Leistungen/Mengen und Objekte ohne Raum bleiben
sichtbare Warnungen; es wird keine Entscheidung automatisch bestätigt.
`POST /api/projects/:projectId/external-csv/initial-import/apply` nimmt Datei,
erwarteten Hash, Konfigurationsversion und Projektrevision erneut entgegen.
Vollständige Entscheidungen ordnen jeden Quellraum optional einem vorhandenen
Raum und einer Standardverteilung sowie jede exakte Familiengruppe optional
einem vorhandenen ProjectDevice zu. Unklassifizierte Familien oder fehlende
Gruppenentscheidungen blockieren. Der Server parst und plant erneut, erzeugt
stabile interne UUIDs und übergibt den vollständigen Zustand an
`external-import.apply-initial`; CSV-Stromkreiswerte bleiben reine Quellwerte
und alle `circuitDeviceRowId` bleiben `null`.
Die Projektseite öffnet über „Revit-CSV“ ein deutsches Modal für Dialekt,
Spaltenzuordnung, zusätzliche Quellfelder und exakte Familie-und-Typ-Regeln.
Nach dem revisionierten Speichern kann eine lokale CSV gewählt und über den
zustandsfreien Endpunkt geprüft werden. Die Oberfläche zeigt Kennzahlen und nur
die ersten 25 erkannten Objekte; die vollständigen Objekte bleiben in der
Response verfügbar, ohne alle Zeilen in den DOM einzufügen. Der anschließende
Erstimport-Wizard gruppiert Quellräume und exakte Familie-und-Typ-Werte. Er
zeigt eindeutige, mehrdeutige und neue Raumtreffer, lässt vorhandene Räume,
Standardverteilungen und ProjectDevices explizit zuordnen und blockiert die
Übernahme bei unklassifizierten Familien. Erst nach einem Bestätigungsdialog
wird der vollständige Import als eine persistente, rückgängig machbare
Projektrevision angewendet. Dabei entstehen keine Stromkreise oder Gerätezeilen.
Fehlende Räume und ProjectDevices können direkt aus der jeweiligen Gruppe über
die regulären Projektformulare angelegt werden. Revit-Werte befüllen diese
Formulare vor, werden aber vor dem Speichern sichtbar vom Nutzer bestätigt.
Die Kataloganlage ist jeweils ein eigener Projekt-Command; der Wizard übernimmt
den neuen Datensatz anschließend in seinen lokalen Auswahlkatalog und markiert
ihn für die betroffene Gruppe.
### Freigegebener Zielpfad ab R2
R2 ersetzt zunächst nur den Transportadapter und endet mit einem realen
Linear-Rückimporttest. R3 bleibt bis zu dessen Freigabe gesperrt. Scheitert der
ExcelJS-Roundtrip, wird vor R3 ein Adapter vorgeschlagen, der die Original-XLSX
direkt auf OOXML-Ebene ändert.
Der Zielzustand verwendet Worksharing-ID als exakten, case-sensitiven externen
Schlüssel; IfcGUID bleibt optional. Die Konfiguration hat keinen eigenen
Versionszähler. `expectedRevision` bindet Vorschau und Apply, Importbatches
speichern eine Konfigurationskopie. Nur der letzte Batch hält Workbook und
Matrix vollständig, ältere Batches nur Metadaten. Apply parst die erneut
übertragene Datei, Undo/Redo arbeitet nur auf persistierten Zuständen.
Die eigene Route `/projects/:projectId/revit` lädt eine kompakte Objektliste
einmal und stellt sie clientseitig gefiltert, sortiert und virtualisiert dar.
Verknüpfte Objekte folgen dem Verteiler ihrer Row. Row-, Circuit- und
Gruppenlöschungen lösen externe Links nach Warnung im selben Command; Undo
stellt sie wieder her. Raum- und Verteilerlöschungen bleiben bei externen
Referenzen gesperrt.
Der vor R3 vorhandene CSV-/IfcGUID-Laufzeitpfad ist entfernt. Seine damalige
Ausgangslage bleibt ausschließlich im [R0-Audit](spec/revit-excel-r0-audit.md)
und in den als abgelöst markierten CSV-Spezifikationen nachvollziehbar. Alte
CSV-Snapshots und CSV-History werden weiterhin ausdrücklich abgelehnt.
## Noch nicht unterstützt
- Mehrbenutzerbetrieb und Konfliktauflösung
- R10-Härtung des Linear-XLSX-Ablaufs für große Datenmengen sowie
Ende-zu-Ende-Nachweise über Snapshot, Transfer und Neustart
- vollständige elektrische Dimensionierung
- Produktionsdeployment