This commit is contained in:
ValueOn AG 2026-04-29 20:23:03 +02:00
parent 8a70c6ea9a
commit 6eeeb962f6
10 changed files with 1637 additions and 0 deletions

View file

@ -0,0 +1,614 @@
<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla -->
# AI Reports: Generisches Style-Management, AI-Call-Konfiguration, Inline-Bilder
## Beschreibung und Kontext
Drei verzahnte Themen rund um AI-erzeugte Reports und AI-Calls in
Workflows / Workspace:
1. **Style-Management generisch und Agent-getrieben.** Die heutigen
Renderer (DOCX, XLSX, PPTX, HTML, PDF) haben hardcoded Farben (z.B.
DOCX-Tabellen-Header `4472C4`), feste Bildbreiten, hartes
`lang="en"` im HTML. Es gibt keinen Mechanismus, dass ein Agent
pro Bericht Styling, Logo, Schrift, Farben mitgeben kann. Wir
wollen **kein** fixes Template-/Theme-Mandate-CI-Konzept, sondern
ein **generisches Style-Schema**, das der Agent fuellt -- die
Renderer mappen daraus.
2. **Per-Call AI-Konfiguration: Neutralisierung + erlaubte Modelle.**
Heute laufen AI-Calls aus Workflows ueber das zentrale AI-Gate
(`mainServiceAi.py`); Neutralisierung wird via Feature/Session/Request-
Flags gewaehlt, aber im FlowEditor ist sie pro Node nicht explizit
einstellbar. Eine **Whitelist erlaubter Modelle pro Node** existiert
ueberhaupt nicht. Im AI-Workspace soll beides automatisch aus dem
Workspace-Kontext kommen, im FlowEditor pro Node.
3. **Inline-Bilder ueberall.** Die heutige MD->JSON-Pipeline
(`_mediaTools._markdownToDocumentJson`,
`subDocumentUtility.markdownToDocumentJson`) flacht alles auf Strings:
Bilder muessen eine eigene Markdown-Zeile sein. Ein Bild in einer
Tabellenzelle, mitten in einem Paragraph, in einer Listenzeile etc.
geht heute nicht.
Zusaetzliche technische Schuld:
- MD->JSON ist **doppelt** implementiert (`_mediaTools` und
`subDocumentUtility`) -- Drift-Risiko.
- Agent ruft `renderReport` ohne `aiService` auf (`_mediaTools.py`
282-287), KI-Style-Anpassung im Renderer-Pfad ist damit inaktiv. Das
wird durch den neuen Agent-Style-Pfad ohnehin obsolet.
**Geschaeftstreiber:** Reports sind das visuelle Endprodukt, das User mit
Kunden teilen. Ein Agent, der pro Bericht Layout-Entscheidungen trifft
(Finanzreport vs. Marketing-Flyer vs. neutrales Memo), hebt die
wahrgenommene Qualitaet deutlich. Per-Node-Neutralisierung +
Modell-Whitelist machen Compliance und Kostenkontrolle pro Workflow
explizit und auditierbar.
## Fokus und kritische Details
- Markdown bleibt User-/Agent-Eingangs-Schicht. JSON ist nur das interne
deterministisch erzeugte Zwischenformat. **Keine** Aenderung am MD-
First-Konzept.
- Style-Schema: muss generisch genug sein, dass die heutigen Renderer
alle relevanten Visual-Konstanten daraus ziehen, aber **kein**
branchenspezifisches Preset-Inventar mit harter Auswahl.
- Defaults sind wichtig: wenn der Agent nichts angibt, muss der Renderer
eine vernuenftige neutrale Default-Style-Set verwenden.
- Inline-Bilder erfordern Schema-Aenderung beim `cell`/`paragraph`/
`bullet_list_item`-Content: weg von `string`, hin zu `inline-Run-
Liste` (`text` | `image` | `link` | `bold` | `italic`).
- Per-Node AI-Konfiguration darf das **zentrale AI-Gate** nicht
umgehen: das Gate bleibt einzige Stelle, die Neutralisierung wirklich
durchsetzt. Node-Parameter werden in den `AiCallRequest` reingefuettert
und folgen den heutigen Praezedenzregeln (Feature -> Session -> Request,
OR-Logik fuer Neutralisierung).
## Ziel und Nicht-Ziele
- Ziel:
- **Style:** Generisches Style-Schema, vom Agent pro Render-Call
fuellbar (`metadata.style`). Renderer mappen einheitlich. Sensible
Defaults wenn nichts gesetzt.
- **AI-Call-Konfig:** Pro `ai.*`-Node im FlowEditor zwei neue Parameter
`requireNeutralization` (boolean) und `allowedModels` (Liste).
Workspace-Kontext fuellt sie automatisch fuer Workspace-Calls.
Validierung am AI-Gate (Whitelist-Check vor Modellwahl).
- **Inline-Bilder:** Bilder in Paragraph, Tabellenzelle, Listenzeile,
Code-Block-Caption etc. moeglich. MD-Syntax erkennt sie an
beliebiger Position. Renderer rendert sie inline wo das Format es
kann; sonst dokumentierter Fallback.
- **Konsolidierung:** 1× MD->JSON-Pipeline (Drift weg).
- NICHT: Komplett neuer Renderer (z.B. WeasyPrint statt reportlab).
- NICHT: Native XLSX-Charts via openpyxl ChartObjects (separater Plan).
- NICHT: Mandate-CI-Konzept, Logo-Inheritance, Theme-Presets.
- NICHT: User-konfigurierbare Custom-Themes via Admin-UI.
- NICHT: Aenderung am bestehenden Neutralisierungs-Engine; nur Eingangs-
Pfad ergaenzen.
## Betroffene Module
- Gateway:
- `gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py`
-- `_markdownToDocumentJson` raus, auf gemeinsamen Helper
umstellen; `renderDocument`-Tool-Schema ergaenzt um optionalen
`style`-Parameter.
- `gateway/modules/serviceCenter/services/serviceGeneration/subDocumentUtility.py`
-- alleinige MD->JSON-Funktion mit Inline-Run-Modell.
- `gateway/modules/serviceCenter/services/serviceGeneration/mainServiceGeneration.py`
-- `_resolveStyle(metadata.style) -> ResolvedStyle` (Defaults +
Agent-Overrides).
- `gateway/modules/serviceCenter/services/serviceGeneration/renderers/*`
-- alle 5 Renderer auf Style-Lookup umstellen, Inline-Run-Renderer.
- `gateway/modules/datamodels/datamodelJson.py` -- Schema-Erweiterung
fuer Inline-Runs und `cellContent`.
- `gateway/modules/datamodels/datamodelAi.py` --
`AiCallRequest.allowedModels: Optional[List[str]]` (Whitelist).
- `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py`
-- Whitelist-Check vor Modellwahl, Fail-fast wenn keiner uebrig.
- `gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py`
-- Standardparameter `requireNeutralization` + `allowedModels` zu
allen `ai.*`-Nodes (gemeinsamer Helper).
- `gateway/modules/workflows/methods/methodAi/actions/*.py` -- Node-
Parameter durchreichen in den `AiCallRequest`.
- `gateway/modules/serviceCenter/services/serviceAgent/conversationManager.py`
-- Workspace-Kontext-Defaults fuer `requireNeutralization` /
`allowedModels` aus Workspace-Config in `ServiceCenterContext`
setzen.
- Frontend:
- FlowEditor: Pro AI-Node die zwei neuen Parameter im Properties-
Panel anzeigen (`requireNeutralization` Toggle, `allowedModels`
Multi-Select). Hilfetexte.
- Workspace-Settings: bestehender Neutralisierungs-Toggle bleibt;
neuer Multi-Select fuer `allowedModels` (Default: alle verfuegbaren
Modelle des Mandate).
- Style-Vorschau (klein): Optional Snippet im FlowEditor, das den
`style`-Parameter eines `file.create`/`renderDocument`-Nodes
visualisieren kann (deferred).
- DB-Migration: nein im engeren Sinne. `Workflow`-Tabelle bekommt keinen
neuen Pflicht-Spalten (Node-Parameter leben im Graph-JSON).
`WorkspaceSettings.allowedModels`-Feld additiv via Auto-Init.
## Style-Schema (generisch, Agent-getrieben)
Der Agent uebergibt einen `style`-Block in `metadata` (alle Felder
optional, Renderer fallen auf Defaults zurueck):
```json
{
"metadata": {
"language": "de",
"documents": [...],
"style": {
"fonts": {
"primary": "Calibri",
"monospace": "Consolas"
},
"colors": {
"primary": "#0F3D7A",
"secondary": "#1F2937",
"accent": "#E11D48",
"background": "#FFFFFF"
},
"headings": {
"h1": { "sizePt": 24, "weight": "bold", "color": "#0F3D7A", "spaceBeforePt": 12, "spaceAfterPt": 6 },
"h2": { "sizePt": 18, "weight": "bold", "color": "#0F3D7A" },
"h3": { "sizePt": 14, "weight": "bold" },
"h4": { "sizePt": 12, "weight": "bold" }
},
"paragraph": { "sizePt": 11, "lineSpacing": 1.15 },
"table": {
"headerBg": "#0F3D7A",
"headerFg": "#FFFFFF",
"rowBandingEven": "#F5F8FC",
"rowBandingOdd": "#FFFFFF",
"borderColor": "#CBD5E1",
"borderWidthPt": 0.5
},
"list": { "bulletChar": "•", "indentPt": 18 },
"image": { "defaultWidthPt": 480, "alignment": "center" },
"page": {
"format": "A4",
"marginsPt": { "top": 60, "bottom": 60, "left": 60, "right": 60 },
"showPageNumbers": true,
"headerHeight": 30,
"footerHeight": 30,
"headerLogo": { "fileId": "abc", "alignment": "right", "heightPt": 24 },
"headerText": "Quartalsbericht Q3/2026",
"footerText": "Vertraulich -- Seite {page}/{pages}"
}
}
}
}
```
**Logo:** Es gibt keinen Mandate-Inheritance-Pfad. Wenn ein Logo in den
Bericht soll, gibt der Agent eine `fileId` an (Datei muss zuvor via
`generateImage`/`writeFile` oder DataSource-Download erzeugt sein und
liegt bereits als `FileItem` vor).
**Defaults (`gateway/modules/serviceCenter/services/serviceGeneration/styleDefaults.py`):**
neutrale Default-Werte fuer alle Felder. `_resolveStyle(agentStyle) ->
ResolvedStyle` macht ein deep-merge `defaults <- agentStyle`. Renderer
sieht immer eine vollstaendig aufgeloeste Struktur.
**Generische "Style-Komponenten" auf Renderer-Seite:** Pro visuellem
Element (heading, paragraph, table, listItem, image, codeBlock, page)
existiert eine Renderer-Funktion, die ausschliesslich die `ResolvedStyle`
und den Content-Knoten konsumiert. So ist die Implementierung pro
Format gleichfoermig (DOCX/XLSX/PPTX/HTML/PDF -- alle haben ein
`renderHeading(headingNode, style.headings.hN)` etc.).
## Inline-Run-Modell (fuer Inline-Bilder ueberall)
Heute: `paragraph.text: str`, `cell: str`, `bullet_list.items: [str]`.
Neu: `inlineRuns: List[InlineRun]` mit
```json
{ "type": "text" | "image" | "link" | "bold" | "italic" | "code",
"value": "...",
"fileId": "...", // bei type=image
"widthPt": 96, // optional bei type=image, default klein/inline
"href": "..." // bei type=link
}
```
Backwards-kompatibel: Wenn der MD-Parser nur Text findet, bleibt es
ein einzelner Run mit `type: text`. Renderer bekommen einen Helper
`_renderInlineRuns(runs, style, container)`, der pro Run das richtige
Element produziert (Text-Run mit Style, Inline-Image-Anchor,
Hyperlink-Run, etc.).
**Markdown-Erkennung:** `![alt](file:xyz)` und `![alt](file:xyz "100pt")`
mitten in einem Paragraph oder in einer Tabellenzelle wird vom Parser
zu einem `image`-Run. Listen-Items dasselbe.
**Renderer-Faehigkeiten (Matrix):**
| Format | Inline-Bild im Paragraph | Inline-Bild in Zelle | Inline-Bild in Listen-Item | Fallback |
|--------|--------------------------|----------------------|-----------------------------|----------|
| DOCX | nativ via `add_picture` | nativ via Cell-Paragraph | nativ | -- |
| XLSX | Bild ueber Zelle anchorn | nativ Cell-Anchor | -- (kein Listen-Konzept) | Bild unter Zeile |
| HTML | trivial `<img>` | trivial `<img>` in `<td>` | trivial `<img>` in `<li>` | -- |
| PPTX | nicht nativ in Text-Run | begrenzt | -- | Bild als separate Shape unter Text |
| PDF | reportlab Inline-Image | reportlab Cell-Image | begrenzt | Bild in eigenem Flowable unter Text |
## AI-Call-Konfiguration pro Node (Neutralisierung + Modell-Whitelist)
### Was es heute schon gibt (wichtig fuer Plan-Verstaendnis)
- **`AiCallRequest.requireNeutralization`** existiert (`datamodelAi.py` Z. 177).
- **`AiCallOptions.allowedProviders`** existiert (`datamodelAi.py` Z. 164) --
Provider-Whitelist auf Anbieter-Ebene (z.B. `anthropic`, `openai`,
`private-llm`).
- **`Workflow.allowedProviders`** wird ueber `__getattr__` durchgereicht
(`mainServiceAi.py` Z. 89).
- **`_calculateEffectiveProviders()`** macht RBAC ∩ Workflow.allowedProviders
und schreibt das in `request.options.allowedProviders` (`mainServiceAi.py`
Z. 175-178).
- **`WorkspaceUserInput.allowedProviders`** + **`requireNeutralization`**
werden vom Frontend pro Request mitgegeben
(`routeFeatureWorkspace.py` Z. 111-113) und in den
`ServiceCenterContext` gesetzt (Z. 716-719).
- **`ServiceCenterContext.requireNeutralization`** existiert (`context.py`
Z. 23).
- **`GET /api/system/ai-models`** liefert die verfuegbaren Modelle
(`routeSystem.py` Z. 937).
### Ziele dieses Plans (additiv zur bestehenden Provider-Logik)
Wir fuehren `allowedModels` als **feinere Granularitaet neben** dem
bestehenden `allowedProviders` ein -- nicht als Ersatz. Ein Provider
darf erlaubt sein, aber nur bestimmte Modelle dieses Providers
nutzbar (z.B. Provider `openai` erlaubt, aber nur `gpt-5-mini` nicht
`gpt-5`).
- **Pro `ai.*`-Node im FlowEditor (NEU):**
- `requireNeutralization` (`boolean`, Default `false`).
- `allowedModels` (`list[string]`, Default `[]` = "alle").
- (`allowedProviders` koennte spaeter analog folgen -- bewusst NICHT in
diesem Plan, weil heute nur Workflow-Level eingestellt wird.)
- **Im AI-Workspace (Default-Persistenz):**
- `requireNeutralization`: heute schon im `WorkspaceUserInput` --
bleibt, jetzt aber **persistent** als User-Default.
- `allowedProviders`: heute schon im `WorkspaceUserInput` -- bleibt,
jetzt ebenfalls persistent als User-Default.
- `allowedModels` (NEU): zusaetzliches Feld in `WorkspaceUserInput` +
UI-Feld in den Workspace-Einstellungen.
- **Persistenz-Entscheidung (siehe Entscheidungs-Tabelle):** Phase 5a
kommt mit neuem Endpoint `GET/PUT /api/workspace/{instanceId}/user-
settings` fuer ALLE DREI Felder als User-spezifische Defaults pro
Workspace -- damit der User die Settings nicht bei jedem Browser-
Open neu setzen muss. Frontend laedt Defaults beim Workspace-Open
und schickt sie pro Call mit.
- **Datenmodell-Erweiterung:**
- `AiCallOptions.allowedModels: Optional[List[str]] = None` (analog
zu `allowedProviders`).
- `Workflow.allowedModels: Optional[List[str]] = None` (analog zu
`Workflow.allowedProviders`).
- **Validierung am zentralen AI-Gate (`mainServiceAi.py`):**
- `requireNeutralization` folgt **bestehender** OR-Logik (keine
Aenderung).
- Neue private Funktion `_calculateEffectiveModels()` analog zu
`_calculateEffectiveProviders()`: RBAC ∩ Workflow.allowedModels ∩
Node.allowedModels (Node setzt es ueber `request.options`).
- **Effektive Filterkette:** Provider-Filter (heute) AND Model-Filter
(neu) AND functionCall-Anforderung (heute).
- Ist die gefilterte Liste leer -> Fail-fast mit
`RuntimeError("No allowed model available for this call (allowedModels=[...], allowedProviders=[...])")`.
### Beispiel: AI-Node-Definition (gekuerzt)
```python
# in gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py
# (allowedProviders existiert bereits auf Workflow-Ebene; hier nur die
# zwei NEUEN Felder pro AI-Node)
_AI_COMMON_PARAMS = [
{"name": "requireNeutralization", "type": "boolean", "required": False,
"frontendType": "checkbox", "default": False,
"description": t("Eingaben fuer diesen Call neutralisieren")},
{"name": "allowedModels", "type": "array", "required": False,
"frontendType": "modelMultiSelect", "default": [],
"description": t("Erlaubte LLM-Modelle (leer = alle erlaubten); zusaetzlicher AND-Filter ueber allowedProviders")},
]
# pro Node anhaengen, z.B. ai.prompt:
{
"id": "ai.prompt",
...
"parameters": [
{"name": "aiPrompt", ...},
{"name": "resultType", ...},
{"name": "documentList", ...},
{"name": "simpleMode", ...},
*_AI_COMMON_PARAMS,
],
...
}
```
### Datenfluss
```mermaid
flowchart LR
Node[ai.* Node Parameters] -->|"requireNeutralization, allowedModels"| Action[methodAi/actions/*.py]
Action -->|"sets request.options.allowedModels<br/>+ request.requireNeutralization"| Gate[mainServiceAi._call]
Gate --> EffMod[_calculateEffectiveModels<br/>RBAC AND Workflow AND Node]
Gate --> EffProv[_calculateEffectiveProviders<br/>existing]
EffMod --> Pick[_pickModel]
EffProv --> Pick
Pick -->|"empty?"| Fail[Fail-fast RuntimeError]
Pick -->|"ok"| Neutral[_shouldNeutralize OR-Logik existing]
Neutral --> LLM[Modell-Call]
WorkspaceUI[WorkspaceUserInput<br/>requireNeutralization+allowedModels] -->|"per-request"| Ctx[ServiceCenterContext]
WorkspaceSet[GET/PUT /workspace/.../user-settings<br/>NEU] -->|"defaults"| WorkspaceUI
```
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | KEIN fixer Theme-Katalog, KEIN Mandate-CI-Konzept | User-Korrektur: generisches Style-Management vom Agent gefuellt |
| 2026-04-29 | Style-Schema generisch in `metadata.style`, Defaults zentral | Agent kann pro Bericht entscheiden; Renderer haben einheitliche Mapping-Schicht |
| 2026-04-29 | Inline-Run-Modell statt String-basierter Content | Voraussetzung fuer "Bilder ueberall" |
| 2026-04-29 | Per-Node `requireNeutralization` + `allowedModels` als Standardparameter ALLER `ai.*`-Nodes | Keine Sonder-Felder pro Node-Typ; im FlowEditor konsistent |
| 2026-04-29 | `allowedModels` ergaenzt das bestehende `allowedProviders`, ersetzt es nicht | Provider = Anbieter-Ebene (existiert), Model = konkrete Modell-Ebene (neu, feiner) |
| 2026-04-29 | `allowedModels` lebt in `AiCallOptions` (analog `allowedProviders`), NICHT direkt in `AiCallRequest` | Symmetrie zur bestehenden Provider-Whitelist |
| 2026-04-29 | Neuer Workspace-User-Settings-Endpoint `GET/PUT /api/workspace/{instanceId}/user-settings` fuer ALLE DREI Felder (`requireNeutralization`, `allowedProviders`, `allowedModels`) | Heute werden `requireNeutralization`/`allowedProviders` pro Request aus dem Frontend mitgeliefert -- ohne Persistenz muesste der User die Settings bei jedem Browser-Open neu setzen. Konsistenz-Argument: wenn `allowedModels` persistiert wird, dann auch die anderen zwei -- sonst zwei Default-Mechanismen mit unterschiedlichem Verhalten. |
| 2026-04-29 | `ServiceCenterContext.requireNeutralization` ist bereits gesetzt; analoge `allowedModels`-Propagation NICHT noetig (Frontend schickt es ohnehin pro Call) | Kontextfeld nur dort, wo Sub-Service ohne Frontend-Input es braucht |
| 2026-04-29 | Whitelist-Check vor Modellwahl; leer -> Fail-fast | Compliance: Workflow soll **deterministisch** scheitern statt stillschweigend ein nicht-erlaubtes Modell zu nehmen |
| 2026-04-29 | MD->JSON-Konsolidierung in `subDocumentUtility.py` | Bereits zentralerer Pfad als `_mediaTools` |
| 2026-04-29 | Native XLSX-Charts via openpyxl spaeter | Heute reicht PNG-Embed; Chart-API ist eigene Komplexitaet |
## Umsetzungs-Checkliste
### Phase 1 -- MD->JSON konsolidieren + Inline-Runs
- [ ] `subDocumentUtility.markdownToDocumentJson` zur einzigen Quelle
machen.
- [ ] `_mediaTools._markdownToDocumentJson` durch Aufruf des Helpers
ersetzen, lokale Funktion entfernen.
- [ ] Schema-Erweiterung in `datamodels/datamodelJson.py`:
- `paragraph.inlineRuns: List[InlineRun]` (alt `text: str` als
Backwards-Compat-Feld weiter unterstuetzt).
- `bullet_list.items: List[List[InlineRun]]`.
- `table.cell` neu strukturiert: `cellContent: List[InlineRun]`
oder `cellBlocks: List[Section]` (fuer komplexere Zellen mit
eigenem Paragraph/Image-Block).
- [ ] MD-Parser im konsolidierten Helper:
- Inline `![alt](file:xyz)` an beliebiger Position erkennen ->
`image`-Run.
- Inline `[text](url)` -> `link`-Run.
- `**bold**` / `*italic*` / `` `code` `` -> entsprechende Runs.
- Tabellen-Zellen: erkennen ob nur Text -> einzelner Text-Run;
sonst voll geparst (inkl. Bild).
- [ ] Helper `_renderInlineRuns(runs, style, container)` als gemeinsame
Spec; pro Renderer eigene Implementierung.
### Phase 2 -- Style-System
- [ ] Defaults-Modul `gateway/modules/serviceCenter/services/serviceGeneration/styleDefaults.py`
mit neutralen Default-Werten fuer alle Style-Felder.
- [ ] Style-Resolver in `mainServiceGeneration.py`:
`_resolveStyle(metadataStyle: dict | None) -> ResolvedStyle`
(deep-merge defaults <- agentStyle).
- [ ] Renderer auf Resolver umstellen: kein Renderer hat mehr eigene
Default-Konstanten.
- [ ] `renderDocument`-Tool (`_mediaTools.py`) Schema:
- Optionaler Parameter `style` (object) wird in `metadata.style`
gemappt.
- Tool-Beschreibung dokumentiert das Schema mit Beispielen.
### Phase 3 -- Renderer-Refactor (generische Style-Komponenten)
- [ ] DOCX (`rendererDocx.py`):
- `_createTableRowXml` (~ 644-697) Header-Farbe/-Schrift aus
`style.table`.
- `_renderJsonImage` (~ 1016-1082) Default-Breite aus
`style.image.defaultWidthPt`.
- Headings aus `style.headings.hX`.
- Inline-Run-Renderer fuer Paragraph/Cell.
- Page-Margins / Header-Logo / Footer-Text aus `style.page`.
- `templateName: corporate|minimal` deprecaten (1 Release lang
warnen, dann raus).
- [ ] XLSX (`rendererXlsx.py`):
- `_renderJsonImage` Bildbreite aus `style.image`.
- Tabellen-Header aus `style.table`.
- Inline-Bilder per Cell-Anchor (siehe Phase 4).
- [ ] PPTX (`rendererPptx.py`):
- Slide-Size aus `style.page`.
- Title/Body aus `style.headings`.
- Inline-Bilder als zusaetzliche Shape (Fallback).
- [ ] HTML (`rendererHtml.py`):
- `lang` aus `metadata.language`.
- Inline-CSS aus `style` generiert (`_generateCssStyles`
umbauen).
- Inline-Bilder als `<img>`.
- [ ] PDF (`rendererPdf.py`):
- Sample-Style-Set aus `style`.
- Page-Margins aus `style.page.marginsPt`.
- Inline-Bilder als reportlab Inline-Image.
### Phase 4 -- Inline-Bilder ueberall (Renderer-Spezifika)
- [ ] DOCX: Cell-Image via `cell.add_paragraph().add_run().add_picture(...)`.
- [ ] DOCX/PDF: Inline-Image im Paragraph als Run-mit-Image.
- [ ] XLSX: `openpyxl.drawing.image.Image` mit Cell-Anchor; pro Bild eine
neue Image-Instance (openpyxl-Constraint).
- [ ] HTML: `<img>` direkt in `<td>` / `<p>` / `<li>` -- trivial.
- [ ] PPTX: Inline-in-Text-Run nicht moeglich -> Image als zusaetzliche
Shape unter dem Text-Frame, mit Warning im JSON-Output
(`_renderingFallback: pptx-image-below-text`).
- [ ] Tests pro Format: 1× Inline-Bild im Paragraph, 1× in
Tabellenzelle, 1× in Listen-Item.
### Phase 5 -- AI-Call-Konfiguration pro Node (Datenmodell + Backend)
- [ ] `gateway/modules/datamodels/datamodelAi.py`:
- `AiCallOptions.allowedModels: Optional[List[str]] = None`
(analog zu `allowedProviders` in Z. 164 -- KEIN neues Feld in
`AiCallRequest`).
- [ ] `gateway/modules/datamodels/datamodelAuto.py` (oder wo immer das
`Workflow`-Modell liegt):
- `Workflow.allowedModels: Optional[List[str]] = None` (analog
`Workflow.allowedProviders`).
- [ ] `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py`:
- Neue private Methode `_calculateEffectiveModels()` analog zu
`_calculateEffectiveProviders()` (Z. 175-178). RBAC-Modelle ∩
`Workflow.allowedModels``request.options.allowedModels`.
- In `_call`/`_callStream`/`callEmbedding` analog zur
Provider-Logik aufrufen und in `request.options.allowedModels`
schreiben.
- `_pickModel` filtert anschliessend AND-verknuepft auf Provider
UND Model-Whitelist.
- Fail-fast `RuntimeError` wenn die Filterkette leer wird, mit
Audit-Log-Zeile (modelle, provider, rbac-permitted, workflow,
node).
- [ ] `__getattr__` in `mainServiceAi.py` (Z. 88-90) erweitern:
Tuple-Liste um `"allowedModels"` ergaenzen (damit
`service.allowedModels` analog `service.allowedProviders`
aus dem Workflow durchgereicht wird).
### Phase 5a -- Workspace User-Settings (Persistenz fuer alle drei Felder)
- [ ] Neue persistente Settings pro User × Workspace-Instanz.
Tabelle `WorkspaceUserSettings` (additiv via Auto-Init):
- `instanceId UUID NOT NULL`
- `userId UUID NOT NULL`
- `requireNeutralization BOOL DEFAULT false`
- `allowedProviders JSONB DEFAULT '[]'`
- `allowedModels JSONB DEFAULT '[]'`
- PK / Unique: (`instanceId`, `userId`).
- [ ] Backend-Endpoints in `routeFeatureWorkspace.py`:
- `GET /api/workspace/{instanceId}/user-settings` -- effektive
Settings fuer current user (Auto-Insert mit Defaults wenn
leer).
- `PUT /api/workspace/{instanceId}/user-settings` -- speichern
(alle drei Felder im Body).
- [ ] `WorkspaceUserInput.allowedModels: List[str] = []` (additiv,
`routeFeatureWorkspace.py` Z. 109-114). `requireNeutralization`
und `allowedProviders` sind dort schon vorhanden (Z. 112-113).
- [ ] In `_runWorkspaceAgent` (Z. 693+): `allowedModels` ebenso
durchreichen wie heute `allowedProviders` (Z. 716-717) -- in das
`request.options.allowedModels`-Feld schreiben (siehe Phase 5
Datenmodell).
- [ ] Frontend Workspace:
- Workspace-Settings-Tab/Panel mit allen drei Feldern:
- Toggle `requireNeutralization`.
- Multi-Select `allowedProviders` (Optionen aus
`GET /api/system/ai-models` -> distinct connectorTypes).
- Multi-Select `allowedModels` (Optionen aus
`GET /api/system/ai-models` -> displayNames).
- Beim Workspace-Open: Settings via
`GET .../user-settings` laden, Defaults in den Workspace-State
setzen.
- "Speichern"-Button im Settings-Panel ruft `PUT .../user-
settings` mit allen drei Feldern.
- Bei jedem Workspace-Call: aktuelle Settings als
`WorkspaceUserInput`-Felder mitgeben (alle drei).
### Phase 5b -- AI-Nodes im FlowEditor
- [ ] `gateway/modules/features/graphicalEditor/nodeDefinitions/ai.py`:
- Modul-lokaler Helper `_AI_COMMON_PARAMS = [...]` mit den 2
neuen Parametern.
- In ALLE 8 Eintraege von `AI_NODES` als `*_AI_COMMON_PARAMS`
anhaengen.
- [ ] `gateway/modules/workflows/methods/methodAi/actions/*.py`:
- In jeder Action (`process.py`, `webResearch.py`,
`summarizeDocument.py`, `translateDocument.py`,
`convertDocument.py`, `generateDocument.py`,
`generateCode.py`, `consolidate.py`):
- `requireNeutralization = parameters.get("requireNeutralization")`
-> in `request.requireNeutralization` setzen.
- `allowedModels = parameters.get("allowedModels", [])` -> in
`request.options.allowedModels` setzen.
- Helper-Funktion `_applyCommonAiParams(parameters, request)` in
`methodAi/_common.py` (neu) zum Reduzieren von Boilerplate.
- [ ] FlowEditor (Frontend):
- Neuer `frontendType: "modelMultiSelect"` -- holt verfuegbare
Modelle ueber `GET /api/system/ai-models` und stellt
Multi-Select dar.
- Frontend-Generic-Form-Generator unterstuetzt diesen Type
(Properties-Panel pro AI-Node).
### Phase 6 -- Trustee-Templates anpassen (entkoppelt vom Bau-Plan B)
- [ ] In `mainTrustee.py`-Templates: `style`-Block in `aiPrompt`-Node
Parametern setzen (Finanzreport-Defaults: dunkelblaue Headings,
konservatives Calibri etc.) -- damit Trustee out-of-the-box gut
aussieht.
- [ ] `requireNeutralization: true` falls Trustee-Daten sensibel
(entscheidet Compliance-Doku, Default empfohlen).
- [ ] `allowedModels` pro Trustee-Workflow optional pinned (z.B. nur
private LLM fuer Finance-Daten).
### Phase 7 -- Tests / Snapshots
- [ ] Pro Format ein Snapshot-Test mit Custom-Style:
`test_render_with_agent_style_<format>.py`.
- [ ] Inline-Bilder-Tests pro Format (siehe Phase 4).
- [ ] AI-Gate-Tests:
`test_allowed_models_filter.py` (Whitelist greift),
`test_allowed_models_empty_fails.py` (Fail-fast),
`test_workspace_propagates_neutralization.py`.
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given Agent ruft `renderDocument` mit `style.colors.primary="#E11D48"`, When DOCX rendert, Then Headings nutzen diese Farbe | must |
| 2 | Given Agent ruft `renderDocument` ohne `style`-Block, When Renderer arbeitet, Then werden neutrale Defaults verwendet (kein Crash) | must |
| 3 | Given Markdown enthaelt `Bei der Sitzung ![Logo](file:abc) wurde besprochen ...`, When DOCX gerendert, Then erscheint das Bild inline mitten im Paragraph | must |
| 4 | Given Markdown-Tabellenzelle `\| ![](file:xyz) \|`, When DOCX/HTML/XLSX gerendert, Then Bild liegt in der Zelle | must |
| 5 | Given Markdown-Listen-Item mit Inline-Bild, When HTML gerendert, Then Bild im `<li>` | should |
| 6 | Given PPTX gerendert mit Inline-Bild im Paragraph, When Output inspiziert, Then Bild ist als separate Shape unterhalb mit Warning im Run-Log | should |
| 7 | Given AI-Node mit `requireNeutralization: true`, When Workflow laeuft, Then Prompt/Context werden im AI-Gate neutralisiert (Beweis im Audit-Log) | must |
| 8 | Given AI-Node mit `allowedModels: ["claude-4-7-opus"]`, When Workflow laeuft und nur dieses Modell verfuegbar, Then Call benutzt es | must |
| 9 | Given AI-Node mit `allowedModels: ["nichtExistent"]`, When Workflow laeuft, Then Fail-fast mit Error-Message inkl. RBAC/Workflow/Node-Filter-Snapshot | must |
| 10 | Given Workspace mit gespeicherten User-Settings `requireNeutralization=true, allowedProviders=["openai"], allowedModels=["gpt-5-mini"]`, When User Workspace neu oeffnet, Then sind ALLE drei Settings vorausgefuellt und werden bei jedem Call mitgeschickt | must |
| 11 | Given Workspace mit `requireNeutralization: true`, When Sub-Agent-AI-Tools laufen, Then alle Calls werden neutralisiert (ServiceCenterContext propagiert) | must |
| 12 | Given Provider-Whitelist `allowedProviders=["openai"]` UND Model-Whitelist `allowedModels=["claude-4-7-opus"]`, When Filterung laeuft, Then leere Liste -> Fail-fast (AND-Logik, nicht OR) | must |
| 13 | Given alte Implementierungen `_mediaTools._markdownToDocumentJson`, When neu gebaut, Then ruft sie nur noch den Helper aus `subDocumentUtility` auf | must |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1,2 | snapshot | ja | gateway/tests/serviceGeneration/test_agent_style_docx.py | pending |
| T2 | 3 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_paragraph.py | pending |
| T3 | 4 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_table_cell.py | pending |
| T4 | 5 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_list_item_html.py | pending |
| T5 | 6 | integration | ja | gateway/tests/serviceGeneration/test_inline_image_pptx_fallback.py | pending |
| T6 | 7 | integration | ja | gateway/tests/serviceAi/test_node_neutralization_flag.py | pending |
| T7 | 8,9 | integration | ja | gateway/tests/serviceAi/test_allowed_models_whitelist.py | pending |
| T8 | 10 | integration | ja | gateway/tests/features/workspace/test_user_settings_persistence.py | pending |
| T9 | 11 | integration | ja | gateway/tests/serviceAi/test_workspace_propagates_settings.py | pending |
| T10 | 12 | unit | ja | gateway/tests/serviceAi/test_effective_models_and_providers.py | pending |
| T11 | 13 | unit | ja | gateway/tests/serviceCenter/test_md_to_json_consolidation.py | pending |
| T12 | -- | unit | ja | frontend_nyla/src/components/flowEditor/__tests__/AiNodeProperties.test.tsx | pending |
## Links
- Aktuelle Pipeline-Architektur: Subagent-Report 2026-04-29.
- Renderer-Code: `gateway/modules/serviceCenter/services/serviceGeneration/renderers/`.
- MD->JSON-Code:
`gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py`,
`gateway/modules/serviceCenter/services/serviceGeneration/subDocumentUtility.py`.
- AI-Gate: `gateway/modules/serviceCenter/services/serviceAi/mainServiceAi.py`.
- Neutralisierung: `wiki/b-reference/platform/neutralization.md`.
- Wiki: `wiki/b-reference/gateway/ai-agent.md`,
`wiki/b-reference/gateway/agent-file-bridge.md`,
`wiki/b-reference/gateway/workflow.md`.
## Abschluss
- [ ] `wiki/b-reference/gateway/ai-agent.md` -- Style-System + per-call
Konfiguration ergaenzen
- [ ] `wiki/b-reference/gateway/workflow.md` -- AI-Node-Standardparameter
dokumentieren
- [ ] `wiki/b-reference/platform/neutralization.md` -- per-Node-Trigger
ergaenzen (Praezedenzkette: Feature -> Workspace -> Node -> Request)
- [ ] `wiki/TOPICS.md` ggf. neuer Eintrag "AI-Node-Konfiguration"
- [ ] Dieses Dokument -> `z-archive/` verschoben

View file

@ -0,0 +1,249 @@
<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla -->
# ComCoach Greenfield-IA: TrainingModule + Sessions
## Beschreibung und Kontext
Die ComCoach-UI ist gewachsen und chaotisch:
- 4 Sidebar-Eintraege (`dashboard`, `coaching`, `dossier`, `settings`),
aber `coaching` und `dossier` rendern die **identische** Komponente
`CommcoachDossierView`
(`frontend_nyla/src/pages/FeatureView.tsx` 168-173).
- RBAC-Definition kennt nur 3 UI-Keys
(`mainCommcoach.py` 19-34) -> Inkonsistenz mit Sidebar.
- Das Dashboard verlinkt mit `?context={id}`, die Dossier-View liest
diesen Query-Param **nicht** -> falscher Default-Kontext nach Klick
(`CommcoachDossierView.tsx` 142-146).
- CRUD fuer "Coachings" (heute `CoachingContext`) ist unvollstaendig:
Backend hat `PUT/DELETE /contexts/{id}`, UI bietet nur Create + Archive.
- Statistik wird doppelt gezeigt (Dashboard + Settings).
- Die Datenstruktur ist heute **flach**: `CoachingContext -> Sessions`,
ohne Modul/Reihen-Konzept. Das passt nicht zum gewuenschten Use-Case
"ein Psychologe (Modul) macht m Sessions zu einem Thema, alle tragen zu
Modul-KPI bei" oder "E-Learning-Modul + Pruefungs-Modul".
**Geschaeftstreiber:** User finden sich nicht zurecht -> ComCoach wird
unterbenutzt. Mit klarer Modul-of-Sessions-IA und 5 sauberen Tabs wird
ComCoach erst zum vermarktbaren Produkt.
## Fokus und kritische Details
- Bestehende Daten (alle aktuellen `CoachingContext`-Eintraege) muessen
migriert werden -> 1:1 Rename auf `TrainingModule` mit Default
`moduleType="coaching"`.
- "Module" und "Coaching" sind im UI gleichwertig zu verwenden -- intern
ist es immer ein `TrainingModule`. Im UI je nach `moduleType` das
Label anpassen ("Coaching", "Training", "Pruefung", "E-Learning").
- KeepAlive-Mechanismus
(`frontend_nyla/src/pages/views/commcoach/CommcoachKeepAlive.tsx`)
beibehalten -- die Voice-Session darf nicht gekillt werden bei
Sidebar-Wechsel.
## Ziel und Nicht-Ziele
- Ziel: 5-Tab-IA (Dashboard, Assistent, Module, Session, Einstellungen)
mit klarer Use-Case-Zuordnung. Datenmodell `TrainingModule` mit KPIs +
N Sessions.
- NICHT: Voice-Pipeline aendern.
- NICHT: Persona-Marketplace (eigener Plan).
- NICHT: Multi-Tenant-Sharing von Modulen ueber Mandate-Grenzen
(eigener Plan, falls je gewollt).
## Betroffene Module
- Gateway:
- `gateway/modules/features/commcoach/datamodelCommcoach.py` --
`CoachingContext` -> `TrainingModule` (Rename + neue Felder
`kpiTargets jsonb`, `moduleType enum`, `goals text`).
- `gateway/modules/features/commcoach/interfaceFeatureCommcoach.py` --
Methoden umbenennen (intern), DB-Migration anwenden.
- `gateway/modules/features/commcoach/routeFeatureCommcoach.py` --
Routes umbenennen `/contexts` -> `/modules` (oder Alias-Periode).
- `gateway/modules/features/commcoach/mainCommcoach.py` -- RBAC-Keys
auf neue Tab-Struktur (5 Keys: dashboard, assistant, modules,
session, settings).
- Frontend:
- `frontend_nyla/src/types/mandate.ts` 277-286 -- Sidebar 4 -> 5
Eintraege.
- `frontend_nyla/src/pages/FeatureView.tsx` 168-173 -- View-Mapping
aktualisieren.
- `frontend_nyla/src/pages/views/commcoach/` -- bestehende
`CommcoachDossierView` zerlegen in:
- `CommcoachDashboardView` (existiert, KPIs ergaenzen).
- `CommcoachAssistantView` (NEU): Wizard-Flow neues Modul + erste
Session.
- `CommcoachModulesView` (NEU): Liste, CRUD, Sessions je Modul.
- `CommcoachSessionView` (NEU, ehem. coaching-Tab): aktive
Session.
- `CommcoachSettingsView` (existiert, Statistik raus).
- `frontend_nyla/src/api/commcoachApi.ts` -- API-Wrapper aktualisieren.
- `frontend_nyla/src/pages/views/commcoach/CommcoachKeepAlive.tsx` --
Komponenten-Mapping aktualisieren (Session bleibt KeepAlive).
- DB-Migration: ja -- Rename `CoachingContext` -> `TrainingModule`,
additive Felder.
- RBAC: ja -- 5 statt 3 UI-Keys.
## Zieldatenmodell
```mermaid
flowchart LR
Inst[FeatureInstance commcoach] --> Mod[TrainingModule]
Mod --> Sess[Session]
Sess --> Msg[Message]
Sess --> Score[Score]
Mod --> Task[Task]
Mod --> Kpi[ModuleKpi]
Mod --> Per[Persona ref]
```
`TrainingModule` Felder (Auszug):
| Feld | Typ | Bedeutung |
|------|-----|-----------|
| `id` | UUID | PK |
| `instanceId` | UUID | FeatureInstance-Scope |
| `userId` | UUID | Owner |
| `mandateId` | UUID | Tenant |
| `title` | text | "Konfliktgespraeche", "JS-Grundkurs" |
| `moduleType` | enum | `coaching` \| `training` \| `exam` \| `elearning` |
| `personaId` | UUID? | Default-Persona fuer Sessions |
| `goals` | text | freie Zielbeschreibung |
| `kpiTargets` | jsonb | strukturierte Ziel-KPIs |
| `status` | enum | `active` \| `archived` \| `completed` |
| `createdAt` / `updatedAt` | timestamp | -- |
## IA: 5 Tabs (final)
| # | Tab | Zweck | Komponente |
|---|------|-------|-------------|
| 1 | Dashboard | Gamification, Streak, Levels, aktive Module-Karten, Tipp des Tages, Schnell-Aktion "Neues Modul" | `CommcoachDashboardView` |
| 2 | Assistent | Wizard: Modul-Typ -> Thema -> Persona -> KPIs -> "Erste Session starten" | `CommcoachAssistantView` |
| 3 | Module | CRUD-Liste aller Module (Filter nach Status/Typ), pro Modul aufklappbare Sessions | `CommcoachModulesView` |
| 4 | Session | aktive/laufende Session als Single-Page (TTS, Voice, Anhaenge, Agent-Aktivitaet, Abschliessen/Abbrechen). Erreichbar nur ueber Modul oder Assistent | `CommcoachSessionView` |
| 5 | Einstellungen | Stimme/Sprache, Erinnerungen, E-Mail-Zusammenfassung. KEINE Statistik mehr | `CommcoachSettingsView` |
## Use-Case-Mapping
| Use-Case | Tab(s) | Hauptaktion |
|----------|--------|-------------|
| 1 Gamification-Dashboard | Dashboard | KPIs + Streak ansehen |
| 2 Assistent: neues Coaching | Assistent -> Session | Wizard durchklicken |
| 3 Session-Operations | Session | TTS, Voice, Tasks waehrend Session |
| 4 Listen CRUD | Module | Module pflegen, Sessions je Modul aufklappen |
| 5 Instanzen-Einstellungen | Einstellungen | Stimme, Erinnerungen |
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | Backend-Entity heisst neu `TrainingModule` (Rename von `CoachingContext`) | Generischer Begriff deckt Coaching, Training, Exam, ELearning ab |
| 2026-04-29 | Im UI je nach `moduleType` Labelt "Coaching/Training/..." | UX-Klarheit |
| 2026-04-29 | Sidebar 5 Tabs (statt 4 mit Doppelung) | Use-Cases brauchen klare Trennung Assistent vs. Liste |
| 2026-04-29 | Statistik im Settings-Tab raus, nur Dashboard | Doppelung weg |
| 2026-04-29 | KeepAlive bleibt nur fuer Session-Tab (nicht Module/Dashboard) | Voice-Session muss persistieren, Listen koennen unmounten |
## Umsetzungs-Checkliste
### Phase 1 -- Backend Datenmodell + Routes
- [ ] `datamodelCommcoach.py`:
- Klasse `CoachingContext` -> `TrainingModule` (Rename, alle
FKs in Childs `contextId` -> `moduleId`).
- Neue Felder `kpiTargets jsonb`, `moduleType enum`, `goals text`.
- [ ] DB-Migration: `script_db_rename_coaching_context_to_training_module.py`.
Tabellenname Postgres-Rename, FK-Spalte `contextId` -> `moduleId`
auf allen abhaengigen Tabellen (`CoachingSession`,
`CoachingTask`, `CoachingScore`).
- [ ] `interfaceFeatureCommcoach.py` -- Methodennamen umstellen
(`createContext` -> `createModule` etc.) mit camelCase intern.
- [ ] `routeFeatureCommcoach.py`:
- Neue Pfade `/api/commcoach/{instanceId}/modules/...`.
- Alias-Periode: alte `/contexts/...`-Routen zeigen 301 auf neue
Routen (1 Release lang), dann raus.
- [ ] `mainCommcoach.py`:
- RBAC-Keys: `dashboard`, `assistant`, `modules`, `session`,
`settings`.
- Templates fuer Standard-Modulvorlagen optional (z.B. "Konflikt-
Coaching", "Sprach-Training").
### Phase 2 -- Frontend Routing & Sidebar
- [ ] `frontend_nyla/src/types/mandate.ts` 277-286 ComCoach-Eintraege
auf 5 Eintraege umstellen.
- [ ] `frontend_nyla/src/App.tsx` 185-187 Routes ergaenzen (`assistant`,
`modules`, `session`).
- [ ] `frontend_nyla/src/pages/FeatureView.tsx` 168-173 View-Mapping
auf neue Komponenten.
- [ ] KeepAlive nur noch fuer `session`-Tab konfigurieren.
### Phase 3 -- Frontend Komponenten
- [ ] `CommcoachDashboardView` -- KPI-Karten verfeinern (pro Modul-Typ
Aggregat), neuer "Modul anlegen"-CTA.
- [ ] NEU `CommcoachAssistantView` -- Wizard 4 Steps (Typ/Thema/Persona/
KPIs) + "Erste Session starten" -> Navigation `session?moduleId=...`.
- [ ] NEU `CommcoachModulesView` -- Liste, Filter (`moduleType`,
Status), CRUD-Dialoge (Edit/Delete vorhanden, jetzt im UI), pro
Zeile aufklappbar Sessions inkl. Sessions-CRUD (zumindest Delete).
- [ ] NEU `CommcoachSessionView` -- aus existierender `Dossier`-Tab-
Logik die "coaching"-Tab-Inhalte rausziehen.
- [ ] `CommcoachSettingsView` -- Statistik-Sektion entfernen.
- [ ] `CommcoachDossierView` ENTFERNEN nach Migration aller Inhalte.
### Phase 4 -- Bugfixes (Vor-Ort)
- [ ] `?context={id}` auf neuen Param `?moduleId={id}` umstellen, in
allen Views konsistent lesen.
- [ ] `commcoachApi.ts` Wrapper `updateContextApi`, `deleteContextApi`,
`activateContextApi`, `updateTaskApi` aus Backend nutzen (heute
ungenutzt).
### Phase 5 -- Doku
- [ ] Neue b-reference `wiki/b-reference/gateway/features/commcoach.md`
anlegen.
- [ ] `wiki/TOPICS.md` Eintrag "ComCoach Architecture".
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given Sidebar ComCoach, When User es oeffnet, Then sieht er 5 Tabs (Dashboard, Assistent, Module, Session, Einstellungen) | must |
| 2 | Given Tab Assistent, When User Wizard durchklickt, Then ist am Ende ein neues Modul + eine erste Session aktiv | must |
| 3 | Given Tab Module, When User auf "Bearbeiten" eines Moduls klickt, Then kann er Titel, Typ, Persona, Goals, KPIs editieren | must |
| 4 | Given Tab Module, When User auf "Loeschen" eines Moduls klickt, Then ist das Modul + alle Sessions weg (mit Confirm) | must |
| 5 | Given Tab Dashboard, When User auf eine Modul-Karte klickt, Then landet er im Tab Module mit dem richtigen Modul aufgeklappt (`moduleId`-Param wird gelesen) | must |
| 6 | Given Tab Einstellungen, When User es oeffnet, Then sieht er KEINE Statistik-Sektion mehr | should |
| 7 | Given Bestand-CoachingContext-Eintraege, When Migration laeuft, Then heissen sie alle `TrainingModule` mit `moduleType=coaching` und alle FKs zeigen korrekt | must |
| 8 | Given Voice-Session laeuft im Tab Session, When User auf Tab Module wechselt und zurueck, Then laeuft die Session weiter (KeepAlive) | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | unit | ja | frontend_nyla/src/types/__tests__/mandate.test.ts | pending |
| T2 | 2 | e2e | ja | frontend_nyla/tests/e2e/commcoach-assistant.spec.ts | pending |
| T3 | 3,4 | e2e | ja | frontend_nyla/tests/e2e/commcoach-modules-crud.spec.ts | pending |
| T4 | 5 | e2e | ja | frontend_nyla/tests/e2e/commcoach-dashboard-link.spec.ts | pending |
| T5 | 6 | unit | ja | frontend_nyla/src/pages/views/commcoach/__tests__/CommcoachSettingsView.test.tsx | pending |
| T6 | 7 | integration | ja | gateway/tests/features/commcoach/test_migration_rename.py | pending |
| T7 | 8 | manual | nein | -- | pending |
## Links
- Audit-Quelle: Subagent-Report 2026-04-29.
- Aktueller Code:
`gateway/modules/features/commcoach/`,
`frontend_nyla/src/pages/views/commcoach/`.
- Aktuelles Konzept (veraltet):
`gateway/modules/features/commcoach/CONCEPT.md`.
## Abschluss
- [ ] `wiki/b-reference/gateway/features/commcoach.md` neu anlegen
- [ ] `wiki/TOPICS.md` Eintrag "ComCoach"
- [ ] `gateway/modules/features/commcoach/CONCEPT.md` aktualisieren
- [ ] Dieses Dokument -> `z-archive/` verschoben

View file

@ -0,0 +1,274 @@
<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla, teams-bot -->
# TeamsBot Greenfield-IA: MeetingModule + Live-Update-Fixes
## Beschreibung und Kontext
Die TeamsBot-UI ist aktuell:
- 3 Tabs (Dashboard "Uebersicht", Sitzungen, Einstellungen).
- Kein Meeting-Entity -- alles ist `TeamsbotSession.meetingLink`. Wer
z.B. ein wiederkehrendes Weekly-Standup ueber Wochen verfolgen will,
muss die Sessions per Datum suchen.
- Kein "Modul"-Konzept fuer Reihen (wie der User wuenscht).
- Live-Updates haengen an SSE in `TeamsbotSessionView.tsx` mit
`useEffect`-Deps `[instanceId, sessionId]` only (Z. 327-328) +
`eslint-disable` -- reagiert daher NICHT zuverlaessig auf
Status-Wechsel und auf neue `agentRun`-Events. Letztere sind nur
`_dlog`, NICHT in der UI sichtbar.
- `stats`-Payload aus `getSession`-API
(`routeFeatureTeamsbot.py` 357-358) wird vom Frontend ignoriert.
- Dashboard-Counts haben 10s Lag (Polling).
**Geschaeftstreiber:** User finden sich nicht zurecht; Live-Bedienung
wirkt fragil ("zeigt das hier ueberhaupt was Aktuelles?"). Mit Modul-
of-Sessions-IA (analog ComCoach) und sauberen Live-Update-Pfaden wird
TeamsBot zum verlaesslichen Meeting-Tool.
## Fokus und kritische Details
- **Live-Update ist UX-kritisch** -- Session-State, agentRun-Indicator
und Stats muessen waehrend laufender Meetings sichtbar pulsieren.
- SSE-`useEffect`-Deps nicht naiv aufschnueren -- sonst Reconnect-
Flicker bei jedem Status-Bit-Wechsel. Loesung: dedizierter Reconnect-
Hook, der nur reconnektet wenn (a) sessionId wechselt oder (b) Session
von "terminal" zurueck zu "active" geht (sehr selten).
- Backend `stats`-Payload bereits vorhanden, nur nicht gebunden.
- `MeetingModule` ist additiv -- alte `TeamsbotSession`-Eintraege
bekommen `moduleId` per Migration (Default-Modul "Adhoc").
## Ziel und Nicht-Ziele
- Ziel:
- 5-Tab-IA (Dashboard, Assistent, Module, Live-Session, Einstellungen).
- `MeetingModule`-Entity (Reihe + KPIs + Default-Bot + Default-
Director-Prompts).
- 4 Live-Update-Fixes (siehe unten F-fix-1 .. F-fix-4).
- NICHT: Aenderung an Bot-Bridge (.NET Media-Bridge bleibt).
- NICHT: WebSocket-Migration im Frontend (SSE bleibt, ist Architektur-
Entscheid).
- NICHT: Multi-Mandate-Sharing von Modulen.
## Betroffene Module
- Gateway:
- `gateway/modules/features/teamsbot/datamodelTeamsbot.py` -- neue
Klasse `TeamsbotMeetingModule`, FK `TeamsbotSession.moduleId`
(nullable in Uebergangsphase).
- `gateway/modules/features/teamsbot/interfaceFeatureTeamsbot.py` --
CRUD fuer `MeetingModule`.
- `gateway/modules/features/teamsbot/routeFeatureTeamsbot.py` --
neue Routen `/modules/...`, `agentRun`-Event sauberer schicken,
`stats`-Payload kontinuierlich anreichern.
- `gateway/modules/features/teamsbot/mainTeamsbot.py` -- RBAC-Keys
auf 5 Tabs, optional Default-MeetingModule-Templates.
- Frontend:
- `frontend_nyla/src/types/mandate.ts` 244-252 -- TeamsBot-Sidebar
3 -> 5 Eintraege.
- `frontend_nyla/src/pages/FeatureView.tsx` 159-163 -- View-Mapping
aktualisieren.
- `frontend_nyla/src/pages/views/teamsbot/` -- vorhandene Komponenten
aufteilen:
- `TeamsbotDashboardView` -- KPIs erweitern (Modul-Aggregate +
Gamification).
- `TeamsbotAssistantView` (NEU) -- Wizard.
- `TeamsbotModulesView` (NEU) -- Modul-CRUD + Sessions je Modul.
- `TeamsbotSessionView` -- bleibt, jetzt nur Live-Session.
- `TeamsbotSettingsView` -- bleibt.
- `frontend_nyla/src/api/teamsbotApi.ts` -- Module-API + neuer
Reconnect-Hook.
- DB-Migration: ja (additive Tabelle + nullable FK).
## Zieldatenmodell
```mermaid
flowchart LR
Inst[FeatureInstance teamsbot] --> Mod[MeetingModule]
Mod --> Sess[TeamsbotSession meetingLink]
Sess --> Trans[Transcript]
Sess --> Resp[BotResponse]
Sess --> Dir[DirectorPrompt]
Mod --> Kpi[ModuleKpi/Goals]
Mod --> DefBot[Default Bot]
Mod --> DefDir[Default Director Prompts]
```
`MeetingModule` Felder (Auszug):
| Feld | Typ | Bedeutung |
|------|-----|-----------|
| `id` | UUID | PK |
| `instanceId` | UUID | FeatureInstance-Scope |
| `mandateId` | UUID | Tenant |
| `ownerUserId` | UUID | Owner |
| `title` | text | "Weekly Standup", "Q3 Sales Review-Reihe" |
| `seriesType` | enum | `weekly` \| `biweekly` \| `monthly` \| `adhoc` \| `project` |
| `defaultBotId` | UUID? | `TeamsbotSystemBot` |
| `defaultDirectorPrompts` | jsonb | Liste vorausgefuellter Director-Prompts |
| `goals` | text | -- |
| `kpiTargets` | jsonb | -- |
| `status` | enum | `active` \| `archived` \| `completed` |
## IA: 5 Tabs (final)
| # | Tab | Zweck | Komponente |
|---|------|-------|-------------|
| 1 | Dashboard | Gamification (Talking-Time, Director-Trefferquote, beliebteste Prompts), aktive Module/Sessions, Quick-Action "Neues Meeting" | `TeamsbotDashboardView` |
| 2 | Assistent | Wizard: Modul waehlen oder neu -> Meeting-Link/Auto-Join -> Bot starten | `TeamsbotAssistantView` |
| 3 | Module | CRUD-Liste aller Meeting-Module, pro Modul aufklappbare Session-Liste | `TeamsbotModulesView` |
| 4 | Live-Session | aktive Sitzung: Regie-Panel, UDB-Sidebar, Transkript, Antworten, Bot-Status, MFA, Stats-Karten | `TeamsbotSessionView` (refactor) |
| 5 | Einstellungen | Bot-Account, Stimme, System-Bots (SysAdmin) | `TeamsbotSettingsView` |
## Use-Case-Mapping
| Use-Case | Tab(s) | Hauptaktion |
|----------|--------|-------------|
| 1 Gamification-Dashboard | Dashboard | Talking-Time + Modul-KPIs |
| 2 Assistent: neues Meeting | Assistent -> Live-Session | Wizard durchklicken |
| 3 Session-Operations | Live-Session | Stop, Director-Prompts, MFA, Stats |
| 4 Listen CRUD | Module | Module + Sessions je Modul pflegen |
| 5 Instanzen-Einstellungen | Einstellungen | Bot, Stimme, System-Bots |
## Live-Update-Fixes (parallel zur IA)
### F-fix-1: SSE-Reconnect-Hook
- Problem: `useEffect` `[instanceId, sessionId]`-only haengt nicht an
`session.status` -> Reconnect bei legitimen Status-Wechseln nicht
garantiert.
- Loesung: Eigener Hook `useTeamsbotSessionStream(instanceId, sessionId,
isTerminal)` der intern reconnectet wenn `isTerminal` von `true` ->
`false` flippt (z.B. nach manuellem Re-Start einer beendeten Session).
Andere Status-Bits triggern KEINEN Reconnect.
### F-fix-2: agentRun-Event sichtbar
- Problem: `agentRun`-SSE-Events sind nur `_dlog`
(`TeamsbotSessionView.tsx` 290-293).
- Loesung: Status-Bubble in der Live-View ("Agent denkt nach: <toolName>...")
als kurzlebige animierte Komponente.
### F-fix-3: stats-Karten
- Problem: `getSession`-API liefert `stats` (Sprechminuten, Bot-
Antworten, Latenz) -- Frontend ignoriert.
- Loesung: Header-Karten in `TeamsbotSessionView` gebunden an
`session.stats`. Aktualisierung kommt automatisch via SSE
`sessionState`-Events (Backend muss `stats` mitliefern -- ggf.
Backend-Anpassung in `routeFeatureTeamsbot.py`).
### F-fix-4: Dashboard-Counts ohne 10s-Lag
- Problem: Dashboard pollt `listSessions` alle 10s.
- Option A (kleiner Eingriff): bei aktiver Session 3s-Polling, sonst
30s.
- Option B (besser): SSE-Channel pro Mandate
`/api/teamsbot/{instanceId}/dashboard/stream` mit "session-tick"-
Events (Counter-Updates push, kein Polling).
- **Default:** Option A in Phase 4, Option B als Follow-up.
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | Neues `MeetingModule`-Entity additiv | Reihen-Konzept gewuenscht; bestehende Sessions in "Adhoc"-Modul |
| 2026-04-29 | 5 Tabs (statt 3) | Use-Cases brauchen klare Trennung Assistent/Module/Live |
| 2026-04-29 | SSE bleibt (kein WS) | Architektur stabil, Browser-Bot nutzt WS, Frontend nicht |
| 2026-04-29 | Dashboard-Counts: Adaptive Polling jetzt, SSE-Channel als Follow-up | Pragmatisch, schnell sichtbar |
## Umsetzungs-Checkliste
### Phase 1 -- Backend Datenmodell + Routes
- [ ] `datamodelTeamsbot.py`:
- Neue Klasse `TeamsbotMeetingModule`.
- `TeamsbotSession.moduleId Optional[UUID]` additiv.
- [ ] DB-Migration:
`script_db_init_teamsbot_meeting_modules.py` -- legt Tabelle an,
erzeugt pro `instanceId` ein Default-`Adhoc`-Modul, setzt
bestehende Sessions auf dieses Modul.
- [ ] `interfaceFeatureTeamsbot.py` -- CRUD-Methoden fuer Module.
- [ ] `routeFeatureTeamsbot.py`:
- Neue Routen `GET/POST/PUT/DELETE /modules/...`.
- `getSession` liefert `stats` (existiert), `listSessions`
ergaenzt `moduleId` im Payload.
- SSE: `sessionState`-Event traegt jetzt auch `stats`.
- SSE: `agentRun`-Event sauberer schicken (toolName, status:
`started`/`finished`/`failed`).
### Phase 2 -- Backend Live-Stats anreichern
- [ ] `_buildSessionStats(session) -> SessionStats` zentral, in
jedem `sessionState`-SSE-Event aufrufen (nicht nur on-demand).
### Phase 3 -- Frontend Routing & Sidebar
- [ ] `frontend_nyla/src/types/mandate.ts` 244-252 TeamsBot-Eintraege
auf 5 Eintraege.
- [ ] `frontend_nyla/src/App.tsx` Routes ergaenzen (`assistant`,
`modules`).
- [ ] `frontend_nyla/src/pages/FeatureView.tsx` View-Mapping
aktualisieren.
### Phase 4 -- Frontend Komponenten
- [ ] NEU `TeamsbotAssistantView` -- Wizard 3 Steps (Modul-
Auswahl/Neu, Meeting-Link, Bot-Auswahl) -> "Bot starten" ->
Navigation `live-session?sessionId=...`.
- [ ] NEU `TeamsbotModulesView` -- Liste, CRUD, aufklappbare Sessions.
- [ ] `TeamsbotSessionView` Refactor:
- Reconnect-Hook (F-fix-1) integrieren.
- Status-Bubble fuer agentRun (F-fix-2).
- Stats-Karten im Header binden (F-fix-3).
- [ ] `TeamsbotDashboardView`:
- Polling adaptiv (F-fix-4 Option A).
- Modul-Aggregate-Sektion ergaenzen (Top-Module nach
Session-Count, Talking-Time-Verteilung etc.).
### Phase 5 -- Doku
- [ ] `wiki/b-reference/teams-bot/architecture.md` Module-Sektion
ergaenzen, Live-Update-Pfade dokumentieren.
- [ ] `wiki/TOPICS.md` ggf. Eintrag "TeamsBot Module".
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given Sidebar TeamsBot, When User es oeffnet, Then sieht er 5 Tabs (Dashboard, Assistent, Module, Live-Session, Einstellungen) | must |
| 2 | Given Tab Module, When User auf "Neues Modul" klickt, Then kann er Titel, Series-Typ, Default-Bot, Director-Prompts, KPIs setzen | must |
| 3 | Given Modul existiert, When User auf "Neue Session" klickt, Then wird eine `TeamsbotSession` mit `moduleId` angelegt und im Live-Tab geoeffnet | must |
| 4 | Given Live-Session laeuft, When der Backend-Agent ein Tool aufruft, Then erscheint im UI ein Status-Bubble "Agent denkt nach" mit Tool-Name | must |
| 5 | Given Live-Session laeuft, When sich Sprechminuten/Bot-Antworten/Latenz aendern, Then aktualisieren die Stats-Karten ohne Page-Reload | must |
| 6 | Given Session terminiert (z.B. Stop), When User auf gleicher Seite bleibt, Then schliesst die SSE sauber, kein Reconnect-Loop | must |
| 7 | Given Bestand-Sessions ohne Modul, When Migration laeuft, Then sind alle Sessions im Default-`Adhoc`-Modul je Instanz einsortiert | must |
| 8 | Given Dashboard offen, When eine aktive Session laeuft, Then aktualisieren die Counter spaetestens nach 3s | should |
| 9 | Given keine aktive Session, When Dashboard offen, Then aktualisiert es nur alle 30s | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | unit | ja | frontend_nyla/src/types/__tests__/mandate.test.ts | pending |
| T2 | 2,3 | e2e | ja | frontend_nyla/tests/e2e/teamsbot-modules-crud.spec.ts | pending |
| T3 | 4 | unit | ja | frontend_nyla/src/pages/views/teamsbot/__tests__/TeamsbotSessionView_agentRun.test.tsx | pending |
| T4 | 5 | integration | ja | frontend_nyla/tests/integration/teamsbot-stats-binding.spec.ts | pending |
| T5 | 6 | manual | nein | -- | pending |
| T6 | 7 | integration | ja | gateway/tests/features/teamsbot/test_migration_modules.py | pending |
| T7 | 8,9 | manual | nein | -- | pending |
## Links
- Audit-Quelle: Subagent-Report 2026-04-29.
- Aktueller Code: `gateway/modules/features/teamsbot/`,
`frontend_nyla/src/pages/views/teamsbot/`.
- Wiki: `wiki/b-reference/teams-bot/architecture.md`,
`wiki/c-work/4-done/2026-04-teamsbot-director-prompts.md`.
## Abschluss
- [ ] `wiki/b-reference/teams-bot/architecture.md` aktualisiert
- [ ] `wiki/TOPICS.md` ggf. neuer Eintrag
- [ ] Dieses Dokument -> `z-archive/` verschoben

View file

@ -0,0 +1,163 @@
<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla -->
# Trustee Budget Comparison Refactor (Excel-only, Prompt-Klarheit)
## Beschreibung und Kontext
Der Trustee-Service "Budget-Vergleich"
(`gateway/modules/features/trustee/mainTrustee.py` 430-461) liefert heute
ein Word-Dokument mit teils kaputten Bildern in Tabellenzellen und mit
Charts pro Konto statt einem Uebersichts-Chart. Ursachen:
- Workflow-Template hat **kein** `resultType` -- das Ausgabeformat wird vom
Modell entschieden, oft DOCX.
- Prompt (Z. 442-454) ist mehrdeutig: "Erstelle **ein** Abweichungs-Chart"
steht direkt neben "**pro Konto**" -- das LLM waehlt mal Lesart A, mal B.
- Bilder-in-Tabellenzellen sind im Markdown->JSON-Pfad
(`gateway/modules/serviceCenter/services/serviceAgent/coreTools/_mediaTools.py`
72-86, 108-127) **nicht** vorgesehen: Tabellenzellen sind reine
Strings, Bilder muessen eine eigene Markdown-Zeile sein. python-docx
koennte Bilder in Zellen, aber unsere MD-Pipeline kann es nicht
ausdruecken.
**User-Entscheid:** Excel ist der natuerliche Container fuer einen
Soll/Ist-Konten-Vergleich (tabellarische Daten, eingebettete Charts,
sortier-/filterbar). Word-Pfad fuer diesen Workflow weg.
## Fokus und kritische Details
- Format-Festlegung darf NICHT vom LLM ueberschrieben werden -- Workflow
muss `resultType: "xlsx"` hart setzen.
- Prompt-Refactor: ein Chart, ueber alle Konten. Few-Shot-Beispiel mit
Zielstruktur direkt im Prompt einbauen.
- "Bild in Tabellenzelle"-Thema gehoert zum generellen Renderer-Refactor
(siehe `2026-04-ai-reports-theming-and-pipeline.md`); hier nur den
Budget-spezifischen Effekt heilen, indem das Layout
Tabelle-darunter-Chart ist (kein Bild in Zelle noetig).
## Ziel und Nicht-Ziele
- Ziel: Budget-Vergleich liefert konsistent EINE .xlsx mit
Tabelle (alle Konten) + 1 Uebersichts-Chart + Management-Summary.
- NICHT: Bilder-in-Tabellenzellen technisch loesen (gehoert zu D).
- NICHT: native XLSX-Charts via openpyxl (Default bleibt PNG-Embed). Kann
in spaeteren Iteration nachgezogen werden.
- NICHT: Aenderungen am `refreshAccountingData`-Node oder
`ai.process`-Action.
## Betroffene Module
- Gateway:
- `gateway/modules/features/trustee/mainTrustee.py` -- Budget-Template
Z. 430-461 (Prompt + `resultType` + Output-Schema-Hinweis).
- Frontend:
- `frontend_nyla/src/pages/views/trustee/TrusteeAnalyseView.tsx` --
File-Karten korrekt rendern (xlsx-Icon).
- DB-Migration: nein (nur Template-Aenderung; bei naechstem
`_copyTemplateWorkflows`-Lauf greift es).
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | Excel ist primaeres Format, Word-Pfad fuer Budget-Vergleich entfernt | Daten-tabellarisch, Charts in Excel native passend |
| 2026-04-29 | Charts via PNG-Embed (kein openpyxl-native), 1 Chart ueber alle Konten | Schon implementiert, kein neuer Renderer-Pfad noetig |
| 2026-04-29 | Bilder-in-Zellen wird NICHT in diesem Plan geloest | Layout Tabelle-darunter-Chart reicht; generischer Fix in D |
## Umsetzungs-Checkliste
### Phase 1 -- Workflow-Template
- [ ] In `mainTrustee.py` Budget-Template:
- Parameter `resultType: "xlsx"` setzen.
- Parameter `documentTheme: "finance"` setzen (Vorbereitung fuer
Plan D, hat heute noch keinen Effekt).
- [ ] `_copyTemplateWorkflows` (`interfaceFeatures.py` 269-338) prueft
idempotent ob bestehende User-Workflows mit ID `trustee-budget-
comparison` aktualisiert werden -- falls nicht, separates
Migrations-Skript-Stub anlegen.
### Phase 2 -- Prompt klarziehen
- [ ] Neuer Prompt-Text in `mainTrustee.py`:
```text
Fuehre einen Budget-Soll/Ist-Vergleich durch und liefere EIN Excel-Dokument
mit folgender Struktur:
1. Tabelle "Konten-Vergleich" -- EINE Tabelle, EINE Zeile pro Konto:
Spalten: Konto-Nr | Konto-Name | Soll | Ist | Abweichung absolut |
Abweichung % | Status (OK / Warnung / Kritisch).
2. EINE Visualisierung "Soll vs. Ist gesamt" -- ein einziges
Balkendiagramm UNTER der Tabelle, das ALLE Konten in einer Grafik
gegenueberstellt (gruppierte Balken: Soll und Ist je Konto).
3. Kurzer Management-Summary-Absatz (3-5 Saetze) UNTER dem Chart
mit den 3 groessten Abweichungen (>10%) und einer fachlichen
Einschaetzung.
Verwende die uebergebene Budget-Datei als Soll-Quelle und die im
Kontext bereitgestellten Buchhaltungsdaten als Ist-Quelle.
WICHTIG: Erstelle KEINEN separaten Chart pro Konto. Nur EIN
Uebersichts-Chart ueber alle Konten ist gewuenscht.
```
- [ ] Few-Shot-Beispiel als zweites System-Block-Append: minimaler
JSON-Stub mit `metadata.documents[0].sections = [table, image,
paragraph]`.
### Phase 3 -- Excel-Renderer-Sicht
- [ ] Verifizieren dass `rendererXlsx` PNG-Bilder unter Tabellen
korrekt rendert (vorhanden, `_renderJsonImage`-Pfad).
- [ ] Default-Bildbreite checken (heute hardcoded `maxWidth=800` in
`gateway/modules/serviceCenter/services/serviceGeneration/renderers/rendererXlsx.py`
~ 1427-1428) -- ok fuer A4-druckbares Diagramm; spaeter via
Theme-System (Plan D) konfigurierbar.
### Phase 4 -- Frontend
- [ ] `TrusteeAnalyseView.tsx` Budget-Tab: Hinweistext "Excel-Bericht"
statt generisch "Datei", File-Icon `description` statt generisch.
- [ ] Wenn Plan A2 vorher gemerged ist: "Im Workspace ansehen"-Button.
### Phase 5 -- Test
- [ ] Smoke-Test: 1 Run mit Demo-Budget-File -> 1 .xlsx mit
genau einer `table`-Section + einer `image`-Section + einer
`paragraph`-Section.
- [ ] Snapshot des produzierten .xlsx in
`gateway/tests/features/trustee/snapshots/`.
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given Budget-Vergleich-Run, When er fertig ist, Then liefert er genau eine .xlsx (kein .docx) | must |
| 2 | Given die .xlsx wird geoeffnet, When User die erste Tabelle sieht, Then sind alle Konten als Zeilen vorhanden | must |
| 3 | Given die .xlsx, When User runter scrollt, Then findet er EIN Uebersichts-Chart (nicht eines pro Konto) | must |
| 4 | Given die .xlsx, When User zum Ende scrollt, Then steht ein Management-Summary-Absatz | should |
| 5 | Given Budget-Vergleich-Karte im Trustee-Dashboard, When der File-Link erscheint, Then zeigt das UI ein Excel-Icon und Label "Excel-Bericht" | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | integration | ja | gateway/tests/features/trustee/test_budget_comparison_format.py | pending |
| T2 | 2,3,4 | integration | ja | gateway/tests/features/trustee/test_budget_comparison_structure.py | pending |
| T3 | 5 | unit | ja | frontend_nyla/src/pages/views/trustee/__tests__/TrusteeAnalyseView.test.tsx | pending |
## Links
- Workflow-Definition: `gateway/modules/features/trustee/mainTrustee.py`
430-461.
- Renderer-Pipeline: `gateway/modules/serviceCenter/services/serviceGeneration/`.
- Verwandter Plan: `wiki/c-work/1-plan/2026-04-ai-reports-theming-and-pipeline.md`.
- Audit-Quelle: Subagent-Report 2026-04-29.
## Abschluss
- [ ] `wiki/b-reference/gateway/features/trustee.md` Budget-Service-Sektion
aktualisieren
- [ ] Dieses Dokument -> `z-archive/` verschoben

View file

@ -0,0 +1,206 @@
<!-- status: plan -->
<!-- started: 2026-04-29 -->
<!-- component: gateway, frontend-nyla -->
# Trustee Workflow-Audit (A1) & Generischer Workflow-Run-Workspace (A2)
## Beschreibung und Kontext
Der User klickt im Trustee-Dashboard Service-Karten an (Budget-Vergleich,
KPI-Dashboard, Cashflow, Forecast, Jahresabschluss-Pruefung) und landet auf
Tabs in `TrusteeAnalyseView` / `TrusteeAbschlussView`, wo der jeweilige
Workflow gestartet wird.
Zwei Themen:
- **A1 Audit:** Sind diese Workflows wirklich auf dem aktuellen
Pick-not-Push / Typed-Action-Stack? -- **JA, alle GREEN** (siehe Befund
unten). Hier wird nur dokumentiert.
- **A2 Result-Sichtbarkeit:** Wenn der User waehrend des Runs die Seite
wechselt, ist das Resultat (KI-Antwort + generierte Files) anschliessend
weg -- es lebt nur im React-State der Source-View. Persistenz existiert
zwar in `AutoRun`/`AutoStepLog`, ist aber nirgends als User-UI
erschlossen.
**Geschaeftstreiber:** UX-Bruch ("ich war kurz auf einer anderen Seite, jetzt
ist mein Report weg") + fehlende Single-Source-of-Truth fuer Workflow-
Outputs. Plus: User-Anforderung, dass jeder Workflow eine
`FeatureInstance` referenziert (auch bei Scheduled-Runs), damit Datenquellen
im Editor sauber gefiltert werden koennen.
## Fokus und kritische Details
- `POST /api/workflows/{instanceId}/execute` ist heute **synchron** -- der
Browser blockiert auf dem Request bis Ende. Der "Workspace" muss diesen
Request nicht aendern, er muss nur die persistierten `AutoRun`-Daten
verlinkbar/auffindbar machen.
- `Workflow.featureInstanceId` ist heute optional (zumindest historisch).
Pflicht-Binding ist breakend fuer existierende Workflows ohne Instanz --
Migration noetig.
- Scheduler-Pfad muss die Pflicht-Binding **respektieren**, sonst laufen
Cron-Workflows ohne Daten-Scope.
- Tab-Position: User wuenscht den Workspace explizit unter
`/automations` (Seite "Nutzung > Automation") als zusaetzlichen Tab neben
Dashboard + Workflows.
## Ziel und Nicht-Ziele
- Ziel A1: Audit-Befund GREEN dokumentiert (Wiki-Update).
- Ziel A2: Generische Workflow-Run-Workspace-View; jeder Workflow hat
Pflicht-FeatureInstance-Binding; Trustee-Views zeigen Resultate ueber
Workspace statt eigenem React-State.
- NICHT: Async-Umstellung des Execute-Endpoints (eigener Plan, falls noetig).
- NICHT: Browser-Push-Notification (User wollte nur Workspace).
- NICHT: Aenderung der Workflow-Engine-Logik selbst.
## Betroffene Module
- Gateway:
- `gateway/modules/features/graphicalEditor/datamodelFeatureGraphicalEditor.py`
(`Workflow.featureInstanceId` Pflicht).
- `gateway/modules/features/graphicalEditor/routeFeatureGraphicalEditor.py`
(Save-Validation, neue Aggregat-Routen unter `/api/automations/runs/...`).
- `gateway/modules/features/graphicalEditor/mainScheduler.py` (Schedule-
Erstellung uebernimmt Instanz aus Workflow).
- `gateway/modules/features/trustee/mainTrustee.py` (Audit-Notiz,
sicherstellen dass Templates `featureInstanceId` setzen).
- Frontend:
- Neuer Tab in `frontend_nyla/src/pages/AutomationsDashboardPage.tsx`.
- Neue Komponenten `WorkflowRunWorkspaceView`,
`WorkflowRunDetailView`.
- FlowEditor-Toolbar bekommt Pflicht-Selector "Feature-Instanz".
- `frontend_nyla/src/pages/views/trustee/TrusteeAnalyseView.tsx` und
`TrusteeAbschlussView.tsx` schlanker (Verlinkung in den Workspace
statt eigene Result-Anzeige; Run-State nicht mehr verloren bei Tab-
Wechsel weil Persistenz uebernimmt).
- DB-Migration: ja -- Bestand-Workflows ohne `featureInstanceId` brauchen
ein Migrations-Skript (interaktiv).
- RBAC: Zugriff auf `/automations/workspace` -- prueft pro Run die
Mandate/FeatureInstance-Rechte des Users.
## Befund A1 (Audit) -- bereits GREEN
| Service | Workflow-ID | Backend-Definition | Status | Beleg |
|---------|-------------|---------------------|--------|-------|
| Budget-Vergleich | `trustee-budget-comparison` | `mainTrustee.py` 430-461 | GREEN | DataRef Trigger-Payload + Refresh-Output, modern |
| KPI-Dashboard | `trustee-kpi-dashboard` | `mainTrustee.py` 463-478 | GREEN | `_buildAnalysisWorkflowGraph` 364-381 |
| Cashflow-Rechnung | `trustee-cashflow` | `mainTrustee.py` 480-492 | GREEN | dito |
| Prognose | `trustee-forecast` | `mainTrustee.py` 494-507 | GREEN | dito |
| Jahresabschluss-Pruefung | `trustee-year-end-check` | `mainTrustee.py` 509-522 | GREEN | dito |
Engine-Pipeline: `executeGraph` ruft `materializeFeatureInstanceRefs` +
`validateGraph` vor jedem Lauf auf
(`gateway/modules/features/graphicalEditor/executionEngine.py` 341-350).
Persistierte Graphs nutzen zunaechst nackte UUID fuer `featureInstanceId`
(Bootstrap `interfaceFeatures.py` 336-338), Laufzeit-Envelope erfolgt in
`executeGraph`.
## Entscheidungen
| Datum | Entscheidung | Begruendung |
|-------|-------------|------------|
| 2026-04-29 | Workspace ist GENERISCH plattformweit, nicht Trustee-spezifisch | Doppelt-Bauen vermeiden; ComCoach/TeamsBot/Workspace profitieren auch |
| 2026-04-29 | Workspace lebt unter `/automations` als Tab "Workspace" | User-Vorgabe; Nutzungspfad "Nutzung > Automation > Workspace" |
| 2026-04-29 | Workflow-Speicherung verlangt `featureInstanceId` | Datenquellen-Tools im Editor sind nur dann sinnvoll filterbar |
| 2026-04-29 | Bestehende Workflows ohne Instanz: interaktives Migrations-Skript pro Mandate | Kein automatischer Default -- der User soll bewusst zuordnen |
| 2026-04-29 | Browser-Push-Notification NICHT umgesetzt | User hat nur Workspace gewaehlt; Toast bei Run-Ende reicht |
## Umsetzungs-Checkliste
### Phase 1 -- FeatureInstance-Pflicht-Binding
- [ ] Backend: `Workflow.featureInstanceId NOT NULL` (Pydantic +
DB-Constraint via Connector-Auto-Init).
- [ ] Save-Validation in `routeFeatureGraphicalEditor.py`: Workflow ohne
`featureInstanceId` -> 400 mit Error-Detail "Feature-Instanz fehlt".
Save-with-errors (AC-9 vom Typed-Action-Plan) bleibt erlaubt -- nur
Run-Start blockt.
- [ ] Run-Start (`POST .../execute`): Vorab-Check, dass Workflow eine
Instanz hat.
- [ ] Migrations-Skript
`gateway/scripts/script_db_migrate_workflow_feature_instance.py`:
listet pro Mandate alle Workflows ohne Instanz, bietet pro Workflow
eine Auswahl (CLI prompt).
- [ ] Scheduler: Schedule-Erstellung uebernimmt Instanz aus Workflow,
Override nicht moeglich.
- [ ] FlowEditor-Toolbar: Pflicht-Dropdown "Feature-Instanz" oben links.
Datenquellen-Tools (z.B. UDB-Listen, FileItems, SourcesTab) filtern
automatisch auf diese Instanz.
- [ ] Trustee-Templates pruefen: alle Workflows haben in
`mainTrustee.py` bereits `featureInstanceId` als
`{{featureInstanceId}}`-Placeholder, der beim
`_copyTemplateWorkflows` ersetzt wird (`interfaceFeatures.py`
269-338) -- nur sicherstellen dass jetzt nichts haengen bleibt.
### Phase 2 -- Generischer WorkflowRunWorkspace
- [ ] Neue Aggregat-API:
`GET /api/automations/runs?scope=mine|mandate|all&status=...&limit=...`
und `GET /api/automations/runs/{runId}/detail`. Detail-Payload
kombiniert AutoRun + AutoStepLog + Outputs + verlinkte FileItems
(joined mit RBAC-Filter).
- [ ] Tab "Workspace" in
`frontend_nyla/src/pages/AutomationsDashboardPage.tsx` (neben
Dashboard, Workflows).
- [ ] Komponente `WorkflowRunWorkspaceView`: Liste mit Filter
(Status, Workflow-Template, Mandate, Zeitraum), 50-er Pagination.
- [ ] Komponente `WorkflowRunDetailView`: Chat-aehnliche Ansicht
- Header: Workflow-Name, Status, Start/Ende, FeatureInstance.
- Eingabe-Bubble: Trigger-Payload (formatiert).
- Step-Bubbles: chronologisch, pro Step-Output kollabierbar.
- Final-Bubble: KI-Antwort als Markdown.
- Documents-Sektion: alle generierten FileItems als Karten mit
Direkt-Download (`a href="/api/files/{id}/download"`).
- [ ] Trustee-Views umbauen:
- `TrusteeAnalyseView`: Result-Anzeige raus (`resultText` /
`resultDocuments` State), stattdessen "Im Workspace ansehen"-
Button mit `runId`.
- `TrusteeAbschlussView`: dasselbe (heute zeigt der gar nichts).
### Phase 3 -- Notifications
- [ ] Toast bei Run-Ende ist heute schon da -- erweitern um Klick-Action
zum Detail-View des Runs.
- [ ] Sidebar-Badge auf Eintrag "Automation" (Counter "neu seit letztem
Besuch", `localStorage`-basiert).
## Akzeptanzkriterien
| # | Kriterium (Given-When-Then) | Prio |
|---|-----------------------------|------|
| 1 | Given Workflow ohne Instanz, When User Save klickt, Then 400 mit klarer Error-Message | must |
| 2 | Given Bestand-Workflow ohne Instanz, When Migrations-Skript laeuft, Then User waehlt interaktiv pro Workflow eine Instanz und das Feld wird gesetzt | must |
| 3 | Given Trustee-Run gestartet, When User waehrend Lauf die Seite wechselt und zurueck zum Tab "Workspace" geht, Then Run mit allen Outputs sichtbar | must |
| 4 | Given Run mit generiertem File, When User auf Document-Karte klickt, Then File wird direkt heruntergeladen | must |
| 5 | Given Run-Ende, When Toast erscheint, Then Klick fuehrt direkt zum WorkflowRunDetailView | should |
| 6 | Given neue Runs seit letztem Besuch, When User die Sidebar sieht, Then "Automation" hat einen Counter-Badge | should |
| 7 | Given FlowEditor offen ohne Instanz-Auswahl, When User Datenquellen-Tool oeffnen will, Then Hinweis "Bitte Feature-Instanz waehlen" | should |
## Testplan
| ID | AC | Art | Automatisiert | Repo-Pfad | Status |
|----|----|-----|--------------|-----------|--------|
| T1 | 1 | api | ja | gateway/tests/features/graphicalEditor/test_workflow_save_requires_instance.py | pending |
| T2 | 2 | manual | nein | gateway/scripts/script_db_migrate_workflow_feature_instance.py | pending |
| T3 | 3 | e2e | ja | frontend_nyla/tests/e2e/workflow-run-workspace.spec.ts | pending |
| T4 | 4 | e2e | ja | wie T3 | pending |
| T5 | 5 | manual | nein | -- | pending |
| T6 | 6 | unit | ja | frontend_nyla/src/pages/__tests__/AutomationsDashboardPage.test.tsx | pending |
| T7 | 7 | unit | ja | frontend_nyla/src/components/flowEditor/__tests__/Toolbar.test.tsx | pending |
## Links
- Audit-Quelle: Subagent-Report 2026-04-29.
- Wiki: `wiki/b-reference/gateway/workflow.md`,
`wiki/b-reference/gateway/features/trustee.md`,
`wiki/c-work/4-done/2026-04-typed-action-architecture.md`,
`wiki/c-work/4-done/2026-04-automation-central-admin.md`.
## Abschluss
- [ ] `wiki/b-reference/gateway/workflow.md` Abschnitt
"Workflow-Run-Workspace" anlegen
- [ ] `wiki/b-reference/gateway/features/trustee.md` Result-UX-Sektion
aktualisieren
- [ ] `wiki/TOPICS.md` ggf. Tab-Beschreibung
- [ ] Dieses Dokument -> `z-archive/` verschoben

View file

@ -0,0 +1,126 @@
<!-- status: done -->
<!-- started: 2026-04-29 -->
<!-- completed: 2026-04-29 -->
<!-- component: gateway -->
<!-- lastReviewed: 2026-04-29 -->
<!-- verifiedAgainst: gateway/modules/interfaces/interfaceBootstrap.py @ HEAD, audit dev 2026-04-29 -->
# Bootstrap-Migrations-Cleanup (idempotente Boot-Routinen)
## Beschreibung und Kontext
Beim App-Start liefen 5 idempotente "Self-Heal"- und "Backfill"-Routinen mit, die
historisch noetig waren, um produktive DBs nachtraeglich auf neue Schemata zu
heben (Mandate-Label-Logik, Slug-Regeln, Legacy-Root-Mandate, sysadmin-Split,
RAG-Bytes-Aggregation). Auf modernen DBs sind sie No-Ops und haben pro Boot
Tabellen-Scans gekostet plus den Lifespan-Pfad unleserlich gemacht.
**Resultat:** alle 5 Routinen aus dem Boot-Pfad entfernt, Tests fuer entfernte
Routinen geloescht, ein leichter Telemetrie-Helper (`runLegacyDataChecks`)
warnt im Boot-Log falls je doch noch Restbestand auftauchen sollte.
## Was wurde gemacht (chronologisch)
1. **Verifikation pro Kandidat** -- Code-Pfade in
`gateway/modules/interfaces/interfaceBootstrap.py` und
`gateway/modules/interfaces/interfaceDbKnowledge.py` neu vermessen.
Ergebnis: Plan-Annahmen waren teils ungenau (Pfad `system/` statt
`interfaces/`, ganze Funktionen statt einzelner Bloecke); Plan-Tabelle in
`1-plan/` korrigiert vor Removal.
2. **Audit-Skript** `gateway/scripts/script_db_audit_legacy_state.py` (NEU)
- Lese-only fuer 4 Mandate-/Role-Checks (App-DB) + 1 RAG-Check (Knowledge-DB).
- Optional `--purge-rag-orphans` Flag fuer den einzigen Daten-Cleanup.
- Exit-Code 0=GREEN / 1=RED / 2=ERROR fuer CI-Gating.
- JSON-Output-Modus.
3. **Audit-Lauf gegen Dev-DB:**
- 4 Checks GREEN sofort.
- 1 Check RED: 20 `FileContentIndex`-Rows ohne `mandateId`+`featureInstanceId`
(alte Test-Datei-Reste).
4. **Purge der 20 RAG-Orphans** via `--purge-rag-orphans`. Re-Audit: 5/5 GREEN.
5. **Telemetrie-Helper** `gateway/modules/interfaces/_legacyMigrationTelemetry.py`
(NEU) -- 4 nicht-blockierende SELECT-Checks (gleiche Logik wie Audit-Skript),
prozessweit gecached, Aufruf am Ende von `initBootstrap`. WARN-Log bei Restbestand.
6. **Code-Removals** in `gateway/modules/interfaces/interfaceBootstrap.py`:
- `_migrateMandateDescriptionToLabel` (Funktion + Aufruf) -- weg.
- `_migrateMandateNameLabelSlugRules` (Funktion + Aufruf) -- weg.
- `initRootMandate` Legacy-Block (`recordFilter={"name":"Root"}` + recordModify) -- weg.
Funktion `initRootMandate` selbst bleibt (legt Root-Mandat an wenn fehlt).
- `_migrateAndDropSysAdminRole` (Funktion + Aufruf) -- weg.
- `aggregateMandateRagTotalBytes` Fallback `try`/`except`-Block (Z. 606-635) --
weg. Funktion bleibt aktiv (4 Caller).
7. **Test-Cleanup** -- folgende Test-Files geloescht (referenzierten entfernte
Funktionen, wuerden ImportError werfen):
- `gateway/tests/unit/bootstrap/test_mandateNameMigration.py` (8 Tests)
- `gateway/tests/unit/rbac/test_sysadmin_migration.py` (5 Tests)
- Verzeichnis `gateway/tests/unit/bootstrap/` ist jetzt leer und entfernt.
8. **Smoke-Tests:** `python -m pytest tests/unit/rbac` -- 17/17 GREEN.
Import-Smoke fuer alle 3 geaenderten Module GREEN.
9. **Telemetrie-Aufruf** in `initBootstrap` als letzten Schritt nach
`_bootstrapBilling()`. Try/Except umschlossen damit Telemetrie nie
den Boot crashed.
## Geaenderte Dateien
| Datei | Aenderung |
|-------|----------|
| `gateway/modules/interfaces/interfaceBootstrap.py` | -3 Funktionen (~120 Zeilen), -3 Aufrufe, +6 Zeilen Telemetrie-Hook |
| `gateway/modules/interfaces/interfaceDbKnowledge.py` | -27 Zeilen Fallback-Block, vereinfachtes Log-Format |
| `gateway/modules/interfaces/_legacyMigrationTelemetry.py` | NEU (~145 Zeilen) |
| `gateway/scripts/script_db_audit_legacy_state.py` | NEU (~290 Zeilen) |
| `gateway/tests/unit/bootstrap/test_mandateNameMigration.py` | GELOESCHT |
| `gateway/tests/unit/rbac/test_sysadmin_migration.py` | GELOESCHT |
## Audit-Befund (Dev-DB, 2026-04-29 19:50)
| # | Check | Ergebnis nach Purge |
|---|-------|---------------------|
| 1 | mandate-description-to-label | GREEN (0) |
| 2 | mandate-name-slug-rules | GREEN (0) |
| 3 | root-mandate-legacy | GREEN (0) |
| 4 | sysadmin-role | GREEN (0) |
| 5 | rag-fallback-orphan-index | GREEN (0, nach Purge von 20 Test-Resten) |
## Operative Hinweise fuer Int + Prod
User-Statement (2026-04-29): "die codebase lief bereits auf int und main/prod" --
d.h. die idempotenten Migrations sind dort schon mehrfach durchgelaufen, das
Removal-Risiko = 0. Trotzdem empfohlen vor dem Deploy:
```pwsh
# (1) Audit lesend laufen lassen -- ohne Aenderung
python -m scripts.script_db_audit_legacy_state
# (2) Falls Check 5 RED (RAG-Orphans): purgen
python -m scripts.script_db_audit_legacy_state --purge-rag-orphans
```
Falls trotzdem Restbestand auftauchen sollte (alter DB-Restore o.ae.), feuert
nach dem Boot der Telemetrie-Helper eine WARN-Logzeile pro betroffenem Check
mit Routine-Name + IDs. Dann manuell mit dem Audit-Skript untersuchen.
## Folgearbeiten (separate Plans)
- **Scripts-Inventar:** subagent hat 27 Files in `gateway/scripts/` kategorisiert.
Klar Archiv-Kandidaten (B): `check_orphan_featureinstance.py` (hardcoded UUIDs),
`script_db_cleanup_duplicate_roles.py` (IS-NULL-Bug-Fix; Bug laengst gefixt).
Unklar (C): `_listMandates.py`, `migrate_async_to_sync.py`,
`i18n_rekey_plaintext_keys.py`, `script_db_migrate_accessrules_objectkeys.py`.
Restliche 21 Files = AKTIV. Entscheidung pro File offen.
- **Telemetrie-Lebenszyklus:** in 30+ Tagen pruefen ob Telemetrie-Helper
Treffer hatte. Wenn 0, Helper komplett entfernen.
## Akzeptanzkriterien
| # | Kriterium | Status |
|---|-----------|--------|
| 1 | Audit-Skript laeuft Lese-only und liefert Exit-Code 0 wenn alle Checks GREEN | erfuellt |
| 2 | Boot mit entfernten Routinen startet ohne Fehler, Telemetrie-Hook laeuft am Ende | erfuellt (Smoke-Test gruen) |
| 3 | Telemetrie-Helper feuert klare WARN-Zeile bei Restbestand | erfuellt (Code-Pfad da, nicht ausgeloest da Dev GREEN) |
| 4 | Tests fuer entfernte Funktionen sind weg, restliche Tests laufen | erfuellt (17/17 rbac+bootstrap green) |
## Links
- Audit-Skript: `gateway/scripts/script_db_audit_legacy_state.py`
- Telemetrie: `gateway/modules/interfaces/_legacyMigrationTelemetry.py`
- Verwandte Done-Plans: `wiki/c-work/4-done/2026-04-mandate-name-label-logic.md`,
`wiki/c-work/4-done/2026-04-sysadmin-authority-split.md`.

View file

@ -14,6 +14,11 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
## 2026-04-29
- 2026-04-29 | refactor | gateway | **Bootstrap-Cleanup ausgefuehrt: 4 idempotente Migrations-Routinen + 1 Aggregations-Fallback aus dem Boot-Pfad entfernt.** Vor Removal Audit-Skript `gateway/scripts/script_db_audit_legacy_state.py` (NEU, lese-only, exit-code-gated) gegen Dev-DB gelaufen -> 4/5 GREEN sofort, Check 5 (RAG-Orphans) RED mit 20 verwaisten `FileContentIndex`-Rows ohne `mandateId`/`featureInstanceId` -> via `--purge-rag-orphans` bereinigt -> Re-Audit 5/5 GREEN. Dann entfernt aus `gateway/modules/interfaces/interfaceBootstrap.py`: `_migrateMandateDescriptionToLabel` (Funktion + Aufruf), `_migrateMandateNameLabelSlugRules` (Funktion + Aufruf, ~64 Zeilen), `initRootMandate`-Legacy-Block (`name="Root"`-Migration, 7 Zeilen; Funktion selbst bleibt), `_migrateAndDropSysAdminRole` (Funktion + Aufruf, ~95 Zeilen). In `interfaceDbKnowledge.py`: `aggregateMandateRagTotalBytes`-Fallback-Block (`try`/`except` mit FileItem-ID-Korrelation aus Management-DB, ~27 Zeilen) entfernt -- die Funktion bleibt aktiv, da sie 4 externe Caller hat. Ersatz: neuer Helper `gateway/modules/interfaces/_legacyMigrationTelemetry.py` mit 4 lese-only WARN-Checks (gleiche Logik wie Audit-Skript, prozessweit gecached) wird am Ende von `initBootstrap` einmalig aufgerufen -- falls je doch Restbestand auftauchen sollte (alter DB-Restore o.ae.), gibt's klare WARN-Logs mit Routine-Name + IDs. Tests `tests/unit/bootstrap/test_mandateNameMigration.py` (8 Tests) und `tests/unit/rbac/test_sysadmin_migration.py` (5 Tests) geloescht (referenzierten entfernte Funktionen, wuerden ImportError werfen). Smoke-Test: 17/17 verbliebene rbac+bootstrap-Tests GREEN, Imports aller drei Module GREEN. User-Statement zur Risk-Lage: "die codebase lief bereits auf int und main/prod" -- d.h. die idempotenten Migrations sind dort schon mehrfach durchgelaufen, das Removal-Risiko = 0; das Audit-Skript bleibt fuer pre-deploy-Gating. Plan urspruenglich in `1-plan/`, jetzt direkt in `4-done/2026-04-bootstrap-migrations-cleanup.md`. (c-work: `4-done/2026-04-bootstrap-migrations-cleanup.md`)
- 2026-04-29 | feat | frontend-nyla, gateway | **Generischer `frontendType: templateTextarea`** fuer Freitext mit `{{nodeId.path}}`-Variablen (DataPicker-Insert); `email.draftEmail.context` und `ai.prompt.aiPrompt` nutzen ihn statt reiner Textarea -- Aufloesung ausschliesslich via bestehendes `resolveParameterReferences` (kein Loop-Spezialcode im Executor). Loop-Preview-Enrichment + IfElse/AI-`responseData`-Picker bleiben. Unit-Test `test_legacy_string_template_loop_current_item_nested` in `test_automation2_graphUtils.py`.
- 2026-04-29 | docs | wiki | **6 neue UX-/Architektur-Plaene angelegt** in `wiki/c-work/1-plan/` aus Topics-TODO A-F: (A1+A2) Trustee-Workflow-Audit (alle 5 Reporting-Workflows GREEN auf Pick-not-Push) + generischer `WorkflowRunWorkspace`-Tab unter `/automations` als Single-Source-of-Truth fuer Workflow-Resultate, plus Pflicht-Binding `Workflow.featureInstanceId` (`2026-04-trustee-workflow-audit-and-run-workspace.md`); (B) Budget-Vergleich auf Excel-only mit eindeutigem 1-Tabelle-1-Chart-Prompt, Word-Pfad raus (`2026-04-trustee-budget-comparison-refactor.md`); (C) Inventar idempotenter Boot-Routinen, Removal-Plan fuer 5 b-Kandidaten via Audit-Skript + 30-Tage-Telemetrie (`2026-04-bootstrap-migrations-cleanup.md` -- noch am gleichen Tag umgesetzt, siehe Eintrag oben, jetzt unter `4-done/`); (D) Theme-System fuer AI-Reports pro Workflow (4 Themes standard/finance/marketing/presentation + Mandate-CI-Overlay), MD->JSON-Konsolidierung, Renderer-Refactor, Bilder-in-Tabellenzellen (`2026-04-ai-reports-theming-and-pipeline.md`); (E) ComCoach Greenfield-IA mit `TrainingModule`-Datenmodell (Rename `CoachingContext`) + 5-Tab-Struktur Dashboard/Assistent/Module/Session/Einstellungen, dossier=coaching-Doppelung weg, `?context=`-Bug fix (`2026-04-comcoach-greenfield-ia.md`); (F) TeamsBot Greenfield-IA analog mit neuer `MeetingModule`-Entity + 5-Tab-Struktur und 4 Live-Update-Fixes (SSE-Reconnect-Hook, agentRun-Status-Bubble, stats-Karten, adaptives Dashboard-Polling) (`2026-04-teamsbot-greenfield-ia-and-live-update.md`). Empfohlene Reihenfolge der Bauphase: C -> A2-Phase-1 (FeatureInstance-Pflicht) -> A2-Phase-2/3 (Workspace) -> B -> D -> E/F parallel. (c-work: 6 neue Dateien in `1-plan/`)
- 2026-04-29 | fix | gateway | **`checkForDuplicateFile` macht jetzt einen RBAC-Cross-Check und liefert keine "Geister-Duplicate" mehr aus.** Ursache des `File with ID ... not found`-Fehlers in `downloadFromDataSource` direkt nach `Duplicate detected for user ...`: `interfaceDbComponent` wird ueber `serviceHub` ohne `featureInstanceId` initialisiert, `checkForDuplicateFile` faellt deshalb auf den `mandateId`-Filter zurueck und benutzte `db.getRecordset` (kein RBAC) -- damit konnte er ein File aus einer FREMDEN featureInstance returnen. Der nachfolgende `updateFile`-Aufruf prallte dann am RBAC-gefilterten `getFile` ab und crashte den ganzen Tool-Call. Sauberer Fix an der Wurzel: nach dem Recordset-Treffer zwingend `self.getFile(fileId)` als RBAC-Cross-Check; wenn `None`, gilt als kein Duplicate fuer den aktuellen Scope und der Caller (`saveUploadedFile`/`createFile`) erstellt eine frische per-Scope-Kopie. Damit entfaellt jeglicher Workaround in `updateFile` (die in der Vorgaenger-Session gebaute Try/Except-Schleife wurde gestern bereits ausgebaut, weil sie in einer Sackgasse stand). Folge: identische Files koennen pro Mandate mehrfach existieren -- eines pro featureInstance -- was gewollt ist, da sie pro Scope eigene Folder-/Tag-/Neutralize-Metadaten brauchen. Symptom-Doku in `wiki/b-reference/gateway/agent-file-bridge.md` (Section "Ghost-Duplicate-Fix"). (c-work: Begleitfix zum Agent-File-Bridge-Build vom selben Tag)
- 2026-04-29 | fix | gateway | **Agent-Tools binden jetzt jede produzierte Datei als ChatDocument an den aktiven Workflow -- damit funktioniert der `documentList`-Resolver fuer Agent-Outputs.** Bisher erzeugten `downloadFromDataSource`, `writeFile mode=create`, `renderDocument`, `generateImage` und `createChart` zwar eine `FileItem` (via `saveUploadedFile`/`saveGeneratedFile`), liessen den Workflow-Document-Graph aber unberuehrt -- statt eines `ChatDocument` floss nur ein `sideEvent fileCreated` an die UI. Folge: `getChatDocumentsFromDocumentList` (und damit `ai_summarizeDocument` / `ai_process` / `context_extractContent` / `context_neutralizeData`) konnte die FileItem-IDs nicht aufloesen, weil der Resolver ausschliesslich `workflow.messages[*].documents[*].id` matcht. Symptom war "Building structure prompt with 0 valid ContentParts" und entsprechend leere Summaries direkt nach einem Agent-Download. Loesung: zentraler Helper `_attachFileAsChatDocument(services, fileItem, label, userMessage)` in `coreTools/_helpers.py`, der intern eine ChatMessage (Rolle `assistant`, Status `step`, generierter `documentsLabel`) inklusive einem ChatDocument (mit `roundNumber`/`taskNumber`/`actionNumber` aus dem aktiven Workflow) via `chatService.storeMessageWithDocuments` persistiert -- exakt das Pattern, das `workflowProcessor.persistTaskResult` und `methodTrustee.extractFromFiles` schon laenger benutzen. Helper wird jetzt aus allen fuenf File-erzeugenden Agent-Tools aufgerufen; der ToolResult-Text traegt einheitlich beide IDs (`documentList ref: docItem:<chatDocId>` fuer AI-Tools, `file id: <fileId>` fuer `readFile`/Embeds). Tool-Descriptions + `conversationManager.buildSystemPrompt` ergaenzt um die explizite Anweisung "use docItem:<id> in documentList, NOT the file id, NOT a `{"documents":[...]}` wrapper". Zusatzlich: `mainServiceAgent.runAgent` propagiert das Workflow-Object jetzt in alle Service-Contexts (`chat._context.workflow` etc.) -- bisher tat das nur `workflowManager._propagateWorkflowToContext`, weshalb der Agent ueber die Workspace-Route ohne aktiven Workflow-Context lief und `chatService._workflow` `None` war. Folge: der Helper konnte ohne diesen Propagations-Fix nie greifen. Die in der vorhergehenden Session eingebaute RBAC-tolerante `updateFile`-Try/Except-Schleife in `_downloadFromDataSource` ist jetzt obsolet und wieder ausgebaut -- mit korrektem ChatDocument-Bind kommt der `featureInstanceId` ueber den Workflow-Pfad und die Duplicate-File-RBAC-Probleme verschwinden an der Wurzel. Pattern-Doc neu unter `wiki/b-reference/gateway/agent-file-bridge.md`. (c-work: kein dedizierter Eintrag, kritischer Fix on top der documentList-Coercer-Aenderung)