# Planungs-KI: Übungssuche & Kontext für Neu-Anlage

**Version:** 0.2  
**Datum:** 2026-05-23  
**Status:** P0–P2 ✅ · Phase A/B/B2 ✅ · **Phase C1–C3 ✅** · **Phase E ✅** (Semantik + Pfad-QA)  
**Bezüge:** `AI_PLANNING_KI_MULTISTAGE_FORECAST.md` · `AI_PROMPT_TARGET_ARCHITECTURE.md` · `SKILL_SCORING_SPEC.md` · `TRAINING_FRAMEWORK_SPEC.md` §3 (Progressionsgraph)

---

## 1. Ziel

Trainer in der **Trainingsplanung** sollen Übungen finden oder anlegen können mit natürlichen Anfragen wie:

- „Vertiefung zu Übung XY“
- „Nächste sinnvolle Übung im Progressionsgraph Z“
- „Baut auf der bisherigen Planung auf — Reaktionsschnelligkeit mit Partnern“
- **Preset:** „Schlage mir die nächste Übung vor“

**Suche** (Bibliothek) und **Neu mit KI-Assistent** (Anlage) nutzen dasselbe **`PlanningExerciseContextPack`** — unterschiedliches Ergebnis (Treffer vs. Entwurf).

---

## 2. Architektur (Mehrstufig)

| Stufe | Name | Technik | P0 |
|-------|------|---------|-----|
| **S0** | Kontext-Pack | SQL/API, deterministisch | ✅ |
| **S1a** | Intent strukturieren | LLM `planning_exercise_search_intent` (Szenario-Pipeline) | ✅ P1 |
| **S1b** | Hybrid-Retrieval | Score: Volltext + Graph + Skills + Plan + **Profil** | ✅ |
| **S1b+** | Profil-Vorselektion | `ExerciseMatchProfile` × `PlanningTargetProfile` | ✅ `profile_v1` |
| **S1c** | Rerank + Begründung | Optional LLM `planning_exercise_search_rank` | Regelbasierte `reasons[]` |
| **S2** | Neu-Anlage | Bestehende `suggestExerciseAi` + Pack als Zusatzkontext | Später |

Zwischen jeder Stufe: **nur erlaubte `exercise_id`s** (Governance / Sichtbarkeit).

---

## 3. Intent-Typen

| `intent_hint` | Bedeutung | Retrieval-Gewichtung (P0) |
|---------------|-----------|---------------------------|
| `suggest_next` | Nächste Übung (Default bei leerer/kurzer Query) | Progression + Skill-Overlap + Plan-Kontinuität |
| `progression_next` | Explizit Graph-Folge | Progression hoch |
| `deepen_exercise` | Vertiefung zu Anker-Übung | Skill-Overlap hoch, ähnlicher Fokus |
| `continue_plan_goal` | Auf bisherigen Plan aufbauen | Plan-Kontinuität, Wiederholungsstrafe |
| `free_search` | Freitext / Stichwort | Volltext hoch |

**S1a (später):** Freitext → JSON `{ intent, skill_hints[], requires_partner, level_hint, … }` validiert per Pydantic.

**P0:** `intent_hint` vom Client oder Keyword-Heuristik auf `query`.

---

## 4. PlanningExerciseContextPack (S0)

Serverseitig aus Request + DB (tokenbewusst für spätere LLM-Stufen):

| Feld | Quelle | UI-Chip |
|------|--------|---------|
| `unit_id`, Titel, `group_id`, Gruppenname | `training_units` + `training_groups` | Gruppe · Einheit |
| `section_order_index`, Abschnittstitel | `training_unit_sections` | Abschnitt |
| `planned_exercise_ids[]` | Items der Einheit (Reihenfolge) | „N Übungen im Plan“ |
| `anchor_exercise_id`, Titel | Request oder letzte Übung vor Einfügepunkt | Anker |
| `anchor_skill_ids[]` | `exercise_skills` | (intern) |
| `progression_graph_id` | Request oder **Auto-Match** vom Anker (sichtbarer Graph mit passenden Ausgangskanten) | Graph |
| `progression_graph_name`, `progression_graph_auto_resolved` | Response `context_summary` | Graph (auto) |
| `anchor_exercise_variant_id` | Request / Abschnitt-Item / DB | (intern) |
| `progression_successor_ids[]` | `exercise_progression_edges` ab Anker (variantenbewusst, Migration **034**) | (intern) |
| `progression_successor_variants` | `to_exercise_variant_id` pro Nachfolger | (intern) |
| `group_recent_exercise_ids[]` | Letzte Einheiten derselben Gruppe | Wiederholungsstrafe |
| `framework_slot_notes` | Rahmen-Slot falls `framework_slot_id` | (später) |

**Berechtigung:** `get_tenant_context` + `_assert_training_unit_permission` wie `GET /training-units/{id}`.

---

## 5. Hybrid-Retrieval (S1b, P0)

Kandidaten: sichtbare Übungen (`library_content_visibility_sql`), ohne `archived`, max. ~400 (recent).

**Score** (0–1, gewichtet nach Intent):

```
score = w_ft * fulltext_rank
      + w_prog * progression_hit
      + w_skill * skill_jaccard(anchor, candidate)
      + w_plan * plan_affinity
      + w_profile * profile_match(exercise, target)
      + w_repeat * (candidate in unit_plan ? -1 : 0)
      + w_group_repeat * (candidate in group_recent ? -0.5 : 0)
```

**`profile_match`** (0–1): siehe §12–§13 — Katalog-Dimensionen + Skill-Gewichte + Skill-Gap.

**`reasons[]`** (regelbasiert, Deutsch): z. B. „Nachfolger im Progressionsgraph“, „Fähigkeiten passen zur Anker-Übung“, „Fokusbereich passend zum Planungsziel“, „Deckt Skill-Lücke im bisherigen Plan“, „Volltext-Treffer“.

---

## 6. API

### `POST /api/planning/exercise-suggest`

**Body:**

```json
{
  "unit_id": 123,
  "section_order_index": 0,
  "phase_order_index": null,
  "parallel_stream_order_index": null,
  "anchor_exercise_id": 456,
  "anchor_exercise_variant_id": 12,
  "progression_graph_id": 7,
  "query": "Schlage mir die nächste Übung vor",
  "intent_hint": "suggest_next",
  "limit": 20,
  "exercise_kind_any": ["simple"]
}
```

**Response:**

```json
{
  "context_summary": {
    "unit_title": "…",
    "group_name": "…",
    "section_title": "Hauptteil",
    "planned_count": 4,
    "anchor_title": "Partner-Fangspiel"
  },
  "target_profile_summary": {
    "sources": ["framework_catalog", "current_unit_plan", "anchor_exercise"],
    "focus_areas": ["Reaktion & Abwehr"],
    "top_skills": [{ "skill_id": 12, "name": "Reaktionsgeschwindigkeit", "weight": 1.0 }],
    "has_skill_gap": true
  },
  "retrieval_phase": "profile_v1",
  "intent_resolved": "suggest_next",
  "hits": [
    {
      "id": 99,
      "title": "…",
      "summary": "…",
      "score": 0.78,
      "reasons": ["Nachfolger im Progressionsgraph", "Fokusbereich passend zum Planungsziel"],
      "focus_area": "…"
    }
  ]
}
```

**Modul:** `backend/planning_exercise_suggest.py` · `backend/planning_exercise_profiles.py` · Router `backend/routers/planning_exercise_suggest.py`

---

## 7. Frontend

| Ort | Verhalten |
|-----|-----------|
| `ExercisePickerModal` | Prop `planningContext` → Planungs-API statt reiner `listExercises`; Kontext-Chips; `reasons` unter Treffer |
| `TrainingUnitEditPage` | `planningContext` aus Einheit + Picker-Ziel (Anker = letzte Übung im Abschnitt) |
| **`ExercisesListPageRoot`** | Schalter **„Neu mit KI-Assistent“**: Planungs-KI-Suche (frei, ohne `unit_id`) + Neuanlage im Modal; **„+ Neu“** ausgeblendet |
| Rahmen / Kombi-Formular | analog, sobald `unit_id` / Slot-Blueprint bekannt |
| Übungsliste (ohne KI-Schalter) | weiter Volltext |

**Zweites Suchfeld** im Picker: Query = Volltext + ergänzender Begriff (ODER in P0 als Konkatenation an Backend).

---

## 8. Neu-Anlage (Anbindung, Phase P1)

Wenn `hits` leer oder Trainer wählt „Mit KI anlegen“:

- `planning_context` im Request-Body → `planning_context_json` in Übungs-Prompts (Migration **085**); Pfad-Builder + Picker ✅ **0.8.208**
- Kurzbeschreibung optional leer (freier Vorschlag) oder aus Intent/Skizze

---

## 9. Phasen-Roadmap

| Phase | Inhalt | Status |
|-------|--------|--------|
| **P0** | Context-Pack, Hybrid-Score, API, Picker in Planung | ✅ |
| **P0.1** | `ExerciseMatchProfile` / `PlanningTargetProfile`, `profile_v1` | ✅ |
| **P1** | Szenario-Pipeline + LLM Query-Intent → Erwartungsprofil | ✅ |
| **P2 / B2** | LLM-Rerank bei engem Top-Feld (max. 2 Calls) | ✅ |
| **P3** | Skill-Discovery / Framework-Ziele im Pack | 🔲 |
| **A** | Voll-Library Hybrid-Ranking | ✅ **0.8.177** |
| **B** | Text-Signale guidance/Rahmen-Ziele | ✅ **0.8.181** |
| **C1** | Graph auto-match + variantenbewusste Nachfolger | ✅ **0.8.183** |
| **C2** | Varianten in Trefferliste / Picker | ✅ **0.8.184** |
| **C3** | Graph-Builder (Ziel → Pfad → speichern) | ✅ **0.8.185** |
| **E** | Semantik-Schicht + Pfad-QA (Lücken/Brücken/LLM-QS) | ✅ **0.8.186** |
| **E2** | Pfad-Neuordnung + KI-Lückenfüller | ✅ **0.8.187** |
| **D** | Neu-Anlage: `planning_context` an `suggestExerciseAi` (Migration **085**) | ✅ **0.8.208** |

---

## 10. Changelog

- **2026-05-23:** Phase C1 — Graph auto-match, variantenbewusste Nachfolger (`planning_exercise_progression.py`).
- **2026-05-23:** Phase B2 — Rerank bei engem Top-Feld; Phase B — Text-Signale; Phase A — Voll-Library (siehe §17–§19).
- **2026-05-22:** Erstfassung; P0 API + Planungs-Picker.
- **2026-05-22:** P0 implementiert (`planning_exercise_suggest.py`, Router, Picker); unsaved Formular-Plan noch nicht an API (nur persistierte Einheit).
- **2026-05-22:** P0.1 — `planning_exercise_profiles.py`, Profil-Score in Hybrid-Retrieval, `retrieval_phase: profile_v1`, `target_profile_summary`.
- **2026-05-22:** P2 — LLM-Rerank optional (`include_llm_rank`); Client `planned_exercise_ids[]`; Prompt Migration 072.

---

## 11. Bekannte Lücken & Backlog

- **Ungespeicherte Plan-Änderungen:** ✅ Client übergibt `planned_exercise_ids[]` aus Formular (TrainingUnitEditPage).
- **Progressionsgraph-ID:** ✅ Auto-Match vom Anker (**C1**); manuelle Auswahl in UI noch offen.
- **Anker-Variante:** ✅ Client + DB (**C1**); Picker wählt Variante bei Treffer (**C2** — Dropdown + Graph-Vorschlag).
- **Graph-Builder (C3):** Ziel → Pfad vorschlagen → in Graph speichern — ✅ **0.8.185**
- **Varianten-Suche:** Library-Picker nutzt `include_variants`; Planungs-KI rankt primär **Übungsebene** — Varianten-Expansion nur gezielt (**C2**).
- **Enrichment:** Superadmin-Tool für Skills; Datenqualität der Bibliothek entscheidend für Profil-Score.
- **LLM-Intent:** ✅ P1 Szenario-Pipeline + `planning_exercise_search_intent` (Migration 073).
- **Preset + LLM:** ✅ Erwartungs-LLM (074) bei Planungsbezug; Preset ohne Plan = kein Erwartungs-LLM.

---

## 16. Szenario-Pipeline & Query-Erwartungsprofil (P1)

Komplexe Planungsanfragen brauchen **Schritte vor** dem Profil-Match — nicht jede Query ist gleich.

### 16.1 Szenario-Klassen

| `scenario_kind` | Typische Anfrage | LLM Intent? |
|-----------------|------------------|-------------|
| `preset_next` | „Nächste Übung vorschlagen“ (Preset) | Erwartungs-LLM (074) wenn Planungsbezug |
| `progression` | Progressionsgraph / Pfad | Ja (wenn Freitext) |
| `deepen` | Vertiefung Anker | Ja |
| `continue_plan` | Auf bisherigen Plan aufbauen | Ja |
| `additive_constraint` | Plan **+** Zusatz (z. B. Schnellkraft) | Ja |
| `free_search` | Offene Stichwortsuche | Ja |

**Routing:** `planning_exercise_target_pipeline.classify_planning_scenario()` → `should_run_llm_intent_pipeline()`.

### 16.2 Pipeline (Reihenfolge)

```
S0 Kontext-Pack
 → Heuristik-Intent + Szenario
 → [optional] LLM planning_exercise_search_intent
 → Basis PlanningTargetProfile (Rahmen, Plan, Anker, Gap)
 → Merge Query-Overlay (Katalog-IDs aus Hints)
 → Hybrid-Retrieval + Profil-Score
 → [optional] LLM-Rerank
```

Module: `planning_exercise_target_pipeline.py` · `planning_exercise_intent.py`

### 16.3 API (Erweiterung)

| Request | Default | Bedeutung |
|---------|---------|-----------|
| `include_llm_intent` | `true` | LLM nur wenn Szenario ≠ preset_next und Query nicht leer |

| Response | Bedeutung |
|----------|-----------|
| `scenario_kind` | Szenario-Klasse |
| `query_intent_summary` | intent, llm_applied, rationale, skill_hints_resolved |
| `intent_heuristic` | Heuristik vor LLM |
| `retrieval_phase` | z. B. `profile_v1+query_intent+llm_rank` |

**Prompt 073:** `planning_exercise_search_intent` — Ausgabe JSON mit `skill_hints`, `focus_hints`, `emphasis` (`additive`|`replace`).

---

## 15. LLM-Rerank (P2)

**Request:**

| Feld | Typ | Default | Bedeutung |
|------|-----|---------|-----------|
| `planned_exercise_ids` | `int[]` | — | Optional: Reihenfolge aus Formular (überschreibt DB-Plan) |
| `include_llm_rank` | `bool` | `true` (Client) | Backend gated (B2): Rerank nur bei engem Top-Feld, max. 2 LLM-Calls |

**Response:**

| Feld | Wert |
|------|------|
| `retrieval_phase` | `profile_v1` oder `profile_v1+llm_rank` |
| `llm_rank_applied` | `true` wenn LLM erfolgreich sortiert hat |
| `hits[].llm_rank` | optional: Position nach LLM (1…n) |

**Fallback:** Kein API-Key, inaktiver Prompt oder Parse-Fehler → Hybrid-Reihenfolge unverändert, `llm_rank_applied: false`.

**Prompt:** Migration **072**, Slug `planning_exercise_search_rank` — Kandidaten als JSON mit Titel, summary, goal (Plaintext), skills; Ausgabe `{ ranked_ids, reasons }`.

---

## 12. ExerciseMatchProfile & PlanningTargetProfile (Phase 1)

Ziel: deterministische Vorselektion über **Profil-Dimensionen** statt nur Titel/Jaccard.

### 12.1 ExerciseMatchProfile (pro Übung)

| Feld | Quelle |
|------|--------|
| `focus_area_ids` | `exercise_focus_areas` (Primary = 1.0, sonst 0.85) |
| `style_direction_ids` | `exercise_style_directions` |
| `training_type_ids` | `exercise_training_types` |
| `target_group_ids` | `exercise_target_groups` |
| `skill_weights` | `exercise_skills` × Intensitäts-Multiplikator (`skill_scoring._skill_link_multiplier`) |

Bulk-Lader: `load_exercise_match_profiles_bulk(cur, exercise_ids)`.

### 12.2 PlanningTargetProfile (Planungsziel)

Zusammensetzung aus mehreren Quellen (`sources[]`):

| Quelle | Inhalt |
|--------|--------|
| `framework_catalog` | Fokus/Stil/Trainingsstil/Zielgruppe aus `training_framework_program_*` |
| `framework_slot_skill_profile` | Skill-Profil des Slot-Blueprints (`profile_for_occurrences`) |
| `framework_overall_skill_profile` | Fallback: alle Blueprint-Einheiten des Rahmens |
| `current_unit_plan` | Skill-Profil der bereits eingeplanten Übungen dieser Einheit |
| `anchor_exercise` | Katalog + Skills der Anker-Übung (Intent-abhängig) |
| `skill_gap_vs_plan` | `target_skills − plan_skills` (normalisiert, Schwelle > 0.08) |

Builder: `build_planning_target_profile(cur, unit=…, planned_exercise_ids=…, anchor_exercise_id=…, intent=…)`.

Rahmen-Anbindung über `unit.framework_slot_id` oder `origin_framework_slot_id`.

---

## 13. Profil-Score (Formeln)

**Gewichtete Überlappung** (Katalog + Skills):

```
overlap(a, b) = Σ min(a[k], b[k]) / Σ max(a[k], b[k])
```

**Skill-Gap-Abdeckung:**

```
gap_coverage(gap, candidate) = Σ min(gap[k], candidate[k]) / Σ gap[k]
```

**Profil-Score** (intent-gewichtet, Summe Dimensionen = 1.0):

```
profile_score = w_focus * overlap(focus)
              + w_style * overlap(style)
              + w_tt    * overlap(training_type)
              + w_tg    * overlap(target_group)
              + w_skill * overlap(skill_weights)
              + w_gap   * gap_coverage(skill_gap)
```

Intent-Gewichte (Auszug): `deepen_exercise` → Skill hoch; `continue_plan_goal` → Gap hoch; `free_search` → Gap + Skill moderat.

Scorer: `score_exercise_against_target(exercise_profile, target_profile, intent=…) → (score, reasons[])`.

---

## 14. Hybrid + Profil (P0.1)

Im Hybrid-Score kommt **`w_profile * profile_score`** hinzu (Intent-abhängig ~0.15–0.35). Jaccard auf Anker-Skills bleibt parallel (schneller Anker-Fokus).

**Response-Felder:**

| Feld | Bedeutung |
|------|-----------|
| `retrieval_phase` | `"profile_v1"` — Phase-1 aktiv, kein LLM-Rerank |
| `target_profile_summary` | Lesbare Kurzinfo für UI-Chips (Fokus, Top-Skills, Quellen) |

**Phase 2 (P2 / B2):** siehe §15 und §18 — `include_llm_rank: true` vom Client, Backend entscheidet.

---

## 17. Phase A — Voll-Library-Ranking (0.8.177)

- Kein OR-Profil-Pool (~500 Übungen) mehr.
- Alle sichtbaren Übungen (bis 8000) werden hybrid gescored (`fetch_all_visible_exercise_rows` + `rank_visible_library_hits`).
- API: `full_library_ranked: true`, `retrieval_phase` enthält `+full_library+`.

---

## 18. Phase B / B2 — Text-Signale & Rerank-Gates (0.8.181–0.8.182)

**B — Text-Signale (`planning_exercise_text_signals.py`):**

- `section_guidance_notes`, Rahmen-Ziele/Notizen → Skill-/Katalog-Gewichte ohne LLM.
- `requires_partner` aus Intent filtert Kandidaten.
- `retrieval_phase +text_signals`.

**B2 — Rerank bei unklarem Ranking:**

- `hybrid_ranking_ambiguous(hits)` (Top-4-/Top-10-Gap).
- Rerank auch nach Erwartungs-/Intent-LLM, wenn Scores eng beieinander.
- Budget: max. **2** LLM-Calls (Profil + optional Rerank).

---

## 19. Phase C1 — Progressionsgraph im Planungskontext (0.8.183)

**Modul:** `planning_exercise_progression.py`

### Auto-Match Graph

Wenn `progression_graph_id` fehlt und Anker-Übung gesetzt: sichtbarer Graph mit passender `next_exercise`-Kante vom Anker (variantenbewusst). Bevorzugung: variantenspezifische Kanten > Anzahl Kanten.

### Variantenbewusste Nachfolger (Migration 034)

Generische Kante (`from_exercise_variant_id IS NULL`) gilt für jeden Anker; variantenspezifische Kante nur bei passender Anker-Variante.

Treffer: optional `hits[].suggested_variant_id`.

### Request / Response

| Feld | Bedeutung |
|------|-----------|
| `anchor_exercise_variant_id` | Request — Variante der Anker-Übung |
| `progression_graph_name` | Response — Name des (auto-)Graphs |
| `progression_graph_auto_resolved` | Response — Auto-Match aktiv |

---

## 20. Phase C2 — Varianten in Treffern (0.8.184) ✅

- API: `variants[]`, `suggested_variant_name` pro Treffer (Batch aus `exercise_variants`).
- **`ExercisePickerModal`:** Dropdown pro Treffer; Graph-`suggested_variant_id` vorausgewählt; Übernahme setzt `exercise_variant_id`.
- **`hydrateExercisePlanningRow`:** übernimmt `exercise_variant_id` / `suggested_variant_id` in die Planungszeile.

---

## 21. Phase C3 — Graph-Builder (0.8.185) ✅

**API:** `POST /api/planning/progression-path-suggest`

| Feld | Bedeutung |
|------|-----------|
| `query` | Ziel / Entwicklungsrichtung (Freitext, min. 3 Zeichen) |
| `max_steps` | 2–10, Default 5 |
| `progression_graph_id` | optional — Graph-Kontext für Nachfolger ab Schritt 2 |
| `include_llm_intent` | LLM nur Schritt 1 (Budget) |

**Response:** `steps[]` mit `exercise_id`, `variant_id`, `title`, `reasons`, `variants`; `retrieval_phase: …+path_builder`.

**Algorithmus:** Iterativ Hybrid-Ranking — Schritt 1 aus Zielprofil, Folgeschritte mit Anker = letzte Übung, ohne Duplikate.

**UI:** `ExerciseProgressionPathBuilder` im Progressionsgraph-Panel — Review, Varianten, `POST …/edges/sequence`.

---

## 22. Phase E — Semantik-Schicht + Pfad-QA (0.8.186) ✅

### Semantic Brief (`planning_exercise_semantics.py`)

Parallel zum Katalog-Overlay — **nicht ersetzend**:

| Feld | Bedeutung |
|------|-----------|
| `primary_topic` | z. B. `mae geri` |
| `must_phrases` / `exclude_phrases` | Phrasen-Match in Titel/Ziel/Varianten |
| `development_arc` | einstieg → … → perfektion |
| `semantic_strength` | 0–1 — steuert dynamisches Blend im Hybrid-Score |
| `retrieval_query` | fokussierte Volltext-Query (nicht ganzer Satz) |

Optional LLM: Prompt `planning_exercise_query_semantics` (Migration **075**).

**Hybrid-Score:** neuer Term `w_semantic * semantic_score` — Profil/Volltext werden bei hoher `semantic_strength` relativ abgeschwächt.

### Pfad-QA (`planning_exercise_path_qa.py`)

Nach Pfad-Bildung:

1. **Lücken-Messung** zwischen benachbarten Schritten (Skill-Jaccard + Semantik zum erwarteten Phasen-Segment)
2. **Brücken-Übungen** bei großen Lücken (zusätzliche Schritte, markiert `is_bridge`)
3. **LLM-QS** (Prompt `planning_exercise_path_qa`): Reihenfolge, Themen-Abdeckung, Empfehlungen

**API-Erweiterung** `progression-path-suggest`: `include_path_qa`, `include_llm_path_qa` · Response: `semantic_brief_summary`, `path_qa`.

**Pfad-Schritte:** Semantic Brief + Entwicklungsphase in **allen** Schritten (nicht nur Schritt 1).

### Phase E2 (0.8.187)

- **LLM-QS → Neuordnung:** `ordered_step_indices` im Prompt `planning_exercise_path_qa` (Migration **076**)
- **KI-Lückenfüller:** `planning_exercise_path_ai_fill.py` — `is_ai_proposal` wenn Bibliothek keine Brücke liefert
- Request: `include_path_reorder`, `include_ai_gap_fill`

---

## 23. Phase E3 (0.8.203) ✅

- Off-Topic aus Pfad entfernen; `gap_fill_offers` mit `goal_for_ai`; voller KI-Call im UI (kein Pre-Vorschlag)
- Migration **077** `suggested_new_exercises` im Pfad-QS-Prompt

---

## 24. Phase F — Roadmap-first Progressionsgraph (0.8.204–217) ✅

**Entscheidung:** Progressionsgraph plant **vom Ziel rückwärts** (Roadmap → Stufenspezifikation → Bibliothek/KI). **Keine Gruppenanalyse** — die gehört zur Trainingsplanung.

**Ist-Stand (vollständig):** `docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`  
**Spec:** `working/PLANNING_PROGRESSION_ROADMAP_SPEC.md` · **Roadmap:** `docs/architecture/PLANNING_KI_ROADMAP.md`

| Teil | Modul / API |
|------|-------------|
| Pipeline | `planning_progression_roadmap.py` (Workflow-lite) |
| Match | `planning_exercise_path_builder.py` — `roadmap_first`, `roadmap_override` |
| Skills | `planning_skill_expectations.py` — pro Stufe + Pfad |
| Gap-KI | `planning_exercise_form_context.py`, `planning_exercise_path_ai_fill.py` |
| Persistenz | `planning_roadmap` JSONB (Migration **088**) |
| API | `progression-path-suggest`, `PUT` Graph, `POST …/edges/sequence` |
| Prompts | **078/079/087** — Slugs nur in `ai_prompts` |
| UI | `ExerciseProgressionPathBuilder`, `ExerciseGapFillPrepModal` |

**Graph-Bias:** `progression_graph_id` bevorzugt **bestehende Nachfolger** ab Schritt 2 (Gewicht ~4–10 %), baut aber **keinen** Pfad aus vorhandenen Knoten — siehe Ist-Doku §5.

**Mitai Workflow-Engine:** bewusst **nicht** jetzt — Pipeline workflow-ready für spätere Anbindung.

---

## 25. Backlog (offen)

Siehe priorisierte Liste in **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`** §10:

1. UI-Wizard (Progressionsgraph) — separater Chat  
2. Graph-Erweiterungsmodus (Start ab Knoten)  
3. Trainingsplanung Phase G (Gruppenkontext, `planning_skill_expectations`)  
4. Kontext auf allen Pfad-Schritten in der UI  
5. Enrichment / Prompt-Feintuning  
6. Mitai Workflow-Engine (langfristig)
