shinkan-jinkendo/.claude/docs/working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md
Lars 207817376d
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 47s
Test Suite / playwright-tests (push) Successful in 1m14s
Enhance Planning Exercise Suggestion with LLM-Rerank and Client Overrides
- Implemented optional LLM-Rerank functionality in the planning exercise suggestion process, allowing for improved exercise ranking based on user-defined criteria.
- Updated the `suggestPlanningExercises` API to accept `planned_exercise_ids` for client-side overrides, enhancing flexibility in exercise selection.
- Enhanced the `ExercisePickerModal` to reflect LLM ranking status and support new planning context features.
- Incremented application version to 0.8.170 and updated changelog to document the new features and improvements in the planning AI capabilities.
2026-05-22 22:09:28 +02:00

11 KiB
Raw Blame History

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

Version: 0.1
Datum: 2026-05-22
Status: P0.1 — Hybrid-Retrieval + Phase-1-Profil-Score (profile_v1); LLM-Rerank P2
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 Optional LLM planning_exercise_search_intent Heuristik
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 (optional) Graph
progression_successor_ids[] exercise_progression_edges ab Anker (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 (01, 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 (01): 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,
  "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)
Rahmen / Kombi-Formular analog, sobald unit_id / Slot-Blueprint bekannt
Übungsliste weiter Volltext; Schalter „Neu mit KI-Assistent“ ohne Planungs-Pack

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_summary an suggestExerciseAi anhängen (Felder planning_context_json o. ä. — noch offen)
  • Kurzbeschreibung optional leer (freier Vorschlag) oder aus Intent/Skizze

9. Phasen-Roadmap

Phase Inhalt
P0 Context-Pack, Hybrid-Score, API, Picker in Planung
P0.1 ExerciseMatchProfile / PlanningTargetProfile, profile_v1, target_profile_summary
P2 (optional) LLM-Rerank planning_exercise_search_rank, include_llm_rank, llm_rank_applied
P1 LLM Intent-JSON; Neu-Anlage mit Pack
P3 Skill-Discovery / Framework-Ziele im Pack

10. Changelog

  • 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 P0-Lücken

  • Ungespeicherte Plan-Änderungen: Client übergibt planned_exercise_ids[] aus Formular (TrainingUnitEditPage).
  • Progressionsgraph-ID: noch nicht aus UI wählbar (progression_graph_id nur per API).
  • LLM-Intent: P1 laut Roadmap §9.

15. LLM-Rerank (P2)

Request:

Feld Typ Default Bedeutung
planned_exercise_ids int[] Optional: Reihenfolge aus Formular (überschreibt DB-Plan)
include_llm_rank bool false Top-32 Hybrid-Kandidaten → OpenRouter Prompt planning_exercise_search_rank

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.150.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): siehe §15 — optional per include_llm_rank.