diff --git a/.claude/docs/working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md b/.claude/docs/working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md index 7d3dbbe..bbce56c 100644 --- a/.claude/docs/working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md +++ b/.claude/docs/working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md @@ -172,7 +172,7 @@ score = w_ft * fulltext_rank Wenn `hits` leer oder Trainer wählt „Mit KI anlegen“: -- Gleiches `context_summary` an `suggestExerciseAi` anhängen (Felder `planning_context_json` o. ä. — noch offen) +- `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 --- @@ -193,7 +193,7 @@ Wenn `hits` leer oder Trainer wählt „Mit KI anlegen“: | **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` | 🔲 | +| **D** | Neu-Anlage: `planning_context` an `suggestExerciseAi` (Migration **085**) | ✅ **0.8.208** | --- diff --git a/backend/ai_prompt_context.py b/backend/ai_prompt_context.py index bd441b4..d3b34b6 100644 --- a/backend/ai_prompt_context.py +++ b/backend/ai_prompt_context.py @@ -5,7 +5,7 @@ Keine Imports aus exercise_ai — vermeidet Zirkelimporte mit ai_prompt_job / ex """ from __future__ import annotations -from typing import List, Optional, Sequence, Tuple +from typing import Any, Dict, List, Optional, Sequence, Tuple from pydantic import BaseModel, Field @@ -31,6 +31,7 @@ class ExerciseFormAiPromptContext(BaseModel): trainer_notes: Optional[str] = None focus_hint: Optional[str] = None focus_areas_context: Optional[List[ExerciseFormAiFocusRow]] = None + planning_context: Optional[Dict[str, Any]] = None def focus_area_tuples(self) -> Optional[List[Tuple[int, bool]]]: if not self.focus_areas_context: @@ -57,6 +58,7 @@ class ExerciseFormAiPromptContext(BaseModel): trainer_notes: Optional[str] = None, focus_area_hint: Optional[str] = None, focus_areas_context: Optional[Sequence[ExerciseFormAiFocusRow]] = None, + planning_context: Optional[Dict[str, Any]] = None, ) -> ExerciseFormAiPromptContext: """Mappt Felder aus POST /exercises/ai/suggest (focus_area_hint → focus_hint).""" hint = (focus_area_hint or "").strip() or None @@ -68,6 +70,7 @@ class ExerciseFormAiPromptContext(BaseModel): trainer_notes=trainer_notes, focus_hint=hint, focus_areas_context=list(focus_areas_context) if focus_areas_context else None, + planning_context=dict(planning_context) if planning_context else None, ) @classmethod diff --git a/backend/ai_prompt_job.py b/backend/ai_prompt_job.py index 38074ba..f0595ca 100644 --- a/backend/ai_prompt_job.py +++ b/backend/ai_prompt_job.py @@ -23,6 +23,7 @@ def resolve_exercise_form_variables(cur, slug: str, ctx: ExerciseFormAiPromptCon focus_areas_context=ctx.focus_area_tuples(), preparation=ctx.preparation, trainer_notes=ctx.trainer_notes, + planning_context=ctx.planning_context, ) diff --git a/backend/exercise_ai.py b/backend/exercise_ai.py index 5b1c73e..c744c9b 100644 --- a/backend/exercise_ai.py +++ b/backend/exercise_ai.py @@ -650,10 +650,13 @@ def build_exercise_placeholder_variables( focus_areas_context: Optional[Sequence[Tuple[int, bool]]], preparation: Optional[str] = None, trainer_notes: Optional[str] = None, + planning_context: Optional[Mapping[str, Any]] = None, ) -> Dict[str, str]: """ Baut die Variable-Map fuer {{platzhalter}} passend zur Slug fuer Uebungs-KI. """ + from planning_exercise_form_context import planning_context_prompt_variables + s = (slug or "").strip().lower() if s == "pipeline": return {} @@ -671,8 +674,19 @@ def build_exercise_placeholder_variables( "exercise_preparation": p_plain or "-", "exercise_trainer_notes": n_plain or "-", } + ctx.update(planning_context_prompt_variables(planning_context)) if s == "exercise_summary": - return {k: ctx[k] for k in ("exercise_title", "exercise_focus_area", "exercise_goal", "exercise_execution")} + return { + k: ctx[k] + for k in ( + "exercise_title", + "exercise_focus_area", + "exercise_goal", + "exercise_execution", + "planning_context_json", + "has_planning_context", + ) + } if s == "exercise_instruction_rewrite": return ctx if s == "exercise_skill_suggestions": @@ -893,6 +907,7 @@ def run_exercise_ai_suggestion( execution=execution, focus_area_hint=focus_area_hint, focus_areas_context=focus_areas_context, + planning_context=form_ctx.planning_context, ) except ValueError as e: raise HTTPException(status_code=500, detail=str(e)) from e @@ -938,6 +953,7 @@ def run_exercise_ai_suggestion( execution=execution, focus_area_hint=focus_area_hint, focus_areas_context=focus_areas_context, + planning_context=form_ctx.planning_context, ) except ValueError as e: raise HTTPException(status_code=500, detail=str(e)) from e @@ -1015,6 +1031,7 @@ def run_exercise_ai_suggestion( trainer_notes=trainer_notes, focus_area_hint=focus_area_hint, focus_areas_context=focus_areas_context, + planning_context=form_ctx.planning_context, ) except ValueError as e: raise HTTPException(status_code=500, detail=str(e)) from e diff --git a/backend/migrations/085_ai_prompt_exercise_planning_context.sql b/backend/migrations/085_ai_prompt_exercise_planning_context.sql new file mode 100644 index 0000000..0811729 --- /dev/null +++ b/backend/migrations/085_ai_prompt_exercise_planning_context.sql @@ -0,0 +1,181 @@ +-- Migration 085: Planungskontext in Übungs-KI-Prompts (Phase D) +-- Platzhalter: {{planning_context_json}}, {{#has_planning_context}} … {{/has_planning_context}} + +UPDATE ai_prompts +SET template = $s$Du bist Assistent fuer Kampfsport-Trainer. +Erstelle eine kurze Kurzbeschreibung fuer Listen und Trainingsplaene. + +Anforderungen: +- Hochstens etwa 200 Zeichen (bei Bedarf gekuerzt fuer Mobile) +- Kern: Welche Trainingsqualitaeten? Wie fuehrt man die Uebung kurz aus? +- Sachlich, auf Deutsch + +Uebung: {{exercise_title}} +Fokuskontext: {{exercise_focus_area}} +Ziel (Fliesstext, kann HTML sein): {{exercise_goal}} +Durchfuehrung (Fliesstext, kann HTML sein): {{exercise_execution}} +{{#has_planning_context}} +Planungskontext (JSON — Einordnung in Trainingsplan oder Progressionspfad): +{{planning_context_json}} +{{/has_planning_context}} + +Antworte NUR mit der Kurzbeschreibung als einfachen Text (keine Markdown-Codeblocks, keine Anfuehrungszeichen um den ganzen Text).$s$, + default_template = $s$Du bist Assistent fuer Kampfsport-Trainer. +Erstelle eine kurze Kurzbeschreibung fuer Listen und Trainingsplaene. + +Anforderungen: +- Hochstens etwa 200 Zeichen (bei Bedarf gekuerzt fuer Mobile) +- Kern: Welche Trainingsqualitaeten? Wie fuehrt man die Uebung kurz aus? +- Sachlich, auf Deutsch + +Uebung: {{exercise_title}} +Fokuskontext: {{exercise_focus_area}} +Ziel (Fliesstext, kann HTML sein): {{exercise_goal}} +Durchfuehrung (Fliesstext, kann HTML sein): {{exercise_execution}} +{{#has_planning_context}} +Planungskontext (JSON — Einordnung in Trainingsplan oder Progressionspfad): +{{planning_context_json}} +{{/has_planning_context}} + +Antworte NUR mit der Kurzbeschreibung als einfachen Text (keine Markdown-Codeblocks, keine Anfuehrungszeichen um den ganzen Text).$s$ +WHERE slug = 'exercise_summary'; + +UPDATE ai_prompts +SET template = $j$Du bist Assistent fuer Kampfsport-Trainer. +Ordne diese Uebung dem globalen Skill-Katalog zu. + +Daten zur Uebung: +Titel: {{exercise_title}} +Fokuskontext (optional): {{exercise_focus_area}} +Ziel (gekuerzt_plain): {{exercise_goal}} +Durchfuehrung (gekuerzt_plain): {{exercise_execution}} +{{#has_planning_context}} +Planungskontext (JSON): +{{planning_context_json}} +{{/has_planning_context}} + +Verfuegbare Faehigkeiten (Auswahl NUR ueber diese IDs — keine anderen IDs verwenden): +{{skills_catalog}} + +Waehle hoechstens 5 passende Skills. Für jede Faehigkeit: +- skill_id: ganze Zahl aus der Liste +- required_level: eines von basis, grundlagen, aufbau, fortgeschritten, optimierung +- target_level: derselbe Wertvorrat +- intensity: eines von niedrig, mittel, hoch +- is_primary (optional): true fuer die Hauptfaehigkeit der Uebung, sondern false/weglassen + +Antworte NUR mit einem JSON-Array ohne Erklaertext, keine Markdown-Fences. + +Beispielformat: +[{"skill_id": 1, "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch", "is_primary": true}] + +Wenn nichts gut passt, antworte mit [].$j$, + default_template = $j$Du bist Assistent fuer Kampfsport-Trainer. +Ordne diese Uebung dem globalen Skill-Katalog zu. + +Daten zur Uebung: +Titel: {{exercise_title}} +Fokuskontext (optional): {{exercise_focus_area}} +Ziel (gekuerzt_plain): {{exercise_goal}} +Durchfuehrung (gekuerzt_plain): {{exercise_execution}} +{{#has_planning_context}} +Planungskontext (JSON): +{{planning_context_json}} +{{/has_planning_context}} + +Verfuegbare Faehigkeiten (Auswahl NUR ueber diese IDs — keine anderen IDs verwenden): +{{skills_catalog}} + +Waehle hoechstens 5 passende Skills. Für jede Faehigkeit: +- skill_id: ganze Zahl aus der Liste +- required_level: eines von basis, grundlagen, aufbau, fortgeschritten, optimierung +- target_level: derselbe Wertvorrat +- intensity: eines von niedrig, mittel, hoch +- is_primary (optional): true fuer die Hauptfaehigkeit der Uebung, sondern false/weglassen + +Antworte NUR mit einem JSON-Array ohne Erklaertext, keine Markdown-Fences. + +Beispielformat: +[{"skill_id": 1, "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch", "is_primary": true}] + +Wenn nichts gut passt, antworte mit [].$j$ +WHERE slug = 'exercise_skill_suggestions'; + +UPDATE ai_prompts +SET template = $t$Du bist Assistent fuer Kampfsport-Trainer. +Ueberarbeite die Anleitung dieser Uebung: verbessere Formulierung, ergaenze fehlende Kernpunkte, kuerze ueberfluessige Passagen. +Wichtig: Texte sollen praezise und nachvollziehbar bleiben — keine Fuellsaetze, keine Wiederholungen, kein Marketing. + +Stil: +- Deutsch, sachlich, direkt an Trainer gerichtet (Durchfuehrung: Imperativ oder klare Schritte) +- Ziel: 1–3 kurze Absaetze (Kern des Trainingsziels) +- Durchfuehrung: klare Schritte (nummerierte Liste oder kurze Absaetze) +- Vorbereitung/Aufbau: nur wenn noetig (Raum, Material, Aufbau) — sonst leerer String +- Trainer-Hinweise: Sicherheit, typische Fehler, Coaching-Tipps — knapp, Stichpunkte oder kurze Absaetze + +Format (HTML fuer Rich-Text-Editor): +- Erlaubt:

,