# KI Skill-Retrieval-Profile (`ai_skill_retrieval_profiles`) **Version:** 0.1 **Datum:** 2026-05-29 **Status:** Umsetzung gestartet (Migration **068**) **Ziel:** Für `POST /api/exercises/ai/suggest` (Skill-Katalogauszug) **Gewichte und Quoten** steuerbar machen: - gebunden an **Übungs-Fokusbereich** (`focus_areas.id`), - ein **Standardprofil** ohne Fokus, - **optional zusammengeführte** Profile bei mehreren Fokusbereichen, - **optional Keyword-Übersteuerungen** aus Ziel/Durchführung (z. B. Rollenspiel vs. Befreiung). **Technische Basis:** Skills mit `skills.main_category_id` → `skill_main_categories.slug` (`karate` | `allgemeine`) und `skills.category_id` → `skill_categories.slug` (`kondition`, `selbstverteidigung`, …). **Bezüge:** `.claude/docs/working/AI_EXERCISE_IMPLEMENTATION_PLAN.md` · `backend/exercise_ai.py` --- ## 1. Datenmodell ### Tabelle `ai_skill_retrieval_profiles` | Spalte | Typ | Beschreibung | |--------|-----|--------------| | `id` | serial | Primärschlüssel | | `focus_area_id` | int NULL FK → `focus_areas(id)` ON DELETE SET NULL | **`NULL`** nur für Standardeintrag möglich (siehe `is_default`) | | `is_default` | boolean | Genau **eine** Zeile mit `true` | | `name` | varchar | Kurzer Name (Admin später) | | `description` | text | Hinweise für Pflege | | `active` | boolean | Nur aktive werden geladen | | `config` | jsonb | Siehe §2 | **Constraints / Indizes** - Eindeutig: `(focus_area_id)` WHERE `focus_area_id IS NOT NULL` - Eindeutig: `(is_default)` WHERE `is_default = true` --- ## 2. JSON-Konfiguration `config.version = 1` Alle Schlüssel **optional**; fehlende Werte fallen auf **einprogrammierten Fallback** in `exercise_ai.py` zurück (entspricht bisher grob „neutral“). ### 2.1 Gewichtungen (Ranking) | Schlüssel | Typ | Bedeutung | |-----------|-----|------------| | `main_slug_weights` | `object[str, float]` | Multiplikator pro Hauptkategorie-Slug (`karate`, `allgemeine`) | | `category_slug_weights` | `object[str, float]` | Multiplikator pro `skill_categories.slug` | Basis-Score (vereinfacht): `(importance oder 3) × main_w × cat_w × text_overlap_bonus × importance_multiplier` ### 2.2 Kapazitätsbegrenzung (Liste) `_MAX_SKILLS_CATALOG_LINES` (aktuell **240**) Zeilen Gesamt: | Schlüssel | Typ | Bedeutung | |-----------|-----|------------| | `category_max_share` | `object[str, float]` | Max. Anteil dieser **Unterkategorie** am Endergebnis (0–1), z. B. `{ "kondition": 0.25 }` | | `main_min_share` | `object[str, float]` | Mindest-Zielanteil Hauptkategorie beim **Auswahl-Greedy** (weich; Rest nach Score aufgefüllt) | ### 2.3 Text / Token-Sparen | Schlüssel | Typ | Standard | Bedeutung | |-----------|-----|----------|------------| | `description_plain_max_len` | int | 160 | Gekürzte Beschreibung pro Zeile | | `karate_relevance_max_len` | int | **0** oder 80 | **`0`** = Feld `karate_relevance`/`relevance_level` in der Promptzeile **weglassen** | ### 2.4 Keyword-Overrides (optional) Liste `keyword_overrides`: jedes Element: ```json { "keywords_any": ["befreiung", "haltegriff"], "case_insensitive": true, "patch": { "category_slug_weights": { "selbstverteidigung": 2.5 }, "category_max_share": { "koordination": 0.1 } } } ``` Textsuche in verkettetem Korpus **Titel, Ziel, Durchführung, Focus-Hint** (bereits plaintext). Reihenfolge: erst Basis-Profile zusammenmergen, dann **alle treffenden Overrides**‑`patch`‑Objekte **flach zusammenführen** (Gewichte multiplikativ übereinander, Caps den strengsten Wert nehmen – aktuelle Implementierung im Code dokumentiert). --- ## 3. Mehrere Fokusbereiche auf der Übung Request-Body: `focus_areas_context: [{ "focus_area_id": n, "is_primary": bool }, …]` **Aktuelle Merge-Strategie (v1):** Profile laden → **gleichgewichtete Mittelwert-Bildung** der numerischen Gewichte / Caps (implementiert für `main_slug_weights`, `category_slug_weights`, `category_max_share`, `main_min_share`, `*_max_len`). Anschließend **Keyword-Overrides** anwenden. **Primär-Fokus:** Im Frontend soll die **primäre** Zeile aus `focus_areas_multi` **zuerst** in der Liste stehen; die Merge-Strategie kann später zu „Primär dominate“ erweitert werden. Ohne Kontext oder ohne Treffer auf aktive Profile: **nur Standardprofil** (`is_default`). --- ## 4. Seed-Daten (Migration) - **`is_default=true`:** ausgewogene Standard-Gewichte, moderate Caps auf `kondition`/`koordination`, Karate-Relevanz gekürzt. - **`Gewaltschutz`:** `focus_area_id` per `(SELECT id FROM focus_areas WHERE name = 'Gewaltschutz' LIMIT 1)` — höhere Gewichte für `kognition`, `psychische_faehigkeiten`, `soziale_faehigkeiten`, `selbstverteidigung`; gedrosseltes `kondition`/`koordination`; `karate_relevance_max_len`: 0; Keyword-Patches wie oben können nachgeschärft werden. Weitere Profile (Karate-Schwerpunkt etc.) später per Admin-SQL oder UI. --- ## 5. API `ExerciseAiSuggestBody` erweitert um **`focus_areas_context`** (Liste). Feld **`focus_area_hint`** bleibt für den **Prompt-Kontext** (bestehende Prompts). `POST …/ai/regenerate` nutzt später dieselbe Retrieval-Logik aus den Detail-Daten der Übung (**To-do:** dort `focus_areas_context` aus `exercise_focus_areas` ableiten). --- ## 6. Changelog - **2026-05-29:** Erstellt; gekoppelt an Migration **068** und erste `exercise_ai`-Integration.