- Introduced a roadmap-first approach for the planning AI, allowing for a structured progression graph that aligns with the overall project roadmap. - Added new functionality to strip off-topic steps from exercise paths, improving the relevance of generated exercise suggestions. - Implemented a detailed goal text generation for AI proposals, enhancing the context provided for new exercises. - Updated the ExerciseProgressionPathBuilder component to support new features, including roadmap previews and improved focus area handling. - Incremented application version to 0.8.205 and updated database schema version to 20260606086 to reflect these changes.
21 KiB
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_ids (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:
{
"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:
{
"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“:
- Gleiches
context_summaryansuggestExerciseAianhängen (Felderplanning_context_jsono. ä. — noch offen) - 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: Pack an suggestExerciseAi |
🔲 |
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); Clientplanned_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_phaseenthä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_partneraus 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_namepro Treffer (Batch ausexercise_variants). ExercisePickerModal: Dropdown pro Treffer; Graph-suggested_variant_idvorausgewählt; Übernahme setztexercise_variant_id.hydrateExercisePlanningRow: übernimmtexercise_variant_id/suggested_variant_idin 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:
- Lücken-Messung zwischen benachbarten Schritten (Skill-Jaccard + Semantik zum erwarteten Phasen-Segment)
- Brücken-Übungen bei großen Lücken (zusätzliche Schritte, markiert
is_bridge) - 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_indicesim Promptplanning_exercise_path_qa(Migration 076) - KI-Lückenfüller:
planning_exercise_path_ai_fill.py—is_ai_proposalwenn 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_offersmitgoal_for_ai; voller KI-Call im UI (kein Pre-Vorschlag) - Migration 077
suggested_new_exercisesim Pfad-QS-Prompt
24. Phase F — Roadmap-first Progressionsgraph (0.8.204+) 🔄
Entscheidung: Progressionsgraph plant vom Ziel rückwärts (Roadmap → Stufenspezifikation → Bibliothek/KI). Keine Gruppenanalyse — die gehört zur Trainingsplanung.
Spec: working/PLANNING_PROGRESSION_ROADMAP_SPEC.md · Roadmap: docs/architecture/PLANNING_KI_ROADMAP.md
| Teil | Modul / API |
|---|---|
| Pipeline | planning_progression_roadmap.py (Workflow-lite) |
| API | include_roadmap_preview, include_llm_roadmap, roadmap_first auf progression-path-suggest |
| Prompts | Migration 078/079 — Slugs in ai_prompts (Admin), kein Template im Python-Code |
| UI | ExerciseProgressionPathBuilder — Roadmap-Box (Major Steps) |
Übergang: include_roadmap_preview=true liefert progression_roadmap parallel zum retrieval-first Pfad. Ziel F3: roadmap_first=true steuert Retrieval.
Mitai Workflow-Engine: bewusst nicht jetzt — Pipeline workflow-ready für spätere Anbindung.