14 KiB
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-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,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"— keintailwind.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:allstartet 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,0–2,0in0,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, TabelleappSettings)
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
- KI-Generierung funktionsunfähig —
generateWithAi()schlägt fehl, vermutlich Capgemini-Netzwerk/Firewall blockiertopenrouter.ai. Root Cause nicht untersucht.
Große Aufgaben (eigene Design-Session nötig)
- SQLite WASM + OPFS — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus.
- Mehrstufiges, pro Dimension konfigurierbares Prompt-System — löst den aktuellen Einzel-Prompt in
buildAiPromptab, braucht Spezialwissen je Dimension + Platzhaltersystem. - 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 oderfieldSizing: '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:
seedIfEmpty()— 3 AssignmentTypen + Legacy-KriterienseedFeedbackStructureIfEmpty()— 5 Dimensionen, 14 Kategorien, ~70 ItemsseedCriterionMappingsIfEmpty()— Legacy-MappingsmigrateToUnifiedCriteriaIfNeeded()— 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
MeetingInstance.generalNotes: Kann in alten Datensätzenundefinedsein → immer?? ''fallbackAssessment.criteriaId: Zeigt aufFeedbackCriterionItem.id, NICHT aufFeedbackCategory.id— das verwirrt, weil der FeldnamecriteriaIdistPhaseKey: Isttype PhaseKey = string(kein Union-Typ mehr)aType2inAssignmentDetail.tsx: Doppelte Variable aus Refactoring —aTypewird zweimal deklariert, zweite heißtaType2. Technische Schuld.expandedCatinMeetingView:Record<instituteeId, categoryId | null>— öffnet pro Institutee genau eine Kategorie gleichzeitig- Backup-Import (
server/db/queries/backup.ts, seit 2026-07-04 SQL-basiert): leert alle Tabellen und fügt Zeilen mit expliziter, ursprünglicheridwieder ein (insertRowWithId), damit Fremdschlüssel-Referenzen im importierten Datensatz stabil bleiben — kein Dexie/bulkAddmehr, siehe CLAUDE.md-Datenbank-Abschnitt - Gewichtete Mittelwerte: immer
Σ(score×weight)/Σ(weight), niescore×weightroh interpretieren; beiΣweight === 0(seit Gewicht0erlaubt ist)null/continuestatt Division — sonstNaN < xüberallfalse→ fällt auf „Fully" durch. SiehecatMode()(AssessmentTab.tsx) undbuildGroupMeetingScores()(ratingTrend.ts). ConversationEntryentspricht einem ganzen Sprecher-Turn (mehrzeiliger Text-Block), nicht einer einzelnen Zeile — ein Zwischenstand mit "ein Entry pro Zeile" wurde verworfen. Kriterium-Zuordnung läuft überlineTags(Matching per Zeilentext, nicht Index).