""" Namensbasierte Fallback-Slots — bis Admin/DB befüllt sind (H1-Registry-Inhalt). DB-Werte in catalog_prompt_slots haben immer Vorrang. Fallbacks füllen nur leere Slot-Keys. """ from __future__ import annotations import re import unicodedata from typing import Dict, Mapping, Optional, Sequence, Tuple _UMLAUT_MAP = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss", "Ä": "ae", "Ö": "oe", "Ü": "ue"}) SlotPack = Dict[str, str] # (catalog_kind, name_pattern_lower) — erste passende Regel gewinnt; * = Default pro Kind _FALLBACK_RULES: Tuple[Tuple[str, str, SlotPack], ...] = ( # --- focus_area --- ( "focus_area", "gewaltschutz", { "description": ( "Planung zielt auf Prävention, Deeskalation, Grenzen und sichere Übungsformen — " "nicht auf Wettkampf-Perfektion oder Technik-Show." ), "hints_on_progression": ( "Phasen: Wahrnehmung → Grenzen → Deeskalation → sichere Übungsformen; " "keine Kumite-Perfektionsstufen erzwingen." ), "hints_on_exercise": ( "Übungen mit Rollen, Kommunikation, Ausweichen; keine rein technischen Kick-Fokus-Inseln ohne Bezug." ), "hints_on_path_qa": ( "Gute Pfade bauen Sicherheit, Kommunikation und Alternativen auf; " "„Lücken“ sind fehlende Deeskalations- oder Rollenspiel-Stufen, nicht fehlende Kick-Varianten." ), "anti_patterns": "Nicht nach Kumite-Tiefe, Explosivität oder Wettkampf-Belastung bewerten.", }, ), ( "focus_area", "selbstverteidigung", { "description": ( "Praktische Selbstverteidigung: realistische Szenarien, Sicherheit und " "anwendungsnahe Progression — nicht Show-Technik oder Wettkampf-Kata." ), "hints_on_progression": ( "Von Wahrnehmung und Distanz zu einfachen Abwehrmustern und kontrollierter Anwendung." ), "hints_on_exercise": "Partnerübungen mit klaren Sicherheitsregeln; Szenario-Bezug wichtiger als Stil-Show.", "hints_on_path_qa": ( "Lücken bei Szenario- oder Sicherheitsstufen sind relevant; " "fehlende Kick-Varianten oder Wettkampftiefe sind kein Mangel." ), "anti_patterns": "Keine Wettkampf- oder Kata-Perfektion als QS-Maßstab.", }, ), ( "focus_area", "fitness", { "description": ( "Fitness- und Konditionsorientierung mit sicherer Belastungssteuerung; " "Technikbezug nur wo fachlich sinnvoll." ), "hints_on_progression": "Progression von niedriger zu moderater Belastung; klare Pausen und Technikhygiene.", "hints_on_path_qa": ( "Keine Wettkampf-Spezialisierung als Pflicht-Kriterium; " "Belastungssteigerung ohne Technikbezug abwerten." ), "anti_patterns": "Keine Kumite-Perfektion oder Wettkampf-Kombinationen als QS-Maßstab verlangen.", }, ), ( "focus_area", "karate", { "description": ( "Technik-Curriculum im Karate-Kontext: aufeinander aufbauende Kihon-Progression " "mit klaren Qualitätsankern (Stand, Hüfte, Kime)." ), "hints_on_progression": ( "Typische Phasen: Einstieg → Grundlagen → Koordination/Kraft → Anwendung → optional Vertiefung; " "Grundlagen vor Perfektion." ), "hints_on_exercise": ( "Kihon und Partnerübungen mit Technikbezug; reine Kraft-/Ausdauer-Inseln nur mit klarer Begründung." ), "hints_on_path_qa": ( "Kohärente Progression Grundlagen → Anwendung → Vertiefung; " "Übergänge ohne Sprünge; themenfremde Kraft-/Ausdauer-Inseln abwerten." ), "anti_patterns": "Keine pauschale Perfektions-Stufe verlangen, wenn Kontext Breitensport ist.", }, ), ( "focus_area", "*", { "description": "Technik- oder Themen-Curriculum mit didaktisch aufeinander aufbauenden Stufen.", "hints_on_progression": "Grundlagen vor Anwendung; moderate Sprünge zwischen Stufen vermeiden.", "hints_on_path_qa": ( "Kohärente Progression zum Anfrage-Thema; " "Lücken sind fehlende Zwischenstufen im Lernpfad, nicht fehlende Nebenthemen." ), "hints_on_exercise": "Übungen mit klarem Bezug zum Pfad-Thema und zur Stufe.", }, ), # --- training_type --- ( "training_type", "breitensport", { "description": ( "Partizipation, Verständlichkeit, Freude am Bewegen; weniger maximale Spezialisierung." ), "hints_on_progression": "Moderater Schwierigkeitsanstieg; Perfektionsphasen optional.", "hints_on_path_qa": ( "Hohe OK-Rate bei moderatem Schwierigkeitsanstieg; " "„Perfektion“-Stufen nur optional, nicht als Pflicht-Lücke." ), "rematch_guard": "Keine leeren Slots erzwingen, nur um eine Leistungs-Perfektionsstufe zu füllen.", }, ), ( "training_type", "leistungssport", { "description": "Leistungsorientiertes Training mit höherer Anspruchskurve und Spezialisierung.", "hints_on_progression": "Belastungs- und Kombinationsprogressionen sind erwünscht.", "hints_on_path_qa": ( "Höhere Anspruchskurven sind passend; Lücken in Spezialisierung können echte Hinweise sein." ), }, ), ( "training_type", "wettkampf", { "description": ( "Wettkampforientiertes Training mit höherer Anspruchskurve und belastungsnahen Phasen." ), "hints_on_progression": "Anwendungs- und Druckphasen zeitig einplanen.", "hints_on_path_qa": ( "Spezialisierung, Kombination und Belastung unter Druck sind relevant; " "Lücken in Anwendungs- oder Perfektionsphasen können echte Hinweise sein." ), }, ), ( "training_type", "*", { "hints_on_path_qa": "Didaktische Kohärenz wichtiger als maximale Spezialisierung — Kontext beachten.", }, ), # --- target_group --- ( "target_group", "kinder", { "description": ( "Kinder: kurze Einheiten, spielerische Einstiege, Sicherheit und altersgerechte Komplexität." ), "hints_on_progression": "Spielerische Einstiege; kurze Abschnitte; Sicherheit vor Perfektion.", "hints_on_path_qa": ( "Didaktik ohne Überforderung; klare Regeln und Sicherheit vor Perfektion; " "Lücken bei Spiel-/Rollenelementen wichtiger als Wettkampftiefe." ), "anti_patterns": "Keine Erwachsenen-Wettkampf-Perfektion als QS-Maßstab.", }, ), ( "target_group", "leistungssportler", { "description": "Leistungsgruppe: höhere Anspruchskurven und Spezialisierung sind fachlich passend.", "hints_on_progression": "Anspruchskurve und Spezialisierung dürfen steiler sein.", "hints_on_path_qa": ( "Höhere Anspruchskurven, Belastungs- und Kombinationsprogressionen sind relevant; " "Lücken in Spezialisierung können echte Hinweise sein." ), }, ), ( "target_group", "breitensportler", { "description": "Breitensport: Partizipation und Verständlichkeit vor maximaler Spezialisierung.", "hints_on_path_qa": ( "Moderate Progression; Perfektions-Lücken sind selten echte Mängel." ), "anti_patterns": "Keine Leistungssport-Perfektion als Pflicht-Kriterium.", }, ), ( "target_group", "*", { "hints_on_path_qa": "Zielgruppe im Tempo und in der Komplexität berücksichtigen.", }, ), # --- style_direction --- ( "style_direction", "shotokan", { "description": ( "Shotokan-Linie: klare Kihon-Struktur, Hüft- und Standarbeit als wiederkehrende Qualitätsanker." ), "hints_on_progression": "Nuancen in Stellung und Hüfttechnik; kein neuer Planungstyp.", "hints_on_path_qa": "Konsistenz von Stand, Hüfte und Kime entlang des Pfads bewerten.", }, ), ( "style_direction", "*", { "hints_on_progression": ( "Stil-spezifische Nuancen (Stand, Hüfte, Rhythmus) einbeziehen — ohne Stilwechsel zu erzwingen." ), }, ), ) def normalize_catalog_name_key(name: str) -> str: s = unicodedata.normalize("NFKD", (name or "").translate(_UMLAUT_MAP)) s = s.encode("ascii", "ignore").decode("ascii").lower() s = re.sub(r"[^a-z0-9]+", "_", s).strip("_") return s or "unknown" def get_fallback_slots_for_entry(catalog_kind: str, name: str) -> SlotPack: kind = (catalog_kind or "").strip().lower() norm = normalize_catalog_name_key(name) default: SlotPack = {} for rule_kind, pattern, pack in _FALLBACK_RULES: if rule_kind != kind: continue if pattern == "*": default = dict(pack) continue if pattern in norm or norm.startswith(pattern) or pattern in (name or "").lower(): return dict(pack) return default def merge_stored_slots_with_fallbacks( stored: Mapping[str, str], *, catalog_kind: str, name: str, stammdaten_description: str = "", ) -> Dict[str, str]: """DB + Stammdaten-Beschreibung + Namens-Fallback.""" fallbacks = get_fallback_slots_for_entry(catalog_kind, name) out: Dict[str, str] = {} for key in ( "description", "hints_on_progression", "hints_on_exercise", "hints_on_path_qa", "anti_patterns", "rematch_guard", ): if key == "description": out[key] = ( (stored.get(key) or "").strip() or (fallbacks.get(key) or "").strip() or (stammdaten_description or "").strip() ) else: out[key] = (stored.get(key) or "").strip() or (fallbacks.get(key) or "").strip() return out __all__ = [ "get_fallback_slots_for_entry", "merge_stored_slots_with_fallbacks", "normalize_catalog_name_key", ]