CG-Feedback-Monitor/docs/TECHNISCHE_ARCHITEKTUR.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

305 lines
13 KiB
Markdown
Raw Permalink 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.

# Technische Architektur — Assignment Monitor
## Stack
| Schicht | Technologie | Version | Zweck |
|---|---|---|---|
| UI-Framework | React | 18 | Component-basiertes UI |
| Sprache | TypeScript | 5 | Typsicherheit |
| Build | Vite | 6 | Dev-Server, HMR, PWA-Plugin |
| CSS | Tailwind CSS | v4 | Utility-First, `@import "tailwindcss"` |
| Datenbank | Dexie.js | 5 | IndexedDB-Wrapper |
| Routing | React Router | v6 | Client-Side-Routing (SPA) |
| PWA | vite-plugin-pwa | — | Offline-Fähigkeit, installierbar |
| KI | OpenRouter API | — | Feedback-Generierung (extern, opt-in) |
**Capgemini Brand Color:** `#0070AD`
---
## Projektstruktur (aktualisiert nach Architektur-Refactoring, 2026-07-02)
```
src/
config/
constants.ts ← RATING_OPTIONS, INST_COLORS, AI_CONFIG, RATING_NUM_MAP, BRAND_COLOR, DEFAULT_RATING_TREND_WEIGHT_STEP, RATING_TREND_DELTA_THRESHOLD
db/
index.ts ← reiner Re-Export von types/schema/seeds (bestehende Imports bleiben kompatibel)
types.ts ← alle Interfaces, FeedbackRating-Typ
schema.ts ← Dexie DB-Klasse + alle Migrationsversionen
seeds/
assignmentTypes.ts ← seedIfEmpty() — 3 AssignmentTypen + Legacy-Kriterien
feedbackStructure.ts ← seedFeedbackStructureIfEmpty() — Dimensionen/Kategorien/Items
migrations.ts ← seedCriterionMappingsIfEmpty(), migrateToUnifiedCriteriaIfNeeded()
index.ts ← Re-Export der Seed-Funktionen
pages/
Dashboard.tsx ← Assignment-Liste (Home)
AssignmentCreate.tsx ← Neues Assignment anlegen
AssignmentDetail.tsx ← Phasen-Flow, Meeting-Übersicht, Bewertungs-Chips
AssignmentEdit.tsx ← Assignment nachträglich bearbeiten
meeting/
MeetingView.tsx ← Route-Komponente: State, DB-Handler, Header, Tabs
ConversationTab.tsx ← Gesprächsprotokoll-UI
AssessmentTab.tsx ← Gesamtbewertungs-UI (Accordion je Kategorie), Notation-Vorschlag
CriterionTagPicker.tsx ← Chip+Popover mit Suche, Kriterium-Zuordnung pro Protokoll-Zeile
AssignmentFeedbackPage.tsx ← Strukturiertes Abschluss-Feedback + KI-Generierung + Trend-Vorschlag
FeedbackDraftPage.tsx ← Einfacher Feedback-Entwurf (Legacy-UI, Datenzugriff auf feedbackCriterionItems migriert)
Evaluation.tsx ← Auswertung — zeitgewichteter Bewertungs-Vorschlag + Trend, gruppiert nach Assignment
Configuration.tsx ← Tab-Container für alle Konfigurationsseiten
FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung
AssignmentTypeConfig.tsx ← AssignmentTypen + Phasen + Kriterien-Auswahl (Baum)
RatingWeightConfig.tsx ← Config-Tab "Gewichtung": Zeitgewichtungs-Faktor für Bewertungs-Vorschlag
Consultants.tsx ← Berater/Gruppen-Verwaltung
utils/
meetingExport.ts ← Markdown + JSON Export-Logik
dbBackup.ts ← Vollständiges DB-Backup (JSON Export/Import)
notationParser.tsx ← +/!/>/[…] Notation → farbiges JSX, splitNoteLines(), detectNotationRating()
ratingTrend.ts ← Zeitgewichteter Bewertungs-Vorschlag + Trend-Erkennung
App.tsx ← Route-Definitionen, Navigation
main.tsx ← React-Einstiegspunkt, DB-Seed-Aufruf
```
---
## Datenmodell (IndexedDB via Dexie v5)
### DB-Version: 6
```typescript
// Konfiguration (Legacy, kein aktiver Seiten-Konsument mehr)
categories: '++id, order'
criteria: '++id, categoryId, order'
// Assignment-Typen
assignmentTypes: '++id'
phaseTemplates: '++id, assignmentTypeId, order'
// Stammdaten
groups: '++id'
consultants: '++id, groupId'
// Assignments
assignments: '++id, status, createdAt'
meetingInstances: '++id, assignmentId, phaseTemplateId'
// Gesprächsprotokoll
conversationEntries: '++id, meetingInstanceId, instituteeId, sequenceIndex'
conversationSkillScores: '++id, conversationEntryId, criteriaId'
// Meeting-Assessment
assessments: '++id, meetingInstanceId, instituteeId, criteriaId'
// Feedback-Entwürfe (Legacy)
feedbackDrafts: '++id, assignmentId, instituteeId'
// Strukturiertes Feedback
feedbackDimensions: '++id, order'
feedbackCategories: '++id, dimensionId, order'
feedbackCriterionItems: '++id, categoryId, order'
assignmentFeedbacks: '++id, assignmentId, instituteeId'
feedbackCategoryRatings: '++id, assignmentFeedbackId, feedbackCategoryId'
feedbackDimensionTexts: '++id, assignmentFeedbackId, feedbackDimensionId'
// Mappings (Legacy, kaum genutzt)
criterionCategoryMappings: '++id, criterionId, feedbackCategoryId'
criterionLevelDescriptions: '++id, criterionItemId, rating'
// App-Einstellungen (Singleton, id immer 1), seit Version 6
appSettings: '++id'
```
`ConversationEntry.lineTags` (Kriterium-Zuordnung pro Zeile) ist ein reines Objektfeld ohne eigenen Index — kein Versions-Bump nötig, da Dexies `.stores()` nur Indizes definiert, nicht das volle Objekt-Schema.
### Wichtige Interface-Details
```typescript
type FeedbackRating = 'na' | 'not_client_ready' | 'partially_client_ready' | 'nearly_client_ready' | 'client_ready'
interface AssignmentType {
id?: number
name: string
defaultPhaseKeys: string[]
criteriaIds?: number[] // FeedbackCriterionItem IDs; leer = alle anzeigen
}
interface Assignment {
id?: number
title: string
client: string
assignmentTypeId: number
instituteeIds: number[]
leadName: string
status: 'active' | 'done'
createdAt: string
assignmentNumber?: string // z.B. "CGI-2024-001"
description?: string
instituteeEnrollments?: Array<{
instituteeId: number
from?: string // ISO date
to?: string // ISO date
}>
}
interface MeetingInstance {
id?: number
assignmentId: number
phaseTemplateId: number
phaseIndex: number // 1 = erste Instanz, 2+ = Wiederholungen
date: string
status: 'planned' | 'active' | 'done'
generalNotes: string
deletedAt?: string // Soft-Delete
}
interface FeedbackCriterionItem {
id?: number
categoryId: number
name: string
weight?: number // 0,02,0 in 0,1-Schritten, default 1 (seit 2026-07-03; vorher grobe ×1×5-Stufen)
order: number
}
interface ConversationLineTag {
text: string // exakter Zeilentext, 1:1 wie in ConversationEntry.note
criterionItemId: number // → FeedbackCriterionItem.id
}
interface ConversationEntry {
id?: number
meetingInstanceId: number
instituteeId: number
sequenceIndex: number
note: string // ganzer Sprecher-Turn, mehrzeilig
fillerCount: number
updatedAt: string
lineTags?: ConversationLineTag[] // Kriterium-Zuordnung pro Zeile, gematcht per Zeilentext
}
interface AppSettings {
id?: number // Singleton, immer 1
ratingTrendWeightStep: number // Zeitgewichtungs-Faktor für den Bewertungs-Vorschlag
}
// Assessment.criteriaId → FeedbackCriterionItem.id (Gesamtbewertung)
// ConversationSkillScore.criteriaId → FeedbackCategory.id (Schnellbewertung)
// ConversationLineTag.criterionItemId → FeedbackCriterionItem.id (Zeilen-Zuordnung, bewusst anders benannt)
```
---
## Routing
```
/ Dashboard (Assignment-Liste)
/assignment/new Neues Assignment anlegen
/assignment/:id AssignmentDetail (Phasen-Flow)
/assignment/:id/edit Assignment bearbeiten
/assignment/:id/meeting/:meetingId MeetingView (Protokoll + Bewertung)
/assignment/:id/feedback/:instituteeId AssignmentFeedbackPage (strukturiertes Feedback)
/assignment/:id/feedback-draft/:instituteeId FeedbackDraftPage (Legacy)
/evaluation Auswertungsübersicht
/consultants Berater-Verwaltung
/config Konfiguration (4 Tabs: Feedback | Ass.-Typen | Gewichtung | Backup)
```
---
## Konfigurationsbereich
### Tab "Feedback" → `FeedbackStructureConfig`
CRUD für:
- Feedback-Dimensionen (mit Reihenfolge ↑/↓)
- Feedback-Kategorien je Dimension
- Feedback-Kriterium-Items je Kategorie (mit Gewichtung `0,02,0` in `0,1`-Schritten)
### Tab "Ass.-Typen" → `AssignmentTypeConfig`
CRUD für:
- Assignment-Typen (Name, Löschschutz wenn Assignments vorhanden)
- Phasen-Templates je Typ (Label, Lead/Kunde, Wiederholbar, Protokoll, Bewertung)
- Kriterien-Auswahl je Typ: Baumstruktur Dimension → Kategorie → Item (mit Checkbox, Indeterminate-State)
- `criteriaIds = []` → alle Kriterien werden angezeigt
### Tab "Gewichtung" → `RatingWeightConfig`
- Ein Regler für `AppSettings.ratingTrendWeightStep` (Zeitgewichtungs-Faktor des Bewertungs-Vorschlags), mit Live-Vorschau der resultierenden Gewichte
### Tab "Backup" → in `Configuration.tsx`
- JSON-Export aller Tabellen
- Zweistufiger JSON-Import mit Überschreib-Warnung
---
## Bewertungs-Aggregation (Implementierungsdetail)
```typescript
const NUM_MAP: Record<FeedbackRating, number> = {
na: 0, not_client_ready: 1, partially_client_ready: 2, nearly_client_ready: 3, client_ready: 4
}
function catMode(cat: FeedbackCategory, fbItems: FeedbackCriterionItem[], getScore: (itemId: number) => FeedbackRating | null): FeedbackRating | null {
const scored = fbItems
.filter(i => i.categoryId === cat.id)
.map(i => ({ score: getScore(i.id!), w: i.weight ?? 1 }))
.filter((x): x is { score: FeedbackRating; w: number } => x.score !== null && x.score !== 'na')
if (scored.length === 0) return null
const wSum = scored.reduce((s, x) => s + x.w, 0)
if (wSum === 0) return null // alle beteiligten Kriterien auf Gewicht 0 → sonst NaN < x überall false, fällt auf "Fully" durch
const avg = scored.reduce((s, x) => s + NUM_MAP[x.score] * x.w, 0) / wSum
if (avg < 1.5) return 'not_client_ready'
if (avg < 2.5) return 'partially_client_ready'
if (avg < 3.5) return 'nearly_client_ready'
return 'client_ready'
}
```
Dieselbe `Σweight === 0`-Absicherung gilt für jede gewichtete Mittelung in der App (auch `buildGroupMeetingScores()` in `ratingTrend.ts`) — seit Gewicht `0` ein gültiger Wert ist, ist das kein theoretischer Randfall mehr.
---
## Export-Dateiname-Schema
```typescript
const buildFilename = (ext: string) => {
const date = new Date(meeting.date).toLocaleDateString('de-DE').replace(/\./g, '-')
const label = template.label // z.B. "Alignment Call"
const idx = phaseIndex > 1 ? `-${phaseIndex}` : ''
const id = assignment.assignmentNumber ?? assignment.title
return `${id}_${date}_${label}${idx}.${ext}`
// Beispiel: CGI-2024-001_02-07-2026_Alignment Call-2.md
}
```
---
## KI-Integration (AssignmentFeedbackPage)
- **Provider:** OpenRouter (https://openrouter.ai) — ermöglicht Modellwechsel ohne eigenen API-Zugang
- **Modelle:** GPT-4o mini, GPT-4o, Claude Sonnet 4.5, Claude Haiku 4.5, Gemini Flash 1.5
- **API-Key:** Lokal in `localStorage` gespeichert (`ai_api_key`, `ai_model`)
- **Datenweitergabe:** Kriterien-Scores + Notizen (Assessment und getaggte Protokoll-Zeilen zusammengeführt pro Kriterium), Kategorie-Bewertungen, zeitgewichteter Trend pro Kategorie werden im Prompt strukturiert übermittelt (`buildAiPrompt`, nutzt korrekt `feedbackCriterionItems`, alter `db.criteria`-Bug ist gefixt)
- **Bekanntes Problem:** Der Call schlägt aktuell fehl (vermutlich Capgemini-Netzwerk/Firewall blockiert `openrouter.ai`), Root Cause nicht untersucht
- **Geplanter Umbau (nicht gescoped):** Mehrstufiges, pro Dimension konfigurierbares Prompt-System mit Platzhaltersystem soll den aktuellen Einzel-Prompt ablösen
---
## Seed-Daten (beim ersten Start)
Die App legt beim ersten Start automatisch an:
1. **3 Assignment-Typen** mit je vollständigem Phasen-Flow (Generisch, Case Interview, Stakeholder Meeting / Pitch)
2. **Legacy-Kriterien** in `categories` + `criteria` (kein aktiver Seiten-Konsument mehr — `Evaluation.tsx` und `FeedbackDraftPage.tsx` sind beide auf `feedbackCriterionItems` migriert; die Tabellen bleiben aus historischen Gründen im Backup/Import)
3. **Feedback-Dimensionsstruktur** mit 5 Dimensionen, 14 Kategorien, ~70 Kriterium-Items (Capgemini-Kompetenzmodell)
4. **Criterion-Mappings** (Legacy, nicht mehr aktiv genutzt)
---
## Bekannte technische Schulden
| Bereich | Problem | Priorität |
|---|---|---|
| `meetingExport.ts` | Bewertungs-Labels im Markdown-Export zeigen `FeedbackRating`-Keys (z.B. `client_ready`) statt sprechender Labels | Niedrig |
| KI-Generierung | `generateWithAi()` schlägt fehl (vermutlich Netzwerk/Firewall), Root Cause offen | Hoch |
| Prompt-System | Aktueller Einzel-Prompt soll mehrstufig/pro Dimension konfigurierbar werden — eigene Design-Session nötig | Hoch (nicht gescoped) |
| Selbstlernendes Tagging | Idee, automatische Tag-Vorschläge aus bisherigen Zuordnungen abzuleiten — eigene Design-Session nötig | Mittel (nicht gescoped) |
| IndexedDB | Keine robuste Persistenz-Garantie (Browser kann löschen) | Hoch → SQLite WASM geplant |
| `db/queries/`-Layer | Fehlt — DB-Zugriffe direkt aus Komponenten, erschwert SQLite-Migration | Mittel |