wiki/c-work/1-plan/2026-04-pwg-pilot-mietzinsbestaetigung-workflow.md

404 lines
28 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: plan -->
<!-- started: 2026-04-16 -->
<!-- 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.
**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).
- **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 |
---
## 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
- [ ] **Schema-Modul `_workflowFileSchema.py`** mit `WORKFLOW_FILE_SCHEMA_VERSION = "1.0"`, Funktionen `_validateFileEnvelope()`, `_normalizeNodePositions()`, `_stripPersistenceFields()`, `_buildFileFromWorkflow()`.
- [ ] **Interface-Methoden** in `interfaceFeatureGraphicalEditor.py`:
- `importWorkflowFromDict(envelope, mandateId, featureInstanceId, mode, targetWorkflowId?) -> workflowId`
- `exportWorkflowToDict(workflowId) -> envelope`
- [ ] **Routen** in `routeFeatureGraphicalEditor.py`:
- `POST /{instanceId}/workflows/import` (Body: `fileId`, `mode`, `targetWorkflowId?`).
- `GET /{instanceId}/workflows/{workflowId}/export` (Query: `asFileId`, `folderId?`).
- [ ] Validierungen:
- Bekannte Schema-Version, Pflichtfelder, Node-Typen vorhanden in `STATIC_NODE_TYPES`.
- Bei Import: `active=false` erzwingen.
- [ ] Unit-Tests: Round-Trip (Export → Import → erneuter Export, Bytes identisch nach Normalisierung).
### Phase 2 — UDB-Erkennung & Frontend-IO
- [ ] **`FilesTab.tsx`**:
- Workflow-File-Detection (Endung + Content-Sniffing über `useFiles.isJsonContent`).
- Workflow-Icon, Context-Menu-Eintrag "In Graph-Editor laden" → öffnet Modal.
- [ ] **Modal "Workflow importieren"**: Editor-Instanz-Auswahl, Mode-Wahl (Neu / Bestehenden ersetzen), Submit → API.
- [ ] **Graph-Editor-Toolbar**: Buttons "Importieren" (Files-Picker) und "Exportieren" (Download-vs-UDB-Modal).
- [ ] **`api/workflowApi.ts`** (oder analog): `importWorkflowFromFile(fileId, …)`, `exportWorkflowToFile(workflowId, …)`.
### Phase 3 — Agent-Tools Full-CRUD
- [ ] In `workflowTools.py` neue Funktionen: `_createWorkflow`, `_createWorkflowFromFile`, `_exportWorkflowToFile`, `_deleteWorkflow` (alle mit `_`-Prefix gemäss Naming-Konvention).
- [ ] Tool-Definitionen in `getWorkflowToolDefinitions()` ergänzen.
- [ ] `toolboxRegistry.py` — Tool-Liste in `workflow`-Toolbox erweitern.
- [ ] Sicherheits-Confirm: `deleteWorkflow` verlangt `confirm: true`.
- [ ] Integration-Test: Agent erstellt Workflow → exportiert → löscht → re-importiert via fileId.
### 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`:**
- [ ] Definition in `gateway/modules/features/graphicalEditor/nodeDefinitions/trustee.py` ergänzen (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"`.
- [ ] Action-Implementierung `gateway/modules/workflows/methods/methodTrustee/actions/queryData.py`:
- Mode `lookup` mit Entity `tenantWithRent` → fuzzy match in `TrusteeDataContact` (Debitor-Filter), dann Aggregat in `TrusteeDataJournalLine` über die für Mietzins relevanten Konten in der Periode.
- Mode `raw` → direkter `FeatureDataProvider.queryTable` mit `entity` als Tabellenname.
- Mode `aggregate``FeatureDataProvider.aggregateTable` mit `extraFilter`.
- Output-Format: `{ matched: bool, contacts: [...], journalLines: [...], expectedRentAmount: number|null, matchConfidence: number }` — direkt verwendbar als `context` in `ai.prompt`.
- [ ] Registrierung in `methodTrustee/__init__.py` und `methodTrustee.py` (analog zu existierenden Actions).
- [ ] Unit-Test `tests/methods/methodTrustee/test_queryData.py`: Mock `FeatureDataProvider`, prüfe Match-Logik mit Tippfehler-Toleranz und Mietzins-Aggregation.
**Sub-Task 4b — Attachment-Support in `email.draftEmail`:**
- [ ] In `nodeDefinitions/email.py` neuen optionalen Parameter ergänzen:
```python
{"name": "attachments", "type": "json", "required": False, "frontendType": "attachmentBuilder",
"description": t("Anhänge (Liste von { name, contentRef | csvFromVariable | base64Content })"),
"default": []},
```
- [ ] In `gateway/modules/workflows/methods/methodOutlook/actions/composeAndDraftEmailWithContext.py` (oder dem aktuellen Pfad) Attachment-Handling implementieren:
- `contentRef` → resolved aus Wire/Variable, als Bytes/String.
- `csvFromVariable` → resolved aus Variable (z. B. Output von `data.consolidate`) und als CSV-Anhang gehängt.
- `base64Content` → direkt dekodieren.
- Hochladen als Outlook-Draft-Attachment via Graph-API (`/me/messages/{id}/attachments`).
- [ ] Frontend: Attachment-Builder im Property-Panel des Editors (kann minimal sein — JSON-Editor reicht für Pilot).
- [ ] Unit-Test mit gemocktem Outlook-Connector: prüfe dass Attachment korrekt im erstellten Draft enthalten ist.
**Sub-Task 4c — Frontend-Anpassungen für neue Nodes:**
- [ ] `trustee.queryData` automatisch in der Node-Palette sichtbar (kommt durch Definition gratis).
- [ ] Property-Panel: Bei `mode: "lookup"` und `entity: "tenantWithRent"` die Tenant-Felder anzeigen, sonst ausblenden (`dependsOn`-Mechanismus wie in `sharepointFolder`).
- [ ] Falls beide Wege zu aufwändig: Pilot-Workflow auf 7 Nodes vereinfachen — Step 5 macht ALLES (Sub-Agent-Query + Klassifikation + Antwort) in einem `ai.prompt`-Node mit `featureSubAgent`-Tool-Zugriff.
### Phase 5 — Pilot-Workflow als File
- [ ] Datei `gateway/demoData/workflows/pwg-mietzinsbestaetigung-pilot.workflow.json` erstellen (Inhalt wie oben spezifiziert).
- [ ] Datei via Test in eine PWG-Demo-Instanz importieren (manueller Smoke-Test).
- [ ] Test-Lauf mit 23 fiktiven Scan-PDFs (in `gateway/demoData/pwg/scans/` ablegen — Sub-Aufgabe).
- [ ] CSV-Ergebnis prüfen: enthält pro Scan eine Zeile mit allen Feldern aus dem Output-Schema.
### Querschnitt
- [ ] **API-Endpunkte:** ja, 2 neue (Import + Export).
- [ ] **DB-Schema / Migration:** **nein**.
- [ ] **Frontend-Komponenten:** FilesTab-Erweiterung, Graph-Editor-Toolbar-Buttons, Import-Modal.
- [ ] **RBAC / Permissions:** Import/Export benötigen gleiche Rechte wie `update_workflow`. `deleteWorkflow`-Tool nur wenn User-Rolle `delete_workflow` darf.
- [ ] **Neutralisierung betroffen?** Indirekt ja: Workflow-Files können sensible Parameter (Connection-Refs, E-Mail-Adressen) enthalten. **Doku-Hinweis** beim Export-Modal: "Vor Weitergabe Datei prüfen". Keine automatische Neutralisierung im Pilot-Scope.
- [ ] **Navigation / Routing:** keine Änderung.
- [ ] **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` als Grössenordnung — gehört in PWG-Preismodell-Action-Item AI3.
---
## 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 |
## 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 | e2e | manuell | PWG-Demo-Instanz mit 3 Test-Scans | pending |
| T5 | 1, 2 | manuell UI | nein | Frontend FilesTab + Graph-Editor in lokaler Dev-Umgebung | 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`
- 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`) referenziert
- [ ] Dieses Dokument → `c-work/2-build/` verschieben sobald Phase 1 startet