""" Gemeinsame KI-Prompt-Laufzeit (Shinkan): DB-Lesezugriff ai_prompts + Kontext-Arten. Bleibt ohne Import von exercise_ai (kein Zirkel). Domänen wie exercise_ai nutzen load_ai_prompt_row und die Enum; Platzhalter bauen sie selbst oder über geteilte Builder. """ from __future__ import annotations from enum import Enum from typing import Any, Dict, Optional _EXERCISE_AI_SLUGS = frozenset( { "exercise_summary", "exercise_skill_suggestions", } ) class AiPromptContextKind(str, Enum): """ Logischer Kontext fuer Platzhalter/Builder — erweiterbar fuer Planung/Rahmen ohne bestehende Slugs zu invalidieren. """ EXERCISE_FORM_AI = "exercise_form_ai" def context_kind_for_slug(slug: str) -> Optional[AiPromptContextKind]: """Ordnet einen DB-Slug einer Kontext-Art zu, sofern registriert.""" s = (slug or "").strip().lower() if s in _EXERCISE_AI_SLUGS: return AiPromptContextKind.EXERCISE_FORM_AI return None def load_ai_prompt_row(cur, slug: str, *, active_only: bool = True) -> Optional[Dict[str, Any]]: """ Laedt eine Zeile ai_prompts fuer Laufzeit-Orchestrierung. active_only=True: inaktive Prompts werden wie fehlend behandelt (503 im Aufrufer). """ if active_only: cur.execute( """ SELECT slug, display_name, template, output_format, active FROM ai_prompts WHERE slug = %s AND active = true """, (slug,), ) else: cur.execute( """ SELECT slug, display_name, template, output_format, active FROM ai_prompts WHERE slug = %s """, (slug,), ) row = cur.fetchone() if not row: return None d = dict(row) if active_only and not d.get("active", True): return None return d __all__ = [ "AiPromptContextKind", "context_kind_for_slug", "load_ai_prompt_row", ]