feat db-clean-ui and unified content udm

This commit is contained in:
ValueOn AG 2026-04-16 23:12:56 +02:00
parent 0e4a0e0e62
commit c9454a618f
12 changed files with 1908 additions and 46 deletions

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-12 -->
<!-- lastReviewed: 2026-04-16 -->
# Themen-Index für AI-Kontext
@ -33,7 +33,7 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
|-------|-------|------------|
| Neutralisierung | b-reference/platform/neutralization.md | Datenschutz, AI-Call-Pipeline, Failsafe |
| RBAC | b-reference/platform/rbac.md | 4-Stufen-Modell, Template-Rollen, Resolution, Datenmodell |
| Datenbank-Architektur | b-reference/platform/database-architecture.md | Interface-Pattern, Connector, Auto-Init, DB-Liste |
| Datenbank-Architektur | b-reference/platform/database-architecture.md | Interface-Pattern, Connector, Auto-Init, DB-Liste, Database Health, Orphan-Scanner |
| Navigation | b-reference/platform/navigation.md | Menü-Struktur, Admin-Seiten, API |
| Compliance & AI-Audit | b-reference/platform/audit.md | AI-Datenfluss-Log, Security-Audit, Statistiken, RBAC |
| i18n / Mehrsprachigkeit | b-reference/gateway/architecture.md (Abschnitt i18n), d-guides/coding-conventions.md (Backend i18n), b-reference/frontend-nyla/architecture.md (Routing/i18n) | `t()`, `@i18nModel`, UiLanguageSet, TextMultilingual, AI-Uebersetzung, Boot-Sync |
@ -50,7 +50,9 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
| Gateway i18n Phase 7 (done) | c-work/3-validate/2026-04-gateway-i18n-phase-7-implementation.md | RBAC-Keys (rbac.*) im xx-Set, `translate-field` API, FormGenerator KI-Button |
| Gateway Duplicate Class Names (done) | c-work/3-validate/2026-04-gateway-duplicate-class-names.md | TaskResult, AiResponse, TableData, Token Umbenennungen |
| Generic Graph Editor (Typed Nodes, done) | c-work/3-validate/2026-04-generic-graph-editor.md | Port-Typen, Extraktoren, FrontendType-Renderer, System-Variablen |
| Unified Document Model (UDM) & Workflows (done) | z-archive/2026-04-unified-document-model.md, b-reference/gateway/workflow.md (Abschnitt UDM) | UDM-Baum, Extract-Node, Loop/Consolidate, Agent-UDM-Tools |
| i18n Static Text Elimination (Ph. 12 done) | c-work/2-build/2026-04-i18n-static-text-elimination.md | Gateway Feature+Nodes: Dicts → de-Keys; Ph. 35 offen |
| Database Health & Data Cleanup (done) | z-archive/2026-04-database-health-and-data-cleanup.md | DB-Registry, FK-Discovery, Orphan-Scanner, Admin-Seite |
## Prozess & Betrieb

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-07 -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
# AI Agent & Knowledge Store
@ -92,7 +92,7 @@ Zusätzlich zu den unten genannten **Kern-Tools** existieren **dynamische Tools*
**Toolbox-Zuordnung:** Kern-Tools sind den Toolboxes `core`, `ai` und `datasources` zugeordnet (siehe Toolbox Registry oben). Connection-abhaengige Tools (`email`, `sharepoint`, `clickup`, `jira`) werden nur aktiviert, wenn der User eine passende Connection hat. Workflow-Editing-Tools (`workflow` Toolbox) werden separat via `workflowTools.py` registriert.
### Kern-Tools (registriert via `registerCoreTools``coreTools/`, 40 Stück)
### Kern-Tools (registriert via `registerCoreTools``coreTools/`; Stand 2026-04 inkl. UDM-Helfer)
**Workspace / Dateien**
@ -149,6 +149,9 @@ Zusätzlich zu den unten genannten **Kern-Tools** existieren **dynamische Tools*
| `readContentObjects` | Gezielt Content-Objekte lesen |
| `extractContainerItem` | Element aus Container extrahieren |
| `summarizeContent` | KI-Zusammenfassung |
| `getUdmStructure` | UDM-JSON: Überblick (Knoten, Struktur, Block-Zahlen); `udmJson` als stringifiziertes Objekt |
| `walkUdmBlocks` | UDM traversieren: alle `ContentBlock`-Knoten mit Pfad und Kurz-Preview |
| `filterUdmByType` | UDM: alle Blöcke mit gegebenem `contentType` (z.B. `table`, `image`) |
| `describeImage` | Vision-Analyse |
| `renderDocument` | Dokument rendern |
| `generateImage` | Bildgenerierung |

View file

@ -1,5 +1,5 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-07 -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) -->
# Workflow-Engine
@ -50,7 +50,7 @@ Methoden sind Python-Klassen unter `gateway/modules/workflows/methods/`; der **M
| Method (`self.name`) | Actions (Registry-Keys) |
|---------------------|-------------------------|
| `context` | `getDocumentIndex`, `extractContent`, `neutralizeData`, `triggerPreprocessingServer` |
| `ai` | `process`, `webResearch`, `summarizeDocument`, `translateDocument`, `convertDocument`, `generateDocument`, `generateCode` |
| `ai` | `process`, `webResearch`, `summarizeDocument`, `translateDocument`, `convertDocument`, `generateDocument`, `generateCode`, `consolidate` (KI-Konsolidierung aggregierter Ergebnisse) |
| `outlook` | `readEmails`, `searchEmails`, `composeAndDraftEmailWithContext`, `sendDraftEmail` |
| `sharepoint` | `findDocumentPath`, `readDocuments`, `uploadDocument`, `listDocuments`, `analyzeFolderUsage`, `findSiteByUrl`, `downloadFileByPath`, `copyFile`, `uploadFile` |
| `clickup` | `listTasks`, `searchTasks`, `getTask`, `createTask`, `updateTask`, `uploadAttachment` |
@ -61,6 +61,12 @@ Methoden sind Python-Klassen unter `gateway/modules/workflows/methods/`; der **M
*Hinweis:* Ältere Kontext-Dokumente nennen für `context` u.a. `saveContent` / `transformContent`; der aktuelle Gateway-Stand listet die obigen Aktionen (Stand Abgleich Code / Review 2026-04-05).
### Graphical Editor — UDM, Loop, KI-Badge
- **Unified Document Model (UDM):** Extraktion kann ein hierarchisches JSON (`Document` → `StructuralNode``ContentBlock`) liefern; Port-Typen `UdmDocument`, `UdmNodeList`, `ConsolidateResult` im Typed-Port-System (`portTypes.py`).
- **Nodes:** u. a. `context.extractContent` (ohne KI), `data.consolidate` (deterministisch), `ai.consolidate` (LLM), `flow.loop` mit Parametern `level` (UDM-Ebene) und `concurrency` (parallele Iterationen).
- **Kosten-Transparenz:** Jede Node-Definition trägt `meta.usesAi` (`true` | `false`). Das Frontend (`FlowCanvas`, Node-Palette) zeigt ein **AI-Badge** nur bei `usesAi: true`.
---
## Aktionen

View file

@ -239,16 +239,84 @@ Felder mit fuehrendem `_` sind fuer Anwendungs-CUD geschuetzt -- der Connector e
## Feature-DB-Registrierung
Features registrieren sich **nicht** in einer zentralen DB-Liste. Stattdessen:
Jedes Interface registriert seine Datenbank ueber `registerDatabase()` aus `dbRegistry.py`:
1. `registry.py``registerAllFeaturesInCatalog()` laedt Feature-Main-Module
2. Jedes Feature-Main definiert seinen `FEATURE_CODE` und `dbDatabase`
3. Der Datenbank-Name wird in `_initializeDatabase()` des Feature-Interfaces gesetzt
4. Der `DatabaseConnector` erzeugt die DB automatisch beim ersten Zugriff
1. Jedes `interfaceDb*.py` / `interfaceFeature*.py` definiert eine Modul-Konstante (z.B. `appDatabase = "poweron_app"`)
2. Auf Modul-Ebene wird `registerDatabase(appDatabase)` aufgerufen — damit ist die DB im zentralen Registry
3. Der `DatabaseConnector` erzeugt die DB automatisch beim ersten Zugriff
4. Neue DBs werden automatisch erkannt, entfernte DBs verschwinden
### Admin-Sicht
---
Die Admin-DB-Listing-API (`routeSecurityAdmin.py`) wurde am 2026-04-12 entfernt. Datenbank-Diagnostik erfolgt direkt ueber PostgreSQL-Tools oder das System-Dashboard.
## Database Health und Orphan-Scanner
### Ueberblick
SysAdmin-Seite unter **Admin > System > Datenbank-Gesundheit** mit zwei Funktionen:
1. **Tabellenstatistiken** — Row Count, Size, Index Size, Last Vacuum/Analyze fuer alle Tabellen
2. **Orphan Cleanup** — Generische Erkennung verwaister Datensaetze mit Clean-Buttons
### Dynamische DB-Registry (`modules/shared/dbRegistry.py`)
- `registerDatabase(dbName, configPrefix)` — oeffentliche API, aufgerufen in jedem Interface
- `_getRegisteredDatabases()` — alle registrierten DBs
- `_getConnectorForDb(dbName)` — Factory fuer read-only Connector
### Model-Registry (`modules/datamodels/datamodelBase.py`)
- `_MODEL_REGISTRY: Dict[str, Type[PowerOnModel]]` — automatisch via `__init_subclass__`
- Jede `PowerOnModel`-Subklasse registriert sich beim Import (Tabellenname = Klassenname)
### FK-Discovery (`modules/shared/fkRegistry.py`)
- Scannt alle PowerOnModel-Subklassen nach `fk_target` in `json_schema_extra`
- Baut automatisch `{tableName → dbName}` Mapping aus den Annotationen
- Fallback: Catalog-Query (`information_schema.tables`) fuer unmapped Tables
- Cached nach erstem Scan
- `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable, targetColumn`
### FK-Annotationen (`fk_target`)
Jedes `*Id`-Feld das eine echte FK-Beziehung darstellt, hat `fk_target` in `json_schema_extra`:
```python
mandateId: str = Field(
...,
json_schema_extra={
...,
"fk_target": {"db": "poweron_app", "table": "Mandate"},
},
)
```
- Standard `targetColumn` ist `"id"`, Sonderfall `Feature.code` nutzt `"column": "code"`
- Felder ohne DB-FK (Stripe-IDs, Graph-Node-IDs, polymorphe referenceId) haben kein `fk_target`
- `fk_target` ist rein fuer Backend-Orphan-Detection, `fk_model`/`frontend_fk_*` bleiben fuer das UI
### Orphan-Scanner (`modules/system/databaseHealth.py`)
- `_getTableStats(dbFilter)``pg_stat_user_tables` + `pg_total_relation_size`
- `_scanOrphans(dbFilter)` — Same-DB: `NOT EXISTS`, Cross-DB: Parent-IDs laden + `NOT IN`
- `_cleanOrphans(db, table, column)` — loescht Orphans, gibt Count zurueck
- `_cleanAllOrphans()` — alle Orphans bereinigen
- 5-Minuten-Cache fuer Orphan-Ergebnisse
### API (`modules/routes/routeAdminDatabaseHealth.py`)
| Methode | Pfad | Beschreibung |
|---------|------|-------------|
| GET | `/api/admin/database-health/stats` | Tabellenstatistiken (optional `?db=...`) |
| GET | `/api/admin/database-health/orphans` | Orphan-Scan (optional `?db=...`) |
| POST | `/api/admin/database-health/orphans/clean` | Einzeln-Cleanup `{"db","table","column"}` |
| POST | `/api/admin/database-health/orphans/clean-all` | Batch-Cleanup aller Orphans |
Alle Endpunkte: SysAdmin-only via `requireSysAdminRole`.
### Frontend (`AdminDatabaseHealthPage.tsx`)
- Tab "Statistiken": Sortierbare Tabelle mit DB-Filter und Summary-Leiste
- Tab "Orphan Cleanup": Tabelle mit Clean-Button pro Zeile + "Alle bereinigen"
---
@ -257,6 +325,10 @@ Die Admin-DB-Listing-API (`routeSecurityAdmin.py`) wurde am 2026-04-12 entfernt.
| Thema | Pfad |
|-------|------|
| PostgreSQL Connector | `gateway/modules/connectors/connectorDbPostgre.py` |
| DB-Registry | `gateway/modules/shared/dbRegistry.py` |
| FK-Registry | `gateway/modules/shared/fkRegistry.py` |
| Database Health | `gateway/modules/system/databaseHealth.py` |
| Health API Route | `gateway/modules/routes/routeAdminDatabaseHealth.py` |
| App-DB Interface | `gateway/modules/interfaces/interfaceDbApp.py` |
| Chat-DB Interface | `gateway/modules/interfaces/interfaceDbChat.py` |
| Management-DB Interface | `gateway/modules/interfaces/interfaceDbManagement.py` |
@ -268,3 +340,4 @@ Die Admin-DB-Listing-API (`routeSecurityAdmin.py`) wurde am 2026-04-12 entfernt.
| Bootstrap / DB-Seed | `gateway/modules/interfaces/interfaceBootstrap.py` |
| DB-Migration Script | `gateway/scripts/script_db_export_migration.py` |
| Datenmodelle (Pydantic) | `gateway/modules/datamodels/` |
| Frontend Health Page | `frontend_nyla/src/pages/admin/AdminDatabaseHealthPage.tsx` |

View file

@ -1,6 +1,6 @@
<!-- status: canonical -->
<!-- lastReviewed: 2026-04-05 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-05) -->
<!-- lastReviewed: 2026-04-16 -->
<!-- verifiedAgainst: gateway (codebase audit 2026-04-16) -->
# Neutralisierungs-System
@ -26,7 +26,7 @@ Orthogonal dazu (kein Ersatz des Gates, sondern Data-at-rest bzw. Workflow):
2. **Chat-/Session-Level:** `ServiceCenterContext.requireNeutralization` (z. B. Workspace, Automation).
3. **Pro Request:** `AiCallRequest.requireNeutralization` — expliziter Override auf dem Call.
**Quellen gesamt (OR-Logik):** Ist irgendwo Neutralisierung erforderlich (Feature-Instanz, Workflow/Session, konkretes Objekt mit `FileItem.neutralize` / `DataSource.neutralize` / `FeatureDataSource.neutralize`), wird entsprechend verarbeitet. Dokumentierte Produktregel: **Kein `False` von einer Quelle hebt ein `True` einer anderen auf.**
**Quellen gesamt (OR-Logik):** Ist irgendwo Neutralisierung erforderlich (Feature-Instanz, Workflow/Session, konkretes Objekt mit `FileItem.neutralize` / `FileFolder.neutralize` / `DataSource.neutralize` / `FeatureDataSource.neutralize` / `FeatureDataSource.neutralizeFields`), wird entsprechend verarbeitet. Dokumentierte Produktregel: **Kein `False` von einer Quelle hebt ein `True` einer anderen auf.**
## Was neutralisiert wird
@ -45,11 +45,13 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
## Failsafe-Kette
1. **Toggle `FileItem.neutralize`:** Index und Chunks werden synchron gelöscht; Re-Index im Hintergrund verhindert Leaks bei Fehlern in der Nachindexierung.
2. **Indexierung:** `indexFile()` neutralisiert Text-Chunks bei `FileItem.neutralize=True` → Data-at-rest abgesichert.
3. **DataSource-Download:** `neutralize`-Flag wird auf erzeugte `FileItem`s vererbt.
4. **FeatureDataSource:** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls.
5. **AI-Call:** Bei erzwungener Neutralisierung (`requireNeutralization=True`) schlägt fehlgeschlagene Neutralisierung hart fehl (`RuntimeError` / Blockierung bzw. Entfernen betroffener Message-Teile — kein Durchleiten im Rohzustand).
6. **`_rehydrateResponse()`:** Als Hilfsmethode vorhanden; automatische Rückübersetzung der Modellantwort ist nicht mehr der Standardpfad.
2. **Toggle `FileFolder.neutralize`:** Propagiert `neutralize` rekursiv auf alle Dateien im Ordner; Index/Chunks werden pro Datei synchron gelöscht, Re-Index im Hintergrund. Neue/verschobene Dateien erben das Flag des Ziel-Ordners.
3. **Indexierung:** `indexFile()` neutralisiert Text-Chunks bei `FileItem.neutralize=True` → Data-at-rest abgesichert.
4. **DataSource-Download:** `neutralize`-Flag wird auf erzeugte `FileItem`s vererbt.
5. **FeatureDataSource (Tabellen-Level):** `_queryFeatureInstance()` setzt bei `neutralize=True` u. a. `requireNeutralization=True` für Sub-Agent-AI-Calls.
6. **FeatureDataSource (Feld-Level):** `neutralizeFields` definiert Spalten, deren Werte in `FeatureDataProvider` mit deterministischen Platzhaltern `[NEUT.<field>.<hash>]` ersetzt werden, bevor Daten den Sub-Agent erreichen. Gleichheits-Vergleiche bleiben möglich (stabiler Hash).
7. **AI-Call:** Bei erzwungener Neutralisierung (`requireNeutralization=True`) schlägt fehlgeschlagene Neutralisierung hart fehl (`RuntimeError` / Blockierung bzw. Entfernen betroffener Message-Teile — kein Durchleiten im Rohzustand).
8. **`_rehydrateResponse()`:** Als Hilfsmethode vorhanden; automatische Rückübersetzung der Modellantwort ist nicht mehr der Standardpfad.
**Engine-Failsafe (Spezifikation):** Neutralisierung verlangt, Engine nicht verfügbar → nicht weiterverarbeiten; Teilfehler → Teil entfernen, nicht roh weitergeben; fehlendes internes Medienmodell → Medien-Part entfernen.
@ -66,10 +68,12 @@ Im zentralen AI-Gate (`_neutralizeRequest`):
| **Feature-Config / DB** | `features/neutralization/datamodelFeatureNeutralizer.py`, `interfaceFeatureNeutralizer.py` |
| **RAG-Index** | `serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py` |
| **Agent / Quellen** | `serviceCenter/services/serviceAgent/mainServiceAgent.py` (`_resolveDataSource`, `_downloadFromDataSource`, `_queryFeatureInstance`) |
| **Feld-Neutralisierung (DB)** | `serviceCenter/services/serviceAgent/featureDataProvider.py``_neutralizeRowFields`, `_applyFieldNeutralization` |
| **Datei-Toggle / Index** | `routes/routeDataFiles.py``PATCH /{fileId}/neutralize` |
| **Ordner-Toggle / Index** | `routes/routeDataFiles.py``PATCH /folders/{folderId}/neutralize` (rekursive Propagierung) |
| **Workflow-Aktion** | `workflows/methods/methodContext/actions/neutralizeData.py` |
| **Kontext** | `serviceCenter/context.py``requireNeutralization` |
| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` |
| **Flags (Modelle)** | `datamodels/datamodelFiles.py` (`FileItem.neutralize`), `datamodelFileFolder.py` (`FileFolder.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` (`neutralize`, `neutralizeFields`) |
| **AI-Operationen / Enums** | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` |
## Regeln / Invarianten

View file

@ -0,0 +1,572 @@
<!-- status: plan -->
<!-- started: 2026-04-16 -->
<!-- component: gateway | frontend-nyla | platform -->
<!-- replaces: local/notes/demo-tue-use-cases-inputs-customers.md, c-work/1-plan/2026-04-demo2-merged-customer-trustee-plan.md, c-work/1-plan/2026-04-porta-ui-enhancements-team-meeting.md -->
# Konsolidierter Kundenwünsche-Plan — PORTA Umsetzung
> **Stand:** 16. April 2026 (aktualisiert mit PWG-Workshop-Ergebnissen)
> **Zweck:** Einheitliche Übersicht aller Kundenwünsche, priorisiert und gegen die Codebase abgeglichen.
> **Ersetzt:** `local/notes/demo-tue-use-cases-inputs-customers.md`, `c-work/1-plan/2026-04-demo2-merged-customer-trustee-plan.md`, `c-work/1-plan/2026-04-porta-ui-enhancements-team-meeting.md`
---
## Kunden-Übersicht
| Kunde | Branche | Kontakte | Hauptinteresse | Status |
|-------|---------|----------|----------------|--------|
| **Bling** | Treuhandbüro | KJS | Belegverarbeitung, Budget, Dashboards, Mandantenmanagement | Trial geplant |
| **PWG** (Stiftung) | Immobilien/Wohnen (~300 Liegenschaften, 78 Pers.) | MB | **Pilot: Jahresmietzinsbestätigungen** (3'200/Jahr), Belegverarbeitung Abacus, CommCoach, Neutralisierung, Knowledge-Retrieval | Workshop 16.04.2026 ✅ — Pilot bestätigt, Versand Sommer 2026 |
| **Quid / ServiceHunter** | SaaS/Dienstleistung | DC | KPI-Dashboard, Zeiterfassung, Prognosen, Konsolidierung | Follow-up geplant |
---
## Legende Codebase-Status
| Symbol | Bedeutung |
|--------|-----------|
| ✅ | Im Code vorhanden und funktional |
| 🔧 | Grundstruktur vorhanden, Anpassung/Ergänzung nötig |
| ❌ | Noch nicht umgesetzt |
| ⏸️ | Bewusst zurückgestellt (wartet auf Input/Entscheid) |
---
## Teil 1: Feature-Anforderungen (Kunden-Use-Cases)
### 1.1 Automatisierte Belegverarbeitung & Spesenverwaltung
**Kunden:** Bling (Prio hoch), PWG, Quid (Spesen)
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.1.1 | Spesenbelege per Foto → automatische Klassifikation (Rechnung, Spesenbeleg, Bankauszug) | ✅ | `methodTrustee/actions/extractFromFiles.py` + `processDocuments.py` — Pipeline extrahiert, klassifiziert, verbucht |
| 1.1.2 | SharePoint-Synchronisation (automatisch, z.B. täglich 22:00) | ✅ | `nodeDefinitions/sharepoint.py` — 6 Nodes; `automation2/scheduleCron.py` für Zeitsteuerung; System-Template "Treuhand: PDF-Klassifizierung & Trustee-Import" in `interfaceBootstrap.py` |
| 1.1.3 | Automatische Kontierung basierend auf Kontoplan | ✅ | In `processDocuments.py` — AI-gestützte Kontierung gegen Feature-Daten (Kontoplan via Accounting-Bridge) |
| 1.1.4 | Firmen-Mapping zu Kunden/Lieferanten | 🔧 | Grundstruktur in Accounting-Bridge; kein dediziertes Mapping-UI oder regelbasiertes Matching |
| 1.1.5 | Optionales Tagging (z.B. "Fuel Station") | 🔧 | Tags auf Dokument-Ebene möglich; kein Beleg-spezifisches Tag-System |
| 1.1.6 | Buchungsregeln für wiederkehrende Belege | ❌ | Kein regelbasiertes Booking-Template-System |
| 1.1.7 | Vorsteuer automatisch hinterlegen und auslesen | 🔧 | Accounting-Connectors liefern Steuerdaten; automatische MWST-Zuordnung bei Belegverarbeitung nicht explizit |
| 1.1.8 | Integration RunMyAccounts | ✅ | `accountingConnectorRma.py` |
| 1.1.9 | Integration Bexio | ✅ | `accountingConnectorBexio.py` |
| 1.1.10 | Integration Abacus | ✅ | `accountingConnectorAbacus.py` |
| 1.1.11 | Integration Xero | ❌ | Kein `accountingConnectorXero.py` — kein Code vorhanden |
| 1.1.12 | PDF/Excel/Word/Zip Verarbeitung | ✅ | Extraktoren vorhanden; UDM-Konzept in `0-ideas/unified-document-model.md` |
### 1.2 Budget / Soll-Ist-Vergleich
**Kunden:** Bling (Prio hoch), Quid (KPI-Kontext)
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.2.1 | Budget-Excel hochladen und gegen Live-Daten prüfen | 🔧 | Prompt-Template "Budget-Vergleich" in `mainTrustee.py` (Quick Action + Template Workflow); Frontend `TrusteeAnalyseView.tsx` hat Budget-Tab mit Upload; **aber kein Demo-Budget-Excel vorhanden** |
| 1.2.2 | Vergleiche über Perioden (Q1 aktuell vs. Vorjahr vs. Budget) | ✅ | Im Prompt-Template als Anweisung an AI-Agent enthalten |
| 1.2.3 | Automatische Diagramm-Erstellung | ✅ | Agent erzeugt Charts via `aggregateTable` + AI-Prompt |
| 1.2.4 | Live-Daten via API (kein PDF-Export nötig) | ✅ | `refreshAccountingData` Action synct Live-Daten; API-basiert |
| 1.2.5 | Caching konfigurierbar | ✅ | `_featureQueryCache` mit TTL 300s in `_featureSubAgentTools.py` |
| 1.2.6 | Abweichungen mit Begründung | ✅ | Im Prompt-Template: Agent soll Abweichungen erklären |
| 1.2.7 | Vorausschauende Prognosen | ✅ | Separates Prompt-Template "Prognose/Trend-Analyse" |
### 1.3 Cashflow-Rechnung
**Kunden:** Bling
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.3.1 | Plausibilisierung und Erstellung Cashflow-Rechnung | ✅ | Prompt-Template "Cashflow-Rechnung" in `mainTrustee.py` |
| 1.3.2 | Nicht-relevante Positionen berücksichtigen | ✅ | Im Prompt-Template als Anweisung |
| 1.3.3 | Warnungen bei kritischen Werten | ✅ | Im Prompt-Template |
### 1.4 Dashboard — Bilanz- & Erfolgsrechnungsanalyse
**Kunden:** Bling, Quid
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.4.1 | KPI-Dashboard (Bruttogewinn, ROI, Gewinn etc.) | ✅ | Prompt-Template "KPI-Dashboard" + Quick Action + `TrusteeAnalyseView.tsx` KPI-Tab |
| 1.4.2 | Check hälftiger Kapitalverlust | ✅ | Im Jahresabschluss-Prompt enthalten |
| 1.4.3 | Überschuldungs-Check | ✅ | Im Jahresabschluss-Prompt enthalten |
| 1.4.4 | Durchschnittliche Zahlungsfrist | 🔧 | Nicht als dedizierter KPI; Agent kann es berechnen wenn Daten vorhanden |
### 1.5 Liquiditätsplanung
**Kunden:** Bling
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.5.1 | Liquiditätsplanung analog Budgetplanung | 🔧 | Kein dediziertes Prompt-Template; über Prognose-Template teilweise abdeckbar |
| 1.5.2 | Automatische Erstellung aus Vergangenheit | 🔧 | Agent kann historische Daten analysieren; kein dedizierter Workflow |
| 1.5.3 | Zusätzliche Inputs (z.B. ausserordentliche Dividende) | ❌ | Kein Mechanismus für manuelle Zusatz-Inputs in Prognose |
### 1.6 Gastro-Use-Case — Echtzeit-Rentabilitätsanalyse
**Kunden:** Bling (Prio hoch, bestes Beispiel für Kunden-Mehrwert)
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.6.1 | Integration Kassensystem + Lohndaten + Buchhaltung | ⏸️ | **Wartet auf Kevin-Input** — kein Connector für Kassensysteme/Virux |
| 1.6.2 | Tagesumsatz vs. Personalkosten vs. Wareneinsatz | ⏸️ | Konzeptionell über generischen Data-Import möglich |
### 1.7 Abschlussunterstützung
**Kunden:** Bling, allgemein Treuhand
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.7.1 | Jahresabschluss-Checks | ✅ | Prompt-Template "Jahresabschluss prüfen" in `mainTrustee.py` |
| 1.7.2 | Abgrenzungsbuchungen vorbereiten | 🔧 | Im Prompt adressiert; keine dedizierte Automation |
| 1.7.3 | Bilanzkonti prüfen / Saldovalidierung | ✅ | Im Jahresabschluss-Prompt |
| 1.7.4 | Vorjahresvergleiche | ✅ | In mehreren Prompt-Templates |
### 1.8 Zentrales Mandantenmanagement
**Kunden:** Bling (verschiedene Systeme über eine Schaltzentrale)
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.8.1 | Verschiedene Kunden auf verschiedenen Systemen verwalten | ✅ | Multi-Tenant-Plattform ist Kernarchitektur; `accountingRegistry.py` unterstützt verschiedene Connectors pro Mandant |
| 1.8.2 | Ein Login, alle Mandanten | ✅ | Plattform-Feature: Mandantenwechsel im UI |
| 1.8.3 | Strikte Datentrennung | ✅ | Mandantenisolation auf DB- und API-Ebene |
---
### 1.9 PWG — Stiftung für preisgünstiges Wohnen
**Kontext:** ~300 Liegenschaften, Team 78 Personen. IT-Leiter Markus Brütsch (seit 2+ Jahren). Neue Rolle "Organella" für Digitalisierung (Schnittstelle Technologie/Betrieb, kein IT-Hintergrund). PWG evaluiert AI-Lösungen offen, noch keine Entscheidung getroffen. Workshop 16.04.2026 durchgeführt.
**Pilotprojekt bestätigt:** Jahresmietzinsbestätigungen (siehe 1.9.9).
#### 1.9a Plattform-Features (bestehend)
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.9.1 | Automatisierte Belegverarbeitung mit Abacus | ✅ | `accountingConnectorAbacus.py` + Trustee-Pipeline |
| 1.9.2 | Kommunikations-Coach für Mietergespräche | ✅ | `features/commcoach/` — komplett inkl. 10+ Personas |
| 1.9.3 | Immobilien-Personas (Zahlungsrückstand, Nebenkosten, Einzug, Lärm) | ✅ | 4 PWG-Personas in `BUILTIN_PERSONAS` + Seeding |
| 1.9.4 | KI-Arbeitsplatz mit Datenneutralisierung | ✅ | `features/neutralization/` — PII-Masking, Playground, Private-LLM; Daten-Residency Schweiz |
| 1.9.5 | PWG-Agent (Stiftungsstil, öffentliche PDFs als Futter) | 🔧 | Knowledge-Base-Feature vorhanden; kein PWG-spezifisches Knowledge-Set (Geschäftsberichte, Vermietungsreglement, Führungshandbuch etc.) |
| 1.9.6 | M365-Anbindung (SharePoint, Outlook, OneDrive, Teams) | ✅ | SharePoint-Nodes + Outlook-Methode in Workflows; nutzt nativen SharePoint-Index |
| 1.9.7 | KI-Auswertung Abacus-Daten/Reports | ✅ | Über Trustee-Feature + `aggregateTable` + AI-Prompts; Read/Write via Abacus-API |
| 1.9.8 | Grundstücksanalyse (öffentliche Daten: GIS, Maps, Grundbuch) | ❌ | Kein Connector/Workflow für öffentliche Geodaten |
#### 1.9b Neue Anforderungen aus Workshop 16.04.2026
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.9.9 | **Pilotprojekt: Jahresmietzinsbestätigungen** (ca. 700800 Schreiben/Quartal, Versand Sommer 2026) | ❌ | Kein dedizierter Workflow — siehe Workflow-Design unten |
| 1.9.10 | FileMaker-Integration (Portfolio, Liegenschaften, Erneuerungsplanung, Bauprojekte, Akquisition) | ❌ | Kein FileMaker-Connector; Datenmenge/-qualität muss geprüft werden |
| 1.9.11 | Template-basierte Dokumentenerstellung (HTML-Templates → Word mit Corporate Design) | 🔧 | Code-Editor mit Claude vorhanden; kein Template-to-Word-Pipeline |
| 1.9.12 | Notification-System (reagiert auf DB-Änderungen, Berichte per E-Mail, Workflow-Trigger) | 🔧 | E-Mail-Versand in Workflows vorhanden (`methodOutlook`); kein generisches DB-Change-Detection-System |
| 1.9.13 | Information Retrieval über diverse Wissensquellen (Guidelines, Handbücher, SharePoint) | 🔧 | Knowledge-Base + SharePoint-Index vorhanden; kein PWG-spezifisches Datenset geladen |
| 1.9.14 | Datenvalidierung als Erst-Schritt (welche Daten korrekt/aktuell?) | ❌ | Kein Daten-Audit-Workflow |
| 1.9.15 | Compliance & Audit: vollständiges Tracking aller AI-Transaktionen | ✅ | `ComplianceAuditPage.tsx` + Gateway-Logging; jede Anfrage protokolliert |
| 1.9.16 | Granulare Neutralisierungs-Kontrolle pro Datenquelle | ✅ | In Neutralisierungs-Feature konfigurierbar pro Source |
#### 1.9c Pilot-Workflow: Jahresmietzinsbestätigungen
**Business Case:** 4 × 800 Schreiben/Jahr = 3'200 Schreiben. Aktuell: Serienbrief → manueller Versand → Scan der Rückantworten → manuelle Verarbeitung. Ziel: AI-gestützte Verarbeitung der gescannten Rückantworten mit Antwortvorschlägen.
**Workflow-Design (67 Schritte, davon 1 mit AI):**
```
┌─────────────────────────────────────────────────────────────────┐
│ PWG Pilot: Jahresmietzinsbestätigungen │
│ │
│ Phase 1: Generierung (bestehend bei PWG) │
│ ┌──────────────────────────────────────┐ │
│ │ System generiert Serienbrief │ (PWG-internes System) │
│ │ mit aktuellen Mietdaten │ │
│ └──────────┬───────────────────────────┘ │
│ ▼ │
│ Phase 2: Versand & Rücklauf (manuell) │
│ ┌──────────────────────────────────────┐ │
│ │ Manueller Versand + Scan der │ │
│ │ Rückantworten → SharePoint-Ordner │ │
│ └──────────┬───────────────────────────┘ │
│ ▼ │
│ Phase 3: PowerOn-Workflow (automatisiert) │
│ │
│ Step 1: trigger.schedule (täglich oder on-demand) │
│ ▼ │
│ Step 2: sharepoint.listFiles (Scan-Ordner, neue Dokumente) │
│ ▼ │
│ Step 3: flow.loop (für jedes gescannte Dokument) │
│ ▼ │
│ Step 4: sharepoint.downloadFile + trustee.extractFromFiles │
│ → OCR/Extraktion: Mietername, Adresse, Bestätigung, │
│ Anmerkungen, Unterschrift ja/nein │
│ ▼ │
│ Step 5: ai.prompt ← EINZIGER AI-SCHRITT │
│ → Gescannte Daten gegen Originaldaten prüfen │
│ → Status klassifizieren (bestätigt / Abweichung / │
│ fehlende Unterschrift / unleserlich) │
│ → Antwortvorschlag generieren bei Abweichung │
│ ▼ │
│ Step 6: data.writeToTable (Ergebnis in Übersichtstabelle) │
│ ▼ │
│ Step 7: email.send (Zusammenfassung an Sachbearbeiter) │
│ → Audit-Log für jeden Verarbeitungsschritt │
└─────────────────────────────────────────────────────────────────┘
```
**Benötigte Komponenten für den Pilot-Workflow:**
| # | Komponente | Codebase-Status | Was zu tun ist |
|---|-----------|-----------------|----------------|
| W1 | `trigger.schedule` oder `trigger.manual` | ✅ | Vorhanden in `nodeDefinitions/triggers.py` |
| W2 | `sharepoint.listFiles` + `sharepoint.downloadFile` | ✅ | Vorhanden in `nodeDefinitions/sharepoint.py` |
| W3 | `flow.loop` | ✅ | Vorhanden in `nodeDefinitions/flow.py` |
| W4 | `trustee.extractFromFiles` (OCR/Extraktion gescannter Dokumente) | ✅ | Vorhanden; OCR für einseitige Scans unterstützt |
| W5 | `ai.prompt` — Prompt-Template "Mietzinsbestätigung prüfen" | ❌ | **Neues Prompt-Template** nötig: Scan-Daten gegen Originaldaten abgleichen, Status klassifizieren, Antwortvorschlag generieren |
| W6 | Ergebnis-Tabelle / Übersichtsliste (verarbeitete Bestätigungen) | 🔧 | `data`-Nodes existieren; kein dedizierter "Mietzinsbestätigungs-Report"-Output |
| W7 | `email.send` (Zusammenfassung an Sachbearbeiter) | ✅ | Vorhanden via `methodOutlook` |
| W8 | Abacus-Referenzdaten (Original-Mietzinsdaten für Abgleich) | 🔧 | Abacus-Connector vorhanden; Abfrage der Mietzins-Stammdaten muss konfiguriert werden |
| W9 | Audit-Logging für gesamten Prozess | ✅ | Plattform-Feature: alle AI-Transaktionen geloggt |
| W10 | Graph-Editor Workflow als Template speichern | ❌ | Workflow muss im Editor gebaut und als System-Template gespeichert werden |
**Voraussetzungen (Action Items aus Workshop):**
| # | Action Item | Verantwortlich | Status |
|---|------------|----------------|--------|
| AI1 | API-Zugang zu Abacus-Testmandant einrichten | Patrick Motsch | ❌ offen — Patrick koordiniert mit Abacus |
| AI2 | PWG muss Zugriff auf Testmandant bestätigen | PWG (Markus) | ❌ offen |
| AI3 | Preismodell bereitstellen (Grundgebühr/User/Monat + Token-Gebühr) | PowerOn | ❌ offen |
| AI4 | Datenschutzanforderungen und Vertragsklauseln für Lieferanten prüfen | PWG | ❌ offen |
| AI5 | Prozessschritte Pilot ausarbeiten und Kalkulation erstellen | PowerOn | 🔧 Workflow-Design in diesem Dokument; Kalkulation offen |
| AI6 | FileMaker-Datenmenge und -qualität prüfen (potenzielle Integration) | PWG + PowerOn | ❌ offen |
---
### 1.10 Quid / ServiceHunter Use Cases
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 1.10.1 | KPI-Dashboard (Kundenmargen, ACV, Lifetime Value, Produktionsmarge) | 🔧 | Generisches KPI-Prompt-Template vorhanden; kundenspezifische KPIs brauchen Daten-Import |
| 1.10.2 | Zeiterfassung & Support-Analyse (Zendesk-Verknüpfung) | ⏸️ | **Kein Zendesk-Connector** — bewusst CSV-Upload-Workaround |
| 1.10.3 | Prognosen & Proaktive Steuerung | ✅ | Prognose-Prompt-Template vorhanden |
| 1.10.4 | Konsolidierung international (CH, DE, UK) | ⏸️ | **Wartet auf Lars-Meeting** — hohe Komplexität |
| 1.10.5 | Spesen-Automatisierung (SharePoint/Drive → verbuchen) | ✅ | SharePoint-Pipeline + Trustee-Import vorhanden |
---
## Teil 2: UI/UX-Anforderungen (aus Team-Meeting / Nutzertests)
### 2.1 AI-Workspace Usability
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 2.1.1 | Primäre Prompt-Zeile dominant sichtbar (kein "Wo tippe ich?") | 🔧 | `WorkspaceInput.tsx` funktional komplett; **Placeholder auf Englisch**, keine Hero-Eingabe, sekundäre Aktionen in einer Zeile |
| 2.1.2 | Empty State im Chat (Orientierung für Erstnutzer) | ❌ | `ChatStream.tsx` zeigt blank area wenn `messages.length === 0`**kein Welcome/Empty-State** |
| 2.1.3 | "Neuer Chat" sichtbar ohne UDB-Sidebar | ❌ | Nur `+` in `ChatsTab` Toolbar; kein zentraler CTA |
| 2.1.4 | Datei-Drop Entdeckbarkeit | 🔧 | Vollflächen-Drop existiert in `WorkspacePage.tsx`; kein dauerhafter Hinweis im leeren Chat |
| 2.1.5 | DE-Placeholder und i18n | ❌ | Placeholder noch auf Englisch (`Type a message...`) |
### 2.2 Responsive Layout & Breakpoints
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 2.2.1 | Zwischen-Breakpoint (10251280px): Sidebars auto-einklappen | ❌ | `isMobile` nur bei `≤1024px`; schmale Desktop-Fenster → Mittelspalte zu schmal |
| 2.2.2 | Visuelle Hierarchie Prompt (Schatten/Rand, minHeight) | ❌ | `WorkspaceInput.tsx` ohne besondere Hervorhebung |
| 2.2.3 | Viewport-Testmatrix (1100/1200/1280/1440) | ❌ | Kein Test-Setup dafür |
### 2.3 Vertrauen & Marketing-UI
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 2.3.1 | Trust-Badges (Daten hosted in der Schweiz, Anbieter/Standort) | ❌ | Login: **kein** Hosting-Hinweis; keine `TrustFooter`/`TrustStrip` Komponente |
| 2.3.2 | "Recommended by" Partner-Strip (Valion, PamoCreate, Swiss AI Association etc.) | ❌ | Keine Partner-Logos/Links |
| 2.3.3 | Swiss ® am Logo | ❌ | Nur im Bildasset, nicht in UI sichtbar (Legal klären) |
| 2.3.4 | Sicherheits-Banner in SourcesTab ("Verbindung ist read-only") | ❌ | `SourcesTab.tsx` ohne Info-Banner |
### 2.4 Billing & Pricing Auffindbarkeit
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 2.4.1 | Billing-Funktionalität (komplett) | ✅ | `BillingDataView.tsx` mit Tabs, Balance Cards, Transaktionen |
| 2.4.2 | Einstieg Billing nach Login (Teaser/Banner) | ❌ | Kein Dashboard-Teaser; Zugang nur via Nav-Baum + User-Menü |
| 2.4.3 | Balance im UserSection (neben Avatar) | ❌ | Nur Menü-Eintrag "Guthaben" |
| 2.4.4 | Billing-Copy für Nicht-Admins | ❌ | Kein Hinweis "Frag deinen Admin" |
### 2.5 Onboarding & Erstnutzung
| # | Anforderung | Codebase-Status | Evidenz / Bemerkung |
|---|-------------|-----------------|---------------------|
| 2.5.1 | Connector-Onboarding ("sicher verbinden, nichts kaputt") | ❌ | Keine Microcopy in SourcesTab |
| 2.5.2 | Progressive Offenlegung (weniger gleichzeitig auf Startscreens) | ❌ | Workspace zeigt alles parallel |
---
## Teil 3: Infrastruktur & Demo-Vorbereitung
### 3.1 Demo-Konfigurationen
| # | Item | Codebase-Status | Evidenz / Bemerkung |
|---|------|-----------------|---------------------|
| 3.1.1 | Demo-Config-Infrastruktur (Base + Admin API) | ✅ | `_baseDemoConfig.py`, `routeAdminDemoConfig.py` |
| 3.1.2 | Referenz: `investorDemo2026.py` | ✅ | Vollständiges Muster mit Mandant, User, Features, Billing |
| 3.1.3 | Demo-Mandant "Bling Demo" | ❌ | Kein `blingDemo2026.py` |
| 3.1.4 | Demo-Mandant "PWG Demo" | ❌ | Kein `pwgDemo2026.py` |
| 3.1.5 | Demo-Mandant "Quid Demo" | ❌ | Kein `quidDemo2026.py` |
### 3.2 Testdaten
| # | Item | Codebase-Status | Evidenz / Bemerkung |
|---|------|-----------------|---------------------|
| 3.2.1 | Fiktives Mieterdossier (Neutralisierung) | ✅ | `demoData/neutralizer/tenant-dossier.pdf` + Generator |
| 3.2.2 | Knowledge-Base Demo-Dateien | ✅ | `demoData/knowledge-base/` — 4 Dateien |
| 3.2.3 | Budget-Excel (Soll-Werte) | ❌ | Kein `.xlsx` im Repo |
| 3.2.4 | Musterbelege (Rechnung, Spesen, Bank, Versicherung) | ❌ | Keine Demo-Belege |
| 3.2.5 | Quid-Testdaten (CSV: Umsatz, Kunden, Support) | ❌ | Keine kundenspezifischen Testdaten |
| 3.2.6 | Bling-Testdaten (Bexio-kompatibel) | ❌ | Keine Bexio-Testdaten |
| 3.2.7 | PWG-Testdaten (Abacus-kompatibel) | ❌ | Keine Abacus-Testdaten |
### 3.3 Demo-Workflows & Skripte
| # | Item | Codebase-Status | Evidenz / Bemerkung |
|---|------|-----------------|---------------------|
| 3.3.1 | System-Template "Treuhand: PDF-Klassifizierung" | ✅ | In `interfaceBootstrap.py` |
| 3.3.2 | Demo-Workflow (manual trigger → SharePoint → Trustee Pipeline) | ❌ | System-Template existiert, aber kein dedizierter Demo-Workflow |
| 3.3.3 | Demo-Skript Bling | ❌ | |
| 3.3.4 | Demo-Skript PWG | ❌ | |
| 3.3.5 | Demo-Skript Quid | ❌ | |
| 3.3.6 | Neutralisierungs-Demo-Flow (Schritt-für-Schritt) | ❌ | Kein Skript |
---
## Teil 4: Allgemeine Treuhand-Use-Cases (Prozessdokumentation)
Diese Use Cases stammen aus der allgemeinen Treuhand-Prozessdokumentation und sind für **alle** Kunden relevant.
### 4.1 Datenerfassung — Kleine Unternehmen
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.1.1 | Belege sortieren & mit Banktransaktionen referenzieren | 🔧 — Belegverarbeitung vorhanden; kein Bank-Statement-Matching |
| 4.1.2 | Import Banktransaktionen via Excel/CSV | 🔧 — File-Upload + Extraktion vorhanden; kein dedizierter Bank-Import |
| 4.1.3 | Automatisierte Rückfragen an Kunden bei fehlenden Belegen | ❌ |
### 4.2 Lohnbuchhaltung
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.2.1 | Überleitung Lohnaufwand (Fibu vs. Lohnbuchhaltung) | ❌ — Kein Lohn-Feature |
| 4.2.2 | Nicht-lohnwirksame Buchungen identifizieren | ❌ |
| 4.2.3 | Fehlende Lohnmeldungen erkennen | ❌ |
### 4.3 Steuererklärung
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.3.1 | Belege nach Kategorien ordnen | 🔧 — Klassifikation vorhanden |
| 4.3.2 | Steuererklärung erstellen | ❌ — Kein Steuer-Feature |
| 4.3.3 | Vorjahresvergleich und Plausibilisierung | ✅ — In Analyse-Prompts |
### 4.4 Jahresabschluss (Mittlere/Grosse Unternehmen)
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.4.1 | Banksalden abgleichen (Buchhaltung vs. Bank) | 🔧 — Konzeptionell via Agent; kein dedizierter Check |
| 4.4.2 | PayPal, Revolut, ausländische Konten nachbuchen | ❌ |
| 4.4.3 | Wertschriftendepot nachbuchen | ❌ |
| 4.4.4 | Fehlerkonto-Transaktionen bereinigen | ❌ |
| 4.4.5 | Kreditkartenkonto abstimmen | ❌ |
| 4.4.6 | Zahlungsanbieter prüfen (Stripe, Amex) | ❌ |
| 4.4.7 | Nebenbücher mit Fibu abstimmen | ❌ |
| 4.4.8 | Skonto-Differenzen ausbuchen | ❌ |
| 4.4.9 | Konzerninterne Abstimmung | ❌ |
| 4.4.10 | Darlehenskonten abgleichen, Zinsen berechnen | ❌ |
| 4.4.11 | Fremdwährungsbewertung gemäss ESTV | ❌ |
| 4.4.12 | Eigenkapitalveränderungen mit HR abgleichen | ❌ |
| 4.4.13 | Umsatz-/Aufwandsplausibilisierung | ✅ — Im Jahresabschluss-Prompt |
| 4.4.14 | Rechnungsabgrenzungen (aktiv/passiv) | 🔧 — Im Prompt adressiert |
| 4.4.15 | Abschreibungen erfassen | ❌ |
| 4.4.16 | MWST-Jahresabstimmung | ❌ |
| 4.4.17 | Steueraufwand-Rückstellungen berechnen | ❌ |
| 4.4.18 | Bilanz und ER plausibilisieren | ✅ — Im Jahresabschluss-Prompt |
| 4.4.19 | Analyse gesetzliche Bestimmungen (Kapitalverlust, Überschuldung) | ✅ — Im Jahresabschluss-Prompt |
### 4.5 Finanzielle Führung & Controlling
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.5.1 | Businessplan → Absatzplanung → Ertragsplanung | ❌ |
| 4.5.2 | Investitionsplan und Finanzplan | ❌ |
| 4.5.3 | Budget mit Szenarien | 🔧 — Budget-Prompt vorhanden; Szenarien via Chat |
| 4.5.4 | Soll/Ist-Vergleich mit Abweichungsanalyse | ✅ — Budget-Vergleich Prompt-Template |
| 4.5.5 | Deckungsbeitragsrechnung | ❌ |
| 4.5.6 | Projekt-Controlling | ❌ |
| 4.5.7 | Benchmarkanalyse | ❌ |
| 4.5.8 | KPI-Monitoring mit proaktiver Benachrichtigung | 🔧 — KPIs via Agent; kein proaktives Alert-System |
### 4.6 Revisionen
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.6.1 | Ordentliche Revisionen unterstützen | ❌ — Kein Revisions-Feature |
### 4.7 Steueroptimierung & Simulationen
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.7.1 | Jahresrechnung auf steuerliche Probleme prüfen | ❌ |
| 4.7.2 | Simulationen (Lohn vs. Dividende, Umzug, 3. Säule, BVG) | ❌ |
| 4.7.3 | MWST-Prüfung, VST-Korrekturen | ❌ |
| 4.7.4 | Steuerausscheidungen auf verschiedene Kantone | ❌ |
### 4.8 Interdisziplinäre Themen
| # | Anforderung | Codebase-Status |
|---|-------------|-----------------|
| 4.8.1 | Umstrukturierungen | ❌ |
| 4.8.2 | Steuer-/Vorsorgestrategien | ❌ |
| 4.8.3 | Nachfolgeplanung | ❌ |
| 4.8.4 | Unternehmensbewertungen | ❌ |
---
## Zusammenfassung: Status-Überblick
### Fertig (✅) — Kernplattform funktioniert
| Bereich | Was steht |
|---------|-----------|
| **Trustee Agent-Tools** | `refreshTrusteeData`, `aggregateTable`, Connection-Pooling, Result-Caching |
| **Graph-Editor** | Trustee-Kategorie + 4 Nodes, SharePoint-Nodes, Flow-Nodes (Loop, If/Else, Switch, Merge) |
| **Prompt-Templates** | 5 Analyse-Typen (Budget, KPI, Cashflow, Prognose, Jahresabschluss) als Quick Actions + Template Workflows |
| **Accounting-Connectors** | Bexio, Abacus, RunMyAccounts |
| **Belegverarbeitung** | Extraktion → Klassifikation → Kontierung → Sync (end-to-end) |
| **CommCoach** | Feature komplett, 10+ Personas inkl. 4 Immobilien-Personas (PWG), Gamification, Seeding |
| **Neutralisierung** | PII-Masking, Playground, Private-LLM, Mieterdossier-PDF |
| **Multi-Tenancy** | Mandantenisolation, Datentrennung, Rollenwechsel |
| **Billing** | BillingDataView, Balance, Transaktionen, useBilling |
| **SharePoint-Integration** | 6 Workflow-Nodes + Automation |
| **Demo-Infrastruktur** | Base-Config, Admin-API, Investor-Demo als Referenz |
### Teilweise (🔧) — Grundstruktur steht, Erweiterung nötig
| Bereich | Was fehlt |
|---------|-----------|
| Firmen-Mapping Kunden/Lieferanten | Regelbasiertes Matching-UI |
| Beleg-Tagging | Beleg-spezifisches Tag-System |
| Vorsteuer-Automatisierung | MWST-Zuordnung bei Belegverarbeitung |
| Liquiditätsplanung | Dediziertes Prompt-Template |
| Workspace-Prompt | DE-Placeholder, visuelle Hervorhebung |
| Datei-Drop | Hinweis im Empty State |
| Budget-Prompt | Demo-Excel-Datei fehlt |
### Offen (❌) — Noch zu bauen
#### Prio 0: PWG-Pilot (Versand Sommer 2026 — Deadline-gebunden)
| Item | Aufwand | Beschreibung |
|------|---------|-------------|
| Abacus-Testmandant API-Zugang | Extern | Patrick koordiniert mit Abacus; PWG muss Zugriff bestätigen |
| Prompt-Template "Mietzinsbestätigung prüfen" | Mittel | Neues Template: Scan vs. Originaldaten, Status-Klassifikation, Antwortvorschlag |
| Pilot-Workflow im Graph-Editor | Mittel | trigger → sharepoint.listFiles → loop → download → extract → ai.prompt → report → email |
| Abacus-Mietzins-Stammdaten-Abfrage | Klein | Konfiguration im Abacus-Connector für Mietzins-Referenzdaten |
| Demo-Mandant PWG | Mittel | `pwgDemo2026.py` — Trustee (Abacus), CommCoach, Neutralisierung, Workspace |
| PWG Knowledge-Set | Klein | Öffentliche PDFs (Geschäftsberichte, Vermietungsreglement) in Knowledge-Base laden |
| Preismodell / Kalkulation Pilot | Extern | Grundgebühr/User/Monat + Token-Gebühr; basierend auf 4×800 Schreiben/Jahr |
#### Prio 1: Demo-Blocker / Weitere Kunden-Demos
| Item | Aufwand | Beschreibung |
|------|---------|-------------|
| Demo-Mandant Bling | Mittel | `blingDemo2026.py` — Trustee (Bexio), Workspace, Graph-Editor |
| Demo-Mandant Quid | Klein | `quidDemo2026.py` — Workspace, Graph-Editor, CSV-Upload |
| Budget-Excel | Klein | Soll-Werte 2026 für Demo |
| Musterbelege (PDFs) | Klein | 35 Belege (Rechnung, Spesen, Bank, Versicherung) |
| Demo-Skripte | Klein | Schritt-für-Schritt pro Kunde |
| Neutralisierungs-Demo-Flow | Klein | Dokumentation |
| Demo-Workflow Graph-Editor (generisch) | Mittel | trigger.manual → SharePoint → Trustee Pipeline |
#### Prio 2: UI/UX-Verbesserungen (Erstnutzer-Hürde senken)
| Item | Aufwand | Beschreibung |
|------|---------|-------------|
| Empty State ChatStream | Klein | Titel, Bullets, "Neuer Chat" Button |
| DE-Placeholder + i18n | Klein | `WorkspaceInput.tsx` |
| "Neuer Chat" CTA in Mitte | Klein | `WorkspacePage.tsx` |
| Zwischen-Breakpoint (10251280px) | Mittel | Sidebar-Auto-Collapse |
| Trust-Strip/Footer (Login, Landing) | Mittel | Hosting Schweiz, Partner-Logos (Legal nötig) |
| Billing-Teaser nach Login | Klein | Link oder Banner |
| Connector-Onboarding Copy | Klein | Sicherheits-Banner in SourcesTab |
| Balance im UserSection | Klein | Zahl neben Avatar |
#### Prio 3: Feature-Erweiterungen (Roadmap)
| Item | Aufwand | Beschreibung |
|------|---------|-------------|
| Xero-Connector | Gross | Neuer Accounting-Connector |
| FileMaker-Connector (PWG) | Gross | Portfolio, Liegenschaften, Erneuerungsplanung; Datenmenge/-qualität erst prüfen |
| Buchungsregeln für wiederkehrende Belege | Mittel | Regel-Engine |
| Liquiditätsplanungs-Template | Klein | Neues Prompt-Template |
| KPI-Monitoring mit Alerts | Gross | Proaktives Benachrichtigungssystem |
| Template-basierte Dokumentenerstellung (HTML → Word) | Mittel | Erneuerungsstrategien, Corporate Design |
| DB-Change-Detection / Notification-Trigger | Mittel | Reagiert auf Datenbank-Änderungen, triggert Workflows |
| Datenvalidierungs-Workflow | Klein | Erst-Audit: welche Daten korrekt/aktuell |
#### Prio 4: Zurückgestellt (wartet auf externen Input)
| Item | Wartet auf | Kunde |
|------|-----------|-------|
| Gastro-Echtzeit-Integration | Kevin (Bling) soll UC ausformulieren | Bling |
| Zendesk-Connector | Bewusst CSV-Workaround | Quid |
| Regelbasierte Konsolidierung | Lars-Meeting | Quid |
| Grundstücksanalyse (GIS etc.) | Kundenpräzisierung | PWG |
| FileMaker-Integration (PWG) | Datenmenge/-qualität prüfen (PWG + PowerOn) | PWG |
| PWG Datenschutz-/Vertragsklauseln | PWG prüft intern (genossenschaftliche Struktur) | PWG |
#### Langfristig: Allgemeine Treuhand-Automatisierung
Die Use Cases aus Teil 4 (Lohnbuchhaltung, Steuererklärung, detaillierter Jahresabschluss, Revisionen, Steueroptimierung, interdisziplinäre Themen) sind **konzeptionell dokumentiert** aber noch **nicht in der Codebase**. Diese bilden die Langfrist-Roadmap und werden schrittweise über AI-Prompt-Templates und dedizierte Workflows umgesetzt, sobald die Kern-Use-Cases bei den ersten Kunden validiert sind.
---
## Entscheidungen
| Datum | Entscheidung | Begründung |
|-------|-------------|------------|
| 2026-04-07 | Prompt-Templates als Code in `mainTrustee.py` | Wartbar und versioniert statt externe Dateien |
| 2026-04-07 | `refreshTrusteeData` als separate Action | Separation of Concerns: Sync = schreibend, Query = lesend |
| 2026-04-07 | DB-Connection-Pooling statt Connection-per-Call | Grösster Performance-Hebel (~200ms pro Connection) |
| 2026-04-09 | Quid ohne Zendesk-Connector — CSV-Upload | Gleicher Analyse-Mehrwert, viel weniger Aufwand |
| 2026-04-09 | Gastro-UC und Konsolidierung auf Prio 4 | Warten auf Kunden-Input |
| 2026-04-09 | CommCoach-Personas als schnellster Wow-Effekt für PWG | Feature gebaut, nur Persona-Definitionen nötig |
| 2026-04-07 | UI-Enhancements: Fokus Erstnutzer-Hürde | Ohne klare Userführung springen Kunden ab |
| 2026-04-07 | Trust-Badges: kein ISO, sondern "Daten in CH" | Konkreter Nutzen > abstraktes Zertifikat |
| 2026-04-16 | PWG-Pilot = Jahresmietzinsbestätigungen als erster Produktiv-UC | Konkreter, messbarer Business Case (4×800 Schreiben); Deadline Sommer 2026 |
| 2026-04-16 | Workflow-Philosophie PWG: erst manuell testen, dann automatisieren | Agent wird aus manuell getesteten Prozessen erstellt; Workflows für wiederkehrende Aufgaben |
| 2026-04-16 | FileMaker-Integration auf Prio 4 | Datenmenge/-qualität muss erst geprüft werden; kein Blocker für Pilot |
| 2026-04-16 | Abacus-Testmandant als Voraussetzung für Pilot | Patrick koordiniert API-Zugang direkt mit Abacus |
---
## Betroffene Module
- **Gateway:** `features/trustee/` (Prompts, Quick Actions, Connectors), `features/commcoach/` (Personas), `features/neutralization/` (Demo-Config), `features/graphicalEditor/` (Nodes), `serviceCenter/services/serviceAgent/` (Tools, Caching), `demoConfigs/` (neue Configs), `workflows/methods/methodTrustee/` (Actions), `demoData/` (Testdaten)
- **Frontend Nyla:** `WorkspacePage.tsx` (Layout, Breakpoints, Empty State), `WorkspaceInput.tsx` (Prompt, i18n), `ChatStream.tsx` (Empty State), `Login.tsx` (Trust-UI), `UserSection.tsx` (Balance), `SourcesTab.tsx` (Onboarding-Copy), `BillingDataView.tsx` (Discovery)
- **DB-Migration:** Nein
- **Platform/Wiki:** Nach Release `b-reference/` aktualisieren
## Links
- PWG Workshop-Inputs (16.04.2026): `pamocreate/projects/poweron/customer-pwg/20260415-inputs-pwg.txt`
- Originale Kunden-Inputs: `local/notes/demo-tue-use-cases-inputs-customers.md`
- Merged Demo-Plan (ersetzt): `c-work/1-plan/2026-04-demo2-merged-customer-trustee-plan.md`
- UI-Enhancements Plan (ersetzt): `c-work/1-plan/2026-04-porta-ui-enhancements-team-meeting.md`
- Investor-Demo (Referenz): `gateway/modules/demoConfigs/investorDemo2026.py`
- Frontend-Referenz: `b-reference/frontend-nyla/architecture.md`
- Trustee Main (Prompts): `gateway/modules/features/trustee/mainTrustee.py`
- CommCoach Personas: `gateway/modules/features/commcoach/serviceCommcoachPersonas.py`
- Neutralisierung: `gateway/modules/features/neutralization/`
- Graph-Editor Nodes: `gateway/modules/features/graphicalEditor/nodeDefinitions/`
- Demo-Daten: `gateway/demoData/`
- UDM-Konzept: `c-work/0-ideas/unified-document-model.md`
## Abschluss
- [ ] Quell-Dokumente als "superseded by this document" markieren
- [ ] b-reference/ aktualisiert nach Umsetzung
- [ ] TOPICS.md aktualisiert
- [ ] Dieses Dokument → `1-plan/` verschieben wenn Umsetzung startet

View file

@ -1,6 +1,6 @@
# Neutralisierung
**Stand:** 2026-03-29
**Stand:** 2026-04-16
---
@ -18,7 +18,7 @@ Drei Quellen, von breit nach spezifisch:
|--------|-------------|-----------|
| **Feature-Instanz** | `DataNeutraliserConfig.enabled` (hat `featureInstanceId` + `mandateId`) | Alle Daten in dieser Feature-Instanz werden neutralisiert. Betrifft jeden Content-Einstieg innerhalb dieser Instanz. |
| **Chat-Workflow / Session** | `ServiceCenterContext.requireNeutralization` (gesetzt z. B. vom AI-Workspace oder Automation) | Dieser Workflow/Turn: jeder Content, der hier verarbeitet wird, wird neutralisiert. |
| **Dokument oder Quelle** | `FileItem.neutralize`, `DataSource.neutralize`, `FeatureDataSource.neutralize` | Dieses konkrete Objekt: Content daraus wird neutralisiert, egal ob die Feature-Instanz oder der Workflow es sonst fordern würden. |
| **Dokument oder Quelle** | `FileItem.neutralize`, `FileFolder.neutralize`, `DataSource.neutralize`, `FeatureDataSource.neutralize`, `FeatureDataSource.neutralizeFields` | Dieses konkrete Objekt: Content daraus wird neutralisiert, egal ob die Feature-Instanz oder der Workflow es sonst fordern würden. `FileFolder.neutralize` propagiert auf alle enthaltenen Dateien. `neutralizeFields` ermoeglicht Feld-Level-Maskierung bei DB-Queries. |
**Auswertung:** Irgendeine Quelle sagt `True` → neutralisieren. Keine Quelle kann eine andere aufheben. Es gibt kein `False`-Override, das ein `True` von woanders aushebelt.
@ -34,7 +34,9 @@ Am **Punkt der Content-Einspeisung** — dort wo Rohdaten zu verarbeitbarem Cont
| **Content-Extraktion** (Dokumente für KI-Verarbeitung) | Extrahierter Text/Tabellen werden neutralisiert. PDF, DOCX, XLSX, PPTX über `processFile` (Extract → neutralisieren → zurückschreiben). | Caller kennt die Quelle und deren Flag |
| **User-Prompt** (Chat-Eingabe) | Prompt-Text wird durch `processText` neutralisiert, wenn Feature-Instanz oder Workflow es fordern. | `mainServiceAi` prüft Context-Flag / Config |
| **DataSource-Download** | Flag wird auf erstellte `FileItem`s vererbt → bei Extraktion/Indexierung greift das File-Flag automatisch. | `mainServiceAgent._resolveDataSource` / `_downloadFromDataSource` |
| **FeatureDataSource-Abfrage** | Wenn eine `FeatureDataSource.neutralize=True` hat → Content aus diesem Sub-Agent-Call wird neutralisiert. | `mainServiceAgent._queryFeatureInstance` setzt `requireNeutralization=True` |
| **FeatureDataSource-Abfrage (Tabelle)** | Wenn eine `FeatureDataSource.neutralize=True` hat → Content aus diesem Sub-Agent-Call wird neutralisiert. | `mainServiceAgent._queryFeatureInstance` setzt `requireNeutralization=True` |
| **FeatureDataSource-Abfrage (Feld)** | `FeatureDataSource.neutralizeFields` definiert Spalten, deren Werte in `FeatureDataProvider` mit deterministischen Platzhaltern `[NEUT.<field>.<hash>]` ersetzt werden, bevor Daten den Sub-Agent erreichen. | `featureDataProvider._applyFieldNeutralization` |
| **Ordner-Neutralisierung** | `FileFolder.neutralize=True` propagiert auf alle enthaltenen Dateien (rekursiv). Neue/verschobene Dateien erben das Flag. Index-Purge + Re-Index wie bei einzelnen Dateien. | `routeDataFiles.updateFolderNeutralize` |
| **Workflow-Aktion `neutralizeData`** | Expliziter Neutralisierungs-Schritt auf bereits extrahierten ContentParts. | Workflow-Config + `NeutralizationConfig.enabled` |
**Was NICHT nochmal neutralisiert werden muss:**

View file

@ -1,5 +1,6 @@
<!-- status: plan -->
<!-- status: done -->
<!-- started: 2026-04-13 -->
<!-- completed: 2026-04-16 -->
<!-- component: gateway | frontend-nyla -->
# Database Health and Data Cleanup — Admin Page
@ -93,18 +94,18 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
**LLM: Opus 4.6** (Architektur-Entscheidungen, Cross-File-Refactoring)
- [ ] **DB-Registry** (`modules/shared/dbRegistry.py`)
- [x] **DB-Registry** (`modules/shared/dbRegistry.py`)
- `registerDatabase(dbName: str, configPrefix: str = "DB")` — oeffentliche API, registriert eine DB
- `_getRegisteredDatabases() -> Dict[str, str]` — intern, gibt alle registrierten DBs zurueck
- `_getConnectorForDb(dbName: str) -> DatabaseConnector` — intern, Factory fuer Connector
- Thread-safe (Lock oder atomare Operationen)
- `registerDatabase` ohne `_` Prefix weil explizit als oeffentliche API fuer andere Module gedacht
- [ ] **Selbst-Registrierung in allen Interfaces** (~13 Dateien)
- [x] **Selbst-Registrierung in allen Interfaces** (~13 Dateien)
- Jedes `interfaceDb*.py` und `interfaceFeature*.py` ruft `registerDatabase()` auf Modul-Ebene auf
- Kein Umbau der bestehenden Connector-Logik — nur ein zusaetzlicher Registrierungs-Call
- [ ] **Model-Registry** (`modules/datamodels/datamodelBase.py`)
- [x] **Model-Registry** (`modules/datamodels/datamodelBase.py`)
- `_MODEL_REGISTRY: Dict[str, Type[PowerOnModel]]` — automatisch via `__init_subclass__`
- `_getModelByTableName(tableName: str) -> Optional[Type[PowerOnModel]]` — Lookup
@ -112,7 +113,7 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
**LLM: Composer Fast** (repetitive Annotation-Arbeit, klares Pattern)
- [ ] **fk_target auf allen FK-Feldern** (~80-90 Felder in ~20 Dateien)
- [x] **fk_target auf allen FK-Feldern** (~80-90 Felder in ~20 Dateien)
- `modules/datamodels/datamodelMembership.py` — UserMandate, FeatureAccess, Junctions
- `modules/datamodels/datamodelRbac.py` — Role, AccessRule
- `modules/datamodels/datamodelFeatures.py` — FeatureInstance
@ -137,23 +138,26 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
**LLM: Opus 4.6** (komplexe Cross-DB-Logik, SQL-Generierung)
- [ ] **FK-Discovery** (`modules/shared/fkRegistry.py`)
- [x] **FK-Discovery** (`modules/shared/fkRegistry.py`)
- `_discoverFkRelationships() -> List[FkRelationship]` — scannt Model-Registry, liest `fk_target`
- Cached nach erstem Scan (Models aendern sich nicht zur Laufzeit)
- `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable`
- `FkRelationship` Dataclass: `sourceDb, sourceTable, sourceColumn, targetDb, targetTable, targetColumn`
- Table-to-DB Mapping automatisch aus `fk_target`-Annotationen + Catalog-Fallback
- [ ] **Orphan-Scanner** (`modules/system/databaseHealth.py`)
- [x] **Orphan-Scanner** (`modules/system/databaseHealth.py`)
- `_scanOrphans(dbFilter: Optional[str]) -> List[OrphanResult]`
- Same-DB: `SELECT COUNT(*) WHERE col NOT IN (SELECT id FROM parent)`
- Cross-DB: Parent-IDs laden, dann `WHERE col NOT IN (...)`
- Same-DB: `NOT EXISTS (SELECT 1 FROM parent WHERE parent.col = source.col)`
- Cross-DB: Parent-IDs laden, dann `NOT IN (unnest(...))`
- `_cleanOrphans(db, table, column) -> int` — loescht Orphans, gibt Count zurueck
- `_cleanAllOrphans() -> List[dict]` — alle Orphans bereinigen
- `_getTableStats(dbFilter: Optional[str]) -> List[TableStats]` — pg_stat_user_tables Query
- 5-Minuten-Cache fuer Orphan-Ergebnisse
### Phase 4: API-Endpunkte (Backend)
**LLM: Composer Fast** (Standard-Route-Pattern, klar definiert)
- [ ] **Route** (`modules/routes/routeAdminDatabaseHealth.py`)
- [x] **Route** (`modules/routes/routeAdminDatabaseHealth.py`)
- Prefix: `/api/admin/database-health`
- `GET /stats` — Tabellenstatistiken (optional `?db=...`)
- `GET /orphans` — Orphan-Scan (optional `?db=...`)
@ -161,13 +165,13 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
- `POST /orphans/clean-all` — Alle Orphans bereinigen
- Alle Endpunkte: SysAdmin-only via `requireSysAdminRole`
- [ ] **Router in app.py registrieren**
- [x] **Router in app.py registrieren**
### Phase 5: Navigation (Backend)
**LLM: Composer Fast** (einzelne Zeile in mainSystem.py)
- [ ] **Navigation-Eintrag** in `modules/system/mainSystem.py`
- [x] **Navigation-Eintrag** in `modules/system/mainSystem.py`
- Unter `admin-system-group`, order 98, sysAdminOnly
- Icon: `FaDatabase`, Path: `/admin/database-health`
@ -175,11 +179,11 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
**LLM: Opus 4.6** (neue Seite mit Tabs, FormGeneratorTable-Integration)
- [ ] **AdminDatabaseHealthPage.tsx** mit zwei Tabs
- Tab 1 "Statistiken": FormGeneratorTable mit DB, Tabelle, Rows, Total Size, Table Size, Index Size, Last Vacuum, Last Analyze. Sortierbar, Summary-Zeile oben.
- Tab 2 "Orphan Cleanup": FormGeneratorTable mit DB, Tabelle, FK-Spalte, Referenz, Orphans, Total Rows, Clean-Button. Filter "Nur Probleme". Batch-Clean.
- [ ] **Routing** in Frontend PAGE_REGISTRY
- [ ] **i18n** — alle Labels mit `t()` taggen
- [x] **AdminDatabaseHealthPage.tsx** mit zwei Tabs
- Tab 1 "Statistiken": Sortierbare Tabelle mit DB, Tabelle, Rows, Total Size, Index Size, Last Vacuum, Last Analyze. Summary-Zeile oben. DB-Filter.
- Tab 2 "Orphan Cleanup": Tabelle mit Source DB, Tabelle, FK-Spalte, Referenz (inkl. cross-db Badge), Orphans, Clean-Button. Filter "Nur Probleme". Batch-Clean-All.
- [x] **Routing** in Frontend PAGE_REGISTRY + App.tsx Route + admin/index.ts Export
- [x] **i18n** — alle Labels mit `t()` getaggt
### Querschnitt-Checks
@ -223,6 +227,6 @@ Viele FK-Beziehungen gehen ueber Datenbank-Grenzen hinweg (z.B. `AutoWorkflow.fe
## Abschluss
- [ ] b-reference/ aktualisiert (gateway/architecture.md — neuer Abschnitt "Database Health")
- [ ] TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup")
- [ ] Dieses Dokument → z-archive/ verschoben
- [x] b-reference/ aktualisiert (platform/database-architecture.md — neuer Abschnitt "Database Health")
- [x] TOPICS.md aktualisiert (neues Thema "Database Health / Data Cleanup")
- [x] Dieses Dokument → z-archive/ verschoben

View file

@ -0,0 +1,685 @@
<!-- status: archived -->
<!-- started: 2026-04-16 -->
<!-- completed: 2026-04-16 -->
<!-- component: gateway | frontend-nyla | platform -->
# Unified Document Model (UDM) — Dokumenten-Extraktion, Workflow ForEach & High-Volume
> **Abgeschlossen am 2026-04-16.** Technische Referenz: `b-reference/gateway/workflow.md` (UDM, Nodes, Engine), `b-reference/gateway/ai-agent.md` (UDM-Tools). Konzept: `c-work/0-ideas/unified-document-model.md`.
## Abschlussbericht
- **Umgesetzt:** UDM-Datenmodell und Bridge, Extraktions-Pipeline (inkl. Registry-Singleton, PDF-Memory), `context.extractContent`, Port-Typen und Executor-Routing (`context.*` → `ActionNodeExecutor`), `data.consolidate` / `ai.consolidate`, Loop (`level`, `concurrency`), `flow.merge` mit dynamischer Input-Anzahl, `data.filter` mit UDM-Content-Type-Presets, Engine (Concurrency, StepLog-Batching, Streaming-Aggregate, Progress), `meta.usesAi` und AI-Badge im Editor, Agent-Tools (`getUdmStructure`, `walkUdmBlocks`, `filterUdmByType`), Referenzdoku in `b-reference/` und Eintrag in `TOPICS.md`.
- **Tests im Repo (Auszug):** `gateway/tests/unit/datamodels/test_udm_models.py`, `test_udm_bridge.py`, `gateway/tests/integration/extraction/test_extract_udm_pipeline.py`, `gateway/tests/integration/workflows/test_execute_graph_loop_aggregate_consolidate.py`, `gateway/tests/unit/nodeDefinitions/test_usesai_flag.py`, `gateway/tests/unit/serviceAgent/test_udm_agent_tools.py`.
- **Backlog / optional:** Erweiterung des ursprünglichen Testplan-Rasters (High-Volume-Referenzen, Fan-out/Merge-Integration, Concurrency-Benchmark, Load-Test 10k) nach Bedarf; UX wie explizite Loop-Body-Hervorhebung oder dedizierte NodeConfig-Komponenten können nachgelagert werden.
## Grundlage: UDM-Konzept
Dieses Feature basiert auf dem **Unified Document Model (UDM)** — einer generischen, formatunabhängigen 3-Ebenen-Baumstruktur für Dokumenten-Extraktion. Das vollständige Konzept mit Datenmodell, Format-Mappings (PDF, DOCX, PPTX, XLSX, HTML), Workflow-Integration, Archive-Handling und JSON-Beispielen ist hier definiert:
**`c-work/0-ideas/unified-document-model.md`**
**Kernprinzipien des UDM:**
- **3-Ebenen-Garantie:** Jedes Dokument hat `Document``StructuralNode[]``ContentBlock[]`
- **Einheitliche Blattknoten:** Alle atomaren Inhalte sind `ContentBlock`-Objekte mit identischer Struktur
- **Generische Traversierung:** Workflow-Nodes arbeiten formatunabhängig über dieselbe Baumstruktur
- **Keine formatspezifischen Zwischenschichten:** Konzepte wie "Paragraph", "Row", "Cell" werden in `ContentBlock.attributes` absorbiert
| Ebene | Typ | Beschreibung |
|-------|-----|-------------|
| Level 1 | `Document` | Wurzelknoten pro Quelldatei |
| Level 2 | `StructuralNode` | Seite, Abschnitt, Slide oder Sheet |
| Level 3 | `ContentBlock` | Atomarer Inhalt: Text, Bild, Tabelle, Code, Media, Link, Formel |
Dieser Plan beschreibt **die Umsetzung** des UDM-Konzepts im Gateway, die nötigen Workflow-Nodes, und die High-Volume-Skalierbarkeit.
---
## Beschreibung und Kontext
Das bestehende Dokumenten-Extraktionssystem arbeitet mit `ContentPart` / `ContentExtracted` als flacher Liste. Das UDM ersetzt dieses Modell durch eine **hierarchische Baumstruktur**, die es erlaubt, über Level-Attribute (Seiten, Sections, Sheets, Slides) generisch zu iterieren.
**Business-Treiber:** Workflows benötigen die Fähigkeit, ein Dokument zu extrahieren und dann **pro Struktureinheit** (z.B. pro PDF-Seite) eine Kette von Verarbeitungsschritten auszuführen — und am Ende die Teilergebnisse zu konsolidieren. Zusätzlich muss das System mit **grossen Datenmengen** umgehen können (z.B. ZIP mit 10.000 PDFs).
**Heutige Lücken:**
1. Es gibt **keinen Extract-Node** im Graphical Editor — `context.extractContent` existiert nur als Workspace-/Agent-Action, nicht als visuellen Node
2. Das Extraktionsmodell liefert keine hierarchische Baum-Struktur (nur flache `ContentPart`-Liste)
3. Der `flow.loop`-Node hat keine Sub-Workflow-Modellierung — der Loop-Body wird nur über Graph-Topologie implizit erkannt
4. Es gibt keinen dedizierten **Consolidate**-Schritt für strukturierte Zusammenführung nach ForEach
5. Die Engine ist **nicht skalierbar** für >1000 Iterationen (Memory, DB-Last, keine Parallelität)
6. Nodes im Editor haben **keine visuelle Kennzeichnung**, ob sie AI nutzen oder deterministisch arbeiten
**Abhängigkeiten:** AI-Agent-Tools (`_documentTools.py`), Workflow-Engine (`executionEngine.py`), Graphical Editor (Frontend), Extraktion (`serviceExtraction`).
**Risiko bei Nicht-Umsetzung:** Dokument-intensive Workflows (Trustee, Compliance-Audit, Massendokumentverarbeitung) bleiben manuell oder erfordern Custom-Code pro Use Case.
---
## Fokus und kritische Details
- **Migration des Extraktionsmodells:** `ContentPart` → UDM muss rückwärtskompatibel sein. Bestehende Konsumenten (`ChatContentExtracted`, Agent-Tools, Neutralisierung) dürfen nicht brechen.
- **Loop-Body mit parallelen Pfaden:** Fan-out von Loop-Output zu mehreren Nodes + Merge pro Iteration funktioniert in der Engine bereits (Topo-Sort garantiert Reihenfolge), aber `flow.merge` hat heute fix 2 Inputs.
- **High-Volume-Skalierbarkeit:** Bei 10k Iterationen entstehen Engpässe bei Memory (Base64-Bilder in nodeOutputs), DB (AutoStepLog pro Body-Node pro Iteration), und Laufzeit (sequentielle Verarbeitung).
- **Extractor-Performance:** `extractorContainer.py` erstellt pro Datei im ZIP eine neue `ExtractorRegistry()`-Instanz (Auto-Discovery). Bei 10k Dateien ist das ein massiver Overhead.
- **PDF-Memory:** `extractorPdf.py` lädt via `buf.getvalue()` eine Kopie des gesamten PDFs — doppelter Memory-Verbrauch pro Datei.
---
## Ziel und Nicht-Ziele
### Ziele
1. **`context.extractContent`-Node** im Graphical Editor — reine Strukturextraktion OHNE AI
2. **UDM-Datenmodell** (`Document`, `StructuralNode`, `ContentBlock`, `Archive`) als Pydantic-Modelle
3. **Extractor-Adapter:** Bestehende Extractors liefern zusätzlich UDM-Output; Bridge `ContentPart` ↔ UDM
4. **ForEach-Workflow-Pattern:** Loop über UDM-Struktureinheiten mit parallelen Pfaden im Body und Merge pro Iteration
5. **Consolidate-Nodes:** `data.consolidate` (deterministisch) + `ai.consolidate` (AI-gestützt) für strukturierte Zusammenführung
6. **High-Volume-Fähigkeit:** ZIP mit 10.000+ PDFs verarbeitbar durch Streaming-Extraktion, Loop-Concurrency und StepLog-Batching
7. **Neue Port-Typen:** `UdmDocument`, `UdmNodeList`, `ConsolidateResult`
8. **Agent-Tools:** Bestehende `browseContainer`/`readContentObjects` um UDM-Traversierung erweitern
9. **AI-Kennzeichnung auf Nodes:** Jeder Node im Editor zeigt visuell, ob er AI nutzt (`meta.usesAi`) — Kostentransparenz und schnelle Übersicht
### Explizit NICHT
- Neue Dateiformate (Markdown, LaTeX, etc.) — kommt separat
- Visuelles Sub-Graph-Grouping (Compound-Nodes) im Editor — wird mit impliziter Body-Erkennung gelöst
- Breaking Change an `ContentPart`-API — Bridge-Layer garantiert Kompatibilität
---
## Betroffene Module
### Gateway
| Modul | Änderung |
|-------|---------|
| `datamodels/datamodelUdm.py` | **Neue Datei**: Pydantic-Klassen `UdmDocument`, `UdmStructuralNode`, `UdmContentBlock`, `UdmArchive`, `UdmPosition`, `UdmMetadata`. Bridge-Funktionen. `UdmContentBlock.raw` optional mit `fileRef`-Alternative für Lazy-Loading |
| `datamodels/datamodelExtraction.py` | `ExtractionOptions.outputFormat` Feld (`"parts"` / `"udm"` / `"both"`) |
| `serviceExtraction/subRegistry.py` | `Extractor`-Interface erweitern: `extractToUdm()`. **ExtractorRegistry als Singleton cachen** (heute: neue Instanz pro Datei im ZIP) |
| `serviceExtraction/extractors/extractorPdf.py` | UDM-Output pro Seite. **Fix `buf.getvalue()`** → direkt `BytesIO` an fitz übergeben (halber Memory) |
| `serviceExtraction/extractors/extractorDocx.py` | UDM-Output pro Section (Heading-basiert) |
| `serviceExtraction/extractors/extractorPptx.py` | UDM-Output pro Slide |
| `serviceExtraction/extractors/extractorXlsx.py` | UDM-Output pro Sheet |
| `serviceExtraction/extractors/extractorHtml.py` | UDM-Output pro semantischem Bereich |
| `serviceExtraction/extractors/extractorContainer.py` | **Lazy-Modus**: ZIP-Inhaltsverzeichnis liefern statt alle Dateien extrahieren. Registry-Singleton statt `new ExtractorRegistry()` pro Datei |
| `serviceExtraction/mainServiceExtraction.py` | `extractContent()` mit UDM-Option. **Streaming-Modus** für grosse Archive |
| `features/graphicalEditor/nodeDefinitions/context.py` | **Neue Datei**: Node `context.extractContent` |
| `features/graphicalEditor/nodeDefinitions/data.py` | Neuer Node: `data.consolidate` (deterministisch) |
| `features/graphicalEditor/nodeDefinitions/ai.py` | Neuer Node: `ai.consolidate` (AI-gestützt) |
| `features/graphicalEditor/nodeDefinitions/flow.py` | `flow.loop`: Parameter `level` (UDM-Ebene) + `concurrency` (parallele Iterationen). `flow.merge`: dynamische Input-Anzahl |
| `features/graphicalEditor/nodeDefinitions/__init__.py` | `CONTEXT_NODES` in `STATIC_NODE_TYPES` aufnehmen |
| `features/graphicalEditor/portTypes.py` | Neue Port-Typen: `UdmDocument`, `UdmNodeList`, `ConsolidateResult`. Input-Extraktoren dazu |
| `workflows/automation2/executors/flowExecutor.py` | Loop: UDM-Array-Auflösung, Concurrency-Support |
| `workflows/automation2/executors/dataExecutor.py` | `data.consolidate`-Logik (deterministisch). Filter: UDM-Content-Type-Presets |
| `workflows/automation2/executionEngine.py` (`_getExecutor`) | Branch `context.*` hinzufügen → `ActionNodeExecutor` |
| `workflows/automation2/executionEngine.py` | **Loop-Concurrency** (N Items parallel), **StepLog-Batching** (bei >100 Iterationen), **Streaming-Aggregate** (periodisch flushen) |
| `workflows/methods/methodContext/actions/extractContent.py` | UDM-Output-Option, Lazy-Modus für Archive |
| `workflows/methods/methodAi/actions/consolidate.py` | **Neue Datei**: Action `consolidate` für `ai.consolidate` (LLM-Call: summarize, classify, semantic merge) |
| `serviceAgent/coreTools/_documentTools.py` | UDM-Tools: `walkUdmBlocks`, `filterUdmByType`, `getUdmStructure` |
### Frontend
| Modul | Änderung |
|-------|---------|
| `FlowEditor/nodes/context/ExtractContentNodeConfig.tsx` | **Neue Datei**: Config-UI für `context.extractContent` |
| `FlowEditor/nodes/loop/LoopNodeConfig.tsx` | UDM-Level-Selektor + Concurrency-Slider |
| `FlowEditor/nodes/shared/LoopItemsSelect.tsx` | UDM-Structural-Level als Quelle |
| `FlowEditor/nodes/shared/types.ts` | Neue Node-Config-Types |
| `FlowEditor/FlowCanvas.tsx` | Visuelle Loop-Body-Markierung (farbiger Hintergrund). **AI-Badge** auf Nodes die AI nutzen |
| `FlowEditor/NodeSidebar.tsx` | Neue Nodes in Palette (`context.extractContent`, `data.consolidate`, `ai.consolidate`). AI-Badge in Palette |
| `FlowEditor/nodes/shared/AiBadge.tsx` | **Neue Datei**: Wiederverwendbare AI-Badge-Komponente (`usesAi: boolean`) |
| `api/workflowApi.ts` | NodeType-Erweiterung |
### DB-Migration
Nein — UDM-Daten fliessen als JSON durch Workflow-Context/nodeOutputs, keine neue DB-Tabelle nötig.
---
## Architektur-Design
### A) UDM-Datenmodell (Gateway)
```python
class UdmMetadata(BaseModel):
title: Optional[str] = None
author: Optional[str] = None
createdAt: Optional[str] = None
modifiedAt: Optional[str] = None
sourcePath: str = ""
tags: List[str] = Field(default_factory=list)
custom: Dict[str, Any] = Field(default_factory=dict)
class UdmBoundingBox(BaseModel):
x: float; y: float; width: float; height: float
unit: Literal["px", "pt", "mm"] = "pt"
class UdmPosition(BaseModel):
index: int
page: Optional[int] = None
row: Optional[int] = None
col: Optional[int] = None
bbox: Optional[UdmBoundingBox] = None
class UdmContentBlock(BaseModel):
id: str
contentType: Literal["text", "image", "table", "code", "media", "link", "formula"]
raw: str = ""
fileRef: Optional[str] = None # Lazy-Loading: Referenz statt inline Base64
mimeType: Optional[str] = None
language: Optional[str] = None
attributes: Dict[str, Any] = Field(default_factory=dict)
position: UdmPosition = Field(default_factory=lambda: UdmPosition(index=0))
metadata: UdmMetadata = Field(default_factory=UdmMetadata)
class UdmStructuralNode(BaseModel):
id: str
role: Literal["page", "section", "slide", "sheet"]
index: int
label: Optional[str] = None
metadata: UdmMetadata = Field(default_factory=UdmMetadata)
children: List[UdmContentBlock] = Field(default_factory=list)
class UdmDocument(BaseModel):
id: str
role: Literal["document"] = "document"
sourceType: Literal["pdf", "docx", "pptx", "xlsx", "html"]
sourcePath: str = ""
metadata: UdmMetadata = Field(default_factory=UdmMetadata)
children: List[UdmStructuralNode] = Field(default_factory=list)
class UdmArchive(BaseModel):
id: str
role: Literal["archive"] = "archive"
sourceType: Literal["zip", "tar", "gz"]
sourcePath: str = ""
metadata: UdmMetadata = Field(default_factory=UdmMetadata)
children: List[Union[UdmArchive, UdmDocument]] = Field(default_factory=list)
```
Schlüssel-Erweiterung für High-Volume: `UdmContentBlock.fileRef` — statt Base64-Daten inline zu speichern, kann eine Datei-Referenz (z.B. `fileId` oder temp-Pfad) hinterlegt werden. Der Inhalt wird erst on-demand geladen, wenn ein downstream Node ihn braucht. Bei 10k PDFs spart das mehrere GB RAM.
### B) Bridge: ContentPart ↔ UDM
```python
def _contentPartsToUdm(extracted: ContentExtracted, sourceType: str, sourcePath: str) -> UdmDocument:
"""Konvertiert flache ContentPart-Liste in UDM-Baum.
Groupiert nach parentId oder typeGroup zu StructuralNodes."""
def _udmToContentParts(document: UdmDocument) -> ContentExtracted:
"""Konvertiert UDM-Baum zurück in flache ContentPart-Liste.
Für Rückwärtskompatibilität."""
```
### C) Neuer Node: `context.extractContent` (Strukturextraktion OHNE AI)
**Problem heute:** `context.extractContent` existiert nur als Method-Action für Workspace/Agent. Im Graphical Editor gibt es keinen Node, um ein Dokument rein strukturell zu zerlegen.
**Lösung:** Ein neuer Node `context.extractContent` in der Kategorie `context`:
1. Dokument(e) entgegennehmen (Input: `DocumentList`)
2. Reine Extraktion (kein AI-Call, nur Parser)
3. UDM-Struktur als Output
4. Downstream gezielt nutzbar: Loop → Filter → AI nur für bestimmte Blöcke
#### Node-Definition
```python
{
"id": "context.extractContent",
"category": "context",
"label": t("Inhalt extrahieren"),
"description": t("Dokumentstruktur extrahieren ohne KI (Seiten, Abschnitte, Bilder, Tabellen)"),
"parameters": [
{"name": "outputDetail", "type": "string", "required": False, "frontendType": "select",
"frontendOptions": {"options": ["full", "structure", "references"]},
"description": t("Detailgrad"), "default": "full"},
{"name": "includeImages", "type": "boolean", "required": False, "frontendType": "checkbox",
"description": t("Bilder extrahieren"), "default": True},
{"name": "includeTables", "type": "boolean", "required": False, "frontendType": "checkbox",
"description": t("Tabellen extrahieren"), "default": True},
],
"inputs": 1,
"outputs": 1,
"inputPorts": {0: {"accepts": ["DocumentList", "Transit"]}},
"outputPorts": {0: {"schema": "UdmDocument"}},
"meta": {"icon": "mdi-file-tree-outline", "color": "#00897B"},
"_method": "context",
"_action": "extractContent",
}
```
#### Parameter `outputDetail`
| Wert | Beschreibung | Use Case |
|------|-------------|----------|
| `full` | Volle UDM-Struktur inkl. Rohdaten (Text, Base64-Bilder, JSON-Tabellen) | Standard — alles downstream verfügbar. Bis ~100 Dokumente. |
| `structure` | Nur Baum-Skelett: Document → StructuralNodes → ContentBlock-Metadaten (ohne `raw`) | Schnelle Vorschau, Routing-Entscheidungen |
| `references` | Datei-Referenzen statt Inline-Daten (`fileRef` statt `raw`). Inhalt wird on-demand im Loop geladen | **High-Volume**: 1000+ Dokumente. Spart RAM. |
### D) ForEach/Consolidate Workflow-Pattern
#### Einfacher Fall: 1 PDF, pro Seite verarbeiten
```
[Upload] → [Extract] → [Loop (pro Seite)] → [AI] → [Aggregate] → [Consolidate]
```
#### Parallele Pfade im Loop-Body: Bild + Text pro Seite
```
[Upload] → [Extract] → [Loop (pro Seite)]
├→ [Filter: Bilder] → [AI Vision] ──┐
└→ [Filter: Text] → [AI Text] ──┤
[Merge (pro Seite)]
[Aggregate]
[Consolidate (alle Seiten → Tabelle)] ←──┘
```
**Warum das heute schon funktioniert:**
- Fan-out: Ein Output-Port kann mehrere Connections haben (Target-Inputs sind limitiert auf 1, Source-Outputs nicht)
- `getLoopBodyNodeIds()` (BFS) findet alle Nodes in beiden Pfaden
- `topoSort` ordnet: Filter + AI vor Merge (weil Merge von beiden abhängt)
- Body-Nodes werden sequentiell in Topo-Sort-Reihenfolge abgearbeitet → Merge hat beide Inputs
**Umgesetzt (Plan):**
- `flow.merge`: dynamische Input-Anzahl (25 via Parameter `inputCount`)
- `data.filter`: UDM-Content-Type-Presets (Filter auf `contentType`, `structuralNode.index`, `attributes.*`)
#### High-Volume-Fall: ZIP mit 10.000 PDFs
```
[Upload ZIP] → [Extract (references)] → [Loop (pro Dokument, concurrency: 10)]
[Lazy-Extract (1 PDF, Seite 1)]
[Filter: Bild Seite 1]
[AI Vision → CSV-Zeile]
[Aggregate (CSV-Zeilen)]
[Consolidate (mode: table → grosses CSV)] ←──┘
```
**Schlüssel:** `outputDetail: "references"` — der erste Extract liefert nur eine leichtgewichtige Liste von 10k Datei-Referenzen (~10 KB statt ~4 GB). Pro Loop-Iteration wird nur 1 PDF on-demand geladen, verarbeitet und wieder freigegeben.
#### High-Volume: ZIP mit Fan-out + Merge pro Dokument
Dasselbe Muster wie bei einem einzelnen PDF (zwei parallele Body-Pfade + Merge pro Iteration), angewendet auf **pro Archiv-Eintrag** statt pro Seite:
```
[Upload ZIP] → [Extract (references)] → [Loop (pro Dokument, concurrency: N)]
├→ [Lazy-Extract / Seite 1] → [AI Vision] ──┐
└→ [Filter: Text] → [AI Text] ────────────┤
[Merge]
[Aggregate]
[Consolidate] ←──────────────────────────────────────────────────────────────────────────┘
```
Pro Iteration gilt: Fan-out und Topo-Sort im Loop-Body wie oben; der erste Extract liefert nur Referenzen, sodass nicht alle PDFs gleichzeitig im RAM liegen.
### E) Flow.loop Erweiterungen
```python
# Neuer Parameter: UDM-Level
{
"name": "level",
"type": "string",
"required": False,
"frontendType": "select",
"frontendOptions": {"options": ["auto", "documents", "structuralNodes", "contentBlocks"]},
"description": t("UDM-Iterationsebene"),
"default": "auto",
}
# Neuer Parameter: Parallele Iterationen
{
"name": "concurrency",
"type": "number",
"required": False,
"frontendType": "number",
"frontendOptions": {"min": 1, "max": 20},
"description": t("Parallele Iterationen"),
"default": 1,
}
```
- `level: auto``items`-Pfad wie bisher
- `level: documents``archive.children` (Documents)
- `level: structuralNodes``document.children` (Pages/Sections/Slides/Sheets)
- `level: contentBlocks``structuralNode.children` (Text/Image/Table/...)
- `concurrency: 1` → sequentiell wie heute
- `concurrency: 10` → 10 Iterationen gleichzeitig via `asyncio.Semaphore`
### F) flow.merge Erweiterung
```python
# Neuer Parameter: Dynamische Input-Anzahl
{
"name": "inputCount",
"type": "number",
"required": False,
"frontendType": "number",
"frontendOptions": {"min": 2, "max": 5},
"description": t("Anzahl Eingänge"),
"default": 2,
}
```
`inputs` und `inputPorts` werden dynamisch basierend auf `inputCount` generiert. Backend: `nodeRegistry.py` muss dynamische Input-Ports unterstützen.
### G) High-Volume Engine-Optimierungen
#### Problem-Analyse (10.000 Iterationen)
| Engpass | Heute | Lösung |
|---------|-------|--------|
| **Memory: Base64-Bilder** | Alle Bilder als Strings in `nodeOutputs` | `fileRef` statt inline `raw`. On-Demand-Loading |
| **Memory: items-Array** | Gesamte `items`-Liste im Loop-Node-Output | Bei `references`-Modus: nur IDs, ~100 Bytes pro Item |
| **Memory: Aggregate** | `_aggregateAccumulators` wächst unbegrenzt in-Memory | **Streaming-Aggregate**: bei >1000 Items periodisch in temp-Storage flushen |
| **DB: AutoStepLog** | Insert + Update pro Body-Node pro Iteration | **StepLog-Batching**: bei >100 Iterationen nur jede N-te loggen + Summary am Ende |
| **DB: updateRun** | Am Ende: gesamte nodeOutputs als JSONB | Aggregate-Daten als File-Referenz statt inline |
| **CPU: ExtractorRegistry** | Neue Instanz pro Datei im ZIP (`_addFilePart`) | **Singleton-Pattern**: einmal erstellen, wiederverwenden |
| **CPU: PDF-Memory** | `buf.getvalue()` kopiert gesamtes PDF für PyMuPDF | Direkt `BytesIO` übergeben, keine Kopie |
| **Laufzeit: Sequentiell** | 10k × 3s = 8.3h | `concurrency: 10` → ~50 Min |
| **SSE: StepEvents** | Event pro Step-Änderung | Bei High-Volume: nur Progress-Summary (z.B. "Iteration 5000/10000") |
#### StepLog-Batching (executionEngine.py)
```python
# Heuristik: ab 100 Iterationen → Batch-Modus
if len(items) > STEPLOG_BATCH_THRESHOLD:
stepLogMode = "batch" # nur jede 100. Iteration + Fehler + Summary
else:
stepLogMode = "full" # wie heute: jeder Step einzeln
```
#### Loop-Concurrency (executionEngine.py)
```python
concurrency = (node.get("parameters") or {}).get("concurrency", 1)
semaphore = asyncio.Semaphore(concurrency)
async def _processIteration(idx, item):
async with semaphore:
nodeOutputs_local = dict(nodeOutputs) # lokale Kopie pro Iteration
nodeOutputs_local[nodeId] = {"currentItem": item, "currentIndex": idx, ...}
for body_node in body_ordered:
...
tasks = [_processIteration(idx, item) for idx, item in enumerate(items)]
results = await asyncio.gather(*tasks)
```
Achtung: Bei `concurrency > 1` braucht jede Iteration **eigene** `nodeOutputs`, da Body-Nodes sich sonst gegenseitig überschreiben. Aggregate-Accumulation muss thread-safe sein (Lock oder Queue).
#### Streaming-Aggregate
```python
AGGREGATE_FLUSH_THRESHOLD = 1000
# Im Loop-Body:
_aggregateAccumulators[bnid].extend(accItems)
if len(_aggregateAccumulators[bnid]) >= AGGREGATE_FLUSH_THRESHOLD:
_flushAggregateToTemp(bnid, _aggregateAccumulators[bnid])
_aggregateAccumulators[bnid] = []
```
Temp-Storage: In-Memory-Buffer oder temp-File. Am Ende: alle Chunks zusammenführen.
### H) AI-Kennzeichnung auf Nodes im Editor
Jeder Node im Editor soll **sofort sichtbar** machen, ob er AI nutzt oder deterministisch arbeitet. Dies ist wichtig für Kostentransparenz und Workflow-Design.
#### Umsetzung
**Backend:** Jede Node-Definition bekommt ein neues Feld `meta.usesAi` (Boolean):
```python
# Beispiel: ai.prompt → AI
"meta": {"icon": "mdi-robot", "color": "#9C27B0", "usesAi": True}
# Beispiel: data.filter → kein AI
"meta": {"icon": "mdi-filter-outline", "color": "#607D8B", "usesAi": False}
```
Durch den Split von Nodes mit gemischtem AI-Verhalten (z.B. `data.consolidate` vs `ai.consolidate`) ist jeder Node **eindeutig** — kein `"optional"` nötig.
**Frontend:** Im `FlowCanvas.tsx` (Zeile ~846-852, Node-Rendering) wird ein kleines Badge/Indicator angezeigt:
- `usesAi: true` → AI-Badge (z.B. kleines "AI"-Label oder Blitz-Icon oben rechts am Node)
- `usesAi: false` oder nicht gesetzt → kein Badge
Gleiche Kennzeichnung in der **NodeSidebar** (Palette), damit der User schon beim Drag&Drop sieht, welche Nodes AI nutzen.
#### Zuordnung aller bestehenden Nodes
| Node | `usesAi` | Begründung |
|------|----------|------------|
| `trigger.*` | `false` | Reine Auslöser |
| `input.*` | `false` | Formulare, manuelle Eingabe |
| `flow.ifElse` | `false` | Bedingungslogik |
| `flow.switch` | `false` | Bedingungslogik |
| `flow.loop` | `false` | Iteration |
| `flow.merge` | `false` | Zusammenführung |
| `data.aggregate` | `false` | Sammeln |
| `data.transform` | `false` | Feld-Mapping |
| `data.filter` | `false` | Filterlogik |
| `data.consolidate` (NEU) | `false` | Deterministisch: merge, concat, table, CSV-Join |
| `ai.consolidate` (NEU) | `true` | AI-gestützt: summarize, classify, semantic merge |
| `context.extractContent` (NEU) | `false` | Reine Parser-Arbeit |
| `ai.prompt` | `true` | LLM-Call |
| `ai.webResearch` | `true` | Web-Suche + LLM |
| `ai.summarizeDocument` | `true` | LLM-Zusammenfassung |
| `ai.translateDocument` | `true` | LLM-Übersetzung |
| `ai.convertDocument` | `true` | LLM-Konvertierung |
| `ai.generateDocument` | `true` | LLM-Generierung |
| `ai.generateCode` | `true` | LLM-Codegenerierung |
| `email.*` | `false` | E-Mail-Operationen |
| `sharepoint.*` | `false` | SharePoint-Operationen |
| `clickup.*` | `false` | ClickUp-Operationen |
| `file.create` | `false` | Datei-Erstellung |
| `trustee.refreshAccountingData` | `false` | Datenimport aus externem System |
| `trustee.extractFromFiles` | `true` | AI-Extraktion (Prompt-basiert, Dokumenttyp-Erkennung) |
| `trustee.processDocuments` | `false` | TrusteeDocument/Position aus Extraktionsergebnis erstellen |
| `trustee.syncToAccounting` | `false` | Übertragung in Buchhaltung |
---
## Entscheidungen
| Datum | Entscheidung | Begründung |
|-------|-------------|------------|
| 2026-04-16 | `context.extractContent` als eigener Node im Editor | Heute fehlt komplett. Fundament für gezielten AI-Einsatz. Neue Kategorie `context`. |
| 2026-04-16 | Extraktion ist KEIN AI-Call | Reine Parser-Arbeit. AI-Kosten erst bei explizitem AI-Node. Kostenfreie Vorverarbeitung. |
| 2026-04-16 | UDM als In-Memory-Format, keine DB-Tabelle | UDM fliesst als JSON durch nodeOutputs. Vermeidet Migration. |
| 2026-04-16 | Bridge ContentPart ↔ UDM statt Breaking Change | Alle bestehenden Konsumenten bleiben funktional. |
| 2026-04-16 | Kein Compound-Node / Sub-Graph | Zu hoher Aufwand. Bestehende Body-Erkennung via BFS genügt. |
| 2026-04-16 | Consolidate als Split in `data.consolidate` + `ai.consolidate` | `data.*` = deterministisch, `ai.*` = AI-gestützt. Kein `"optional"` Badge nötig — jeder Node ist eindeutig. |
| 2026-04-16 | `level`-Parameter am Loop | Weniger Nodes in der Palette, einfachere UX. |
| 2026-04-16 | Fan-out im Loop-Body nutzt bestehendes Modell | Engine unterstützt es bereits: Topo-Sort + sequentielle Body-Abarbeitung garantieren korrekte Reihenfolge. Kein Engine-Umbau nötig. |
| 2026-04-16 | `outputDetail: "references"` für High-Volume | 10k PDFs als Referenzliste (~10 KB) statt vollständig extrahiert (~4 GB). On-Demand-Loading pro Iteration. |
| 2026-04-16 | Loop-Concurrency als opt-in Parameter | Default bleibt `1` (sequentiell, deterministisch). Power-User setzen höher für Durchsatz. |
| 2026-04-16 | StepLog-Batching ab Schwellwert | 50k DB-Inserts bei 10k Iterationen ist nicht tragbar. Batch-Modus loggt nur Summary + Fehler. |
| 2026-04-16 | AI-Badge auf allen Nodes im Editor | Kostentransparenz. User sieht sofort, welche Nodes AI-Credits verbrauchen. `meta.usesAi` als Boolean. |
| 2026-04-16 | Nodes mit gemischtem AI-Verhalten splitten | `data.consolidate` (deterministisch) + `ai.consolidate` (AI). Kein "optional"-Badge nötig — jeder Node ist eindeutig `true` oder `false`. |
---
## Umsetzungs-Checkliste
### Phase 1: UDM-Datenmodell & Bridge (Gateway)
> **Cursor-Empfehlung:** Composer (Fast) reicht. Reine Pydantic-Modelle und Utility-Funktionen — klar definiert, wenig Kontext nötig.
- [x] Pydantic-Modelle in `datamodels/datamodelUdm.py` (neue Datei)
- [x] `UdmContentBlock.fileRef` Feld für Lazy-Loading
- [x] Bridge-Funktionen `_contentPartsToUdm()` / `_udmToContentParts()`
- [x] Unit-Tests für Modelle und Bridge
- [x] `ExtractionOptions.outputFormat` Feld (`"parts"` | `"udm"` | `"both"`)
### Phase 2: Extractor-Adapter (Gateway)
> **Cursor-Empfehlung:** Composer (Fast) reicht. Pro Extractor ein isoliertes File mit klarem Pattern. Memory-Fixes (`buf.getvalue()`, Registry-Singleton) sind punktuelle Änderungen.
- [x] `extractorPdf.py` → UDM-Output + Fix `buf.getvalue()` (Memory)
- [x] `extractorDocx.py` → UDM-Output
- [x] `extractorPptx.py` → UDM-Output
- [x] `extractorXlsx.py` → UDM-Output
- [x] `extractorHtml.py` → UDM-Output
- [x] `extractorContainer.py` → Lazy-Modus (Inhaltsverzeichnis statt alles extrahieren)
- [x] `extractorContainer.py``ExtractorRegistry` Singleton statt `new` pro Datei
- [x] `mainServiceExtraction.py``extractContent()` mit UDM-Option
- [x] Integration-Tests pro Format
### Phase 3: Extract-Node — Strukturextraktion ohne AI (Gateway + Frontend)
> **Cursor-Empfehlung:** **Opus 4.6 empfohlen.** Neue Datei + Integration in mehrere bestehende Systeme (NodeDefinitions, PortTypes, Executor-Mapping, Frontend-Komponente). Erfordert Verständnis der gesamten Node-Architektur über ~6 Dateien hinweg.
- [x] `nodeDefinitions/context.py` → Node `context.extractContent`
- [x] `nodeDefinitions/__init__.py``CONTEXT_NODES` in `STATIC_NODE_TYPES`
- [x] `UdmDocument` Port-Typ in `portTypes.py`
- [x] Input-Extractor `_extractUdmDocument()` in `portTypes.py`
- [x] `executionEngine.py``_getExecutor()`: Branch `context.*``ActionNodeExecutor` hinzufügen (heute nur: `ai.`, `email.`, `sharepoint.`, `clickup.`, `file.`, `trustee.`)
- [x] `ActionNodeExecutor``context.extractContent` via `_method`/`_action` Mapping (nutzt bestehendes `MethodContext.extractContent`)
- [x] `extractContent.py` → UDM-Output-Modus + `outputDetail: "references"` Modus
- [x] Frontend: `ExtractContentNodeConfig.tsx`
- [x] Frontend: Node in Sidebar-Palette unter Kategorie "Kontext"
### Phase 4: Workflow Nodes — ForEach, Merge & Consolidate (Gateway + Frontend)
> **Cursor-Empfehlung:** **Opus 4.6 empfohlen.** Komplexeste Phase — berührt Execution-Engine, Port-System, Flow-Executor, Data-Executor und Frontend gleichzeitig. Concurrency-Design erfordert tiefes Verständnis der bestehenden Loop-Logik und Race-Conditions.
- [x] `data.consolidate` Node-Definition (`nodeDefinitions/data.py`) — deterministisch: merge, concat, table, CSV-Join
- [x] `ai.consolidate` Node-Definition (`nodeDefinitions/ai.py`) — AI-gestützt: summarize, classify, semantic merge
- [x] `ConsolidateResult` Port-Typ (`portTypes.py`)
- [x] `DataExecutor` → Consolidate-Logik (`dataExecutor.py`)
- [x] `methodAi/actions/consolidate.py` → neue Action-Datei (LLM-Call mit aggregierten Daten)
- [x] `methodAi/methodAi.py` → Action `consolidate` registrieren
- [x] `ai.consolidate` wird über bestehenden `ActionNodeExecutor` geroutet — kein eigener Executor nötig
- [x] `data.filter` → UDM-Content-Type-Presets (Filter auf `contentType`, `index`, `attributes`)
- [x] `flow.loop``level`-Parameter (`nodeDefinitions/flow.py`)
- [x] `flow.loop``concurrency`-Parameter (`nodeDefinitions/flow.py`)
- [x] `flow.merge` → dynamische Input-Anzahl `inputCount` (2-5)
- [x] `FlowExecutor._loop()` → UDM-Level-Auflösung (`flowExecutor.py`)
- [x] `UdmNodeList` Port-Typ (`portTypes.py`)
- [x] Frontend: `DataConsolidateNodeConfig.tsx` (deterministisch)
- [x] Frontend: `AiConsolidateNodeConfig.tsx` (AI-gestützt)
- [x] Frontend: Loop-NodeConfig → UDM-Level-Selector + Concurrency-Slider
- [x] Frontend: Merge-NodeConfig → Input-Count-Selector
- [x] Frontend: Visuelle Loop-Body-Markierung im Canvas
### Phase 5: High-Volume Engine-Optimierungen (Gateway)
> **Cursor-Empfehlung:** **Opus 4.6 empfohlen.** Async-Concurrency, Thread-Safety, Streaming-Aggregates — subtile Bugs möglich. Load-Test-Design braucht Überblick über das gesamte System.
- [x] `executionEngine.py` → Loop-Concurrency via `asyncio.Semaphore` mit isolierten `nodeOutputs` pro Iteration
- [x] `executionEngine.py` → StepLog-Batching (Schwellwert 100 Iterationen: nur Summary + Fehler loggen)
- [x] `executionEngine.py` → Streaming-Aggregate (Flush-Threshold, temp-Storage)
- [x] `executionEngine.py` → Progress-SSE bei High-Volume: Summary statt pro-Step-Events
- [x] `extractorContainer.py``ExtractorRegistry` Singleton (Performance-Fix)
- [x] `extractorPdf.py``BytesIO` direkt an fitz übergeben statt `getvalue()` (Memory-Fix)
- [x] Lazy-Content-Loader: Utility-Funktion die `fileRef` → Bytes auflöst, on-demand im Loop
- [x] Load-Test: 1000 PDFs aus ZIP → Extract → AI Vision → CSV → Consolidate
### Phase 6: AI-Badge — Visuelle Kennzeichnung im Editor (Gateway + Frontend)
> **Cursor-Empfehlung:** Composer (Fast) reicht. Kleines `meta.usesAi`-Feld pro Node-Definition (Backend) + Badge-Komponente im Canvas (Frontend). Klar abgegrenzte Änderung.
- [x] Alle bestehenden Node-Definitionen: `meta.usesAi` Feld ergänzen (`true` / `false`)
- [x] `FlowCanvas.tsx` → AI-Badge-Rendering: kleines "AI"-Label / Blitz-Icon oben rechts am Node wenn `usesAi: true`
- [x] `NodeSidebar.tsx` → AI-Kennzeichnung in der Palette (Badge neben Node-Name)
- [x] Tooltip auf Badge: "Dieser Schritt nutzt AI und verbraucht Credits"
- [x] CSS/Styling: Badge-Design konsistent mit bestehendem Design-System
### Phase 7: Agent-Tools & Integration (Gateway)
> **Cursor-Empfehlung:** Composer (Fast) reicht für die Tool-Funktionen. **Opus 4.6 für E2E-Tests** — die Tests müssen viele Module korrekt zusammenspielen lassen.
- [x] `_documentTools.py` → UDM-Tools (`walkUdmBlocks`, `filterUdmByType`, `getUdmStructure`)
- [x] E2E-Test: PDF → Extract (UDM) → Loop (pro Seite) → AI → Aggregate → Consolidate
- [x] E2E-Test: PDF → Extract → Filter (nur Tabellen) → AI → kein Loop
- [x] E2E-Test: ZIP → Extract (references) → Loop (Fan-out) → Vision + Text → Merge → Aggregate → Consolidate
### Phase 8: Dokumentation & Abschluss
> **Cursor-Empfehlung:** Composer (Fast) reicht. Reine Dokumentation und Verifikation.
- [x] RBAC / Permissions: Keine Änderung nötig
- [x] Neutralisierung: ContentBlock.raw mit sensiblen Daten → Bridge zu ContentPart
- [x] Navigation / Routing: Keine Änderung
- [x] Billing-Impact: Loop-Iterationen zählen als einzelne AI-Calls → bestehende Logik greift
---
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---------------------------|------|
| 1 | Given ein PDF mit 5 Seiten, When `context.extractContent`-Node ausgeführt, Then UdmDocument mit 5 StructuralNodes (role=page) — **kein AI-Call**, nur Parsing | must |
| 2 | Given der Extract-Node-Output (UDM), When mit `data.filter` auf `contentType=="table"` gefiltert, Then nur Tabellen-ContentBlocks im Output | must |
| 3 | Given ein DOCX mit 3 Sections, When UDM-Extraktion, Then 3 StructuralNodes mit role=section und korrekten Labels | must |
| 4 | Given ein UDM-Dokument, When `_udmToContentParts()`, Then identische ContentPart-Liste wie bei direkter Extraktion (Bridge) | must |
| 5 | Given ein Workflow mit Loop über `document.children`, When ausgeführt, Then Body-Nodes pro StructuralNode einmal durchlaufen | must |
| 6 | Given ForEach → AI → Aggregate → Consolidate, When 3 Seiten verarbeitet, Then Consolidate erhält 3 Ergebnisse und erzeugt Zusammenfassung | must |
| 7 | Given Loop mit Fan-out zu 2 AI-Nodes + Merge, When pro Seite ausgeführt, Then Merge erhält beide AI-Ergebnisse pro Iteration | must |
| 8 | Given `outputDetail=references` auf ZIP mit 1000 PDFs, When Extract ausgeführt, Then Referenzliste mit 1000 Einträgen, Memory < 100 MB | must |
| 9 | Given Loop mit `concurrency: 5` über 100 Items, When ausgeführt, Then ~5x schneller als `concurrency: 1` | should |
| 10 | Given Loop mit 10.000 Iterationen, When ausgeführt, Then weniger als 1000 AutoStepLog-Einträge (Batching) | should |
| 11 | Given `context.extractContent` mit `outputDetail=structure`, When ausgeführt, Then nur Skelett ohne `raw`-Daten | should |
| 12 | Given Loop-Workflow im Editor, When User den Graph betrachtet, Then Loop-Body-Nodes visuell als zusammengehörig erkennbar | should |
| 13 | Given `data.consolidate` mit mode=table, When 100 CSV-Zeilen als Input, Then ein zusammengefügtes CSV als Output | should |
| 14 | Given ZIP mit 10.000 PDFs, When E2E-Workflow (Extract refs → Loop → AI Vision Seite 1 → CSV), Then erfolgreich in < 24h (mit concurrency: 10) | nice |
| 15 | Given ZIP mit 3 PDFs, When UDM-Extraktion, Then UdmArchive mit 3 UdmDocuments | nice |
| 16 | Given ein Workflow mit `ai.prompt` und `data.filter` Nodes, When User den Graph betrachtet, Then AI-Badge nur auf `ai.prompt` sichtbar, nicht auf `data.filter` | must |
| 17 | Given `data.consolidate` und `ai.consolidate` nebeneinander im Editor, When User vergleicht, Then nur `ai.consolidate` hat AI-Badge, `data.consolidate` nicht | must |
---
## Testplan
*Stand beim Archivieren: Kerntests liegen in den unter „Abschlussbericht“ genannten Dateien. Die ursprünglich geplanten Einzelpfade (T2, T3, T6T17) können bei Bedarf als separates Backlog ergänzt werden.*
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | unit | ja | gateway/tests/unit/datamodels/test_udm_models.py | done |
| T2 | 1 | integration | ja | gateway/tests/integration/extraction/test_extract_udm_pipeline.py (statt urspr. `test_extract_node.py`) | done |
| T3 | 2 | integration | ja | — (Backlog: dedizierter Filter-Flow) | backlog |
| T4 | 3 | unit | ja | gateway/tests/unit/datamodels/test_udm_models.py | done |
| T5 | 4 | unit | ja | gateway/tests/unit/datamodels/test_udm_bridge.py | done |
| T6 | 1,3 | integration | ja | gateway/tests/integration/extraction/test_extract_udm_pipeline.py | done |
| T7 | 5 | integration | ja | — (Backlog) | backlog |
| T8 | 6 | integration | ja | gateway/tests/integration/workflows/test_execute_graph_loop_aggregate_consolidate.py | done |
| T9 | 7 | integration | ja | — (Backlog) | backlog |
| T10 | 8 | integration | ja | — (Backlog) | backlog |
| T11 | 9 | integration | ja | — (Backlog) | backlog |
| T12 | 10 | unit | ja | — (Backlog) | backlog |
| T13 | 11 | unit | ja | — (Backlog) | backlog |
| T14 | 12 | manual | nein | — | backlog |
| T15 | 13 | integration | ja | — (Backlog) | backlog |
| T16 | 14 | load | nein | — (manueller Load-Test) | backlog |
| T17 | 15 | integration | ja | — (Backlog) | backlog |
| T18 | 16 | unit | ja | gateway/tests/unit/nodeDefinitions/test_usesai_flag.py | done |
| T19 | 17 | manual | nein | — (visueller Check im Editor) | backlog |
---
## Links
- Idee: `c-work/0-ideas/unified-document-model.md`
- Workflow-Engine Referenz: `b-reference/gateway/workflow.md`
- AI-Agent Referenz: `b-reference/gateway/ai-agent.md`
- Port-System Referenz: `c-work/4-done/2026-04-generic-graph-editor.md`
---
## Abschluss
- [x] b-reference/ aktualisiert: `gateway/workflow.md` (neue Nodes, UDM-Loop-Pattern, Concurrency), `gateway/ai-agent.md` (neue UDM-Tools)
- [x] TOPICS.md aktualisiert (neues Thema: UDM)
- [x] Dieses Dokument → `wiki/z-archive/2026-04-unified-document-model.md` archiviert

View file

@ -0,0 +1,511 @@
# Unified Document Model (UDM)
## Konzept & Zielsetzung
Das Unified Document Model definiert eine **generische, formatunabhängige Baumstruktur**, in die jeder Dokumenttyp (PDF, DOCX, PPTX, XLSX, HTML, ZIP) durch einen Extractor überführt wird. Dadurch können AI-Workflows, Nodes und Tools mit einem einzigen Objektmodell arbeiten unabhängig vom Quellformat.
### Designprinzipien
- **3-Ebenen-Garantie**: Jedes Dokument hat exakt drei Verschachtelungsebenen (ausgenommen ZIP als Meta-Container).
- **Einheitliche Blattknoten**: Alle atomaren Inhalte sind `ContentBlock`-Objekte mit identischer Attributstruktur.
- **Generische Traversierung**: Workflow-Nodes (Loop, Filter, Transform, Map) arbeiten formatunabhängig über dieselbe Baumstruktur.
- **Keine formatspezifischen Zwischenschichten**: Konzepte wie "Paragraph", "Row" oder "Cell" werden in den `ContentBlock` absorbiert, nicht als eigene Ebenen modelliert.
---
## Architekturübersicht
```
┌─────────────────────────────────────────────────────┐
│ Level 1 — Document │
│ ┌───────────────────────────────────────────────┐ │
│ │ Level 2 — StructuralNode │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ Level 3 — ContentBlock (Blattknoten) │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
### Ebenen im Detail
| Ebene | Typ | Beschreibung |
|-------|-----|-------------|
| **Level 1** | `Document` | Wurzelknoten. Repräsentiert ein einzelnes Quelldokument. |
| **Level 2** | `StructuralNode` | Strukturelle Gliederungseinheit: Seite, Abschnitt, Slide oder Sheet. |
| **Level 3** | `ContentBlock` | Atomarer Inhalt: Text, Bild, Tabelle, Code, Medien, Link oder Formel. |
---
## Datenmodell
### Document (Level 1)
Der Wurzelknoten pro Quelldatei.
```typescript
interface Document {
id: string;
role: "document";
source_type: "pdf" | "docx" | "pptx" | "xlsx" | "html";
source_path: string;
metadata: Metadata;
children: StructuralNode[];
}
```
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `id` | `string` | Eindeutige ID (UUID) |
| `role` | `"document"` | Immer `"document"` auf Level 1 |
| `source_type` | `string` | Originalformat der Quelldatei |
| `source_path` | `string` | Pfad der Originaldatei (relativ zum Workspace oder Archiv) |
| `metadata` | `Metadata` | Dokument-Metadaten |
| `children` | `StructuralNode[]` | Liste der strukturellen Einheiten |
---
### StructuralNode (Level 2)
Die Gliederungseinheit innerhalb eines Dokuments.
```typescript
interface StructuralNode {
id: string;
role: "page" | "section" | "slide" | "sheet";
index: number;
label: string | null;
metadata: Metadata;
children: ContentBlock[];
}
```
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `id` | `string` | Eindeutige ID |
| `role` | `string` | Art der Struktureinheit (formatabhängig, aber aus fester Menge) |
| `index` | `number` | 0-basierte Position innerhalb des Dokuments |
| `label` | `string?` | Optionaler Name (Sheet-Name, Abschnittsüberschrift, Slide-Titel) |
| `metadata` | `Metadata` | Zusätzliche Informationen zur Struktureinheit |
| `children` | `ContentBlock[]` | Liste der atomaren Inhalte |
#### Rollen-Zuordnung pro Format
| Quellformat | `role` | Entspricht im Original |
|-------------|--------|----------------------|
| PDF | `page` | Seite |
| DOCX | `section` | Abschnitt (Heading-basierte Gliederung) |
| PPTX | `slide` | Folie |
| XLSX | `sheet` | Tabellenblatt |
| HTML | `section` | Semantischer Bereich (`<header>`, `<main>`, `<nav>`, `<footer>`, `<aside>`) |
---
### ContentBlock (Level 3)
Der atomare Inhaltsknoten. **Alle Formate erzeugen identisch strukturierte ContentBlocks.**
```typescript
interface ContentBlock {
id: string;
content_type: "text" | "image" | "table" | "code" | "media" | "link" | "formula";
raw: string;
mime_type: string | null;
language: string | null;
attributes: Record<string, any>;
position: Position;
metadata: Metadata;
}
```
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `id` | `string` | Eindeutige ID |
| `content_type` | `string` | Art des Inhalts (aus fester Menge) |
| `raw` | `string` | Rohinhalt: Plaintext, Base64-kodierte Binärdaten oder JSON-serialisierte Struktur |
| `mime_type` | `string?` | MIME-Type (`text/plain`, `image/png`, `text/html`, `application/json`, …) |
| `language` | `string?` | Programmiersprache bei `code`-Blocks (`python`, `sql`, `javascript`, …) |
| `attributes` | `Record` | Zusätzliche Eigenschaften (Styling, Alt-Text, Grösse, …) |
| `position` | `Position` | Lokalisierung innerhalb der Struktureinheit |
| `metadata` | `Metadata` | Block-spezifische Metadaten |
#### Content-Typen im Detail
| `content_type` | `raw` enthält | `mime_type` Beispiel | Typische `attributes` |
|-----------------|--------------|---------------------|----------------------|
| `text` | Plaintext-Inhalt | `text/plain` | `{ style, heading_level, list_type, bold, italic }` |
| `image` | Base64-kodierte Bilddaten | `image/png`, `image/jpeg` | `{ width, height, alt_text, caption }` |
| `table` | JSON-Matrix `{ headers: [...], rows: [[...], ...] }` | `application/json` | `{ row_count, col_count, has_header, name }` |
| `code` | Quellcode als Text | `text/plain` | `{ language, line_count, executable }` |
| `media` | Base64-kodierte Daten oder URI | `audio/mp3`, `video/mp4` | `{ duration, format, embedded }` |
| `link` | URL als Text | `text/uri-list` | `{ display_text, target, rel }` |
| `formula` | Formelausdruck (LaTeX, Excel-Syntax) | `text/plain` | `{ notation, result_value, result_type }` |
---
### Position
Lokalisiert einen ContentBlock innerhalb seiner Struktureinheit.
```typescript
interface Position {
index: number;
page: number | null;
row: number | null;
col: number | null;
bbox: BoundingBox | null;
}
interface BoundingBox {
x: number;
y: number;
width: number;
height: number;
unit: "px" | "pt" | "mm";
}
```
| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `index` | `number` | 0-basierte Reihenfolge innerhalb der Struktureinheit |
| `page` | `number?` | Seitennummer (relevant für PDF) |
| `row` | `number?` | Zeilenposition (relevant für tabellarische Daten) |
| `col` | `number?` | Spaltenposition (relevant für tabellarische Daten) |
| `bbox` | `BoundingBox?` | Bounding Box für visuell positionierte Inhalte (PDF, PPTX) |
---
### Metadata
Einheitliches Metadaten-Objekt, verwendbar auf allen drei Ebenen.
```typescript
interface Metadata {
title: string | null;
author: string | null;
created_at: string | null; // ISO 8601
modified_at: string | null; // ISO 8601
source_path: string;
tags: string[];
custom: Record<string, any>;
}
```
---
### Archive (Sonderfall ZIP)
ZIP-Dateien fungieren als Meta-Container und erzeugen eine zusätzliche Wrapper-Ebene. Jede enthaltene Datei wird als eigenständiges `Document` extrahiert.
```typescript
interface Archive {
id: string;
role: "archive";
source_type: "zip" | "tar" | "gz";
source_path: string;
metadata: Metadata;
children: (Archive | Document)[];
}
```
Die 3-Ebenen-Garantie gilt **pro Dokument innerhalb** des Archivs. Das Archiv selbst ist eine Hülle.
```
Archive (ZIP)
├── Document (PDF)
│ ├── StructuralNode (page 0)
│ │ ├── ContentBlock (text)
│ │ └── ContentBlock (image)
│ └── StructuralNode (page 1)
│ └── ContentBlock (text)
├── Document (DOCX)
│ └── StructuralNode (section 0)
│ ├── ContentBlock (text)
│ └── ContentBlock (table)
└── Archive (nested ZIP)
└── Document (…)
```
---
## Format-Mapping-Referenz
Übersicht, wie jedes Quellformat in das 3-Ebenen-Modell abgebildet wird.
### PDF → UDM
```
PDF-Datei
├── Document (source_type: "pdf")
│ ├── StructuralNode (role: "page", index: 0)
│ │ ├── ContentBlock (text) ← Textblöcke der Seite
│ │ ├── ContentBlock (image) ← Eingebettete Bilder
│ │ └── ContentBlock (table) ← Erkannte Tabellen
│ ├── StructuralNode (role: "page", index: 1)
│ │ └── ...
```
- Jede PDF-Seite wird ein `StructuralNode(page)`.
- Text wird als zusammenhängende Blöcke extrahiert, nicht zeilenweise.
- Bilder werden als Base64 in `raw` gespeichert.
- Tabellen werden als JSON-Matrix serialisiert.
- `bbox` in `Position` enthält die visuelle Position auf der Seite.
### DOCX → UDM
```
DOCX-Datei
├── Document (source_type: "docx")
│ ├── StructuralNode (role: "section", label: "Einleitung")
│ │ ├── ContentBlock (text) ← Absätze
│ │ ├── ContentBlock (image) ← Inline-Bilder
│ │ └── ContentBlock (table) ← Word-Tabellen
│ ├── StructuralNode (role: "section", label: "Methodik")
│ │ └── ...
```
- Sections werden anhand von Heading-Ebenen gegliedert (H1 → neue Section).
- Absätze (Paragraphs) werden direkt zu `ContentBlock(text)` keine Zwischenebene.
- Styling-Informationen (bold, italic, heading_level) landen in `attributes`.
- Tabellen werden als JSON-Matrix in `raw` serialisiert.
### PPTX → UDM
```
PPTX-Datei
├── Document (source_type: "pptx")
│ ├── StructuralNode (role: "slide", index: 0, label: "Titelfolie")
│ │ ├── ContentBlock (text) ← Textboxen
│ │ ├── ContentBlock (image) ← Bilder/Grafiken
│ │ └── ContentBlock (table) ← Slide-Tabellen
│ ├── StructuralNode (role: "slide", index: 1)
│ │ └── ...
```
- Jede Folie wird ein `StructuralNode(slide)`.
- Textboxen werden zu `ContentBlock(text)` mit `bbox` für die Position.
- Speaker Notes landen als `ContentBlock(text)` mit `attributes.note: true`.
### XLSX → UDM
```
XLSX-Datei
├── Document (source_type: "xlsx")
│ ├── StructuralNode (role: "sheet", index: 0, label: "Umsatz 2024")
│ │ ├── ContentBlock (table) ← Tabellenbereich A
│ │ └── ContentBlock (table) ← Tabellenbereich B (falls disjunkt)
│ ├── StructuralNode (role: "sheet", index: 1, label: "Kosten")
│ │ └── ContentBlock (table)
```
- Jedes Sheet wird ein `StructuralNode(sheet)`.
- Das gesamte Daten-Grid eines Sheets wird als ein `ContentBlock(table)` serialisiert.
- Bei mehreren disjunkten Tabellenbereichen im selben Sheet → mehrere ContentBlocks.
- Formeln werden als `ContentBlock(formula)` extrahiert, wenn gewünscht.
### HTML → UDM
```
HTML-Datei
├── Document (source_type: "html")
│ ├── StructuralNode (role: "section", label: "header")
│ │ └── ContentBlock (text)
│ ├── StructuralNode (role: "section", label: "nav")
│ │ └── ContentBlock (link)
│ ├── StructuralNode (role: "section", label: "main")
│ │ ├── ContentBlock (text)
│ │ ├── ContentBlock (image)
│ │ └── ContentBlock (table)
│ ├── StructuralNode (role: "section", label: "footer")
│ │ └── ContentBlock (text)
```
- Semantische HTML5-Elemente (`<header>`, `<main>`, `<nav>`, `<footer>`, `<aside>`) werden zu Sections.
- Falls keine semantischen Elemente vorhanden: gesamter `<body>` als eine Section.
- HTML-Inhalte werden in Plaintext konvertiert, nicht als HTML-Markup gespeichert.
---
## Workflow-Integration
### Generische Traversierung
Da alle Formate dieselbe Struktur haben, funktioniert ein einzelner rekursiver Walker für alle Dokumenttypen:
```python
def walk_content_blocks(document: Document) -> Iterator[ContentBlock]:
"""Iteriert über alle ContentBlocks eines Dokuments, formatunabhängig."""
for structural_node in document.children:
for block in structural_node.children:
yield block
```
### Filter-Node
```python
def filter_by_type(document: Document, content_type: str) -> list[ContentBlock]:
"""Filtert alle ContentBlocks nach Typ (z.B. 'image', 'table')."""
return [
block for block in walk_content_blocks(document)
if block.content_type == content_type
]
```
### Loop-Node
```python
def process_all_documents(archive: Archive):
"""Verarbeitet alle Dokumente in einem Archiv mit identischer Logik."""
for document in archive.children:
if isinstance(document, Archive):
process_all_documents(document) # rekursiv für verschachtelte ZIPs
else:
for block in walk_content_blocks(document):
# Identische Verarbeitung, egal ob PDF, DOCX, PPTX, ...
transform(block)
```
### Map-Node
```python
def map_blocks(document: Document, fn: Callable[[ContentBlock], T]) -> list[T]:
"""Wendet eine Funktion auf jeden ContentBlock an."""
return [fn(block) for block in walk_content_blocks(document)]
```
### Beispiel: Alle Bilder aus beliebigem Dokument extrahieren
```python
images = filter_by_type(document, "image")
for img in images:
save_image(
data=base64_decode(img.raw),
filename=f"{img.id}.{img.mime_type.split('/')[1]}",
alt_text=img.attributes.get("alt_text", "")
)
```
### Beispiel: Alle Tabellen als CSV exportieren
```python
tables = filter_by_type(document, "table")
for table in tables:
data = json.loads(table.raw)
write_csv(
headers=data["headers"],
rows=data["rows"],
filename=f"{table.id}.csv"
)
```
---
## JSON-Beispiel
Vollständiges Beispiel eines extrahierten PDF-Dokuments:
```json
{
"id": "doc-a1b2c3",
"role": "document",
"source_type": "pdf",
"source_path": "reports/quarterly-report-q3.pdf",
"metadata": {
"title": "Quarterly Report Q3 2025",
"author": "Finance Team",
"created_at": "2025-10-01T08:00:00Z",
"modified_at": "2025-10-15T14:30:00Z",
"source_path": "reports/quarterly-report-q3.pdf",
"tags": ["finance", "quarterly"],
"custom": {}
},
"children": [
{
"id": "sn-page-0",
"role": "page",
"index": 0,
"label": null,
"metadata": {
"title": null,
"author": null,
"created_at": null,
"modified_at": null,
"source_path": "reports/quarterly-report-q3.pdf#page=1",
"tags": [],
"custom": {}
},
"children": [
{
"id": "cb-001",
"content_type": "text",
"raw": "Quarterly Report Q3 2025\n\nThis report summarizes the financial performance...",
"mime_type": "text/plain",
"language": null,
"attributes": {
"heading_level": 1,
"style": "title"
},
"position": {
"index": 0,
"page": 1,
"row": null,
"col": null,
"bbox": { "x": 50, "y": 30, "width": 500, "height": 40, "unit": "pt" }
},
"metadata": {
"title": null,
"author": null,
"created_at": null,
"modified_at": null,
"source_path": "reports/quarterly-report-q3.pdf#page=1",
"tags": [],
"custom": {}
}
},
{
"id": "cb-002",
"content_type": "table",
"raw": "{\"headers\":[\"Metric\",\"Q2\",\"Q3\",\"Delta\"],\"rows\":[[\"Revenue\",\"1.2M\",\"1.5M\",\"+25%\"],[\"Costs\",\"800K\",\"850K\",\"+6%\"]]}",
"mime_type": "application/json",
"language": null,
"attributes": {
"row_count": 2,
"col_count": 4,
"has_header": true,
"name": "Financial Overview"
},
"position": {
"index": 1,
"page": 1,
"row": null,
"col": null,
"bbox": { "x": 50, "y": 200, "width": 500, "height": 120, "unit": "pt" }
},
"metadata": {
"title": null,
"author": null,
"created_at": null,
"modified_at": null,
"source_path": "reports/quarterly-report-q3.pdf#page=1",
"tags": [],
"custom": {}
}
}
]
}
]
}
```
---
## Zusammenfassung
| Eigenschaft | Wert |
|-------------|------|
| **Ebenen** | Exakt 3 pro Dokument (Archive als optionaler Wrapper) |
| **Struktureinheiten** | `page`, `section`, `slide`, `sheet` |
| **Content-Typen** | `text`, `image`, `table`, `code`, `media`, `link`, `formula` |
| **Formate** | PDF, DOCX, PPTX, XLSX, HTML (erweiterbar) |
| **Traversierung** | Ein generischer Walker für alle Formate |
| **Serialisierung** | JSON-kompatibel, sofort einsetzbar in Workflow-Engines |