# Handover-Dokument — Assignment Monitor Dieses Dokument ermöglicht einem neuen Chat-Kontext, die Arbeit am Projekt nahtlos fortzuführen. --- ## Stand & nächste Schritte Zuletzt abgeschlossen (2026-07-04): Umstellung von Dexie/IndexedDB auf einen lokalen Node/Express-Server mit `node:sqlite` (RPC-Bridge `POST /api/rpc`), damit mehrere Browser auf demselben Laptop dieselben Daten sehen. Details: `CLAUDE.md` → Abschnitt "Datenbank: lokaler Server statt IndexedDB". **Sofort nächster Schritt: die echten IndexedDB-Daten müssen noch per Backup-Export/-Import in die neue SQLite-DB migriert werden** — siehe CLAUDE.md-Offene-Punkte-Tabelle, Prio M. Davor abgeschlossen (2026-07-02/03): zeitgewichteter Bewertungs-Vorschlag (`ratingTrend.ts`), Kriterium-Zuordnung für Freitext-Notizen (`ConversationEntry.lineTags` + `CriterionTagPicker.tsx`), Notation → Bewertungs-Vorschlag in der Gesamtbewertung, feinere Kriterium-Gewichtung (0,0–2,0) inkl. Division-durch-Null-Fix, diverse Export-/Backup-Fixes. Design-Hintergrund dazu: `docs/EVALUATION_FEEDBACK_REDESIGN.md`. **Vollständige, priorisierte Liste der offenen Punkte:** siehe `CLAUDE.md` → Abschnitt "Offene Punkte (priorisiert)" — das ist die laufend gepflegte Quelle, hier nicht dupliziert. Größere offene Themen, die eine eigene Design-Session brauchen: mehrstufiges Prompt-System für die KI-Generierung, selbstlernendes Tagging. --- ## Worum geht es? **Assignment Monitor** ist eine PWA für Lars Stommer (Capgemini Management Consultant Trainee Program Lead). Die App wird auf dem Smartphone genutzt, um Trainees (intern: „Institutees") während ihrer Kunden-Assignments live zu begleiten und zu bewerten. Lars führt die Meetings, beobachtet die Trainees und möchte sofort Notizen und Bewertungen erfassen, ohne Stift/Papier. ## Tech-Stack - **React 18 + TypeScript + Vite** (PWA-Frontend) - **Tailwind CSS v4** (`@import "tailwindcss"` — kein `tailwind.config.js`!) - **Lokaler Node/Express-Server + `node:sqlite`** (seit 2026-07-04) — löst Dexie/IndexedDB als Datenspeicher ab, siehe CLAUDE.md. `npm run dev:all` startet Frontend + Server zusammen. - **React Router v6** - **Capgemini-Brandfarbe:** `#0070AD` - Arbeitsverzeichnis: `C:\dev\AssigmentMonitor` **Wichtig für den TypeScript-Check:** `npx tsc --noEmit` prüft am Root **nichts** (Project-References-Setup, `files: []`) — immer `npx tsc -b` verwenden. ## Bewertungsskala (kritisch für alle Berechnungen) ```typescript type FeedbackRating = 'na' | 'not_client_ready' | 'partially_client_ready' | 'nearly_client_ready' | 'client_ready' // Numerisch: na=0, not=1, partially=2, nearly=3, fully=4 // Labels: N/A, Not, Partially, Nearly, Fully // Farben: grau, rot, orange, hellgrün, dunkelgrün // N/A gilt als "nicht gesetzt" — aus allen Berechnungen ausgeschlossen! ``` Aggregation: **gewichteter Durchschnitt** (avg < 1.5 → Not, < 2.5 → Partially, < 3.5 → Nearly, ≥ 3.5 → Fully) ## Datenmodell-Kern (was zu wissen ist) ### Schlüssel-Unterschied: zwei `criteriaId`-Semantiken ``` Assessment.criteriaId → FeedbackCriterionItem.id (Gesamtbewertung nach Meeting) ConversationSkillScore.criteriaId → FeedbackCategory.id (Schnellbewertung während Gespräch) ConversationLineTag.criterionItemId → FeedbackCriterionItem.id (Kriterium-Zuordnung einer Protokoll-Zeile, bewusst anders benannt als "criteriaId") ``` Das klingt inkonsistent — ist aber so gewollt und funktioniert so. ### AssignmentType.criteriaIds ```typescript // Leer = alle Kriterien anzeigen // Befüllt = nur diese FeedbackCriterionItem.id anzeigen criteriaIds?: number[] // → FeedbackCriterionItem.id ``` Filterlogik (in MeetingView + AssignmentDetail): ```typescript const itemFilter = aType?.criteriaIds?.length ? new Set(aType.criteriaIds) : null const filteredItems = itemFilter ? allItems.filter(i => itemFilter.has(i.id!)) : allItems const visibleCatIds = new Set(filteredItems.map(i => i.categoryId)) const filteredCats = itemFilter ? allCats.filter(c => visibleCatIds.has(c.id!)) : allCats ``` ### Dateiname für Meeting-Exporte ```typescript `${assignment.assignmentNumber ?? assignment.title}_${date}_${template.label}${phaseIndex > 1 ? `-${phaseIndex}` : ''}.${ext}` // Beispiel: CGI-2024-001_02-07-2026_Alignment Call-2.md ``` ## Routing-Übersicht ``` / Dashboard /assignment/new Neues Assignment /assignment/:id AssignmentDetail /assignment/:id/edit AssignmentEdit /assignment/:id/meeting/:meetingId MeetingView (src/pages/meeting/) ← WICHTIG: meetingId != assignmentId /assignment/:id/feedback/:instituteeId AssignmentFeedbackPage /assignment/:id/feedback-draft/:instituteeId FeedbackDraftPage (Legacy) /evaluation Auswertung — zeitgewichteter Bewertungs-Vorschlag + Trend, nach Assignment gruppiert /consultants Berater /config Konfiguration (4 Tabs: Feedback | Ass.-Typen | Gewichtung | Backup) ``` MeetingView liest den Param als: `const { id, meetingId: meetingIdParam } = useParams()` ## Konfigurationsbereich **Tab "Feedback"** (`FeedbackStructureConfig.tsx`): - Dimensionen → Kategorien → Kriterium-Items (mit Gewichtung `0,0–2,0` in `0,1`-Schritten, seit 2026-07-03 — vorher grobe `×1`–`×5`-Stufen) - Nur Feedback-Struktur, kein AssignmentType-CRUD mehr! **Tab "Ass.-Typen"** (`AssignmentTypeConfig.tsx`): - AssignmentTypen CRUD - Pro Typ: Sub-Tabs "Phasen" und "Kriterien" - Kriterien-Tab: Baumstruktur Dimension → Kategorie → Items (Checkbox, Indeterminate) **Tab "Gewichtung"** (`RatingWeightConfig.tsx`, seit 2026-07-02): - Ein Regler für den Zeitgewichtungs-Faktor des Bewertungs-Vorschlags (`AppSettings.ratingTrendWeightStep`, Tabelle `appSettings`) **Tab "Backup"** (`Configuration.tsx`): - JSON-Export aller Tabellen via `exportFullBackup()` - JSON-Import via `importFullBackup(file)` (löscht alles, dann bulkAdd) ## Bekannte offene Punkte (Priorität nach Dringlichkeit) ### Mittlere Aufgaben 1. **KI-Generierung funktionsunfähig** — `generateWithAi()` schlägt fehl, vermutlich Capgemini-Netzwerk/Firewall blockiert `openrouter.ai`. Root Cause nicht untersucht. ### Große Aufgaben (eigene Design-Session nötig) 2. **SQLite WASM + OPFS** — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus. 3. **Mehrstufiges, pro Dimension konfigurierbares Prompt-System** — löst den aktuellen Einzel-Prompt in `buildAiPrompt` ab, braucht Spezialwissen je Dimension + Platzhaltersystem. 4. **Selbstlernendes Tagging** — aus bisherigen Zeile-zu-Kriterium-Zuordnungen automatische Tag-Vorschläge beim Tippen ableiten. **Erledigt (2026-07-04):** `db/queries/`-Layer eingeführt — DB-Zugriffe aus allen 20 betroffenen Komponenten/Utils in domänenspezifische Query-Funktionen unter `src/db/queries/` extrahiert (Details siehe CLAUDE.md). Dabei die drei unabhängigen Implementierungen der gewichteten-Durchschnitt-Logik auf eine gemeinsame Funktion in `ratingTrend.ts` konsolidiert (Nebeneffekt: fehlender `Σweight===0`-Guard in `AssignmentDetail.tsx` gefixt) sowie die duplizierte `criteriaIds`-Filterlogik und die doppelten Meeting-Export-Queries zusammengeführt. **Erledigt (2026-07-03, zweite Runde):** N/A-Aggregationsbug in `buildAiPrompt` gefixt. `FeedbackDraftPage.tsx` vollständig auf `feedbackCriterionItems`/`feedbackCategories` migriert (Notizen jetzt korrekt pro Kriterium über `lineTags`, Score-Anzeige über `RATING_OPTIONS`). Toter Code in `FeedbackStructureConfig.tsx` entfernt. "Dashboard zeigt keine Assignment-Nummer" war bereits gelöst — nur die Doku war veraltet. **Erledigt (2026-07-02):** Zeitgewichteter Bewertungs-Vorschlag mit Trend-Erkennung — `src/utils/ratingTrend.ts` berechnet pro Kategorie einen zeitgewichteten Vorschlag aus den Meeting-Assessments (Gewichtungsfaktor konfigurierbar unter Config → Gewichtung, neue Tabelle `appSettings`), erkennt Verbesserung/Verschlechterung über die Laufzeit und zeigt beides als Badge auf `AssignmentFeedbackPage` (mit „Übernehmen" + „Trend in Text übernehmen"). `buildAiPrompt` wurde um einen Trend-Abschnitt erweitert; dabei wurde auch der alte `db.criteria`-Bug mitgefixt. **Erledigt (2026-07-03):** Kriterium-Zuordnung für Freitext-Notizen (`ConversationEntry.lineTags`, `CriterionTagPicker.tsx`, nachträglich pro Zeile innerhalb eines zusammenhängenden Sprecher-Blocks). Notation → Bewertungs-Vorschlag in der Gesamtbewertung (`detectNotationRating()`, „Vorschlag aus Protokoll"-Badge + Kategorie-weite 💡-Zähler). `meetingExport.ts` und `dbBackup.ts` gefixt (Legacy-`db.criteria`-Bug bzw. fehlende `appSettings`-Tabelle im Backup). Assignment-Nummer ist jetzt Pflichtfeld. Kriterium-Gewichtung auf `0,0–2,0` verfeinert, dabei einen Division-durch-Null-Bug bei Gewicht `0` in allen gewichteten Mittelwerten gefunden und gefixt. ## Stil-Regeln (bitte einhalten) - **Tailwind v4**: kein `bg-[#0070AD]` via config, direkt als CSS-Variable nicht nötig — `bg-[#0070AD]` im Class-String funktioniert direkt - **Keine Kommentare** außer für nicht-offensichtliche Invarianten - **Kein `rows={1}` + JS-Autoresize** — besser View/Edit-Toggle oder `fieldSizing: 'content'` via Inline-Style - **Rating-Buttons** immer mit `RATING_OPTIONS`-Array (nicht neu definieren): ```typescript { value, short, color (inaktiv), active (aktiv) } ``` - **Weighted-Average** immer: N/A excludieren, dann `Σ(score×weight)/Σ(weight)` ## Notations-Parser `src/utils/notationParser.tsx` exportiert `` — rendert `+`, `(+)`, `!`, `(!)`, `>`, `[…]` farbig. Überall verwenden wo Protokoll-Text angezeigt wird. ## DB-Backup `src/utils/dbBackup.ts` exportiert: ```typescript exportFullBackup(): Promise // Download als JSON importFullBackup(file: File): Promise<{ ok: boolean; error?: string }> // Clears + bulkAdd ``` Alle 21 Tabellen-Namen sind in `TABLE_NAMES` aufgelistet — bei neuen Tabellen dort ergänzen (ein fehlender Eintrag fällt nicht auf, bis jemand ein Backup zurückspielt und Daten fehlen — genau so ist `appSettings` einmal vergessen worden, siehe Kommentar über der Liste). ## Seed-Daten `src/main.tsx` ruft beim Start auf: 1. `seedIfEmpty()` — 3 AssignmentTypen + Legacy-Kriterien 2. `seedFeedbackStructureIfEmpty()` — 5 Dimensionen, 14 Kategorien, ~70 Items 3. `seedCriterionMappingsIfEmpty()` — Legacy-Mappings 4. `migrateToUnifiedCriteriaIfNeeded()` — Migration auf FeedbackCategory-IDs in PhaseTemplates ## Datenschutz-Hinweise - Alle Daten lokal — seit 2026-07-04 in `server/data/assignment-monitor.sqlite3` (lokaler Server, kein Cloud-Backend), `server/data/` ist gitignored - GDPR-relevant: Institutee-Namen + Kundennamen in der DB - KI-API: OpenRouter, API-Key in localStorage (nicht im Klartext loggen!) - Backup-JSON enthält alle personenbezogenen Daten → vertraulich behandeln ## TypeScript-Build ```bash npx tsc -b # Muss ohne Fehler durchlaufen — NICHT --noEmit, das prüft am Root nichts (siehe Tech-Stack-Abschnitt) npm run dev:all # Frontend + lokaler Server zusammen starten ``` Vor jeder Abgabe: `npx tsc -b` ausführen und alle Fehler beheben. ## Wichtige Eigenheiten / Fallstricke 1. **`MeetingInstance.generalNotes`**: Kann in alten Datensätzen `undefined` sein → immer `?? ''` fallback 2. **`Assessment.criteriaId`**: Zeigt auf `FeedbackCriterionItem.id`, NICHT auf `FeedbackCategory.id` — das verwirrt, weil der Feldname `criteriaId` ist 3. **`PhaseKey`**: Ist `type PhaseKey = string` (kein Union-Typ mehr) 4. **`aType2`** in `AssignmentDetail.tsx`: Doppelte Variable aus Refactoring — `aType` wird zweimal deklariert, zweite heißt `aType2`. Technische Schuld. 5. **`expandedCat`** in `MeetingView`: `Record` — öffnet pro Institutee genau eine Kategorie gleichzeitig 6. **Backup-Import** (`server/db/queries/backup.ts`, seit 2026-07-04 SQL-basiert): leert alle Tabellen und fügt Zeilen mit expliziter, ursprünglicher `id` wieder ein (`insertRowWithId`), damit Fremdschlüssel-Referenzen im importierten Datensatz stabil bleiben — kein Dexie/`bulkAdd` mehr, siehe CLAUDE.md-Datenbank-Abschnitt 7. **Gewichtete Mittelwerte**: immer `Σ(score×weight)/Σ(weight)`, nie `score×weight` roh interpretieren; bei `Σweight === 0` (seit Gewicht `0` erlaubt ist) `null`/`continue` statt Division — sonst `NaN < x` überall `false` → fällt auf „Fully" durch. Siehe `catMode()` (`AssessmentTab.tsx`) und `buildGroupMeetingScores()` (`ratingTrend.ts`). 8. **`ConversationEntry`** entspricht einem ganzen Sprecher-Turn (mehrzeiliger Text-Block), nicht einer einzelnen Zeile — ein Zwischenstand mit "ein Entry pro Zeile" wurde verworfen. Kriterium-Zuordnung läuft über `lineTags` (Matching per Zeilentext, nicht Index).