2026-07-02 10:10:06 +02:00
# 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`
---
2026-07-03 11:56:22 +02:00
## Projektstruktur (aktualisiert nach Architektur-Refactoring, 2026-07-02)
2026-07-02 10:10:06 +02:00
```
src/
2026-07-03 11:56:22 +02:00
config/
constants.ts ← RATING_OPTIONS, INST_COLORS, AI_CONFIG, RATING_NUM_MAP, BRAND_COLOR, DEFAULT_RATING_TREND_WEIGHT_STEP, RATING_TREND_DELTA_THRESHOLD
2026-07-02 10:10:06 +02:00
db/
2026-07-03 11:56:22 +02:00
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
2026-07-02 10:10:06 +02:00
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
2026-07-03 11:56:22 +02:00
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
2026-07-03 13:54:19 +02:00
FeedbackDraftPage.tsx ← Einfacher Feedback-Entwurf (Legacy-UI, Datenzugriff auf feedbackCriterionItems migriert)
2026-07-03 11:56:22 +02:00
Evaluation.tsx ← Auswertung — zeitgewichteter Bewertungs-Vorschlag + Trend, gruppiert nach Assignment
2026-07-02 10:10:06 +02:00
Configuration.tsx ← Tab-Container für alle Konfigurationsseiten
FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung
AssignmentTypeConfig.tsx ← AssignmentTypen + Phasen + Kriterien-Auswahl (Baum)
2026-07-03 11:56:22 +02:00
RatingWeightConfig.tsx ← Config-Tab "Gewichtung": Zeitgewichtungs-Faktor für Bewertungs-Vorschlag
2026-07-02 10:10:06 +02:00
Consultants.tsx ← Berater/Gruppen-Verwaltung
utils/
meetingExport.ts ← Markdown + JSON Export-Logik
dbBackup.ts ← Vollständiges DB-Backup (JSON Export/Import)
2026-07-03 11:56:22 +02:00
notationParser.tsx ← +/!/>/[…] Notation → farbiges JSX, splitNoteLines(), detectNotationRating()
ratingTrend.ts ← Zeitgewichteter Bewertungs-Vorschlag + Trend-Erkennung
2026-07-02 10:10:06 +02:00
App.tsx ← Route-Definitionen, Navigation
main.tsx ← React-Einstiegspunkt, DB-Seed-Aufruf
```
---
## Datenmodell (IndexedDB via Dexie v5)
2026-07-03 11:56:22 +02:00
### DB-Version: 6
2026-07-02 10:10:06 +02:00
```typescript
2026-07-03 13:54:19 +02:00
// Konfiguration (Legacy, kein aktiver Seiten-Konsument mehr)
2026-07-02 10:10:06 +02:00
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'
2026-07-03 11:56:22 +02:00
// App-Einstellungen (Singleton, id immer 1), seit Version 6
appSettings: '++id'
2026-07-02 10:10:06 +02:00
```
2026-07-03 11:56:22 +02:00
`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.
2026-07-02 10:10:06 +02:00
### 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
2026-07-03 11:56:22 +02:00
weight?: number // 0,0– 2,0 in 0,1-Schritten, default 1 (seit 2026-07-03; vorher grobe × 1– × 5-Stufen)
2026-07-02 10:10:06 +02:00
order: number
}
2026-07-03 11:56:22 +02:00
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)
2026-07-02 10:10:06 +02:00
```
---
## 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
2026-07-03 11:56:22 +02:00
/config Konfiguration (4 Tabs: Feedback | Ass.-Typen | Gewichtung | Backup)
2026-07-02 10:10:06 +02:00
```
---
## Konfigurationsbereich
### Tab "Feedback" → `FeedbackStructureConfig`
CRUD für:
- Feedback-Dimensionen (mit Reihenfolge ↑/↓)
- Feedback-Kategorien je Dimension
2026-07-03 11:56:22 +02:00
- Feedback-Kriterium-Items je Kategorie (mit Gewichtung `0,0– 2,0` in `0,1` -Schritten)
2026-07-02 10:10:06 +02:00
### 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
2026-07-03 11:56:22 +02:00
### Tab "Gewichtung" → `RatingWeightConfig`
- Ein Regler für `AppSettings.ratingTrendWeightStep` (Zeitgewichtungs-Faktor des Bewertungs-Vorschlags), mit Live-Vorschau der resultierenden Gewichte
2026-07-02 10:10:06 +02:00
### 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)
2026-07-03 11:56:22 +02:00
if (wSum === 0) return null // alle beteiligten Kriterien auf Gewicht 0 → sonst NaN < x überall false , fällt auf " Fully " durch
2026-07-02 10:10:06 +02:00
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'
}
```
2026-07-03 11:56:22 +02:00
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.
2026-07-02 10:10:06 +02:00
---
## 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` )
2026-07-03 11:56:22 +02:00
- **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
2026-07-02 10:10:06 +02:00
---
## 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)
2026-07-03 13:54:19 +02:00
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)
2026-07-02 10:10:06 +02:00
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 |
|---|---|---|
2026-07-03 11:56:22 +02:00
| `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) |
2026-07-02 10:10:06 +02:00
| IndexedDB | Keine robuste Persistenz-Garantie (Browser kann löschen) | Hoch → SQLite WASM geplant |
2026-07-03 11:56:22 +02:00
| `db/queries/` -Layer | Fehlt — DB-Zugriffe direkt aus Komponenten, erschwert SQLite-Migration | Mittel |