Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced a new function `_resolve_entry_slot_values` to streamline the merging of stored slot values with fallbacks, improving code clarity and maintainability. - Updated `get_catalog_entry_slots` and `resolve_catalog_prompt_variables` functions to utilize the new fallback logic, enhancing the handling of catalog entries. - Enhanced the `CatalogPromptSlotsEditor` component to display fallback information for slots, improving user experience in managing catalog prompts. - Incremented version numbers and updated changelog to reflect the new features and improvements.
285 lines
11 KiB
Python
285 lines
11 KiB
Python
"""
|
|
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",
|
|
]
|