""" Admin-Vorschau: Platzhalter für Planungs-Prompts (Progressionsgraph, Pfad-QS, Suggest). Nutzt repräsentative Beispieldaten + echte Katalog-Auszüge aus der DB. """ from __future__ import annotations import json from typing import Any, Dict, List, Mapping, Optional from pydantic import BaseModel, Field from planning_exercise_semantics import brief_to_summary_dict, build_semantic_brief from planning_intent_context import build_planning_intent_context PLANNING_PROMPT_SLUGS = frozenset( { "planning_progression_start_target", "planning_progression_goal_analysis", "planning_progression_roadmap", "planning_progression_stage_spec", "planning_exercise_query_semantics", "planning_exercise_path_qa", "planning_exercise_search_intent", "planning_exercise_search_rank", "planning_exercise_expectation_profile", } ) class PlanningPromptPreviewInput(BaseModel): goal_query: str = Field( default="Mae Geri vom Grundschritt bis zur kontrollierten Kumite-Nähe", max_length=2000, ) user_notes: str = Field(default="Fokus Breitensport, ohne Wettkampfdruck.", max_length=2000) max_steps: int = Field(default=5, ge=2, le=10) search_query: Optional[str] = Field(default=None, max_length=2000) def is_planning_prompt_slug(slug: str) -> bool: return (slug or "").strip().lower() in PLANNING_PROMPT_SLUGS def _compact_json(obj: Any) -> str: return json.dumps(obj, ensure_ascii=False, separators=(",", ":")) def _sample_goal_analysis() -> Dict[str, Any]: return { "primary_topic": "Mae Geri", "start_assumption": "Grundstellung und einfache Frontkick-Bewegung bekannt", "target_state": "Kontrollierter Mae Geri in Kumite-Nähe mit Hüftöffnung", "success_criteria": [ "Hüfte öffnet vor dem Kick", "Ballen trifft Zielzone", "Rückzug ohne Balanceverlust", ], "constraints": { "partner_required": False, "excluded_themes": ["reine Kraft ohne Technikbezug"], "trainer_notes": "Breitensport, kein Wettkampf", }, } def _sample_major_steps(max_steps: int) -> List[Dict[str, Any]]: phases = ["einstieg", "grundlage", "vertiefung", "anwendung", "perfektion"] titles = [ "Grundstellung und Mae Geri Einstieg", "Hüftöffnung und Ballen-Fokus", "Koordination und Rückzug", "Anwendung in Partnerübung", "Qualität unter leichtem Druck", ] out: List[Dict[str, Any]] = [] for i in range(max_steps): out.append( { "index": i, "phase": phases[min(i, len(phases) - 1)], "title": titles[min(i, len(titles) - 1)], "learning_goal": titles[min(i, len(titles) - 1)], } ) return out def _sample_path_steps() -> List[Dict[str, Any]]: return [ { "index": 1, "exercise_id": 101, "title": "Mae Geri — Stand und Hüftöffnung", "goal": "Frontkick mit geöffneter Hüfte aus Grundstellung", "is_bridge": False, "is_ai_proposal": False, "reasons": ["Stufen-Gate: Grundlagen"], }, { "index": 2, "exercise_id": 102, "title": "Mae Geri — Ballen und Rückzug", "goal": "Präziser Ballentreffer mit kontrolliertem Rückzug", "is_bridge": False, "is_ai_proposal": False, "reasons": ["Nachfolger im Graph"], }, ] def _sample_planning_context() -> Dict[str, Any]: return { "scope": "progression_path", "goal_query": "Mae Geri vom Grundschritt bis zur Kumite-Nähe", "stage_index": 1, "learning_goal": "Hüftöffnung und Ballen-Fokus", } def _sample_target_profile() -> Dict[str, Any]: return { "primary_focus": "Kihon", "training_type": "Breitensport", "skill_expectations": ["Geri Waza", "Koordination"], } def _sample_candidates() -> List[Dict[str, Any]]: return [ { "exercise_id": 101, "title": "Mae Geri — Stand und Hüftöffnung", "summary": "Frontkick mit Hüftöffnung", "skill_names": ["Geri Waza"], "score_hint": 0.82, }, { "exercise_id": 102, "title": "Mae Geri — Ballen und Rückzug", "summary": "Ballentreffer mit Rückzug", "skill_names": ["Geri Waza", "Koordination"], "score_hint": 0.76, }, ] def _load_catalog_variables(cur) -> Dict[str, str]: from planning_exercise_intent import ( _load_compact_catalog, _load_skills_catalog_compact, ) return { "skills_catalog_json": _compact_json(_load_skills_catalog_compact(cur)), "focus_areas_catalog_json": _compact_json(_load_compact_catalog(cur, "focus_areas", "id")), "training_types_catalog_json": _compact_json(_load_compact_catalog(cur, "training_types", "id")), "style_directions_catalog_json": _compact_json(_load_compact_catalog(cur, "style_directions", "id")), "target_groups_catalog_json": _compact_json(_load_compact_catalog(cur, "target_groups", "id")), } def resolve_planning_prompt_preview_variables( cur, slug: str, body: PlanningPromptPreviewInput, ) -> Dict[str, str]: """Mustache-Variablen für Planungs-Prompt-Vorschau im Admin.""" s = (slug or "").strip().lower() if s not in PLANNING_PROMPT_SLUGS: raise ValueError(f"Kein Planungs-Prompt-Slug: {slug!r}") goal_query = (body.goal_query or "").strip() or "Mae Geri Progression" search_query = (body.search_query or "").strip() or goal_query max_steps = int(body.max_steps) brief = build_semantic_brief(goal_query) brief_json = _compact_json(brief_to_summary_dict(brief)) goal_analysis = _sample_goal_analysis() major_steps = _sample_major_steps(max_steps) intent_ctx = build_planning_intent_context( goal_query=goal_query, goal_analysis=goal_analysis, semantic_brief=brief, extra_context=(body.user_notes or "").strip() or None, ) intent_ctx_json = _compact_json(intent_ctx.to_api_dict()) ctx = _sample_planning_context() target = _sample_target_profile() catalogs = _load_catalog_variables(cur) if s == "planning_progression_start_target": return { "goal_query": goal_query, "semantic_brief_json": brief_json, "user_notes": (body.user_notes or "").strip(), } if s == "planning_progression_goal_analysis": return { "goal_query": goal_query, "semantic_brief_json": brief_json, } if s == "planning_progression_roadmap": return { "goal_query": goal_query, "goal_analysis_json": _compact_json(goal_analysis), "semantic_brief_json": brief_json, "max_steps": str(max_steps), } if s == "planning_progression_stage_spec": return { "goal_query": goal_query, "goal_analysis_json": _compact_json(goal_analysis), "major_steps_json": _compact_json(major_steps), "intent_context_json": intent_ctx_json, "semantic_brief_json": brief_json, } if s == "planning_exercise_query_semantics": return { "search_query": search_query, "semantic_brief_json": brief_json, } if s == "planning_exercise_path_qa": return { "goal_query": goal_query, "semantic_brief_json": brief_json, "steps_json": _compact_json(_sample_path_steps()), "gaps_json": _compact_json([]), "bridge_inserts_json": _compact_json([]), } if s == "planning_exercise_search_intent": return { "search_query": search_query, "heuristic_intent": "progression_next", "scenario_hint": "preset_next", "planning_context_json": _compact_json(ctx), "target_profile_json": _compact_json(target), **catalogs, } if s == "planning_exercise_search_rank": return { "search_query": search_query, "intent": "progression_next", "planning_context_json": _compact_json(ctx), "target_profile_json": _compact_json(target), "candidates_json": _compact_json(_sample_candidates()), "result_limit": "5", } if s == "planning_exercise_expectation_profile": return { "heuristic_intent": "suggest_next", "planning_context_json": _compact_json(ctx), "target_profile_json": _compact_json(target), **{k: v for k, v in catalogs.items() if k != "style_directions_catalog_json"}, } raise ValueError(f"Planungs-Prompt-Slug nicht implementiert: {slug!r}") __all__ = [ "PLANNING_PROMPT_SLUGS", "PlanningPromptPreviewInput", "is_planning_prompt_slug", "resolve_planning_prompt_preview_variables", ]