CG-Feedback-Monitor/docs/TECHNISCHE_ARCHITEKTUR.md

13 KiB
Raw Blame History

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, nutzt noch alte Criterion-Tabelle)
    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

// Konfiguration (Legacy, nur noch für FeedbackDraftPage.tsx + Seeds)
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

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)

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

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 (nur noch von FeedbackDraftPage.tsx genutzt, Evaluation.tsx ist längst auf feedbackCriterionItems migriert)
  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
FeedbackDraftPage.tsx Nutzt alte Category/Criterion-Tabellen statt neue feedbackCriterionItems; letzter Konsument der Legacy-Tabellen außer den Seeds Mittel
meetingExport.ts Bewertungs-Labels im Markdown-Export zeigen FeedbackRating-Keys (z.B. client_ready) statt sprechender Labels Niedrig
FeedbackStructureConfig.tsx Enthält toten Code (Funktionen für gelöschte AssignmentType-CRUD) Niedrig
AI-Prompt & FeedbackDraftPage N/A-Aggregationsbug: score !== null schließt 'na' nicht aus, verfälscht Mittelwerte Hoch
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)
Dashboard Zeigt keine Assignment-Nummer Niedrig
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