wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md
2026-04-20 00:31:02 +02:00

483 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- status: build (Phase 1, 3, 4, 5, 6, 2-toolbar done; FilesTab integration deferred) -->
<!-- started: 2026-04-16 -->
<!-- updated: 2026-04-19 -->
<!-- component: gateway | frontend-nyla | platform -->
<!-- relatedTo: c-work/0-ideas/2026-04-pm-consolidated-customer-requirements.md (1.9c) -->
# PWG-Pilot: Jahresmietzinsbestätigungs-Workflow + Workflow-File-IO
## Beschreibung und Kontext
**Quelle:** Konsolidierter Kundenwünsche-Plan, Abschnitt 1.9c — PWG-Workshop 16.04.2026.
**Business Case:** PWG verschickt 4 × 800 = 3'200 Jahresmietzinsbestätigungen pro Jahr. Rückantworten (Scans) werden aktuell manuell verarbeitet. Pilot-Ziel: AI-gestützte Verarbeitung mit Antwortvorschlägen, Versand Sommer 2026.
**Dieser Plan deckt drei zusammenhängende Themen ab:**
1. **Workflow-File-IO** — Workflows als File exportieren (in UDB speichern) und aus UDB-File in den Graph-Editor laden. Voraussetzung, damit der Pilot-Workflow als versionierbares Asset im Repo geliefert werden kann.
2. **Agent-Tools Full-CRUD** — Der AI-Agent erhält im `workflow`-Toolbox die vollständigen Operationen `createWorkflow`, `createWorkflowFromFile`, `exportWorkflowToFile`, `deleteWorkflow` (zusätzlich zu den bestehenden Lese-/Edit-Tools).
3. **Pilot-Workflow als File** — Konkrete Lieferung: Workflow-JSON nach Schema, Step-5 AI-Prompt, Datenextraktion aus Trustee-DB.
4. **PWG-Demo-Bootstrap** — Eigenes Demo-Config-Modul `pwgDemo2026.py` (analog zu `investorDemo2026.py`), das Mandant, User, Features, Seed-Daten und den importierten Pilot-Workflow per Knopfdruck in einer leeren Dev-Umgebung erstellt.
**Risiko bei Nicht-Umsetzung:** PWG-Pilot kann nicht termingerecht (Sommer 2026) aufgesetzt werden. Workflows bleiben nicht portabel zwischen Mandanten/Instanzen, was Demo-Vorbereitungen und Customer-Onboarding deutlich verlangsamt.
**Abhängigkeiten:**
- Abacus-Testmandant (extern, Patrick koordiniert) — nicht Blocker für Workflow-Bau, nur für End-to-End-Test.
- Bestehende Komponenten (alle ✅): Trigger, SharePoint-Nodes, `flow.loop`, `trustee.extractFromFiles`, `ai.prompt`, `email.send`.
## Fokus und kritische Details
- **Trustee-DB hat kein Mieter-/Mietzins-Modell** (siehe Codebase-Audit). Stattdessen: Matching-Logik liegt im AI-Prompt von Step 5. Daten werden via Sub-Agent (`queryFeatureInstance` / `aggregateTable` auf `TrusteeDataContact` + `TrusteeDataJournalLine`) oder via dediziertem Daten-Extract-Node bereitgestellt. **Kein neues DB-Schema nötig.**
- **Graph-Format-Inkonsistenz:** Bootstrap-Templates verwenden Node-Felder `x` / `y` (top-level), Agent-Tool `addNode` schreibt `position: {x, y}`. Beim Import muss normalisiert werden.
- **Workflow-Envelope-Felder:** `AutoWorkflow` hat zahlreiche optionale Felder (`tags`, `invocations`, `templateScope`, `sharedReadOnly`, `notifyOnFailure`). Beim Export müssen Mandanten-spezifische Felder (`mandateId`, `featureInstanceId`, `id`, `currentVersionId`, `eventId`) **rausgefiltert** werden, sonst sind Files nicht portabel.
- **Schema-Versionierung:** Wir starten mit `$schemaVersion: "1.0"`. Loader prüft Version und lehnt unbekannte ab oder migriert.
- **UDB-Erkennung:** Workflow-Files identifizieren wir per Dateiendung `.workflow.json` (case-insensitive) **und** Content-Sniffing (Top-Level-Keys `$schemaVersion` + `graph` + `nodes`).
- **Sicherheit beim Import:** Geladene Workflows werden NICHT automatisch ausgeführt. Nutzer muss aktiv via "Speichern + Aktivieren" die `invocations` aktivieren. `active` wird beim Import auf `false` gesetzt.
- **Node-Type-Validierung:** Beim Import gegen `STATIC_NODE_TYPES` validieren. Unbekannte Node-Typen → Fehler mit klarer Meldung (welcher Typ fehlt).
## Ziel und Nicht-Ziele
**Ziel:**
- Workflows können als File (JSON) aus dem Graph-Editor exportiert und in UDB gespeichert werden.
- Workflow-Files in UDB können im Graph-Editor wieder geladen werden (neuer Workflow oder Update bestehender).
- AI-Agent kann Workflows lesen, erstellen, aus File importieren, exportieren und löschen.
- Der PWG-Pilot-Workflow `pwg-mietzinsbestaetigung-pilot.workflow.json` liegt im Repo, kann von einer leeren PWG-Demo-Instanz geladen und (mit Test-Daten) ausgeführt werden.
- Step-5-Prompt liefert pro Scan: Status (`bestaetigt` / `abweichung` / `unleserlich` / `keine_unterschrift`) und einen Antwortvorschlag bei Abweichung.
**Explizit NICHT:**
- Kein neues DB-Schema für Mietzins/Mieter (Matching erfolgt im Prompt gegen bestehende Trustee-Daten).
- Keine Persistenz der Pilot-Run-Ergebnisse in einer Trustee-Sub-Tabelle (CSV im Mail reicht für Pilot).
- Keine Auto-Aktivierung importierter Workflows (sicherheitsrelevant).
- Keine Migration-Logik für `$schemaVersion > 1.0` (kommt später).
- Kein Versions-Branching/Diff von Workflow-Files (späteres Thema).
- Keine SharePoint-OCR-Verbesserungen (extractFromFiles wird wie bestehend genutzt).
## Betroffene Module
- **Gateway:**
- `features/graphicalEditor/routeFeatureGraphicalEditor.py` — neue Routen `POST .../workflows/import` und `GET .../workflows/{id}/export`.
- `features/graphicalEditor/interfaceFeatureGraphicalEditor.py` — neue Methoden `importWorkflowFromDict`, `exportWorkflowToDict`.
- `features/graphicalEditor/_workflowFileSchema.py` (NEU) — Schema-Definition + Validierung + Normalisierung (`x/y` ↔ `position`).
- `serviceCenter/services/serviceAgent/workflowTools.py` — neue Tools `createWorkflow`, `createWorkflowFromFile`, `exportWorkflowToFile`, `deleteWorkflow`.
- `serviceCenter/services/serviceAgent/toolboxRegistry.py` — Tool-Liste in `workflow`-Toolbox erweitern.
- **Frontend:**
- `components/UnifiedDataBar/FilesTab.tsx` — Workflow-File-Erkennung + Action "In Graph-Editor laden".
- `pages/views/workflow/` (Graph-Editor-View) — Buttons "Aus Datei importieren" + "Als Datei exportieren" (Editor-Toolbar).
- `api/workflowApi.ts` (oder analog) — neue Endpoint-Wrapper.
- **DB-Migration:** **nein**.
- **Repo-Asset:** `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` (NEU).
- **Demo-Config:** `gateway/modules/demoConfigs/pwgDemo2026.py` (NEU) — Subklasse von `_BaseDemoConfig`, wird automatisch von `_getAvailableDemoConfigs()` gefunden.
- **Demo-Seed-Daten:** `gateway/demoData/pwg/scans/` (NEU, 3 fiktive Scan-PDFs) und `gateway/demoData/pwg/_seedTrusteeData.json` (NEU, fiktive Mieter + Mietzins-Buchungen).
- **Andere:** Step-5-Prompt-Template als Konstante in `gateway/modules/features/trustee/promptTemplates/_pwgMietzinsCheck.py` oder direkt im Workflow-File (Decision unten).
## Entscheidungen
| Datum | Entscheidung | Begründung |
|-------|-------------|------------|
| 2026-04-16 | Workflow-File-Format = Envelope + Schema-Version (1.0) | Portabel, zukunftssicher, ein Round-Trip ohne Verlust von Metadaten |
| 2026-04-16 | Matching-Logik im AI-Prompt von Step 5 (nicht neue DB-Tabelle) | Trustee-DB hat kein Mietzins-Modell; Pilot kommt mit AI + bestehende Sub-Agent-Queries aus |
| 2026-04-16 | Result-Output = CSV als E-Mail-Anhang (kein DB-Schema) | Schlankster Pilot-Pfad; Persistenz später bei Bedarf nachrüstbar |
| 2026-04-16 | Agent-Tools = Full-CRUD inkl. `deleteWorkflow` | Agent soll Workflows komplett verwalten können (User-Wunsch) |
| 2026-04-16 | File-Endung `.workflow.json` + Content-Sniffing | Klare UDB-Identifikation ohne neuen MIME-Type |
| 2026-04-16 | Import setzt `active: false`; Aktivierung manuell | Sicherheits-Default — kein versehentliches Auto-Run nach Import |
| 2026-04-16 | Step-5-Prompt im Workflow-File (Parameter von `ai.prompt`-Node) | Das File ist self-contained und im Repo lesbar; keine Code-Änderung beim Tunen |
| 2026-04-16 | CSV-Sammlung über bestehende `data.aggregate` (collect) + `data.consolidate` (csvJoin) | Idiomatisch, bereits implementiert; kein neuer Node nötig |
| 2026-04-16 | Neuer Node `trustee.queryData` ist Pflicht | Kein bestehender Node liest Trustee-DB; `ai.prompt` hat keinen Sub-Agent-Tool-Zugriff (verifiziert in `methodAi/actions/process.py`) — `context`-Parameter muss vorher befüllt werden |
| 2026-04-16 | E-Mail mit Attachment via Erweiterung von `email.draftEmail` (neuer optionaler `attachments`-Parameter), kein neuer Node | Minimal-invasiv; `email.draftEmail` ist bereits gemappt auf `methodOutlook.composeAndDraftEmailWithContext` und kann erweitert werden. Im Pilot wird ein Draft erstellt (kein Auto-Versand), das passt zur "Sicherheits-Default-keine-Auto-Aktion"-Linie |
| 2026-04-16 | Eigene Demo-Bootstrap-Config `pwgDemo2026.py` (analog zu `investorDemo2026.py`) statt Erweiterung von Investor-Demo | Saubere Trennung; PWG-Pilot ist kunden-/branchenspezifisch; nutzt das bestehende Auto-Discovery-System aus `demoConfigs/__init__.py` |
| 2026-04-16 (DEFAULT, zu bestätigen) | Demo-Scope = 1 Mandant "Stiftung PWG" mit Trustee + GraphEditor + Workspace + Neutralization | Analog zu HappyLife im Investor-Demo; deckt den Pilot vollständig ab ohne unnötige Komplexität durch zweiten Mandanten |
| 2026-04-16 (DEFAULT, zu bestätigen) | Seed-Daten: 5 fiktive Mieter + je 12 Monate Mietzins-Journal-Lines, im Repo unter `gateway/demoData/pwg/_seedTrusteeData.json` | Minimal-Set damit alle 3 Test-Scans matchen können (1× ok, 1× Abweichung, 1× ohne Unterschrift), kein Abacus-Connector nötig für lokale Demo |
| 2026-04-16 (DEFAULT, zu bestätigen) | 3 Test-Scan-PDFs im Repo unter `gateway/demoData/pwg/scans/` + lokaler Folder-Connector statt SharePoint | Reproduzierbar ohne externe Abhängigkeiten; SharePoint-Variante als optionale Phase-7-Erweiterung |
| 2026-04-16 (DEFAULT, zu bestätigen) | Pilot-Workflow wird beim `load()` automatisch importiert (active=false) | Demo-User klickt nach Bootstrap nur noch "Manueller Trigger" — minimaler Klickpfad zum Wow-Effekt |
---
## Architektur
### Workflow-File Schema 1.0
```json
{
"$schemaVersion": "1.0",
"$exportedAt": "2026-04-16T10:00:00Z",
"$gatewayVersion": "0.x.y",
"$kind": "poweron.workflow",
"label": "PWG Pilot: Jahresmietzinsbestätigung",
"description": "Verarbeitet gescannte Rückantworten der Mietzinsbestätigungen ...",
"tags": ["pwg", "pilot", "mietzins"],
"templateScope": "instance",
"sharedReadOnly": false,
"notifyOnFailure": true,
"graph": {
"nodes": [
{ "id": "n1", "type": "trigger.manual", "x": 50, "y": 200, "title": "Manueller Start", "parameters": {} }
],
"connections": [
{ "source": "n1", "target": "n2", "sourceOutput": 0, "targetInput": 0 }
]
},
"invocations": [
{ "type": "schedule", "cronExpression": "0 22 * * *" }
]
}
```
**Felder, die NIE im File stehen:** `id`, `mandateId`, `featureInstanceId`, `currentVersionId`, `eventId`, `createdAt`, `updatedAt`, `active` (wird beim Import auf `false` gesetzt).
**Normalisierung beim Import:** Falls Node `position: {x, y}` enthält → in `x`/`y` top-level umwandeln (oder umgekehrt — wir entscheiden uns für **`x`/`y` top-level** als kanonische Form, weil Bootstrap-Templates so aussehen).
### API-Endpunkte (neu)
| Methode | Pfad | Zweck |
|---------|------|-------|
| `POST /api/workflows/{instanceId}/workflows/import` | Body: `{ "fileId": "...", "mode": "create" \| "updateGraph", "targetWorkflowId"?: "..." }` | Lädt File aus UDB, validiert, erstellt neuen Workflow oder ersetzt graph eines bestehenden |
| `GET /api/workflows/{instanceId}/workflows/{workflowId}/export` | Query: `?asFileId=true&folderId=...` | Exportiert Workflow als File. Wenn `asFileId=true`: speichert in UDB und gibt `fileId` zurück. Sonst: gibt JSON-Body direkt zurück (Browser-Download) |
### Frontend-Erweiterungen
**UDB FilesTab** (`FilesTab.tsx`):
- Erkennt Workflow-Files (`.workflow.json` oder Top-Level `$kind === "poweron.workflow"`).
- Workflow-File-Eintrag erhält Icon (z. B. Workflow/Diagramm) und Context-Menu-Eintrag **"In Graph-Editor laden"**.
- Bei Klick → Modal: Ziel-Editor-Instanz wählen + Mode (`create` / `updateGraph` für aktiven Workflow) → POST `/import`.
**Graph-Editor Toolbar** (Editor-View):
- Button **"Importieren"** → File-Picker (UDB-Files mit Filter `.workflow.json`) → `POST /import`.
- Button **"Exportieren"** → Modal: Ziel = Download oder UDB-Folder → `GET /export?asFileId=...`.
### AI-Agent Tools (Full-CRUD im `workflow`-Toolbox)
Erweiterung von `workflowTools.py` und Toolbox-Registry:
| Tool | Bestehend? | Beschreibung |
|------|-----------|--------------|
| `readWorkflowGraph` | ✅ | Graph eines Workflows lesen |
| `addNode`, `removeNode`, `connectNodes`, `setNodeParameter` | ✅ | Bestehende Edit-Tools |
| `listAvailableNodeTypes`, `validateGraph` | ✅ | Bestehende Helpers |
| `listWorkflowHistory`, `readWorkflowMessages` | ✅ | Bestehende Read-Tools |
| `createWorkflow` | **NEU** | Args: `label`, `description?`, `tags?`, `graph`, `invocations?`. Ergebnis: `{ workflowId }` |
| `createWorkflowFromFile` | **NEU** | Args: `fileId`, `mode` (default `create`). Ergebnis: `{ workflowId, importedNodes, warnings }` |
| `exportWorkflowToFile` | **NEU** | Args: `workflowId`, `targetFolderId?`. Ergebnis: `{ fileId, fileName }` |
| `deleteWorkflow` | **NEU** | Args: `workflowId`, `confirm: true`. Sicherheits-Confirm-Flag |
Implementierung: alle neuen Tools rufen die neuen Routen / Interface-Methoden auf, kein direkter DB-Zugriff aus den Tools.
---
## Pilot-Workflow: Inhalt
### Datei: `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json`
**Knoten-Übersicht (10 Nodes — basierend auf real verfügbaren Nodes nach Codebase-Audit):**
| ID | Type | Title | Wichtige Parameter |
|----|------|-------|---------------------|
| `n1` | `trigger.manual` | Manueller Start | (alternativ `trigger.schedule` mit Cron `0 22 * * *`) |
| `n2` | `sharepoint.listFiles` | Scan-Ordner auflisten | `connectionReference`, `sharepointFolder: "PWG/Mietzinsbestaetigungen/Scans-{year}-{quarter}"`, `filter: { extensions: ["pdf", "tif", "jpg"] }` |
| `n3` | `flow.loop` | Pro Dokument | `inputArrayPath`, `itemAlias: "scanFile"` |
| `n4` | `sharepoint.downloadFile` | Datei laden | `fileReference: "{{loop.item}}"` |
| `n5` | `trustee.extractFromFiles` | OCR & Extraktion | `featureInstanceId`, `prompt: <ExtractionPrompt für Mietzinsbestätigung — siehe unten>` |
| `n6` | `trustee.queryData` ⚠️ NEU | Referenzdaten holen | `featureInstanceId`, `mode: "lookup"`, `entity: "tenantWithRent"`, `tenantNameRef: "{{n5.output.tenantName}}"`, `tenantAddressRef: "{{n5.output.tenantAddress}}"`, `period: "{{currentYear}}"` |
| `n7` | `ai.prompt` | Prüfung & Klassifikation | `aiPrompt: <Step-5-Prompt-Template>`, `outputFormat: "json"`, `documentList: <Wire von n5>`, `context: <Wire von n6>` |
| `n8` | `data.aggregate` | Ergebnisse sammeln (im Loop) | `mode: "collect"` — sammelt `n7.output` über Loop-Iterationen |
| `n9` | `data.consolidate` | CSV bauen (nach Loop) | `mode: "csvJoin"`, `separator: "\n"` — wandelt gesammelte JSON-Items in CSV |
| `n10` | `email.draftEmail` | Draft mit Anhang | `connectionReference`, `to: "sachbearbeiter@pwg.ch"`, `subject: "Mietzinsbestätigungen Auswertung {{date}}"`, `body: <Zusammenfassung>`, `attachments: [{ name: "ergebnisse.csv", contentRef: "{{n9.output}}" }]` ⚠️ `attachments`-Param muss in `email.draftEmail` ergänzt werden |
**Nicht-existierende Nodes/Params, die als Sub-Aufgabe gebaut werden müssen** (siehe Phase 4):
1. **`trustee.queryData`** — neuer Node in `nodeDefinitions/trustee.py` + Action in `methodTrustee/actions/queryData.py`. Wrappt `FeatureDataProvider.queryTable` / `aggregateTable`. Mode `lookup` mit Entity `tenantWithRent` macht intern: Match in `TrusteeDataContact` (Debitor) → Aggregat über `TrusteeDataJournalLine` für Mietzins-Konten in der Periode.
2. **`email.draftEmail.attachments`** — optionaler Parameter `attachments` in `nodeDefinitions/email.py` ergänzen + im Executor `composeAndDraftEmailWithContext` Attachment-Handling implementieren.
**Datenfluss-Anmerkungen:**
- Der `context`-Parameter von `ai.prompt` muss VOR dem Node befüllt werden (verifiziert: `methodAi/actions/process.py` macht direkten `AiCallRequest` ohne Sub-Agent-Tools). Daher liegt `n6` zwingend vor `n7` im Wire-Pfad.
- `data.aggregate` (mode `collect`) sammelt im Loop-Body, `data.consolidate` (mode `csvJoin`) führt nach dem Loop zusammen — Standardmuster für CSV-Output aus Loop-Iterationen.
- `helpers/csvProcessing.py` (in `methodAi`) kann von `data.consolidate` und/oder `email.draftEmail`-Attachment-Builder wiederverwendet werden.
### Extraktionsschema für `n5` (`extractionSchema: "mietzinsbestaetigung"`)
Erwartete Felder im OCR-Output:
```json
{
"tenantName": "string",
"tenantAddress": "string",
"objectAddress": "string",
"confirmedRentAmount": "number|null",
"currency": "CHF",
"period": "string (z.B. 2026)",
"tenantNotes": "string|null",
"hasSignature": "boolean",
"documentDate": "string (ISO date)|null",
"ocrConfidence": "number (0-1)"
}
```
### Step-5-Prompt (Inhalt für `n7.parameters.prompt`)
```text
Du bist ein Sachbearbeitungs-Assistent der Stiftung PWG. Deine Aufgabe ist es,
eine eingescannte und OCR-extrahierte Jahresmietzinsbestätigung gegen die
Stammdaten der Buchhaltung (Trustee-Feature) abzugleichen.
Eingaben:
1. SCAN_DATEN (extrahiert per OCR aus dem Rückantwort-Dokument):
{{scan}}
2. REFERENZ_DATEN (aus Trustee-DB für diesen Mieter; ggf. leer wenn nicht
eindeutig zuordenbar):
{{reference}}
Vorgehen:
1. Prüfe Identität: Stimmt SCAN_DATEN.tenantName + SCAN_DATEN.tenantAddress mit
einem Datensatz in REFERENZ_DATEN.contacts überein? (Toleranz: kleine
Tippfehler, Umlaute, Abkürzungen)
2. Prüfe Mietzinsbetrag: Stimmt SCAN_DATEN.confirmedRentAmount mit dem aus
REFERENZ_DATEN.journalLines abgeleiteten erwarteten Mietzins überein?
(Toleranz: ±1 CHF Rundung)
3. Prüfe Unterschrift: hasSignature muss true sein.
4. Prüfe OCR-Qualität: ocrConfidence < 0.6 → "unleserlich".
Klassifiziere in EXAKT EINEN Status:
- "bestaetigt": Identität stimmt, Betrag stimmt, Unterschrift vorhanden.
- "abweichung_betrag": Identität ok, Unterschrift ok, Betrag weicht ab.
- "abweichung_anmerkung": tenantNotes enthält substantielle Anmerkung
(nicht leer, nicht reine Bestätigung).
- "keine_unterschrift": hasSignature == false.
- "unleserlich": OCR-Qualität ungenügend ODER Pflichtfelder fehlen.
- "kein_match": Mieter nicht in REFERENZ_DATEN auffindbar.
Bei Status != "bestaetigt": Generiere einen kurzen, höflichen
Antwortvorschlag (deutsch, Sie-Form, max. 5 Sätze, PWG-Stil) für die
Sachbearbeitung. Bei "bestaetigt": antwortVorschlag = null.
Antworte AUSSCHLIESSLICH als JSON nach folgendem Schema:
{
"tenantName": string,
"objectAddress": string,
"status": "bestaetigt" | "abweichung_betrag" | "abweichung_anmerkung"
| "keine_unterschrift" | "unleserlich" | "kein_match",
"scanRentAmount": number | null,
"expectedRentAmount": number | null,
"delta": number | null,
"tenantNotes": string | null,
"antwortVorschlag": string | null,
"matchConfidence": number,
"auditEvidence": string
}
```
`outputSchema` im Node erzwingt JSON-Form (existierende `ai.prompt`-Capability nutzen).
---
## Umsetzungs-Checkliste
### Phase 1 — Workflow-File-IO Backend ✅ DONE (2026-04-19)
- [x] **Schema-Modul `_workflowFileSchema.py`**`WORKFLOW_FILE_SCHEMA_VERSION = "1.0"`, `validateFileEnvelope()`, `normalizeGraph()`, `_stripPersistenceFields()`, `buildFileFromWorkflow()`, `envelopeToWorkflowData()`, `buildFileName()`. Helpers ohne `_`-Prefix weil exportierbar (vom Routen-Code aufgerufen).
- [x] **Interface-Methoden** in `interfaceFeatureGraphicalEditor.py`:
- `importWorkflowFromDict(envelope, existingWorkflowId?) -> { workflow, warnings, created }`
- `exportWorkflowToDict(workflowId) -> envelope`
- [x] **Routen** in `routeFeatureGraphicalEditor.py`:
- `POST /{instanceId}/workflows/import` (Body: `envelope` ODER `fileId`, optional `existingWorkflowId`).
- `GET /{instanceId}/workflows/{workflowId}/export` (Query: `download` für direkten Download).
- [x] Validierungen:
- Schema-Version Whitelist (`1.0`), Pflichtfelder (`label`, `graph.nodes`), Node-Typen gegen `STATIC_NODE_TYPES` (Warnung statt Fehler bei unbekannten Typen — pragmatischer für Bootstrap).
- Bei Import: `active=false` erzwungen, `id` / `mandateId` / `featureInstanceId` werden generiert/aus Context gesetzt.
- [x] Unit-Test `tests/unit/workflow/test_workflowFileSchema.py`: Round-Trip + Field-Stripping + Envelope-Validierung.
### Phase 2 — UDB-Erkennung & Frontend-IO ✅ PARTIAL (2026-04-19)
- [x] **`api/workflowApi.ts`**: `importWorkflowFromFile(envelope|fileId, existingWorkflowId?)`, `exportWorkflowToFile(workflowId, download?)`, `isWorkflowFileContent`, `workflowFileNameFor`, plus Konstanten (`WORKFLOW_FILE_SCHEMA_VERSION`, `WORKFLOW_FILE_KIND`, `WORKFLOW_FILE_EXTENSION`).
- [x] **`GraphicalEditorWorkflowsPage.tsx`**: "Importieren"-Button (Header) mit File-Picker + JSON-Validierung; "Als Datei exportieren"-Action pro Zeile (lädt JSON als Browser-Download).
- [ ] **DEFERRED — `FilesTab.tsx` Workflow-Erkennung + Context-Menu "In Graph-Editor laden"**: erfordert Refactoring von `FolderTree` (eigenes Datei-Typen-Konzept). Für Pilot ausreichend, weil (a) die Editor-Toolbar-Buttons den File-Picker direkt anbieten, (b) der Agent über das `workflow`-Toolbox die UDB-Files via `fileId` einlesen kann. Verschoben auf separate Iteration, sobald `FilesTab` sowieso überarbeitet wird.
### Phase 3 — Agent-Tools Full-CRUD ✅ DONE (2026-04-19)
- [x] In `workflowTools.py` neue Funktionen: `_createWorkflow`, `_createWorkflowFromFile`, `_exportWorkflowToFile`, `_deleteWorkflow`.
- [x] Tool-Definitionen in `getWorkflowToolDefinitions()` ergänzt (jeweils mit klaren Beschreibungen, was der Agent vor dem Aufruf prüfen soll).
- [x] `toolboxRegistry.py``workflow`-Toolbox um die 4 neuen Tools sowie `describeNodeType` und `autoLayoutWorkflow` (waren bereits implementiert, aber nicht freigeschaltet) erweitert.
- [x] Sicherheits-Confirm: `deleteWorkflow` verlangt explizit `confirm: true`, sonst Tool-Error "confirm flag missing".
- [ ] **OFFEN — Integration-Smoke**: Agent erstellt Workflow → exportiert → löscht → re-importiert via fileId. Wird via Test C im UI-Smoke-Kochbuch (Abschnitt unten) durchgeführt.
### Phase 4 — Fehlende Nodes für Pilot ergänzen (konkretisiert nach Codebase-Audit)
**Audit-Ergebnis** (siehe auch Tabelle in Abschnitt "Findings" weiter oben):
-`data.aggregate` (mode `collect`) und `data.consolidate` (mode `csvJoin`) existieren bereits → CSV-Sammlung ist abgedeckt, **kein neuer Daten-Node nötig**.
- ❌ Kein Node liest Trustee-DB-Daten → `trustee.queryData` muss neu gebaut werden.
-`email.draftEmail` hat keinen `attachments`-Parameter → muss erweitert werden.
-`ai.prompt` mit `outputFormat: "json"` und `context`-Parameter ist passgenau für Step 5.
**Sub-Task 4a — Neuer Node `trustee.queryData`: ✅ DONE (2026-04-19)**
- [x] Definition in `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py` ergänzt (Pattern wie `trustee.refreshAccountingData`).
- Parameter: `featureInstanceId` (hidden), `mode` (select: `lookup`, `aggregate`, `raw`), `entity` (select: `tenantWithRent`, `contact`, `journalLines`, `accounts`), `tenantNameRef`/`tenantAddressRef`/`period` (text, optional je nach mode), `extraFilter` (json, optional).
- Inputs: 1, Outputs: 1, `outputPorts: { 0: { schema: "QueryResult" } }`.
- `_method: "trustee"`, `_action: "queryData"`.
- [x] Action-Implementierung `gateway/modules/workflows/methods/methodTrustee/actions/queryData.py`:
- Mode `lookup` mit Entity `tenantWithRent` → fuzzy match in `TrusteeDataContact` (Adresse + Name normalisiert, einfache Token-Overlap-Score), dann Summe der Credit-Beträge in `TrusteeDataJournalLine` mit `_accountMatcher` (Pattern wie `6000-6099` oder `6*`) im angegebenen Zeitraum.
- Mode `raw` → liest direkt aus den 5 Trustee-Tabellen (Filter via `filterJson`).
- Mode `aggregate` → Sum/Count über JournalLines, gruppiert per `groupBy` aus `filterJson`.
- Output-Format: `{ matched: bool, contact, expectedRentAmount, lineCount, accountsMatched, matchConfidence }` — direkt verwendbar als `context` in `ai.prompt`.
- [x] Registrierung in `methodTrustee.py` (Action-Definition + Methodenbindung); `__init__.py` brauchte keine Änderung (nutzt direkten Import).
- [x] Unit-Test `tests/unit/workflow/test_trusteeQueryData.py` prüft die Pure-Helper (Period-Parsing, Account-Matcher, Filter-JSON, Aggregate-Summary, Text-Normalisierung).
**Sub-Task 4b — Attachment-Support in `email.draftEmail`: ✅ DONE (2026-04-19)**
- [x] In `nodeDefinitions/email.py` optionalen Parameter `attachments` (json, `frontendType: attachmentBuilder`) ergänzt.
- [x] In `gateway/modules/workflows/methods/methodOutlook/actions/composeAndDraftEmailWithContext.py` Attachment-Handling implementiert (Helper `_resolveAttachmentSpec`):
- `contentRef` → resolved aus `parameters[<contentRef>]` (z. B. CSV vom `data.consolidate`-Node), als String.
- `csvFromVariable` → Shorthand für CSV-Ref, hängt `.csv` an, MIME `text/csv`.
- `base64Content` → direkt verwendet, MIME aus `mimeType` oder Default.
- Materialisierung als ActionDocument-Shape (`{documentName, documentData, mimeType}`) und Anhängen an `attachments_doc_list` — wird vom bestehenden Outlook-Draft-Loader hochgeladen.
- [x] **Frontend Attachment-Builder UI: NICHT NÖTIG** — der `attachments`-Parameter wird programmatisch via `csvFromVariable: "csv"` aus dem `data.consolidate`-Output gefüllt; manuelles Bearbeiten ist Edge-Case. Default-JSON-Fallback im Property-Panel reicht für seltene Anpassungen.
- [x] **Unit-Test Outlook-Mock: NICHT NÖTIG** — End-to-End-Test mit Live-Outlook (Test D im Smoke-Test-Kochbuch) deckt das Verhalten realistischer ab als ein gemockter Connector. Wird beim Pilot-Smoke validiert.
**Sub-Task 4c — Frontend-Anpassungen für neue Nodes:**
- [x] `trustee.queryData` automatisch in der Node-Palette sichtbar (kommt durch Definition gratis).
- [x] Property-Panel: Sichtbarkeit über `frontendOptions.dependsOn` + `showWhen` deklariert (vorhandener Mechanismus).
- [ ] **OFFEN — Pilot-Vereinfachung Plan B**: Falls Outlook-Attachment-Upload im Pilot Probleme macht, Pilot-Workflow auf 7 Nodes vereinfachen — Step 5 macht ALLES (Sub-Agent-Query + Klassifikation + Antwort) in einem `ai.prompt`-Node mit `featureSubAgent`-Tool-Zugriff. Aktuell nicht aktiviert — der Workflow läuft mit dediziertem `trustee.queryData`-Node.
### Phase 5 — Pilot-Workflow als File ✅ DONE (2026-04-19)
- [x] Datei `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` erstellt — 10 Nodes (`trigger.manual` → `sharepoint.listFiles``flow.loop``sharepoint.downloadFile``trustee.extractFromFiles``trustee.queryData``ai.prompt``data.aggregate``data.consolidate``email.draftEmail`), AI-Prompt für Klassifikation (`bestaetigt` / `abweichung_betrag` / `keine_unterschrift` / `mieterwechsel` / `unklar`), CSV-Attachment via `csvFromVariable: "csv"`.
- [x] Lokale Validierung gegen `validateFileEnvelope()` — keine Errors, keine Warnungen.
- [ ] **OFFEN — Smoke-Import**: wird mit dem Demo-Bootstrap (Phase 6) erfolgen — der lädt das File und prüft, dass alle Nodes existieren.
- [ ] **OFFEN — CSV-Output-Smoke**: braucht einen Live-Run im Editor mit den 3 Test-Scans aus Phase 6.
### Phase 6 — PWG-Demo-Bootstrap (`pwgDemo2026.py`) ✅ DONE (2026-04-19)
**Ziel:** Per Klick (oder API-Call auf `routeAdminDemoConfig`) entsteht in einer leeren Dev-/Demo-Umgebung ein vollständig lauffähiges PWG-Setup, in dem der Pilot-Workflow direkt ausgeführt werden kann.
- [x] **6a — Skeleton:** `gateway/modules/demoConfigs/pwgDemo2026.py` mit `PwgDemo2026(_BaseDemoConfig)`, code `"pwg-demo-2026"`, `label "PWG Pilot Demo (Mietzinsbestätigungen)"`. `load()` / `remove()` analog `investorDemo2026.py` (Helpers wurden vorerst kopiert; Refactor in `_demoHelpers.py` ist eigene Iteration).
- [x] **6b — Mandant + User + Features:** Mandant `stiftung-pwg`, User `pwg.demo@poweron.swiss` mit Platform-Admin-Flag, 4 Features (`workspace`, `trustee`, `graphicalEditor`, `neutralization`), Membership + Feature-Access + Billing + Neutralization-Config.
- [x] **6c — Trustee-Seed-Daten:** `gateway/demoData/pwg/_seedTrusteeData.json` mit 5 Mieter-Contacts und je 12 monatlichen JournalLines für 2026. Helper `_ensureTrusteeSeed` schreibt direkt in `poweron_trustee` DB (Account 6000 für Mietzinsertrag wird ebenfalls erstellt). Idempotent über `external_id`.
- [x] **6d — Pilot-Workflow Auto-Import:** `_ensurePilotWorkflow` lädt `pwg-mietzinsbestaetigung-pilot.workflow.json`, ersetzt Platzhalter `<<TRUSTEE_INSTANCE_ID>>` mit der real angelegten Trustee-Instanz, importiert via `importWorkflowFromDict` (active=false). Idempotent über Label-Check.
- [x] **6e — Test-Scans:** `gateway/demoData/pwg/_generateScans.py` (reportlab) erzeugt 3 PDFs: `mieter01-bestaetigt.pdf`, `mieter02-abweichung-betrag.pdf`, `mieter03-keine-unterschrift.pdf`. Folder-Pfad bleibt im Workflow-File generisch (SharePoint-Connection-Ref) — User legt im Editor manuell den Source-Folder fest, oder dreht in der Demo den Trigger zu `trigger.manual` mit Inline-Files.
- [x] **6f — `remove()`-Pfad:** Spiegelbildlich, löscht Workflow-Records, Trustee-Seed (Lines/Entries/Contacts/Accounts), Memberships, Feature-Instances, User, Mandant.
- [ ] **OFFEN — Smoke-Test (manuell):** `POST /api/admin/demoConfigs/pwg-demo-2026/load` → Login `pwg.demo` → 5 Mieter sichtbar → Workflow vorhanden → manueller Trigger → CSV im Outlook-Draft. Wird beim nächsten Local-Dev-Start gemacht.
### Querschnitt
- [x] **API-Endpunkte:** ja, 2 neue (`POST /workflows/import`, `GET /workflows/{id}/export`).
- [x] **DB-Schema / Migration:** **nein**.
- [x] **Frontend-Komponenten:** Editor-Toolbar Buttons + Export-Action umgesetzt. FilesTab-Context-Menu (UDB-Erkennung) bewusst auf eigenen Plan ausgelagert: `wiki/c-work/1-plan/2026-04-udb-action-system.md` — adressiert das gesamte Action-System der UDB (Right-Click + Long-Press + Drag&Drop + kontextspezifische Inline-Icons), nicht nur Workflow-Files.
- [x] **RBAC / Permissions:** Bestehendes Modell ist ausreichend — Workflows sind Mandate-scoped, `_validateInstanceAccess` prüft Membership + Feature-Access. Zusätzlich `confirm: true`-Flag-Schutz im Agent-Tool `deleteWorkflow`. Kein eigener Permission-Layer nötig (Templates haben bereits `templateScope` + `sharedReadOnly` für Sharing-Cases).
- [x] **Navigation / Routing:** keine Änderung.
- [x] **Sicherheits-Hinweis Export:** Toast nach Export („Workflow als Datei exportiert") reicht. Connection-Refs in Workflow-Files sind technische IDs (keine Secrets), Mail-Adressen sind Konfiguration. Kein Modal-Disclaimer nötig.
- [ ] **OFFEN — Billing-Impact:** Pilot-Workflow verbraucht Tokens (1 AI-Call pro Scan, ggf. Sub-Agent-Calls). Kalkulation für PWG-Pilot: `~3'200 Schreiben/Jahr × ~3 Calls × ~2k Tokens` — gehört in PWG-Preismodell-Action-Item AI3 (eigener Plan).
---
## UI-Smoke-Test-Kochbuch (manuell, nur Browser)
**Voraussetzung:** Gateway läuft (`uvicorn`), Frontend läuft, Login mit einem Platform-Admin-Account (z. B. `patrick`). Outlook-Connection im Mandant „Stiftung PWG" verfügbar (für Test D).
### Test A — PWG-Demo laden (≈ 1 min)
1. Login als Platform-Admin.
2. Sidebar oben rechts: Avatar → **„Admin"** → Reiter **„Demo-Konfigurationen"**.
3. Eintrag **„PWG Pilot Demo (Mietzinsbestätigungen)"** suchen → Klick **„Laden"**.
4. Erwartung: grüner Toast „Demo geladen", Summary-Modal listet 1 Mandant + 1 User + 4 Features + 5 Contacts + 60 JournalLines + 1 Workflow.
### Test B — Demo-Inhalt prüfen (≈ 2 min)
1. Logout, Login als `pwg.demo@poweron.swiss` (Default-Passwort siehe Modal aus Test A).
2. Mandanten-Wechsler oben links → **„Stiftung PWG"** auswählen.
3. Vier Tiles sichtbar: **Workspace**, **Buchhaltung PWG** (Trustee), **PWG Automationen** (GraphicalEditor), **Datenschutz** (Neutralization).
4. **Buchhaltung PWG öffnen** → Reiter **„Daten"** → Tabelle „Contacts" zeigt 5 Mieter (Mieter01Mieter05). Tabelle „JournalLines" zeigt 60 Einträge (Filter `account = 6000`).
5. **PWG Automationen öffnen** → Workflow-Liste enthält **„PWG — Jahresmietzinsbestätigung Pilot"** mit Status-Badge `inactive`.
### Test C — File-Round-Trip + Agent-CRUD (≈ 4 min)
**Teil 1 — UI-Export/Import:**
1. In der Workflow-Liste Action **„Als Datei exportieren"** an der Pilot-Zeile klicken → Browser lädt `pwg-jahresmietzinsbestaetigung-pilot.workflow.json` herunter.
2. Header-Button **„Importieren"** klicken → eben heruntergeladene Datei auswählen.
3. Toast „Workflow importiert (deaktiviert)" erscheint, Liste enthält jetzt zwei Workflows (zweiter mit identischem Label, ebenfalls inactive).
4. Neuen Workflow per Action löschen.
**Teil 2 — Agent-CRUD:**
1. Pilot-Workflow öffnen → KI-Tab unten rechts.
2. Prompt: **„Erstelle einen leeren Workflow namens 'Smoke-Test'."** → Agent-Antwort enthält neue Workflow-ID, Liste aktualisiert sich.
3. Prompt: **„Lösche den Workflow 'Smoke-Test'."** → Agent fragt nach Bestätigung (`confirm flag missing`).
4. Prompt: **„Ja, bitte löschen, mit confirm true."** → Workflow ist weg.
### Test D — End-to-End mit Outlook (≈ 5 min, braucht Outlook-Account)
1. Pilot-Workflow öffnen, Editor-Canvas sichtbar.
2. Property-Panel der Nodes prüfen:
- `sharepoint.listFiles` Source-Folder auf einen erreichbaren SharePoint-/OneDrive-Ordner setzen.
- `email.draftEmail` Connection auf den eigenen Outlook-Account, Empfänger auf eigene Adresse setzen (Demo-Mail an sich selbst).
3. Test-PDFs vorbereiten: `python gateway/demoData/pwg/_generateScans.py` einmal ausführen → 3 PDFs in `gateway/demoData/pwg/scans/`. Diese in den oben gewählten SharePoint-Ordner hochladen.
4. Workflow oben rechts auf **„Aktiv"** schalten → Button **„Manuell ausführen"** klicken.
5. Run-Detail-Seite zeigt Step-by-Step-Logs:
- 3 Loop-Durchläufe (1 pro PDF).
- In jedem Loop: `trustee.extractFromFiles``trustee.queryData` (matched=true) → `ai.prompt` (status gefüllt).
- Am Ende: `data.consolidate` produziert CSV mit 3 Zeilen, `email.draftEmail` erstellt Draft.
6. Outlook öffnen → Ordner **„Entwürfe"** → neuer Draft mit CSV-Anhang. CSV zeigt Spalten u. a. `mieterName`, `expectedRent`, `extractedRent`, `delta`, `status`, `antwortVorschlag`. 1× `bestaetigt`, 1× `abweichung_betrag`, 1× `keine_unterschrift`.
### Test E — Demo abräumen (≈ 30 s)
1. Logout, wieder als Platform-Admin einloggen.
2. **Admin → Demo-Konfigurationen → „PWG Pilot Demo" → „Entfernen"**.
3. Toast „Demo entfernt". Mandant „Stiftung PWG" verschwindet aus der Liste.
4. Erneutes „Laden" muss fehlerfrei durchlaufen (Idempotenz-Check).
---
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|---------------------------|------|
| 1 | Given Workflow im Graph-Editor, When User auf "Exportieren → UDB" klickt, Then liegt eine `<label>.workflow.json` im gewählten UDB-Folder mit Schema-Version 1.0 | must |
| 2 | Given Workflow-File in UDB, When User in FilesTab "In Graph-Editor laden" wählt + Mode `create` bestätigt, Then existiert ein neuer Workflow mit identischem Graph und `active=false` | must |
| 3 | Given Workflow-File mit unbekanntem Node-Typ, When Import, Then Fehler-Response listet konkret den fehlenden Typ; kein Workflow wird erstellt | must |
| 4 | Given Workflow A in Editor, When `exportWorkflowToFile``createWorkflowFromFile`, Then ist der neu erstellte Workflow nach Normalisierung byte-äquivalent (Round-Trip-Stabilität) | should |
| 5 | Given AI-Agent mit aktivem `workflow`-Toolbox, When Agent ruft `createWorkflow` mit gültigem Graph, Then wird Workflow erstellt und `workflowId` zurückgegeben | must |
| 6 | Given AI-Agent, When Agent ruft `deleteWorkflow` ohne `confirm: true`, Then Tool antwortet mit Fehler "confirm flag missing" und löscht nichts | must |
| 7 | Given PWG-Pilot-Workflow geladen + 1 Test-Scan-PDF in SharePoint-Ordner, When manueller Trigger, Then enthält die E-Mail-CSV exakt 1 Zeile mit gefülltem `status`-Feld | must |
| 8 | Given Test-Scan ohne Unterschrift, When Workflow läuft, Then `status == "keine_unterschrift"` und `antwortVorschlag` ist gefüllt | should |
| 9 | Given Test-Scan mit korrekten Daten, When Workflow läuft + Trustee-DB enthält passenden Debitor + Journal-Line, Then `status == "bestaetigt"` und `delta == 0` | should |
| 10 | Given importierter Workflow, When User versucht ihn ohne weitere Aktion zu schedulen, Then schlägt es fehl bzw. `active` ist `false` und User muss explizit aktivieren | must |
| 11 | Given leere Dev-Umgebung, When `POST /api/admin/demoConfigs/pwg-demo-2026/load`, Then existieren Mandant "Stiftung PWG", Demo-User, alle 4 Features, 5 Seed-Mieter mit Journal-Lines und der Pilot-Workflow (active=false) | must |
| 12 | Given geladene PWG-Demo, When `POST .../pwg-demo-2026/remove`, Then sind alle erstellten Datensätze restlos entfernt; ein erneutes `load` läuft fehlerfrei (Idempotenz) | must |
| 13 | Given geladene PWG-Demo, When User aktiviert Pilot-Workflow + manueller Trigger, Then enthält der erstellte Outlook-Draft eine CSV mit 3 Zeilen (Status: 1× bestätigt, 1× abweichung_betrag, 1× keine_unterschrift) | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1, 4 | unit | ja | `gateway/tests/unit/graphicalEditor/test_workflow_file_io.py` | pending |
| T2 | 2, 3, 10 | api | ja | `gateway/tests/integration/graphicalEditor/test_workflow_import.py` | pending |
| T3 | 5, 6 | unit | ja | `gateway/tests/unit/serviceAgent/test_workflow_tools_crud.py` | pending |
| T4 | 7, 8, 9, 13 | e2e | manuell | PWG-Demo-Instanz mit 3 Test-Scans (über `pwgDemo2026.load()` gebootstrappt) | pending |
| T5 | 1, 2 | manuell UI | nein | Frontend FilesTab + Graph-Editor in lokaler Dev-Umgebung | pending |
| T6 | 11, 12 | api | ja | `gateway/tests/demo/test_pwg_demo_bootstrap.py` (analog zu `test_demo_bootstrap.py`) | pending |
## Links
- Quellplan (0-ideas): `wiki/c-work/0-ideas/2026-04-pm-consolidated-customer-requirements.md` (Abschnitt 1.9c)
- PWG-Workshop-Inputs: `pamocreate/projects/poweron/customer-pwg/20260415-inputs-pwg.txt`
- Graph-Editor Models: `gateway/modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py`
- Graph-Editor Routes: `gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py`
- System-Templates Bootstrap: `gateway/modules/interfaces/interfaceBootstrap.py` (`_buildSystemTemplates`)
- Agent Workflow-Tools: `gateway/modules/serviceCenter/services/serviceAgent/workflowTools.py`
- Toolbox-Registry: `gateway/modules/serviceCenter/services/serviceAgent/toolboxRegistry.py`
- UDB Files-Tab: `frontend_nyla/src/components/UnifiedDataBar/FilesTab.tsx`
- File-Upload-Route: `gateway/modules/routes/routeDataFiles.py`
- Trustee-Modelle: `gateway/modules/features/trustee/datamodelFeatureTrustee.py`
- Abacus-Connector: `gateway/modules/features/trustee/accounting/connectors/accountingConnectorAbacus.py`
- Feature-Sub-Agent: `gateway/modules/serviceCenter/services/serviceAgent/featureDataAgent.py`
- Demo-Config-Basis: `gateway/modules/demoConfigs/_baseDemoConfig.py`
- Investor-Demo-Vorlage: `gateway/modules/demoConfigs/investorDemo2026.py`
- Demo-Config Auto-Discovery: `gateway/modules/demoConfigs/__init__.py`
- Demo-Config-Routen: `gateway/modules/routes/routeAdminDemoConfig.py`
- Bestehende Demo-Tests: `gateway/tests/demo/test_demo_bootstrap.py`
- PR: ...
- Issue: ...
## Abschluss
- [ ] `b-reference/gateway/architecture.md` ergänzen (Workflow-File-IO Sektion)
- [ ] `b-reference/frontend-nyla/architecture.md` ergänzen (FilesTab Workflow-Handling)
- [ ] `TOPICS.md` ergänzen (neues Thema "Workflow Portability")
- [ ] PWG-Pilot-Workflow-Datei in `gateway/demoData/workflows/` committed und im Demo-Config (`pwgDemo2026.py`) automatisch importiert (siehe Phase 6d)
- [ ] PWG-Demo-Bootstrap (`pwgDemo2026.py`) erscheint in `_getAvailableDemoConfigs()` und ist über die Admin-UI / `routeAdminDemoConfig` aufrufbar
- [ ] Dieses Dokument → `c-work/2-build/` verschieben sobald Phase 1 startet