190 lines
12 KiB
Markdown
190 lines
12 KiB
Markdown
# 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-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)
|
||
- **Tailwind CSS v4** (`@import "tailwindcss"` — kein `tailwind.config.js`!)
|
||
- **Dexie.js v5** als IndexedDB-Wrapper (lokale DB im Browser, kein Backend)
|
||
- **React Router v6**
|
||
- **Capgemini-Brandfarbe:** `#0070AD`
|
||
- Arbeitsverzeichnis: `C:\dev\AssigmentMonitor`
|
||
|
||
## 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)
|
||
|
||
### Sofort-Fixes (einfach)
|
||
1. **Dashboard zeigt keine Assignment-Nummer** — `Dashboard.tsx` Zeile ~57: `{a.assignmentNumber && <span>...{a.assignmentNumber}</span>}` hinzufügen
|
||
2. **Toter Code in FeedbackStructureConfig** — State-Variablen `assignmentTypes`, `phaseTemplates`, `showCatalogs`, `showTypeConfig`, `expandedType`, `newTypeName`, `newPhase`, `typeError` + alle Handler `toggleCatalogCategory`, `addAssignmentType`, `updateAssignmentTypeName`, `deleteAssignmentType`, `addPhaseTemplate`, `updatePhaseTemplate`, `deletePhaseTemplate` entfernen
|
||
3. **N/A-Aggregationsbug**: Filter der Form `a.score !== null` schließen `'na'` nicht aus (N/A ist ein String, kein `null`!) → verfälscht Mittelwerte. Bereits gefixt in `Evaluation.tsx`. Noch offen in `AssignmentFeedbackPage.tsx:63` (buildAiPrompt) und `FeedbackDraftPage.tsx:66`.
|
||
|
||
### Mittlere Aufgaben
|
||
4. **FeedbackDraftPage.tsx** — nutzt noch Legacy `categories`/`criteria`, sollte auf `feedbackCriterionItems` migriert werden (analog zu `Evaluation.tsx`); letzter Konsument der Legacy-Tabellen außer den Seeds
|
||
5. **`db/queries/`-Layer** — DB-Zugriffe aus Komponenten in domänenspezifische Query-Funktionen extrahieren, bereitet SQLite-Migration vor
|
||
6. **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)
|
||
7. **SQLite WASM + OPFS** — Migration weg von IndexedDB. Konzept steht, Implementierung steht aus.
|
||
8. **Mehrstufiges, pro Dimension konfigurierbares Prompt-System** — löst den aktuellen Einzel-Prompt in `buildAiPrompt` ab, braucht Spezialwissen je Dimension + Platzhaltersystem.
|
||
9. **Selbstlernendes Tagging** — aus bisherigen Zeile-zu-Kriterium-Zuordnungen automatische Tag-Vorschläge beim Tippen ableiten.
|
||
|
||
**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 `<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, kein Cloud-Backend
|
||
- 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 --noEmit # Muss ohne Fehler durchlaufen
|
||
npm run dev # Dev-Server starten
|
||
```
|
||
|
||
Vor jeder Abgabe: `npx tsc --noEmit` 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. **Dexie v5**: `bulkAdd` schlägt fehl wenn IDs schon vorhanden → Backup-Import leert erst alle Tabellen
|
||
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).
|