CG-Feedback-Monitor/docs/HANDOVER.md
Lars 234f87d408 V2-Dev-Stand: Skala 1-10, Benefits/Concerns, DEV-Isolation und Gitea-Doku.
Bündelt die Neuentwicklung in AssigmentMonitorV2 (eigene Ports/DB), die Umstellung auf numerische Bewertungen, Meeting-Checklisten, KI-Tagging und die geplante Feedback-Kaskade — als Basis für Versionsverwaltung in Gitea.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 17:35:05 +02:00

199 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# Handover-Dokument — Assignment Monitor
Dieses Dokument ermöglicht einem neuen Chat-Kontext, die Arbeit am Projekt nahtlos fortzuführen.
> **DEV-Isolation (V2):** Dieses Repo (`AssigmentMonitorV2`) läuft getrennt vom Produktivsystem (`c:\dev\AssigmentMonitor`). Ports **5174 / 4001**, eigene SQLite `assignment-monitor-v2-dev.sqlite3`. Details und Regeln: [`docs/DEV_ISOLATION.md`](./DEV_ISOLATION.md). Produktiv-Daten und -Code niemals anfassen.
---
## Stand & nächste Schritte
Zuletzt abgeschlossen (2026-07-13, dritte Runde): Kriterium-Zuordnung für „Allgemeine Notizen" (`MeetingInstance.generalNoteTags`) — wirkt anders als die bestehende `ConversationEntry.lineTags`-Zuordnung nicht auf ein einzelnes Institutee, sondern auf alle Institutees des Assignments gleichzeitig; alle fünf bestehenden Konsumenten von `lineTags` (Vorschlags-Badges, KI-Prompt, Markdown-Export, Legacy-Feedback-Entwurf, Meeting-Historie) wurden entsprechend erweitert. Details: `CLAUDE.md` → Erledigt-Historie (2026-07-13, dritte Runde).
Davor abgeschlossen (2026-07-13, zweite Runde): Chronologisches Gesamtprotokoll aller Meetings eines Assignments (`MeetingHistory.tsx`, `/assignment/:id/history`, erreichbar von `AssignmentDetail.tsx` und `MeetingView.tsx` aus) — für Recap-Calls nach Kundenmeetings und die Gesamtdurchsprache im Feedback-Call. Details: `CLAUDE.md` → Erledigt-Historie (2026-07-13, zweite Runde).
Davor abgeschlossen (2026-07-13): Papierkorb + Archiv für Assignments — Dashboard zeigt standardmäßig nur aktive Assignments, Löschen abgeschlossener Assignments erst möglich wenn für alle Institutees finales Feedback vorliegt, zweistufige Löschung (Papierkorb → automatische endgültige Löschung nach 30 Tagen, mit Wiederherstellen-Option) sowie ein unabhängiges, umkehrbares Archiv für dauerhafte Aufbewahrung. Details: `CLAUDE.md` → Erledigt-Historie (2026-07-13).
Davor 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,02,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,02,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,02,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 `<NotationText text={string} />` rendert `+`, `(+)`, `!`, `(!)`, `>`, `[…]` farbig. Überall verwenden wo Protokoll-Text angezeigt wird.
## DB-Backup
`src/utils/dbBackup.ts` exportiert:
```typescript
exportFullBackup(): Promise<void> // 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<instituteeId, categoryId | null>` ö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).