# 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 gespeicherte `exercise_focus_areas` zur gleichen Retrieval-Logik wie Suggest.

**Pflege der Profile:** Superadmin ohne Mandantenwahl — **`GET|POST /api/admin/ai-skill-retrieval-profiles`**, **`GET|PUT|DELETE /api/admin/ai-skill-retrieval-profiles/{id}`** (`routers/ai_skill_retrieval_admin.py`); Web-UI Superadmin unter **`/admin/ai-skill-retrieval`**.

## 6. Changelog

- **2026-05-29:** Superadmin-Pflege-Endpoints + UI‑Route dokumentiert (`/admin/ai-skill-retrieval`).
- **2026-05-29:** Erstellt; gekoppelt an Migration **068** und erste `exercise_ai`-Integration.
