# 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: ` | | `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: `, `outputFormat: "json"`, `documentList: `, `context: ` | | `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: `, `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 2–3 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 `