From ae318a51ac05baadf606a8ec3865df2674376ada Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Tue, 29 Sep 2026 21:57:40 +0200 Subject: [PATCH] Audit Linear Excel integration --- docs/spec/revit-excel-r0-audit.md | 521 ++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 docs/spec/revit-excel-r0-audit.md diff --git a/docs/spec/revit-excel-r0-audit.md b/docs/spec/revit-excel-r0-audit.md new file mode 100644 index 0000000..721e402 --- /dev/null +++ b/docs/spec/revit-excel-r0-audit.md @@ -0,0 +1,521 @@ +# R0 – Audit der Linear-Excel-Anbindung + +Stand: 29.09.2026. Geprüfter Code: `cf022b2`. + +**Status: R0 abgeschlossen, Freigabe für R1–R10 ausstehend.** Dieser Bericht +ist ein Umsetzungsvorschlag, kein Nachweis einer bereits implementierten +Excel-Anbindung. Der neue Nutzerauftrag ersetzt die CSV-/IfcGUID-Vorgaben und +die alte Phasenliste 14.0–14.6. R1 bereinigt die bestehenden Dokumente; R0 +ändert weder Laufzeitcode noch Datenbank noch Oberfläche. + +## 1. Geprüfter Bestand und Keep/Change/Remove + +Gelesene Grundlagen: `AGENTS.md`, `docs/current-architecture.md`, +`docs/project-history-and-external-model-architecture.md`, +`docs/spec/revit-csv-integration-requirements.md` und +`docs/spec/revit-csv-phase-14-audit-and-plan.md`. Der alte Plan widerspricht +sich bereits intern: Sein Status nennt die Erstübernahme noch nicht vorhanden, +während Implementierung und Tests sie nachweisen. Alte Erledigt-Markierungen +werden deshalb nicht übernommen. + +| Bereich / Codebeleg | Entscheidung | Begründung und Änderung | +| --- | --- | --- | +| `src/db/repositories/project-command-transaction.persistence.ts` | Keep | Eine Grenze für Fachänderung, Inverse, Revision und History; auch automatische Snapshots bleiben darin. | +| `src/db/repositories/external-model-state.repository.ts`, `src/db/schema/external-*.ts` | Change | Quelle, Batches, Raum-Mappings und Objekte wiederverwenden; neue Identität, Konfiguration und typisierte Matrix. Schlanke Objekt-Reads von großen Batch-Blobs trennen. | +| `src/domain/models/external-initial-import-project-command.model.ts`, zugehöriger Store | Change | Vollständiger erwarteter/Zielzustand und Row-freier Import bleiben; Worksharing-ID, Gerätearten und atomare Regelübernahme ergänzen. | +| `src/external-model/application/external-initial-import-plan.ts`, `external-initial-import-target.ts` | Change | Raumvorschläge, Kataloge, vollständige Entscheidungen und stabile UUIDs behalten. Objektentscheidungen und Gerätearten ergänzen. | +| `src/db/repositories/external-object-assignment.persistence.ts` und drei Zuordnungs-Stores | Change | Mengeninvariante und exakte Inversen behalten; Geräteart, effektiven Verteiler, Anwesenheit und Übernahme der Row-Werte beim Lösen ergänzen. | +| `CircuitDeviceRow.manualQuantity`, bestehende Mengenprüfungen | Keep | `quantity = manualQuantity + Summe(externer effektiver Mengen)`; Mehrfachsteckdosen bleiben unteilbar. | +| `src/external-model/application/external-circuit-list-objects.ts` | Change | Listen-/Projektprüfung behalten; vollständige Planungswerte, Geräteart, effektiver Verteiler, Status und Row-Anzahlen liefern. | +| `src/domain/models/project-state-snapshot.model.ts`, Snapshot-Persistenz und Transfer | Change | Schema 10; historische CSV-Zustände ausdrücklich ablehnen; alle neuen internen Referenzen remappen. | +| `src/external-model/csv/*`, CSV-Preview, CSV-Validierungen und Controller | Remove/replace | XLSX ersetzt CSV vollständig; kein zweiter aktiver Transportpfad. | +| `src/frontend/components/revit-csv-modal.tsx`, Einbindung auf Projektseite | Remove/replace | Eigene Revit-Seite; Einstellungen nur im Modal, Import in eigenem Wizard. | +| `src/frontend/components/Sidebar.tsx`, `src/frontend/utils/api.ts`, `src/frontend/types.ts` | Change | Route `revit`, neue DTOs und Endpunkte, CSV-Imports entfernen. | +| `src/frontend/components/circuit-tree-editor.tsx`, Grid-Drop-/Projektionsmodule | Change | Separater externer Drag-Payload; Row-, Circuit- und Freiplatz-Ziele bewusst unterscheiden. Bestehende Grid-Bedienung behalten. | +| Raum-/ProjectDevice-Formulare und Commands | Keep | Sichtbar vorbelegte, ausdrücklich bestätigte Anlage als eigener Undo-Schritt. | +| `scripts/verify-revit-reference-csv.ts`, Transporttests | Replace | Pfadargument für lokalen XLSX-Prüfer; ausschließlich synthetische Workbooks in Tests. | + +### Konkrete Lücken gegenüber dem Auftrag + +- `external-model-contracts.ts` importiert heute CSV-Verträge. Die neutralen + Konfigurations-/Matrixverträge gehören künftig in `external-model/domain`; + nur `external-model/xlsx` darf ExcelJS kennen. +- `ifc_guid` ist derzeit NOT NULL und je Quelle eindeutig; Matching, + Snapshots, DTOs und Commands setzen diese Identität voraus. +- `internalDeviceType` ist derzeit ein freier String in Planungswerten. + Es gibt keinen Geräteart-Katalog mit stabilen IDs und Standardwerten. +- Der Erstimport setzt den ProjectDevice-Link, verwendet im Target aber + weiterhin `suggestedPlanningValues`. Die neue Priorität ProjectDevice vor + Geräteart muss ausdrücklich implementiert werden; ein Link allein genügt + nicht. Revit-Leistung wird künftig nicht automatisch zum Planungswert. +- Der Import-Target setzt `distributionBoardId = null`; die Raumvorgabe liegt + separat im Mapping. Projektion und Zuordnungsprüfung vergleichen heute + direkt das Objektfeld. Eine zentrale Auflösung des effektiven Verteilers + fehlt und ist vor der Editor-Nutzung erforderlich. +- Die bisherige Row-Zuordnung darf laut Validator am Objekt ausschließlich + `circuitDeviceRowId` ändern; der Store schreibt auch nur dieses Feld. + Lösen mit aktuellen Row-Planungswerten benötigt eine neue Command-Version. +- Die Kompatibilitätsprüfung vergleicht Raum, Kategorie, Auswahlkenner und + Planungswerte, aber keine stabile Geräteart. Unterschiedliche Gerätearten + dürfen nicht versehentlich in dieselbe Row aggregiert werden. +- Rows speichern `powerPerUnit` in **kW**, externe Planung + `powerPerUnitW` in **W**. Eine zentrale bidirektionale Konvertierung ist + nötig; Exportleistung darf nicht um Faktor 1000 falsch sein. +- Externe FKs auf Row, ProjectDevice, Raum-Mapping und Verteiler verwenden + teilweise `ON DELETE SET NULL`. Bestehende allgemeine Löschpfade sichern + externe Objektübergänge nicht durchgängig in ihren Inversen. Auch + Unterbaum-/History-Löschungen müssen abgesichert werden. +- Ein Editor-Drawer, projektweite Objekttabelle, Exportnachweis und + ausführbarer Folgeimport fehlen. `follow_up` als vorhandener Enumwert ist + kein implementierter Folgeimport. +- Konfigurationsversionen steigen heute nur bei normalen Updates gegenüber + dem aktuellen Wert; Undo setzt alte Versionen zurück. Für eine tatsächlich + monotone Generation muss diese Semantik korrigiert werden. +- XLSX-Parsing ist asynchron. Das heutige erneute synchrone CSV-Parsing + innerhalb der SQLite-Transaktion darf nicht einfach durch `await` ersetzt + werden. + +## 2. Prüfung der lokalen Musterdatei + +`docs/spec/kbp-Stromkreis.xlsx` wurde ausschließlich lesend auf ZIP-/OOXML- +Ebene untersucht; keine Inhalte wurden hochgeladen oder in Fixtures kopiert. +Die Datei ist unversioniert und darf nicht committed werden. + +| Befund | Ergebnis | +| --- | --- | +| Dateigröße / entpackte ZIP-Einträge | 56.765 / 337.740 Bytes | +| Arbeitsblätter / Blattnamenlänge | 1 / 31 Zeichen | +| Zellbereich | A1:O490 | +| Kopfzeile / Leerzeile / Objekte / Abschluss | 1 / 2 / 3–489 / 490 | +| Objektidentitäten | 487 syntaktisch passende Worksharing-IDs, keine Duplikate | +| Kopfzeilenkommentare | 14, zu den 14 Spalten nach der ID-Spalte | +| Fehlende Raumangaben | 67 Objekte ohne Raumnummer und Raumname | +| IfcGUID-Duplikate | 2 Gruppen nichtleerer doppelter Werte | +| Formeln | Keine; synthetische Tests dafür erforderlich | +| Spaltendefinitionen | 16, obwohl nur 15 Datenspalten vorhanden sind | + +Das beobachtete Profil aus dem Auftrag wird damit bestätigt. Zusätzliche +Spaltenformatdefinitionen dürfen beim Export nicht auf die gemappten Spalten +gekürzt werden. Das Muster enthält die neuen getrennten Stromkreisfelder und +die Bezeichnung noch nicht. Fehlende Rückgabespalten werden angezeigt; sie +dürfen nicht ohne Linear-Parameterbindung erfunden werden. + +Nicht nachgewiesen: erfolgreicher Rückimport in Linear, dessen genaue +Auswertung der Kommentar-/VML-Metadaten und der ExcelJS-Roundtrip. R2 prüft +die Struktur, R8/R10 benötigen zusätzlich einen realen Linear-Abnahmelauf. + +## 3. Bibliothek, Adapter und Uploadgrenzen + +**Vorschlag: genau eine neue direkte Abhängigkeit `exceljs`.** Seine +öffentliche API unterstützt XLSX, Zellnotizen, Formate, Spaltenbreiten und +typisierte Werte. Das passt zum beobachteten klassischen Kommentarformat. +Version und transitive Abhängigkeiten werden in R2 festgeschrieben und +geprüft. Die veröffentlichte Version ist laut Paketregister 4.4.0; eine +aktuelle Releasepflege ist damit nicht bewiesen. Dies ist ein ausdrückliches +Wartungsrisiko, kein als erfüllt abgehakter Bibliotheksnachweis. + +Quellen, am 29.09.2026 geprüft: +[ExcelJS-Projekt](https://github.com/exceljs/exceljs), +[offizielle Paketdokumentation](https://www.npmjs.com/package/exceljs). +Keine Bibliothek wird in R0 installiert. R2 muss vor Integration Kommentar- +Roundtrip, Node-24-Verträglichkeit und Abhängigkeitsrisiken prüfen; bei einem +Blocker wird die Bibliotheksentscheidung neu vorgelegt. + +Vorgeschlagene Grenzen, zentral konfiguriert und synthetisch getestet: + +- ausschließlich `.xlsx`, passender OOXML-Inhalt und ZIP-Signatur; kein + `.xlsm`, verschlüsseltes Archiv, VBA, ActiveX oder externe Workbook-Links; +- maximal 10 MiB Upload, 100 MiB tatsächlich entpackte Daten, 2.000 ZIP- + Einträge, 50.000 Zeilen, 256 Spalten und 2 Millionen materialisierte Zellen; +- genau ein Arbeitsblatt; keine Formelausführung, kein Nachladen von URLs; +- ZIP-Vorprüfung vor ExcelJS: Verzeichniseinträge, doppelte Pfade, + Kompressionsverfahren und tatsächliche entpackte Bytezahl prüfen. + Zentralverzeichnisangaben allein schützen nicht vor manipulierten Archiven. + Begrenzte Dekompression mit Node-Bordmitteln, keine zweite direkte Library; +- Parser in begrenztem Worker mit Zeit-/Speichergrenze; Abbruch ohne DB- + Schreibvorgang. API-Body-Limit auf Uploadgrenze einschließlich Base64- + Overhead abstimmen, statt global beliebige Requestgrößen zuzulassen. + +Neutrale Matrix: Blattname, Kopfzeilen-/Startpositionen, Spaltenmetadaten +(Index, Name, Kommentartext, erkannter Parametertyp/Einheit), geordnete +Zeilen mit Originalzeilennummer und Klasse sowie Zellen als diskriminierte +Union `string | number | boolean | empty`. Formeltext und Cache-Status sind +optionale Transportmetadaten; fachlich zählt nur der gecachte skalare Wert. +Fehlender Cache oder Fehlerwert erzeugt einen Hinweis bzw. bei Pflichtfeldern +einen Blocker. Originalbytes bleiben unverändert gespeichert. + +Fehlende ID bei sonstigen Objektdaten blockiert; Leer-/eindeutige Summenzeilen +werden ignoriert. Nicht passende nichtleere IDs blockieren ebenfalls. +Worksharing-ID bleibt als Quelltext erhalten, Matching ist exakt; ungültige +Leerzeichen/uneindeutige Schreibweisen werden gemeldet statt still korrigiert. +IfcGUID wird optional gespeichert, doppelte Werte sind nur Hinweise. +Numerische Projektion rundet Leistung auf 0,01 W, nicht die gespeicherte +Quellzelle. Mengen aus `socketCount` müssen positive ganze Zahlen sein. + +Rückgabe lädt eine Kopie der Originalbytes, bewahrt den Bereich oberhalb der +ersten Datenzeile samt Kommentaren, Formatierung und Spaltenbreiten und +ersetzt den Datenbereich durch gewählte Objektzeilen in Originalreihenfolge. +Alle Spalten bleiben enthalten; Summenzeilen entfallen. Unveränderte Zellen +behalten ihre importierten Werte und Typen. Formeln in Objektzeilen werden +als gecachte Werte ausgegeben, damit Zeilenauswahl keine falschen relativen +Bezüge erzeugt; dies wird vor Export als Hinweis ausgewiesen. Keine Formeln +werden berechnet. Roundtrip bedeutet strukturelle/semantische Gleichheit, +nicht identische ZIP-Bytes. + +## 4. Konfigurationsschema + +Neuer neutraler Vertrag `ExternalModelConfiguration`, Schema-Version 1: + +```ts +{ + schemaVersion: 1, + layout: { + headerRow: 1, firstDataRow: 3, firstDataColumn: 1, + idColumn: "Worksharing-ID" + }, + circuitMode: "combined" | "separate", + columns: { + worksharingId: string, familyAndType: string, + ifcGuid: string | null, roomNumber: string | null, + roomName: string | null, selectionMarker: string | null, + socketCount: string | null, sourceRemark: string | null, + circuitCombined: string | null, boardName: string | null, + circuitIdentifier: string | null, designation: string | null, + electricalPowerW: string | null, voltageV: string | null, + currentA: string | null + }, + deviceTypes: [{ + id: UUID, name: string, + category: "lighting" | "single_phase" | "three_phase", + defaults: { + powerPerUnitW: number | null, simultaneityFactor: number | null, + cosPhi: number | null, costGroup: string | null + } + }], + familyTypeRules: [{ + id: UUID, exactFamilyAndType: string, deviceTypeId: UUID, + quantityRule: "one" | "socketCount" + }] +} +``` + +Zeilen/Spalten sind intern 1-basiert; UI stellt Spalten als A, B, … dar. +`layout.idColumn` und `columns.worksharingId` werden gemeinsam gesetzt und +müssen übereinstimmen. Jede gemappte Kopfzeile muss eindeutig auflösbar sein; +doppelte Ausgabebelegungen werden abgelehnt. Freie Eingabe bleibt zulässig, +aber ein nicht existierender Name erzeugt vor Import/Export einen konkreten +Fehler. Erkennung und Vorbelegung nutzen die exakten Standardnamen aus dem +Auftrag. Unbekannte Spalten bleiben ohne Mapping erhalten. + +Geräteart-IDs sind interne UUIDs, keine Namen als Relationsschlüssel. Regeln +sind exakt und je Familien-/Typtext eindeutig; jede importierte Familie muss +eine Geräteart haben. Gerätearten und Regeln werden als ein versioniertes +Konfigurationsaggregat gespeichert. Keine zusätzliche Gerätearttabelle ist +für diesen Umfang nötig; Validatoren erzwingen referenzielle Integrität. +Referenzierte Gerätearten können nicht kommentarlos entfernt werden. + +Startwerte: je Feld ProjectDevice vor Geräteart vor leer. Anzeigename: +Auswahlkenner, sonst Geräteartname, sonst Familie/Typ. Quellleistung bleibt +Quelle; eine Übernahme geschieht nur als sichtbare Planungsentscheidung. +Fehlender GZF bleibt vor Zuordnung leer; verlangt der Row-Vertrag einen Wert, +wird `1` sichtbar vorgeschlagen und ausdrücklich bestätigt. Geräteart- +Änderungen schreiben keine vorhandenen Objekt-Overrides oder Rows um. + +## 5. Konkretes Persistenzschema + +Bestehende UUID-/Projekt-FKs und Eigentumsprüfungen werden beibehalten. +`JSON` bezeichnet validierten JSON-Text in SQLite, nicht ExcelJS-Objekte. + +| Tabelle | Zielspalten / Constraints | +| --- | --- | +| `external_model_configurations` (ersetzt CSV-Tabelle) | `id TEXT PK`, `project_id TEXT NOT NULL UNIQUE FK projects`, `configuration_version INTEGER NOT NULL CHECK > 0`, `configuration JSON NOT NULL`. Enthält obiges vollständiges Aggregat. | +| `external_model_sources` | `id`, `project_id UNIQUE`, `name`, `source_type = revit_xlsx`. Genau eine implizite Quelle je Projekt. | +| `external_import_batches` | Bestehende IDs, Importart, Zeit, Dateiname, SHA-256, `applied_project_revision`, `configuration_version`, `configuration_snapshot JSON`, `original_bytes BLOB`, `document JSON`; Matrix jetzt typisiert und transportneutral. Unveränderlich innerhalb des aktiven Zustands; Undo/Restore darf ganze Batches entfernen/wiederherstellen. | +| `external_room_mappings` | Vorhandene Felder einschließlich normalisiertem Quellraumschlüssel, `room_id`, `default_distribution_board_id`; UNIQUE `(source_id, normalized_source_room_key)` bleibt. | +| `external_model_objects` | Bestehende interne IDs/Links; neu `worksharing_id TEXT NOT NULL`, UNIQUE `(source_id, worksharing_id)`; `ifc_guid TEXT NULL`, ohne Unique; `device_type_id TEXT NOT NULL` als validierter Verweis in das Konfigurationsaggregat. | +| Objektzustand | `accepted_source_values JSON` mit rohen typisierten Werten für Raum, Familie/Typ, Marker, Menge, Bemerkung und alle Rückgabefelder; `planning_values JSON` mit Anzeigename, Kategorie, Anschlussart, effektiver Menge, Einzelleistung W, nullable GZF/cosPhi/Kostengruppe/Bemerkung; `overridden_fields JSON`; bisherige Batch-/Raum-/Board-/ProjectDevice-/Row-Verweise und `presence_status` bleiben. | +| `external_export_batches` (neu) | `id TEXT PK`, `project_id FK`, `project_revision INTEGER`, `import_batch_id TEXT`, `exported_at_iso TEXT`, `file_name TEXT`, `sha256 TEXT`, `configuration_version INTEGER`, `scope TEXT`, `object_count INTEGER`. Append-only Audit-Store. | + +`import_batch_id` des Exportnachweises ist absichtlich eine historische UUID +ohne löschenden FK: Undo eines Imports oder Snapshot-Restore darf den Nachweis +nicht entfernen. Exportnachweise liegen außerhalb rücksetzbarer Fachsnapshots +und werden beim Projektduplikat nicht als dort erfolgte Exporte ausgegeben. +Sie bleiben beim Originalprojekt erhalten; diese Abgrenzung wird in R1 +dokumentiert. Importbatches selbst gehören vollständig zu Snapshot/Transfer. + +Indices: bisherige Projekt-/Presence-, Board- und Row-Indices behalten; +zusätzlich `(project_id, device_type_id)`, `external_room_mapping_id`, +`linked_project_device_id`, Export `(project_id, exported_at_iso)`. +Cross-Project-Links und Geräteart-Verweise prüft jeder Command in der +Transaktion; bloße Existenz eines UUID-FK genügt nicht. + +Effektiver Verteiler: ein gemeinsamer Resolver verwendet bei verknüpften +Objekten die Row/Circuit-Verteilung, sonst die Objektabweichung, sonst die +Raumvorgabe. `distributionBoardId = null` bedeutet Raumvorgabe verwenden; +bei fehlender Vorgabe ist das Objekt ohne Verteiler. Room-/Board- +Sammeländerungen prüfen alle betroffenen Row-Links und lehnen inkonsistente +Umbuchungen ab, bis diese ausdrücklich gelöst/verschoben sind. + +Planungswerte bleiben von Quellwerten getrennt. Nach Row-Zuordnung sind +Objekt-Planungswerte eingefrorene Startwerte; Anzeige, Abweichung und Export +verwenden die Row. Beim Lösen werden aktuelle Row-Werte einschließlich +Einheitenkonvertierung in die Objektplanung übernommen und als explizite +Overrides markiert. Effektive Objektmenge wird nicht durch die Row-Gesamtmenge +ersetzt. Die Row und ihr manueller Anteil bleiben erhalten. + +## 6. Commands und Transaktionsgrenze + +Alle öffentlichen Fach-Writes verlangen `expectedRevision`, Anmeldung und +`X-Leistungsbilanz-Request: 1`. `expectedRevision` gehört wie bisher zum +Ausführungsumschlag. Jede Transition enthält vollständige erwartete/Ziel- +Snapshots der betroffenen Datensätze, keine ungesicherten Teilpatches. + +| Command | Payload-Skizze und Gegenrichtung | +| --- | --- | +| `external-model-configuration.update` v1 | `{ expected, target }` einschließlich Layout, Mapping, Gerätearten und Regeln. Inverse tauscht Fachinhalt; Versionsgeneration siehe unten. | +| `external-import.apply-initial` v2 | `{ expectedConfiguration, targetConfiguration, expected, target }`. Quelle, Batch, Mapping, Objekte und neue Regeln zusammen; Inverse stellt leeren externen und vorherigen Konfigurationsinhalt wieder her. Keine Rows. | +| `external-import.apply-follow-up` v1 | `{ configuration: transition, batch: transition, roomMappings: transitions, objects: transitions, rows: quantityTransitions, decisions }`; Server erzeugt deterministischen Diff und alle UUIDs. Inverse tauscht alle betroffenen Zustände. | +| `external-model-object.update-planning` v1 | `{ objects: transitions }`; nur ungebundene Objekte; geänderte Felder in Overrides. Einzel-/Sammelbearbeitung identischer Regeln. | +| `external-model-object.update-distribution-board` v1 | `{ objects: transitions }`; explizite Objektabweichung, Projekt-/Row-Konsistenz. | +| `external-model-room-mapping.update` v1 | `{ mappings: transitions }`; interner Raum/Standardverteiler; betroffene Objekt-/Row-Konsistenz mitprüfen. | +| `external-model-object.update-project-device-link` v1 | `{ objects: transitions, confirmedPlanningFields }`; keine Vorlagenänderung; neue Startwerte nur für ausdrücklich bestätigte Felder. Gebundene Objekte werden über ihre Row bearbeitet. | +| `external-object.update-row-assignment` v2 | `{ rows: transitions, objects: transitions, confirmedConflictObjectIds }`; Linkwechsel, materialisierte Mengen, beim Lösen aktuelle Row-Planungswerte; vollständige Inverse. | +| `external-object.assign-to-new-row` v2 | Bestehender vollständiger Row-/Objekt-/Reserveplan mit neuen Objektverträgen; history-only Inverse entfernt nur exakt unveränderte Erzeugnisse. | +| `external-object.assign-to-new-circuit` v2 | Bestehender Circuit-/Protection-/Row-/Objektplan; BMK einmal planen und festschreiben, gleiche UUIDs bei Redo. | +| `external-model-object.delete-missing` v1 | `{ objects: transitionsToNull, rows: quantityTransitions }`; ausschließlich ausdrücklich bestätigte weiterhin fehlende Objekte, kein Row-/Circuit-Löschen; Inverse stellt IDs, Links und Mengen wieder her. | + +Bestehende `project-device.insert`, Room-/Floor-Commands bleiben eigenständige +Undo-Schritte. Familien-/Geräteartentscheidungen im Wizard sind lokale +Entwürfe und werden erst mit dem Import atomar persistiert. Nach separater +Kataloganlage aktualisiert der Wizard die Projektrevision und plant neu, +ohne lokale Entscheidungen zu verlieren. + +Konfigurationsgeneration: `configurationVersion` erhält bei jedem +Konfigurationswechsel einschließlich Undo/Redo/Restore die neu entstehende +Projektrevision. Sie ist damit strikt steigend, aber nicht lückenlos. +Historienbefehle vergleichen den erwarteten Fachinhalt und die aktuelle +Projektversion; die administrative Generation wird serverseitig neu erzeugt. +Die Inverse restauriert alle Fachwerte exakt, nicht einen alten Zählerstand. +Importbatches behalten ihre ursprüngliche Generation und ihren Snapshot. +Ein Restore/Projekttransfer erhält diese Herkunft; die aktive Konfiguration +wird an die Revision des Zielprojekts gebunden. Prüfen: Änderung → Undo → +anderer Zweig darf niemals denselben Vorschau-Token wieder gültig machen. + +XLSX wird vor der synchronen SQLite-Transaktion in einer serverinternen +Vorbereitung geparst. Apply überträgt Bytes erneut; Hash, Matrix und +Entscheidungen werden erneut validiert. Im Store werden Revision, +Konfigurationsinhalt/-generation, erwartete Objekte, Links und vollständiger +vorbereiteter Plan atomar geprüft. Vorbereitete Daten sind keine vom Client +behauptete Validierung: Generic-Command-Requests dürfen Importcommands nicht +an dieser serverseitigen Vorbereitung vorbeischleusen. Undo/Redo benutzen +denselben Prüfdienst für persistierte Bytes vor der Transaktion. Keine +asynchronen Callbacks in `better-sqlite3`-Transaktionen. + +Allgemeine Row-/Circuit-/Gruppen-/Board-/Raum-/ProjectDevice-Löschungen +erhalten Guards, solange externe Referenzen betroffen sind. Nutzer lösen die +Referenzen zuvor ausdrücklich über die passenden Commands. Guards gelten +auch in Unterbaum- und History-only-Pfaden. Dadurch werden vorhandene +Commands nicht unbemerkt zu unvollständigen externen Löschcommands. + +## 7. Migration, Snapshot 10, History und Transfer + +Neue additive Migration nach `0013_users` (voraussichtlich `0014`, Nummer +beim Beginn von R3 erneut prüfen). Bestehende SQL-/Metadateien unverändert. + +1. Vor R3-Anwendung API-Schreibzugriffe stoppen und `npm run db:backup` + ausführen; verifiziertes Backup samt Wiederherstellungsmöglichkeit sichern. + R0 verändert keine DB und benötigt daher kein Migrationsbackup. +2. Vorhandene externe CSV-Entwicklungsdaten dürfen gemäß Auftrag verworfen + werden. Zuerst je verknüpfter Row `quantity = manual_quantity` setzen; + sonst bliebe nach Entfernen der externen Objekte eine Phantomlast. + Rows, Circuits, manuelle Anteile, UUIDs und BMKs bleiben bestehen. +3. Externe Objekte → Mappings → Batches → Quellen → CSV-Konfiguration in + FK-sicherer Reihenfolge entfernen. Tabellen gezielt neu aufbauen, wo + SQLite NOT-NULL-/Unique-Änderungen dies erfordern. Neue Zieltabellen und + Indices erstellen. Keine leeren Worksharing-IDs als Backfill erfinden. +4. Generiertes SQL und Drizzle-Metadaten prüfen; saubere und bestehende + Test-DBs migrieren; FK-/Integritätsprüfung und Mengenabgleich ausführen. + Der Reset ist eine dokumentierte einmalige Entwicklungsmigration, + kein stiller Laufzeit-Import. +5. Alte Revisionsdaten nicht umschreiben, keine CSV-History heimlich + überspringen. An der Ausführungsgrenze alte CSV-Konfigurationscommands, + Initial-/Zuordnungscommands v1 einschließlich ihrer History-Inversen und + Restores mit CSV-Inhalt vor jedem Write ablehnen. + +Fehlermeldung für History: +„Dieser Verlaufsschritt verwendet die frühere CSV-/IfcGUID-Anbindung und +kann mit der Excel-Anbindung nicht ausgeführt werden.“ +Der History-Status sollte den blockierten obersten Schritt samt Grund zeigen; +keine automatisch geleerten Stacks und keine Änderung bei Ablehnung. Frühere +allgemeine Commands können nach dem Reset zusätzlich an ihren exakten +Vorbedingungen scheitern; das bleibt eine sichere, sichtbare Ablehnung. + +Snapshot v10 enthält aktive Excel-Konfiguration, Gerätearten/Regeln, Quelle, +alle Batches mit Originalbytes/Matrix, Raum-Mappings, Objekte und Links. +Upgrade v1–9: bestehende nicht-externe Upgrader erhalten. Nur wenn +`externalCsvConfiguration` fehlt/null ist **und** der externe Zustand leer +ist, verlustfrei auf v10 heben. Auch eine gespeicherte CSV-Konfiguration +allein zählt als nicht leerer CSV-Zustand und wird nicht still verworfen. +Snapshot-Checksumme am gespeicherten Original vor Upgrade prüfen. + +Fehlermeldung: +„Dieser Projektstand enthält Daten der früheren CSV-/IfcGUID-Anbindung. +Eine automatische Übernahme in die Excel-Anbindung ist nicht möglich. +Bitte verwenden Sie einen Projektstand ohne CSV-Daten.“ + +Der aktuelle portable Transfer ist bereits **Format 2** (Grobbilanzen), +nicht Format 1 wie im alten Audit. Envelope 2 und dessen Format-1-Lesepfad +bleiben; enthaltenes Projektzustandsschema wird 10. Duplizieren remappt +Projekt-, Konfigurations-, Geräteart-, Regel-, Quellen-, Batch-, Mapping-, +Objekt-UUIDs und alle internen Links, auch in Konfigurationssnapshots der +Batches. Worksharing-IDs, optionale IfcGUIDs und Originalbytes bleiben +unverändert. Grobbilanzen und globale Office-/Auth-Daten behalten ihre +bisherige Abgrenzung. Exportnachweise werden nicht als neue Projektexporte +dupliziert. + +## 8. Read-Modelle, Oberfläche und Rückgabe + +Vorgeschlagene API-Basis `/api/projects/:projectId/revit`: +`configuration` (GET/PUT), `profile/preview` (POST, zustandslos), +`initial-import/plan|apply`, `follow-up-import/plan|apply` (POST), +`status` und `objects` (GET), `return-workbook` (POST mit Export-Audit). +Der bestehende listenbezogene External-Objects-GET bleibt erhalten. +Objektmutationen verwenden den vorhandenen Project-Command-Endpunkt mit +neuen typisierten Verträgen. CSV-Endpunkte werden entfernt, keine Aliasse. + +Export verwendet `expectedRevision` für einen konsistent gelesenen Stand; +die Datei-Erzeugung erfolgt außerhalb des kurzen Read-Snapshots. Danach +separater append-only Audit-Write, ohne Projektrevision oder Undo-Schritt. +Bei Auditfehler kein Erfolg/download; ein Eintrag belegt Dateierzeugung, +nicht einen tatsächlich abgeschlossenen Browserdownload. + +Die Revit-Seite zeigt Status, Aktionen und eine paginierte Objekttabelle +(Vorschlag: 100 Zeilen pro Seite). Filter-/Sortierzustand, UUID-Auswahl und +Sammelaktionen sind unabhängig vom DOM. „Alle gefilterten“ verwendet eine +explizit aufgelöste Auswahl zur bekannten Revision; keine Mutation anhand +eines inzwischen veränderten Filters. Große Auswahlmengen werden nicht in +getrennte Undo-Schritte aufgeteilt. Kein vollständiges Batch-Blob im DTO. + +Einstellungen verwenden Musterdatei-Erkennung mit editierbaren Positionen, +Feld-Dropdowns/freier Eingabe und Kommentar-Typ-/Einheitenanzeige. Regeln +entstehen im Wizard und sind auf der Seite einklappbar nachbearbeitbar. +Fehlende Raumangaben bleiben zulässig, Board-Zuordnung ist auch ohne Raum +möglich. ProjectDevice-Zuordnung bleibt optional. + +Rechter Editor-Drawer: nur vorhandene, ungebundene konfliktfreie Objekte des +effektiven Verteilers. Gruppierung nach internem Raum, Auswahlkenner, +Geräteart-ID, Kategorie und identischen Planungswerten; unterschiedliche +ProjectDevice-Links bleiben ebenfalls getrennt, da eine Row nur einen Link +haben kann. Effektive Einzelmengen sind kein Gleichheitsfeld: Einfach- und +Mehrfachsteckdosen sollen gemeinsam gruppierbar sein. Anzeige enthält +Mengensumme und Objektanzahl. Fachlich heterogene Auswahl darf nicht +willkürlich auf eine einzelne Row zusammenfallen; Konfliktanzeige verlangt +eine kompatible Auswahl. Row-Indikator öffnet Objekte und explizites Lösen. + +Eine Funktion `getRevitBoardName(board)` liefert ausschließlich `board.name`. +Gemeinsam: Name + unverändertes `equipmentIdentifier`, ohne Separator. +Getrennt: Name und BMK in eigene Parameter. Ohne Circuit bleiben beide +Stromkreiswerte leer, auch wenn ein Planungsverteiler bekannt ist. + +Exportprojektion und Abweichungsanzeige teilen dieselbe reine Funktion: +Row-Anzeigename/-Leistung bei Link, sonst Objektplanung; Objektleistung in W += effektive Menge × Einzelleistung (Row-kW vorher × 1000), ohne GZF. +Spannung vom Circuit, sonst Projektstandard passend zur Kategorie. +Strom aus Objektleistung, U, cosPhi und Phasenart; Drehstrom mit Faktor √3, +auf 0,01 A. Fehlende/ungültige Werte ergeben leer plus Hinweis. Nullleistung +bleibt bei gültigem Nenner numerisch 0. Kein still angenommenes cosPhi. +Abweichungsvergleich normalisiert Zahlpräzision, Leere und skalare Typen +gegen den **zuletzt importierten**, nicht den zuletzt exportierten Stand. + +Umfang: alle im letzten Batch vorhandenen Objekte / abweichende / explizite +Auswahl. Fehlende Objekte haben keine aktuelle Originalzeile und werden +nicht künstlich wieder eingefügt. Alle Spalten bleiben, nicht ausgegebene +Parameter behalten importierte Werte. Fehlende benötigte Zielspalten bzw. +Linear-Bindungen blockieren die betreffende Exportkonfiguration mit Hinweis +auf einen neuen vollständigen Linear-Export. Dateiname gemäß Auftrag mit +bereinigtem Projektnamen und Tagesdatum. + +Folgeimport matcht ausschließlich Worksharing-ID. Ein neuer Batch speichert +immer den vollständigen letzten Quellstand. Unveränderte Objekte werden +nicht allein zum Fortschreiben von `lastSeenImportBatchId` neu geschrieben; +aktuelle Sichtbarkeit/Quellwerte kommen aus Batch/Matrix. Akzeptierte +Planungsgrundlage und letzte importierte Beobachtung sind getrennt. +Raum-/Familien-/Marker-/Mengenänderungen brauchen Entscheidungen. Bei +unvereinbarer Row-Zuordnung blockiert die Übernahme bis zum ausdrücklichen +Lösen/Umordnen, statt Rows automatisch zu erzeugen. Übernommene Mengen +ändern alle betroffenen Row-Summen im selben Importcommand. Fehlend setzt +nur den Status, erhält vorerst Link und Menge. Rückgabefeld-Abweichungen +sind Hinweise, keine Übernahmeentscheidungen. Ein völlig unveränderter +Folgeimport erzeugt einen nachvollziehbaren Batch, aber keine unnötigen +Objekt-/Row-Updates. + +## 9. Pakete, Verifikation und Risiken + +R0 liefert nur diesen Bericht und einen eigenen Commit. R1–R10 starten erst +nach Freigabe; keine Fortschreibung der alten 14.0–14.6-Liste. + +| Paket | Arbeit und entscheidende Abnahme | +| --- | --- | +| R1 | Neue Excel-Anforderungen und R0–R10-Plan; alte Dokumente archivieren/auf Verweise kürzen; AGENTS, Architektur, History-Doku, Phase-14-Abschnitt, LLM-Kontext und alle Links nachziehen. Referenzdatei gezielt ignorieren. | +| R2 | XLSX-Adapter und lokaler Referenzprüfer mit Pfadargument. Synthetischer Roundtrip für Kommentare, Blattname, Spaltenbreiten, Typen, Header-/Startpositionen, unbekannte Spalten, Auswahl und Summenausschluss; ZIP-/Formelgrenzen. Keine DB-Änderung. | +| R3 | Neue Konfiguration/Identität, additive Migration, Snapshot 10, Transfer und History-Ablehnungen; Backend-CSV entfernen. Parser-Vorbereitung von synchroner Transaktion trennen. | +| R4 | Revit-Seite, Navigation, Einstellungen; Projektseitenmodal und dessen CSV-Frontend-Verträge entfernen. | +| R5 | Erstimport mit atomaren Regeln, Geräten, Raum-/Board- und Objektentscheidungen; ProjectDevice-Vorbelegung nach Priorität; ein Import-Undo-Schritt. | +| R6 | Objekt-Reads, Paging/Filter, Sammel-Commands und Overrides; verwandte Lösch-/Referenzguards vollständig absichern. | +| R7 | Gruppierung, Drag-and-drop, Row-Indikatoren und Lösen inklusive aktueller Row-Werte; drei Drop-Ziele und Reload/Undo/Redo. | +| R8 | Drei Exportumfänge, gemeinsame Abweichungsprojektion, typisierte Rückgabe, append-only Nachweis und Linear-Strukturabnahme. | +| R9 | Kontrollierter Folgeimport, fehlende Objekte, explizites Löschen, mengenwirksame Entscheidungen und atomare Inverse. | +| R10 | Vollständige synthetische Ende-zu-Ende-Kette einschließlich Neustart, Snapshot/Restore und Duplikat; mehrere Tausend Objekte; Doku auf ausgelieferten Stand. | + +Die temporäre Grenze zwischen R3 und R4 wird ausdrücklich behandelt: +Buildfähige, nicht aktive Kompatibilitätstypen für das alte Modal dürfen bis +R4 bestehen, aber kein aktiver CSV-Backendpfad. Veraltete Projektaktion bis +zur neuen Seite deaktivieren/entfernen, keinen funktionslosen Upload anbieten. +Die Dokumentation darf Zwischenstände nicht als fertigen Workflow ausweisen. + +Pflicht je R1–R10: fokussierte Tests, `npm test`, `npm run build:api`, +`npm run build:web`, `npm run typecheck:scripts`, +`npx tsc --noEmit -p tsconfig.next.json`, je ein knapper imperativer Commit. +R0: 31 vorhandene Tests aus sechs Dateien erfolgreich ausgeführt: +`external-model-foundation`, `external-initial-import-plan`, +`external-initial-import-project-command.repository`, +`external-object-row-assignment-project-command.repository`, +`external-circuit-list-objects`, +`external-csv-configuration-project-command.repository` (je `.test.ts`). +Diese decken vorhandene Commit-/Rollback-, Undo-/Redo-, Snapshot-/Transfer- +Grundlagen ab, nicht die neue Excel-Funktion. Der erste Start scheiterte an +Sandbox-`spawn EPERM`; derselbe Lauf außerhalb der Sandbox bestand. +Vollständige Builds/Tests sind für diesen reinen R0-Bericht nicht ausgeführt. + +Zusätzlich erforderliche neue Prüfungen: + +- Migration mit externen Row-Anteilen; keine Phantomlast und unveränderte BMKs. +- v1–9 ohne CSV verlustfrei, mit CSV klare Ablehnung; alte Command-Inversen + und alte Restore-Payloads ebenfalls ohne Teilwrites ablehnen. +- Config-Änderung/Undo/Neuzweig invalidiert alte Pläne; Apply mit anderem + Dateiinhalt, fehlenden Entscheidungen oder fremden UUIDs rollt vollständig + zurück. Später Historyfehler rollt auch Regeln, Links und Mengen zurück. +- Gebundene Row ändern → Export → Lösen → Undo/Redo behält fachliche Werte. +- Mehrfachsteckdosen: unterschiedliche effektive Mengen, identische + Einzelwerte, richtige Gruppierung und objektweise Rückgabeleistung. +- Löschen über alle Unterbaum-/History-Pfade kann externe FKs nicht still + nullen. Fremde Verteilungen/Räume/Kategorien und Gerätearten blockieren. +- Export-Audit scheitert: keine fachliche Revision; SHA-256 entspricht exakt + den gelieferten Bytes. Snapshot/Restore entfernt keine Exportnachweise. +- 5.000/10.000 synthetische Objekte: Parserzeit, Peak-Speicher, Payloadgröße, + Command-/Snapshot-Dauer und UI-Reaktionszeit messen; höchstens eine + paginierte Seite im DOM, stabile Auswahl über Seitenwechsel. + +Wesentliche Restrisiken: Linear-Kommentarbindung ist nur anhand des +Exports beobachtet; ExcelJS-Releasepflege und Roundtrip sind in R2 zu +belegen. Große Originaldateien vervielfachen sich in History/Snapshots; +Limits und Messungen müssen reale Speichergrößen berücksichtigen. Die +CSV-Entwicklungsbereinigung ist absichtlich verlustbehaftet, aber auf den +externen Zustand begrenzt und nur nach verifiziertem Backup zulässig. +Fehlende Zielparameter im Muster verlangen einen neuen vollständigen +Linear-Export, keine geratenen GUID-Kommentare. + +**Freigabepunkt:** Der Nutzerauftrag verlangt wörtlich +„R0. Audit (danach stoppen und Freigabe abwarten)“. +Mit Freigabe dieses Vorschlags beginnt R1; R0 selbst autorisiert keine +Installation, Migration oder Implementierung von R1–R10.