CG-Feedback-Monitor/CLAUDE.md
Lars 911dcace72 fix: N/A-Aggregationsbug behoben, FeedbackDraftPage migriert, toten Code entfernt
- buildAiPrompt (AssignmentFeedbackPage.tsx): 'na' wird jetzt korrekt aus dem
  Score-Mittelwert ausgeschlossen (war bislang als 0 mitgerechnet)
- FeedbackDraftPage.tsx auf feedbackCriterionItems/feedbackCategories migriert,
  letzter Konsument der Legacy-Tabellen außer den Seeds. Notizen hängen jetzt
  korrekt pro Kriterium über lineTags, statt ungefiltert an jedes Kriterium.
  Score-Anzeige nutzt RATING_OPTIONS-Labels statt rohem /5-Wert.
- FeedbackStructureConfig.tsx: verwaisten AssignmentType/PhaseTemplate-CRUD-Code
  entfernt (Funktionalität war bereits nach AssignmentTypeConfig.tsx verschoben)
- Doku aktualisiert: CLAUDE.md, docs/HANDOVER.md, docs/STATUS_UND_OFFENE_PUNKTE.md,
  docs/TECHNISCHE_ARCHITEKTUR.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 13:54:19 +02:00

13 KiB
Raw Blame History

CLAUDE.md — Assignment Monitor

Progressive Web App zur Live-Bewertung von Capgemini-Trainees (Institutees) während Kunden-Assignments.

Vollständige Dokumentation: docs/ — bei neuer Session zuerst docs/HANDOVER.md lesen.


Stack

  • React 18 + TypeScript + Vite (PWA)
  • Tailwind CSS v4 — @import "tailwindcss" in CSS, kein tailwind.config.js; Brandfarbe als @theme { --color-brand } in src/index.css
  • Dexie.js v5 — IndexedDB-Wrapper, kein Backend
  • React Router v6
  • Capgemini-Brandfarbe: #0070AD (Tailwind-Klasse brand, JS-Konstante BRAND_COLOR in src/config/constants.ts)

Projektstruktur (nach Architektur-Refactoring, Stand 2026-07-02)

src/
  config/constants.ts     ← RATING_OPTIONS, INST_COLORS, AI_CONFIG, RATING_NUM_MAP, BRAND_COLOR, DEFAULT_RATING_TREND_WEIGHT_STEP
  db/
    index.ts              ← reiner Re-Export (types + schema + seeds)
    types.ts               ← alle Interfaces, FeedbackRating, AppSettings
    schema.ts               ← Dexie-Klasse + Migrationsversionen
    seeds/                   ← assignmentTypes.ts, feedbackStructure.ts, migrations.ts
  pages/
    meeting/                ← MeetingView.tsx, ConversationTab.tsx, AssessmentTab.tsx, CriterionTagPicker.tsx
    RatingWeightConfig.tsx   ← Settings-Tab für Zeitgewichtungs-Faktor (in Configuration.tsx eingebunden)
    ...übrige Pages unverändert am alten Ort
  utils/
    ratingTrend.ts           ← zeitgewichteter Bewertungs-Vorschlag + Trend-Erkennung (genutzt von Evaluation.tsx & AssignmentFeedbackPage.tsx)

Bestehende Imports aus '../db' funktionieren weiterhin unverändert (Re-Export).

Entwicklung

npm run dev          # Dev-Server starten
npx tsc --noEmit     # TypeScript-Check — muss vor jeder Abgabe fehlerfrei sein

Kritische Eigenheiten

Doppelte criteriaId-Semantik

Assessment.criteriaId                     → FeedbackCriterionItem.id   (Gesamtbewertung)
ConversationSkillScore.criteriaId          → FeedbackCategory.id       (Schnellbewertung pro Protokoll-Eintrag)
ConversationLineTag.criterionItemId        → FeedbackCriterionItem.id  (Kriterium-Zuordnung einer einzelnen Zeile innerhalb eines ConversationEntry.note-Blocks, seit 2026-07-02 — bewusst anders benannt als "criteriaId" um obige Verwechslungsgefahr nicht fortzuschreiben)

AssignmentType.criteriaIds

Referenziert FeedbackCriterionItem.id[]. Leer = alle Kriterien anzeigen.

Filterlogik (immer so anwenden):

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

Rating-Skala

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
// N/A gilt als "nicht gesetzt" — aus allen Berechnungen ausschließen!

Aggregation: gewichteter Durchschnitt → avg < 1.5 Not · < 2.5 Partially · < 3.5 Nearly · ≥ 3.5 Fully

Bug-Klasse, auf die immer prüfen: Filter der Form a.score !== null schließen N/A NICHT aus (N/A ist der String 'na', nicht null!). Richtig ist immer a.score !== null && a.score !== 'na'. Gefunden und gefixt in Evaluation.tsx (2026-07-02); dieselbe Prüfung nötig überall, wo Assessment.score oder ConversationSkillScore.score gemittelt wird.

Zweite Bug-Klasse, auf die immer prüfen (Kriterium-Gewichtung): FeedbackCriterionItem.weight geht seit 2026-07-03 von 0,0 bis 2,0 (Config → Feedback, vorher grobe ×1×5-Stufen). Jede gewichtete Mittelung MUSS normieren — Σ(score×weight) / Σ(weight), niemals score×weight direkt als Endwert interpretieren (würde die Bewertungsstufe verzerren, z.B. Score 3 „Nearly" × Gewicht 0,5 = 1,5, was fälschlich zwischen Not/Partially läge). Zusätzlich: wenn Σweight === 0 (z.B. alle beteiligten Kriterien einer Kategorie auf Gewicht 0 gesetzt), nicht dividieren — NaN < 1.5/2.5/3.5 ist überall false und fällt sonst still auf den letzten Bucket „Fully" durch. Immer if (wSum === 0) return null (bzw. continue) vor der Division. Umgesetzt in catMode() (AssessmentTab.tsx) und buildGroupMeetingScores() (ratingTrend.ts) — beim nächsten neuen gewichteten Durchschnitt genauso prüfen.

Routing

/assignment/:id/meeting/:meetingId    ← meetingId ist MeetingInstance.id, nicht Assignment-ID

MeetingView liest: const { id, meetingId: meetingIdParam } = useParams()

Sonstige Fallstricke

  • MeetingInstance.generalNotes kann undefined sein → immer ?? '' als Fallback
  • PhaseKey ist string, kein Union-Typ
  • Tailwind v4: Brandfarbe ist brand (Tailwind-Klasse, via @theme in index.css) — kein bg-[#0070AD] mehr neu schreiben
  • Auswertungslogik greift auf zwei komplett unabhängige Bewertungs-Strukturen zu, die nicht automatisch synchron sind:
    • Assessment (Tabelle assessments) — pro Meeting erfasste Kriterium-Bewertungen, Basis für /evaluation
    • FeedbackCategoryRating — ausschließlich manuell auf AssignmentFeedbackPage gesetzt, unabhängig von Meeting-Daten, Basis für das finale Abschluss-Feedback
    • Seit 2026-07-02: src/utils/ratingTrend.ts berechnet aus den Assessment-Daten einen zeitgewichteten Vorschlag pro Kategorie (spätere Meetings zählen stärker, Gewichtungsfaktor konfigurierbar unter Config → Gewichtung) inkl. Trend-Erkennung (Verbesserung/Verschlechterung). Der Vorschlag wird auf AssignmentFeedbackPage als Badge angezeigt und bleibt frei überschreibbar — die zwei Strukturen bleiben bewusst getrennt.
  • Meeting-Filter bei Aggregationen über mehrere Meetings: immer !m.deletedAt && m.status === 'done' — sonst fließen Papierkorb-/unfertige Meetings mit ein
  • Weichmacher/Füllwörter in Formulierungen (z.B. "ein bisschen") werden bewusst nicht über eine eigene Text-Erkennungslogik behandelt — stattdessen legt man dafür ein eigenes, niedrig gewichtetes Kriterium an (z.B. "Sprachliche Präzision", Gewicht < 1) und taggt/notiert entsprechende Zeilen ganz normal (meist mit (!)). Die bestehende Tagging- + Notation-Vorschlag-Mechanik deckt das automatisch ab.
  • Der äh-Zähler (ConversationEntry.fillerCount) bleibt bewusst rein informativ — keine automatische Schwellenwert-Bewertung. Würde eine eigene Design-Entscheidung brauchen (ab wann gilt die Zahl als "zu hoch"), aktuell reicht die reine Anzeige als Grundlage für die manuelle Bewertung.
  • ConversationEntry entspricht weiterhin einem ganzen Sprecher-Turn (ein zusammenhängender, mehrzeiliger Text-Block bis "✓ Schließen") — bewusst so belassen, ein Zwischenstand mit "ein Entry pro Zeile" wurde am 2026-07-02 wieder verworfen, weil er die Lesbarkeit des Protokolls zerstört hat. Kriterium-Zuordnung passiert stattdessen pro Zeile innerhalb eines Entry über ConversationEntry.lineTags (Matching per exaktem Zeilentext, nicht per Index — robust gegenüber nachträglichem Einfügen/Löschen von Zeilen). Zeilen splitten immer über splitNoteLines() in src/utils/notationParser.tsx, nicht erneut note.split('\n') inline schreiben (sonst laufen Trim-/Filter-Verhalten auseinander).

Stil-Regeln

  • Keine Kommentare außer für nicht-offensichtliche Invarianten (kein „Was", nur „Warum")
  • Keine Auto-Resize-Textarea via rows={1} + JS — stattdessen View/Edit-Toggle oder fieldSizing: 'content' als Inline-Style
  • RATING_OPTIONS-Array nicht neu definieren — immer aus dem bestehenden Array in der jeweiligen Datei verwenden
  • Kein useEffect für DB-Calls außer bei initialem Load — lieber direkte async-Funktionen

Offene Punkte (priorisiert)

Prio Aufgabe Datei
H KI-Generierung derzeit funktionsunfähig: generateWithAi() schlägt beim Ausführen fehl, vermutlich blockiert das Capgemini-Netzwerk/Firewall den Call zu openrouter.ai. Root Cause noch nicht untersucht — ggf. mit internem IT-Support klären. src/pages/AssignmentFeedbackPage.tsx
H Größeres Vorhaben (noch nicht gescoped): Mehrstufiges, pro Dimension konfigurierbares Prompt-System mit Spezialwissen je Dimension + Platzhaltersystem — Ablösung des aktuellen Einzel-Prompts in buildAiPrompt. Braucht eigene Design-Session. src/pages/AssignmentFeedbackPage.tsx
M db/queries/-Layer einführen (Vorbereitung SQLite-Migration, DB-Calls aus Komponenten extrahieren) src/db/queries/ (neu)
M Selbstlernendes Tagging (Idee, noch nicht gescoped): Zeile-zu-Kriterium-Zuordnungen sammeln und daraus beim Tippen automatische Tag-Vorschläge ableiten (ähnliche Formulierungen → gleiches Kriterium). Braucht eigene Design-Session (Ähnlichkeits-/Lernmechanik, Datenbasis, UI). src/pages/meeting/CriterionTagPicker.tsx
L SQLite WASM + OPFS Migration (kein festes Datum) src/db/schema.ts

Erledigt (2026-07-02): Konfiguration zentralisiert (config/constants.ts), db/index.ts in types/schema/seeds aufgeteilt, MeetingView.tsx in 3 Dateien aufgeteilt (pages/meeting/), Evaluation.tsx auf feedbackCriterionItems migriert + nach Assignment gruppiert + Papierkorb/Status-Filter + N/A-Bug gefixt, zustand entfernt (war ungenutzt). Zeitgewichteter Bewertungs-Vorschlag mit Trend-Erkennung umgesetzt (src/utils/ratingTrend.ts, neue Tabelle appSettings, neuer Config-Tab „Gewichtung“, Vorschlags-Badge + Übernehmen/Trend-in-Text auf AssignmentFeedbackPage, buildAiPrompt um Trend-Abschnitt erweitert und Legacy-db.criteria-Bug dabei gefixt). Kriterium-Zuordnung für Freitext-Notizen umgesetzt: ConversationEntry.lineTags (Zeilentext → Kriterium, nach einem verworfenen Zwischenstand mit "ein Entry pro Zeile" — siehe Sonstige Fallstricke), CriterionTagPicker.tsx (Chip+Popover mit Suche, unverändert seit erster Version) wird pro Zeile direkt an der Zeile via NotationTexts neuer optionaler renderLineAddon-Prop gerendert; Zuordnung passiert bewusst nur nachträglich (nicht live während des Tippens), um den Schreibfluss nicht zu stören. buildAiPrompt führt Assessment-Notizen und getaggte Zeilen jetzt pro Kriterium zusammen, unzugeordnete Zeilen bleiben im allgemeinen Protokoll-Block. (2026-07-03) Vier Folge-Fixes: meetingExport.ts löste Kriterien über die Legacy-Tabelle db.criteria auf (identischer Bug wie der frühere buildAiPrompt-Fix) — dadurch war die Gesamtbewertung im Export faktisch leer; jetzt auf feedbackCriterionItems umgestellt, Markdown-Export zeigt zusätzlich das Kriterium pro getaggter Protokollzeile. dbBackup.tss TABLE_NAMES enthielt appSettings nicht — Voll-Backups verloren die Gewichtungs-Config stillschweigend; jetzt ergänzt samt Kommentar, dass neue Tabellen dort manuell nachgezogen werden müssen. Notation → Bewertungs-Vorschlag: detectNotationRating() in notationParser.tsx (!→Not, (!)→Partially, (+)→Nearly, +→Fully) kombiniert mit lineTags liefert in AssessmentTab.tsx einen "Vorschlag aus Protokoll"-Badge pro Kriterium-Item, unabhängig vom bestehenden "Aus Protokoll"-Button (andere Datenquelle). AssignmentCreate.tsx: Assignment-Nummer ist jetzt Pflichtfeld (nur auf Formular-Ebene, Typ bleibt optional wegen Altdaten). Kriterium-Gewichtung auf 0,02,0 in 0,1-Schritten verfeinert (FeedbackStructureConfig.tsx, vorher ×1×5), dabei einen latenten Division-durch-Null-Bug gefixt, der durch das neu erlaubte Gewicht 0 real wurde (catMode() in AssessmentTab.tsx, buildGroupMeetingScores() in ratingTrend.ts — siehe zweite Bug-Klasse oben). Sprecher eines bereits erfassten Protokoll-Beitrags kann nachträglich geändert werden (ConversationTab.tsx, im Bearbeiten-Modus neben Füllwörtern — ändert sofort ConversationEntry.instituteeId, kein separater Speichern-Schritt nötig). N/A-Aggregationsbug in buildAiPrompt (AssignmentFeedbackPage.tsx) gefixt — war die letzte offene Stelle dieser Bug-Klasse außer der jetzt migrierten FeedbackDraftPage.tsx. FeedbackDraftPage.tsx vollständig auf feedbackCriterionItems/feedbackCategories migriert (letzter Konsument der Legacy-Tabellen ist jetzt nur noch der Seed-Code), Notizen jetzt korrekt pro Kriterium über lineTags statt aller Protokoll-Notizen ungefiltert an jedes Kriterium gehängt, Score-Anzeige auf RATING_OPTIONS-Labels umgestellt (statt /5-Rohwert). Toter Code (verwaiste AssignmentType/PhaseTemplate-CRUD-Reste) aus FeedbackStructureConfig.tsx entfernt. „Dashboard zeigt keine Assignment-Nummer" war bereits gelöst, nur die Doku war veraltet — richtiggestellt.


Datenschutz

  • Alle Daten lokal (IndexedDB) — kein Public Cloud Upload
  • GDPR-relevant: Institutee-Namen, Kundennamen, Gesprächsnotizen
  • KI-API-Key in localStorage — nie loggen oder ausgeben
  • Backup-JSON enthält alle personenbezogenen Daten → vertraulich behandeln