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

14 KiB
Raw Blame History

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. 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)

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

// Leer = alle Kriterien anzeigen
// Befüllt = nur diese FeedbackCriterionItem.id anzeigen
criteriaIds?: number[]   // → FeedbackCriterionItem.id

Filterlogik (in MeetingView + AssignmentDetail):

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

`${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ähiggenerateWithAi() schlägt fehl, vermutlich Capgemini-Netzwerk/Firewall blockiert openrouter.ai. Root Cause nicht untersucht.

Große Aufgaben (eigene Design-Session nötig)

  1. SQLite WASM + OPFS — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus.
  2. Mehrstufiges, pro Dimension konfigurierbares Prompt-System — löst den aktuellen Einzel-Prompt in buildAiPrompt ab, braucht Spezialwissen je Dimension + Platzhaltersystem.
  3. 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):
    { 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:

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

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).