# Revit-Integration über Linear-Excel – verbindliche Anforderungen Stand: 29.09.2026. Dieses Dokument ist die fachliche und technische Arbeitsgrundlage für die neue Phase 14. Es ersetzt die frühere CSV-/IfcGUID- Spezifikation. Der Umsetzungsstand steht im [R0–R10-Plan](revit-excel-phase-14-plan.md); der geprüfte Ausgangszustand im [R0-Audit](revit-excel-r0-audit.md). ## 1. Ziel und Grenzen Der Austausch mit Revit erfolgt ausschließlich über den XLSX-Export/-Import des Linear-Plugins. Eine vollständige Revit-Bauteilliste wird importiert, in Leistungsbilanz geplant und mit gleichem Tabellenaufbau an Linear zurückgegeben. Linear ordnet enthaltene Zeilen anhand der Worksharing-ID zu. Nicht Teil dieser Phase sind Dynamo, Revit-API, JSON-Transport, automatische Stromkreisbildung, Übernahme vorhandener Revit-Stromkreiswerte, Teilimporte, mehrere Modellquellen, PostgreSQL und neue Rollen/Rechte. Die Integration bleibt optional. Manuelle Projekte, Räume, ProjectDevices, Stromkreise und Gerätezeilen funktionieren ohne externes Modell. Der Circuit-First-Aufbau, stabile interne UUIDs und BMKs, ausdrückliche Neunummerierung sowie die gemeinsame Project-Command-Transaktionsgrenze bleiben unverändert. ## 2. Verbindliche fachliche Regeln - Import erzeugt keine `CircuitDeviceRow`, keinen `Circuit` und kein BMK. - Quellwerte, lokale Planungswerte und Overrides bleiben getrennt. - Ein Folgeimport überschreibt keine lokale Planung still. - Fehlende Objekte werden markiert und erst nach ausdrücklicher Bestätigung entfernt. - Mehrfachsteckdosen sind unteilbare externe Objekte. Ihre wirksame Menge kann größer als eins sein. - Die Row-Menge ist `manualQuantity + Summe(effectiveQuantity der verknüpften Objekte)`. - Nach einer Row-Zuordnung ist die `CircuitDeviceRow` die Planungsebene für alle verknüpften Objekte. - Bestehende ProjectDevices bleiben kanonische Vorlagen und werden durch Importe nie verändert. - Sortieren, Filtern, Importieren, Zuordnen und Verschieben nummeriert keine Stromkreise automatisch neu. ## 3. Linear-XLSX-Profil Die lokale Referenzdatei `docs/spec/kbp-Stromkreis.xlsx` enthält vertrauliche Projektdaten. Sie wird nicht committed und nicht in Tests eingebettet. Tests erzeugen synthetische Workbooks. Der lokale Referenzprüfer erhält den Pfad als Argument. Beobachtetes Profil: - genau ein Arbeitsblatt; Blattname nach Excel-Regel maximal 31 Zeichen; - Kopfzeile in Zeile 1, Zeile 2 leer, Daten ab Zeile 3, erste Datenspalte A; - Spalte A `Worksharing-ID` im Format `-<8 Hex>`; - Parameterbindungen stehen als klassische Excel-Kommentare in den übrigen Kopfzellen; - Zahlen sind typisierte Excel-Zahlen; importierte Werte bleiben unverändert, fachlich verwendete Leistung wird auf 0,01 W projiziert; - Objektzeilen dürfen keinen MEP-Raum besitzen; - eine Schluss-/Summenzeile ohne ID kann vorkommen und wird weder importiert noch zurückgegeben. Die Referenzdatei enthält 487 Objektzeilen, eindeutige Worksharing-IDs und keine doppelten nichtleeren IfcGUIDs bei exaktem, case-sensitivem Vergleich. Ihre Summenzeile steht in Spalte B (`CAx_Auswahlkenner`), während `Familie und Typ` leer ist. ### Zeilen ohne Worksharing-ID Nur eine Zeile ohne Worksharing-ID, aber mit befülltem Feld `Familie und Typ`, gilt als verdächtige Objektzeile. Sie erzeugt einen Hinweis und wird übersprungen; sie blockiert den Import nicht. Andere Zeilen ohne ID, einschließlich einer nur in Spalte B befüllten Summenzeile, werden ohne Hinweis übersprungen. Eine Objektzeile ist für den Import eine Zeile mit nichtleerer Worksharing-ID. Worksharing-ID und IfcGUID werden stets exakt, ordinal und mit Groß-/Kleinschreibung verglichen. Es findet kein Trimmen, Normalisieren oder Case-Folding für die Identität statt. Eine nichtleere Worksharing-ID mit unerlaubten äußeren Leerzeichen oder falschem Format blockiert. Doppelte Worksharing-IDs blockieren. IfcGUID ist optional, kein Schlüssel und darf doppelt vorkommen; Duplikate erzeugen lediglich einen Hinweis. ## 4. Upload und Transportadapter Der Server akzeptiert ausschließlich Dateien mit Endung `.xlsx` bis 10 MiB. `.xls` und `.xlsm` werden abgelehnt. Parsing findet serverseitig statt. Nach dem Einlesen gelten feste Zeilen-, Spalten- und Zellgrenzen, die in R2 anhand synthetischer Grenztests festgelegt und zentral definiert werden. Es gibt in dieser Phase keinen eigenen ZIP-Prüfer und keinen Parser-Worker. Der Adapter unter `src/external-model/xlsx` liest eine transportneutrale Zellmatrix. Eine Zelle ist `string`, `number`, `boolean` oder `empty`. Zusätzlich werden Blattname, Originalzeilennummer, Kopfzeilenkommentare, Spaltenbreiten und die für Hinweise nötigen Formelmetadaten erfasst. Domain-Services kennen weder ExcelJS noch XLSX, React oder SQLite. Formeln werden niemals ausgeführt. Ist ein gecachter skalarer Wert vorhanden, wird er verwendet und als Hinweis ausgewiesen. Fehlt er oder ist er ein Fehlerwert, bleibt das Feld leer; bei der Worksharing-ID oder einem anderen Pflichtfeld blockiert dies die betreffende Objektzeile beziehungsweise den Import. Genau eine gepflegte Bibliothek wird ergänzt. Vorgesehen ist `exceljs`, sofern R2 die Node-24-Verträglichkeit und den Roundtrip von Kopfkommentaren, Blattname, Spaltenbreiten, Formaten und Zelltypen bestätigt. ## 5. Projektkonfiguration Die Konfiguration ist projektweit, revisionssicher und besitzt keine eigene Versionszählung. Jeder Plan und jedes Apply tragen `expectedRevision`. Da die Projektrevision auch bei Undo/Redo strikt steigt, wird ein alter Plan automatisch ungültig. Jeder Importbatch speichert eine vollständige Kopie der beim Import verwendeten Konfiguration. Das Einstellungsmodal auf der Revit-Seite erkennt aus einer zustandslos hochgeladenen Musterdatei Kopfzeile, erste Datenzeile und erste Datenspalte. Diese Werte bleiben editierbar; Standard ist 1 / 3 / A. Es gibt nur ein Feld für die Identitätsspalte: `columns.worksharingId`. Ein separates `layout.idColumn` existiert nicht. Zielvertrag: ```ts interface ExternalModelConfiguration { schemaVersion: 1; layout: { headerRow: number; firstDataRow: number; firstDataColumn: number; }; circuitMode: "combined" | "separate"; columns: { worksharingId: string; ifcGuid: string | null; roomNumber: string | null; roomName: string | null; familyAndType: string; 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: DeviceTypeConfiguration[]; familyTypeRules: FamilyTypeRule[]; } ``` Standardzuordnung: | Schlüssel | Bedeutung | Standardspalte | | --- | --- | --- | | `worksharingId` | externe Identität, Pflicht | `Worksharing-ID` | | `ifcGuid` | optionale Redundanz | `IfcGUID` | | `roomNumber` | Raumnummer | `MEP-Raum: Nummer` | | `roomName` | Raumname | `MEP-Raum: Name` | | `familyAndType` | Familie und Typ, Pflicht | `Familie und Typ` | | `selectionMarker` | CAx-Auswahlkenner | `CAx_Auswahlkenner` | | `socketCount` | Anzahl Einzelsteckdosen | `CAX_AnzahlObjekte` | | `sourceRemark` | Anmerkung | `CAx_Anmerkung` | | `circuitCombined` | Stromkreis gesamt | `kbp_Stromkreisnummer` | | `boardName` | Verteiler | `kbp-E-Verteiler` | | `circuitIdentifier` | Stromkreis-BMK | `kbp-E-Stromkreis` | | `designation` | Bezeichnung | `kbp-E-Bezeichnung` | | `electricalPowerW` | elektrische Leistung [W] | `kbp-E-Elektrische Leistung` | | `voltageV` | Spannung [V] | `kbp-E-Spannung` | | `currentA` | Stromstärke [A] | `kbp-E-Stromstärke` | `worksharingId` und `familyAndType` sind Pflichtmappings. Im Modus `combined` ist `circuitCombined` für den Rückexport Pflicht; im Modus `separate` sind `boardName` und `circuitIdentifier` Pflicht. Fehlende optionale Rückgabespalten `designation`, `electricalPowerW`, `voltageV` und `currentA` werden mit Hinweis übersprungen und blockieren den Export nicht. Jedes interne Feld besitzt im Modal ein Dropdown aus den Kopfzellen und eine freie Eingabe. Exakte kbp-Standardnamen werden vorbelegt. Typ und Einheit aus dem Linear-Kommentar werden angezeigt. Nicht gemappte Spalten bleiben beim Rückexport unverändert. ## 6. Gerätearten und Familie-und-Typ-Regeln Eine Geräteart besitzt stabile interne UUID, Name, Kategorie `lighting | single_phase | three_phase` sowie optionale Standardwerte für Leistung je Einheit in W, GZF, cosPhi und Kostengruppe. Jede exakte `Familie und Typ`-Regel verweist auf genau eine Geräteart und verwendet die Mengenregel `1` oder `socketCount`. Mehrere Revit-Familien dürfen auf dieselbe Geräteart zeigen. Jedes Revit- Objekt bleibt trotzdem einzeln über seine Worksharing-ID nachverfolgbar. Unbekannte Familien müssen im Importwizard zugeordnet oder über eine neue Geräteart klassifiziert werden; andernfalls blockiert Apply. Planungsstartwerte folgen dieser Reihenfolge: 1. ausdrücklich verknüpftes ProjectDevice; 2. Geräteart-Standard; 3. leer. Der Anzeigename wird aus CAx-Auswahlkenner, dann Geräteartname, dann `Familie und Typ` vorgeschlagen. Ein fehlender GZF wird mit `1` vorbelegt, ohne zusätzliche Bestätigung. Standardwerte sind Startwerte, keine dauerhafte Synchronisierung. ## 7. Persistenter externer Zustand Pro Projekt existiert genau eine implizite Revit-XLSX-Quelle. Ein externes Objekt besitzt eine interne UUID und ist je `(sourceId, worksharingId)` eindeutig. `worksharingId` ist NOT NULL; `ifcGuid` ist nullable und nicht eindeutig. Interne Relationen verwenden ausschließlich interne UUIDs. Gespeichert werden: - akzeptierte Quellwerte einschließlich aller Rückgabefelder; - lokale Planungswerte und Override-Felder; - Geräteart; - optionales Raum-Mapping, abweichender Verteiler, ProjectDevice und `CircuitDeviceRow`; - Anwesenheitsstatus und Verweis auf den zuletzt relevanten Import; - effektive Menge als fachlicher Wert des unteilbaren Objekts. Der effektive Verteiler eines verknüpften Objekts ist immer der Verteiler seiner Row. Für ein unzugeordnetes Objekt gilt zuerst die explizite Objektabweichung, danach der Standardverteiler seines Raum-Mappings. Ändert sich der Standardverteiler eines Raums, betrifft das nur unzugeordnete Objekte; verknüpfte Objekte bleiben über ihre Row unverändert und die Änderung wird deshalb nicht gesperrt. ## 8. Importbatch-Aufbewahrung, Snapshot und Transfer Der aktive Projektzustand enthält den letzten Importbatch vollständig: Originalbytes, klassifizierte Matrix, Dateiname, SHA-256, Zeitpunkt, Objektzahl, Importart, Projektrevision und Konfigurationskopie. Ältere Batches werden zu Metadaten reduziert: Datum, Dateiname, SHA-256 und Objektzahl. Sie enthalten keine Originalbytes, Matrix oder Konfigurationskopie mehr. Snapshot-Schema 10 und portabler Projekttransfer übernehmen genau diese Aufteilung. Beim Duplizieren werden alle internen UUIDs und Links remapped; Worksharing-IDs, optionale IfcGUIDs, Quellwerte und das letzte Workbook bleiben unverändert. Versionen 1–9 ohne externe CSV-Daten werden verlustfrei auf Version 10 hochgestuft. Enthält ein alter Stand eine CSV-Konfiguration oder einen nicht leeren CSV-Zustand, wird er mit klarer deutscher Fehlermeldung abgelehnt. Alte CSV-History-Commands werden ebenfalls vor jedem Write abgelehnt und nie stillschweigend übersprungen. ## 9. Erstimport Der Ablauf ist Upload → zustandslose Vorschau/Planung → erneute Dateiübertragung bei Apply. Apply prüft SHA-256, `expectedRevision`, Konfigurationskopie, Matrix und sämtliche Entscheidungen erneut. Nur Apply liest die Excel-Datei erneut ein. Undo und Redo lesen keine Excel-Datei und parsen keine gespeicherten Bytes. Sie arbeiten ausschließlich auf den im Command gespeicherten vollständigen Vorher-/Nachher-Zuständen. Dadurch bleiben sie nach Neustart deterministisch und unabhängig vom Parser. Der Wizard umfasst: 1. Vorschau mit Objektzahl, übersprungenen und verdächtigen Zeilen, doppelten Worksharing-IDs, doppelten IfcGUID-Hinweisen, unbekannten Familien, nicht lesbaren Zahlen und Formeln; 2. Räume anlegen oder vorhandenen Räumen/Ebenen zuordnen; 3. Standardverteiler je Raum, Sammelzuordnung, Objektabweichungen und Objekte ohne Raum; 4. Familien exakt Gerätearten und einer Mengenregel zuordnen; 5. optional vorhandene oder neu angelegte ProjectDevices verknüpfen; 6. Zusammenfassung und ausdrückliche Bestätigung. Die Übernahme ist ein atomarer Command und ein Undo-Schritt. Im Import entstehen keine Rows. Neu angelegte Räume und ProjectDevices verwenden ihre vorhandenen Commands und bleiben eigene Undo-Schritte. ## 10. Objektplanung und Revit-Seite Route: `/projects/:projectId/revit`, erreichbar aus der Projektnavigation. Der Revit-Button und das CSV-Modal auf der Projektübersicht entfallen. Die Seite besitzt: - Statuskarte mit letztem Import, Objektzahl, ohne Verteiler, ohne Stromkreis, fehlend und Abweichung zu Revit; - Aktionen `Import`, `Rückgabe-Excel`, `Einstellungen`; - einklappbare Geräteart-/Familienregel-Übersicht; - kompakte Objekttabelle mit Suche, Spaltenfiltern, Sortierung, Mehrfachauswahl und Sammelzuordnung für Verteiler, ProjectDevice und Planungswerte. Die kompakte Liste wird einmal geladen, vollständig im Client gefiltert und sortiert und virtualisiert dargestellt. Es gibt keine Server-Pagination. Originalbytes, Matrix und Command-Payloads gehören nicht in dieses DTO. Vor Row-Zuordnung lassen sich Leistung je Einheit, GZF, cosPhi, Kostengruppe, Anzeigename und Bemerkung einzeln oder gesammelt ändern. Geänderte Felder werden Overrides. Nach Row-Zuordnung werden diese Werte im Grid an der Row bearbeitet. Beim ausdrücklichen Lösen erhält das Objekt die aktuellen Row-Werte als Planungswerte und entsprechende Overrides. ## 11. Editor-Zuordnung und Löschen Der rechte, überlagernde Editorbereich zeigt unzugeordnete konfliktfreie Objekte des aktuellen Verteilers. Gruppierungsschlüssel sind interner Raum, Auswahlkenner, Geräteart, Kategorie, identische Planungswerte und kompatibler ProjectDevice-Link. Die Anzeige nennt Summe der wirksamen Mengen und Zahl der externen Objekte. Gruppen sind aufklappbar; Einzel- und Mehrfachauswahl sowie Filter bleiben möglich. Drag-and-drop: - auf vorhandene Row: genau diese Row; - auf Circuit: neue Row mit `manualQuantity = 0`; - auf freien Bereich: neuer Circuit, Schutz und Row in einem Command. Neue IDs und BMK werden einmal geplant; Redo rechnet sie nicht neu. Neue Rows übernehmen den bestätigten Gruppenzustand und die Summe der effektiven Mengen. An Rows zeigt ein Hinweis die Zahl verknüpfter Revit-Objekte; einzelne Objekte können ausdrücklich gelöst werden. Wird eine Row, ein Circuit oder eine Gruppe mit verknüpften Revit-Objekten gelöscht, zeigt die UI vor Bestätigung: `X Revit-Objekte werden wieder unzugeordnet`. Derselbe Löschcommand löst die Objekte, entfernt die Struktur und speichert alle Links in seiner Inversen. Undo stellt Struktur und Links exakt wieder her. Diese Löschungen werden nicht gesperrt. Das Löschen eines Verteilers oder Raums mit referenzierten externen Objekten bleibt gesperrt. Der Nutzer muss die Verknüpfungen beziehungsweise Zuordnungen zuvor ausdrücklich ändern. Die Regel gilt auch für Unterbaum- und History- Pfade. ## 12. Rückgabe-Excel Der Export ist eine Leseoperation ohne Projektrevision. Ein append-only Nachweis speichert Projektrevision, zugrunde liegenden letzten Import, Zeitpunkt, Dateiname, SHA-256, Konfigurationskopie beziehungsweise ihren Hash und Objektzahl. Umfang: - alle im letzten Import vorhandenen Objekte (Standard); - nur Objekte mit Abweichung zum zuletzt importierten Revit-Wert; - Auswahl aus der Objekttabelle. Die Rückgabe basiert auf einer Kopie des zuletzt importierten Workbooks. Zeilen oberhalb der ersten Datenzeile, Kopfkommentare, Formate, Spaltenbreiten, Blattname und Spaltenreihenfolge bleiben erhalten. Danach folgen nur die gewählten Objektzeilen in ihrer Originalreihenfolge. Alle Spalten bleiben enthalten; Summenzeilen entfallen. Stromkreiswerte: - `combined`: `DistributionBoard.name + Circuit.equipmentIdentifier`, ohne Trennzeichen; - `separate`: Verteilername und unverändertes BMK in getrennten Spalten; - ohne Circuit: Stromkreisfeld beziehungsweise beide Felder leer. Die Wahl des Verteilerwerts liegt in genau einer Funktion und verwendet zunächst `DistributionBoard.name`. Weitere Rückgabewerte: - Bezeichnung = Row-`displayName`, ohne Row Planungs-Anzeigename; - Leistung [W] = effektive Menge × Einzelleistung, ohne GZF; - Spannung = Circuit-Spannung, sonst Projektstandard der Kategorie; - Strom = `P / (U × cosPhi)` beziehungsweise `P / (sqrt(3) × U × cosPhi)`, auf 0,01 A; - nicht berechenbar = leere Zelle plus Hinweis. Zahlen werden als Excel-Zahlen geschrieben. Fehlende optionale Zielspalten werden mit Hinweis ausgelassen. Nur fehlende Stromkreisspalten des gewählten Modus blockieren. Dateiname: `_Revit-Rueckgabe_.xlsx`. ## 13. Verbindliche R2-Abnahme mit Linear R2 endet mit einer Rückgabedatei, die aus der lokalen realen Musterdatei erzeugt wurde und einige klar identifizierbare Rückgabewerte verändert. Diese Datei wird nicht committed. Der Nutzer testet sie in Linear. R3 darf erst nach erfolgreichem Linear-Rückimport beginnen. Scheitert der Linear-Import, wird vor R3 eine Alternative zur Freigabe vorgelegt: Die Original-XLSX wird direkt auf OOXML-Ebene kopiert und nur die erforderlichen Zellwerte/-typen sowie Zeilenbereiche angepasst. Die Alternative bleibt hinter demselben transportneutralen Adaptervertrag. ## 14. Folgeimport Jeder Import ist eine vollständige Projektbauteilliste. Matching erfolgt ausschließlich über exakt und case-sensitiv verglichene Worksharing-ID. - neu: Wizard-Schritte nur für neue Objekte und unbekannte Familien; - geänderte Quellwerte: Einzel- oder Sammelentscheidung; - akzeptierte Mengenänderung: verknüpfte Row-Menge im selben Command; - fehlend: markieren, Link und Menge zunächst beibehalten; - Löschen: erst nach gesonderter Bestätigung; - abweichende Rückgabefelder: Hinweis `in Revit noch nicht aktualisiert`, keine Übernahmeentscheidung. `external-import.apply-follow-up` ist atomar und ein Undo-Schritt. Unveränderte Objekte erzeugen keine unnötigen Objekt- oder Row-Änderungen. Der neue vollständige Batch wird letzter Batch; der zuvor letzte Batch wird auf Metadaten reduziert. ## 15. Commands, Persistenz und Sicherheit Projektbezogene Writes verwenden typisierte Commands mit vollständiger Gegenrichtung, `expectedRevision`, Anmeldung, CSRF-Header und `project-command-transaction.persistence.ts`. Dazu gehören mindestens: - `external-model-configuration.update`; - `external-import.apply-initial`; - `external-import.apply-follow-up`; - `external-model-object.update-planning`; - `external-model-object.update-distribution-board`; - `external-model-room-mapping.update`; - `external-model-object.update-project-device-link`; - `external-object.update-row-assignment`; - `external-object.assign-to-new-row`; - `external-object.assign-to-new-circuit`; - `external-model-object.delete-missing`; - die erweiterten Row-/Circuit-/Gruppen-Löschcommands mit Objekt-Link- Transitionen. Die Konfiguration selbst ist Teil der erwarteten/gewünschten Snapshots, aber hat keinen eigenen monotonen Zähler. Importbatches halten eine unveränderliche Konfigurationskopie. Preview/Plan sind zustandslos. Apply überträgt und parst die Datei erneut; Undo/Redo führt nur gespeicherte Zustandsübergänge aus. Neue Migrationen sind additiv. Vor Anwendung wird die Datenbank gesichert und das generierte SQL geprüft. Da nur Entwicklungsdaten existieren, darf die Migration alte externe CSV-Daten verwerfen. Vor dem Löschen externer Objekte werden betroffene Row-Mengen auf `manualQuantity` zurückgesetzt. Der Verlust des alten externen Entwicklungszustands wird dokumentiert. ## 16. Abnahme und Leistung Die Oberfläche muss mehrere Tausend Objekte flüssig bedienen. R10 misst Import, Command-/Snapshot-Größe, Speicherbedarf und Client-Interaktion mit synthetischen großen Workbooks. Die virtualisierte Tabelle rendert nur den sichtbaren Ausschnitt, während Filterung, Sortierung und Auswahl auf der einmal geladenen kompakten Liste arbeiten. Jedes Paket R1–R10 führt fokussierte Tests sowie aus: ```text npm test npm run build:api npm run build:web npm run typecheck:scripts npx tsc --noEmit -p tsconfig.next.json ``` Jedes Paket erhält einen eigenen knappen imperativen Commit. Die konkrete Paketfolge und ihre Freigabepunkte stehen im [R0–R10-Plan](revit-excel-phase-14-plan.md).