""" 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 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 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, ) -> 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, ) @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", ]