257 lines
9.3 KiB
Markdown
257 lines
9.3 KiB
Markdown
|
|
# 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
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/
|
|||
|
|
db/
|
|||
|
|
index.ts ← Dexie DB-Klasse, alle Interfaces, Seed-Funktionen, Migrations
|
|||
|
|
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
|
|||
|
|
MeetingView.tsx ← Live-Meeting: Protokoll + Gesamtbewertung
|
|||
|
|
AssignmentFeedbackPage.tsx ← Strukturiertes Abschluss-Feedback + KI-Generierung
|
|||
|
|
FeedbackDraftPage.tsx ← Einfacher Feedback-Entwurf (Legacy)
|
|||
|
|
Evaluation.tsx ← Auswertung (teilweise veraltet, nutzt alte Criterion-Tabelle)
|
|||
|
|
Configuration.tsx ← Tab-Container für alle Konfigurationsseiten
|
|||
|
|
FeedbackStructureConfig.tsx ← Dimensionen/Kategorien/Kriterien CRUD + Gewichtung
|
|||
|
|
AssignmentTypeConfig.tsx ← AssignmentTypen + Phasen + Kriterien-Auswahl (Baum)
|
|||
|
|
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
|
|||
|
|
App.tsx ← Route-Definitionen, Navigation
|
|||
|
|
main.tsx ← React-Einstiegspunkt, DB-Seed-Aufruf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Datenmodell (IndexedDB via Dexie v5)
|
|||
|
|
|
|||
|
|
### DB-Version: 5
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// Konfiguration (Legacy, nur noch für Evaluation.tsx)
|
|||
|
|
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'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 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 // Gewichtung ×1–×5, default 1
|
|||
|
|
order: number
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Assessment.criteriaId → FeedbackCriterionItem.id (Gesamtbewertung)
|
|||
|
|
// ConversationSkillScore.criteriaId → FeedbackCategory.id (Schnellbewertung)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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 (3 Tabs: Feedback | Ass.-Typen | Backup)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Konfigurationsbereich
|
|||
|
|
|
|||
|
|
### Tab "Feedback" → `FeedbackStructureConfig`
|
|||
|
|
CRUD für:
|
|||
|
|
- Feedback-Dimensionen (mit Reihenfolge ↑/↓)
|
|||
|
|
- Feedback-Kategorien je Dimension
|
|||
|
|
- Feedback-Kriterium-Items je Kategorie (mit Gewichtung ×1–×5)
|
|||
|
|
|
|||
|
|
### 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 "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)
|
|||
|
|
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'
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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, Protokoll-Notizen, Kategorie-Bewertungen werden im Prompt strukturiert übermittelt
|
|||
|
|
- **Hinweis:** Der AI-Prompt nutzt noch die alte `criteria`-Tabelle (Legacy) — Aktualisierung auf `feedbackCriterionItems` steht aus
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 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` (für Evaluation.tsx)
|
|||
|
|
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 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `Evaluation.tsx` | Nutzt alte `Category`/`Criterion`-Tabellen statt neue `feedbackCriterionItems` | Mittel |
|
|||
|
|
| `meetingExport.ts` | Bewertungs-Labels im Markdown-Export zeigen `FeedbackRating`-Keys statt Labels | Niedrig |
|
|||
|
|
| `FeedbackStructureConfig.tsx` | Enthält toten Code (Funktionen für gelöschte AssignmentType-CRUD) | Niedrig |
|
|||
|
|
| AI-Prompt in `AssignmentFeedbackPage` | Nutzt `db.criteria` (alt), sollte `feedbackCriterionItems` nutzen | Mittel |
|
|||
|
|
| Dashboard | Zeigt keine Assignment-Nummer | Niedrig |
|
|||
|
|
| `AssignmentCreate` | Kein Feld für Assignment-Nummer | Niedrig |
|
|||
|
|
| IndexedDB | Keine robuste Persistenz-Garantie (Browser kann löschen) | Hoch → SQLite WASM geplant |
|