shinkan-jinkendo/backend/ai_prompt_context.py
Lars 779e2477ba
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 33s
Test Suite / playwright-tests (push) Successful in 1m13s
Implement Planning Context Integration for Exercise AI Suggestions
- Added `planning_context` to the `suggestExerciseAi` endpoint, enabling structured planning context for new exercise creation.
- Updated relevant components and backend logic to handle the new planning context, enhancing the AI's exercise suggestion capabilities.
- Incremented application version to 0.8.208 to reflect these changes.
2026-06-08 15:15:03 +02:00

109 lines
3.7 KiB
Python

"""
Gemeinsame Pydantic-Modelle fuer Uebungs-KI-Kontext (Formularfelder → Prompt-Platzhalter).
Keine Imports aus exercise_ai — vermeidet Zirkelimporte mit ai_prompt_job / exercise_ai.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Sequence, Tuple
from pydantic import BaseModel, Field
class ExerciseFormAiFocusRow(BaseModel):
"""Fokusbereich fuer Skill-Retrieval (ai_skill_retrieval_profiles)."""
focus_area_id: int = Field(..., ge=1)
is_primary: Optional[bool] = False
class ExerciseFormAiPromptContext(BaseModel):
"""
Inhaltliche Eingabe fuer Uebungs-Prompts (Kurzfassung / Skills / Anleitung).
Wird genutzt von Admin-Prompt-Vorschau und POST /exercises/ai/suggest (via Mapping).
"""
title: Optional[str] = ""
goal: Optional[str] = None
execution: Optional[str] = None
preparation: Optional[str] = None
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:
return None
return [(int(x.focus_area_id), bool(x.is_primary)) for x in self.focus_areas_context]
def has_instruction_source_text(self) -> bool:
"""Mindestens ein Anleitungsfeld oder Titel fuer instruction_rewrite."""
if (self.title or "").strip():
return True
for val in (self.goal, self.execution, self.preparation, self.trainer_notes):
if val and str(val).strip():
return True
return False
@classmethod
def from_api_suggest(
cls,
*,
title: Optional[str] = None,
goal: Optional[str] = None,
execution: Optional[str] = None,
preparation: Optional[str] = None,
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
return cls(
title=(title or "").strip(),
goal=goal,
execution=execution,
preparation=preparation,
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
def from_focus_tuples(
cls,
*,
title: str = "",
goal: Optional[str] = None,
execution: Optional[str] = None,
preparation: Optional[str] = None,
trainer_notes: Optional[str] = None,
focus_hint: Optional[str] = None,
focus_tuples: Optional[Sequence[Tuple[int, bool]]] = None,
) -> ExerciseFormAiPromptContext:
rows = None
if focus_tuples:
rows = [
ExerciseFormAiFocusRow(focus_area_id=int(fid), is_primary=bool(prim))
for fid, prim in focus_tuples
]
return cls(
title=(title or "").strip(),
goal=goal,
execution=execution,
preparation=preparation,
trainer_notes=trainer_notes,
focus_hint=(focus_hint or "").strip() or None,
focus_areas_context=rows,
)
__all__ = [
"ExerciseFormAiFocusRow",
"ExerciseFormAiPromptContext",
]