"""Tests wiederverwendbare Fähigkeiten-Erwartungen (Progressionsgraph + später Planung).""" from unittest.mock import MagicMock from planning_skill_expectations import ( SCOPE_PROGRESSION_PATH, SCOPE_PROGRESSION_STAGE, PlanningSkillExpectationInput, apply_expectations_to_target, build_planning_skill_expectations, expectation_input_from_progression_path, expectation_input_from_progression_stage, ) class _FakeCursor: def __init__(self, skills): self._skills = skills def execute(self, query, params=None): del query if params and len(params) >= 2: needle = str(params[0]).strip("%").lower() exact = str(params[1]).lower() matches = [ s for s in self._skills if needle in s["name"].lower() or s["name"].lower() == exact ] self._row = matches[0] if matches else None else: self._row = None def fetchone(self): return self._row def _fake_skill_rows(): return [ (1, "Koordination", 0), (2, "Timing", 0), (3, "Distanz", 0), (4, "Kime", 0), ] def test_expectation_input_from_progression_stage_merges_sources(monkeypatch): monkeypatch.setattr( "planning_skill_expectations._load_skills_for_text_match", lambda cur: _fake_skill_rows(), ) inp = expectation_input_from_progression_stage( goal_query="Kumite Beinarbeit", goal_analysis={ "primary_topic": "Kumite", "start_assumption": "gleichförmige Steppbewegung", "target_state": "explosiver Angriff", }, resolved_structured={"roadmap_notes": "Kindergruppe"}, stage_spec={ "learning_goal": "variable Rhythmen", "load_profile": ["timing", "distanz"], "phase": "vertiefung", }, semantic_brief_summary={"must_phrases": ["Beinarbeit"], "primary_topic": "Kumite"}, major_step={"phase": "vertiefung", "learning_goal": "Major-Ziel"}, ) assert inp.scope == SCOPE_PROGRESSION_STAGE assert inp.primary_topic == "Kumite" assert inp.start_situation == "gleichförmige Steppbewegung" assert inp.load_profile == ["timing", "distanz"] assert "Beinarbeit" in inp.skill_hints def test_build_planning_skill_expectations_load_profile_and_text(monkeypatch): monkeypatch.setattr( "planning_skill_expectations._load_skills_for_text_match", lambda cur: _fake_skill_rows(), ) cur = _FakeCursor( [ {"id": 2, "name": "Timing"}, {"id": 3, "name": "Distanz"}, ] ) inp = PlanningSkillExpectationInput( scope=SCOPE_PROGRESSION_STAGE, primary_topic="Kumite", goal_query="Kumite Beinarbeit mit Timing", load_profile=["timing", "distanz"], ) exp = build_planning_skill_expectations(cur, inp) assert exp.scope == SCOPE_PROGRESSION_STAGE assert "load_profile" in exp.sources or "text_match" in exp.sources names = {it.skill_name for it in exp.items} assert "Timing" in names or "Distanz" in names assert exp.skill_weights def test_expectation_input_from_progression_path_scope(): inp = expectation_input_from_progression_path( goal_query="Mae Geri Perfektion", goal_analysis={"primary_topic": "Mae Geri"}, resolved_structured={"start_situation": "Grundstellung", "target_state": "freier Kick"}, ) assert inp.scope == SCOPE_PROGRESSION_PATH assert inp.start_situation == "Grundstellung" assert inp.target_state == "freier Kick" def test_apply_expectations_to_target_noop_without_weights(): class _Target: skill_weights = {} def to_summary_dict(self, cur): return {} target = _Target() from planning_skill_expectations import PlanningSkillExpectations empty = PlanningSkillExpectations(scope=SCOPE_PROGRESSION_STAGE, skill_weights={}, items=[], sources=[]) assert apply_expectations_to_target(target, empty) is target