shinkan-jinkendo/backend/tests/test_planning_progression_roadmap.py
Lars 9dd44ce3ca
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
Add Structured Roadmap Inputs and Enhance Goal Analysis Features
- Introduced `RoadmapStructuredInput` to encapsulate structured inputs for start situation, target state, and roadmap notes.
- Updated `ProgressionPathSuggestRequest` to include new fields for structured roadmap inputs.
- Implemented parsing logic for goal queries to extract start and target states, enhancing the goal analysis process.
- Enhanced `build_goal_analysis` to utilize structured inputs, improving the clarity and relevance of generated goals.
- Updated the `ExerciseProgressionPathBuilder` component to support new structured input fields, enhancing user experience.
- Incremented application version to 0.8.210 to reflect these changes.
2026-06-09 11:10:46 +02:00

195 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Tests Planungs-KI Phase F — Progressions-Roadmap Pipeline."""
from planning_progression_roadmap import (
PROMPT_SLUG_GOAL_ANALYSIS,
PROMPT_SLUG_ROADMAP,
PROMPT_SLUG_STAGE_SPEC,
MajorStep,
RoadmapStructuredInput,
StageSpecArtifact,
build_goal_analysis,
build_roadmap_unfilled_gap_specs,
consolidate_micro_to_major,
develop_micro_objectives,
parse_start_target_from_goal_query,
progression_roadmap_to_api_dict,
resolve_step_exercise_kind_filter,
run_progression_roadmap_pipeline,
stage_spec_exercise_kind_filter,
stage_spec_retrieval_query,
normalize_major_steps_for_override,
roadmap_context_from_override,
RoadmapOverridePayload,
)
from planning_exercise_semantics import build_semantic_brief
KUMITE_GOAL = (
"Kumite Beinarbeit von einer gleichartigen Steppbewegung bis zur dynamischen "
"unvorhersehbaren Bewegung mit explosivartigem Angriff und ausweichen"
)
def test_run_progression_roadmap_pipeline_major_step_count():
ctx = run_progression_roadmap_pipeline(
"Von Erlernen bis zur Perfektion des Fußtritts Mae Geri",
max_steps=5,
)
assert ctx.roadmap is not None
assert len(ctx.roadmap.major_steps) == 5
assert len(ctx.roadmap.micro_objectives) >= 6
assert len(ctx.stage_specs) == 5
assert ctx.goal_analysis is not None
assert "Mae" in ctx.goal_analysis.primary_topic or "mae" in ctx.goal_analysis.primary_topic.lower()
def test_consolidate_micro_to_major_reduces_count():
brief = build_semantic_brief("Mae Geri")
ga = build_goal_analysis("Mae Geri Perfektion", brief)
micro = develop_micro_objectives(brief, goal_analysis=ga, min_count=8)
majors, notes = consolidate_micro_to_major(micro, max_steps=5)
assert len(majors) == 5
if len(micro) > 5:
assert notes
assert all(m.learning_goal for m in majors)
def test_major_steps_have_learning_goals():
ctx = run_progression_roadmap_pipeline("Mae Geri Grundlagen", max_steps=3)
for step in ctx.roadmap.major_steps:
assert step.learning_goal.strip()
assert step.consolidates
def test_stage_spec_retrieval_query_includes_learning_goal():
brief = build_semantic_brief("Mae Geri Perfektion")
spec = StageSpecArtifact(
major_step_index=1,
learning_goal="Koordination und Präzision vertiefen",
load_profile=["präzision"],
exercise_type="kihon_einzel",
)
major = MajorStep(index=1, phase="vertiefung", learning_goal=spec.learning_goal, consolidates=["m3"])
q = stage_spec_retrieval_query(
semantic_brief=brief,
goal_query="Mae Geri Perfektion",
stage_spec=spec,
major_step=major,
)
assert "vertiefung" in q.lower()
assert "Koordination" in q or "Präzision" in q
def test_stage_spec_exercise_kind_filter_maps_combination():
spec = StageSpecArtifact(major_step_index=0, exercise_type="kombination")
assert stage_spec_exercise_kind_filter(spec) == ["combination"]
assert resolve_step_exercise_kind_filter(spec, ["simple"]) == ["simple"]
def test_build_roadmap_unfilled_gap_specs():
brief = build_semantic_brief("Mae Geri")
spec = StageSpecArtifact(major_step_index=2, learning_goal="Anwendung im Partnerdrill")
major = MajorStep(index=2, phase="anwendung", learning_goal=spec.learning_goal, consolidates=["m5"])
specs = build_roadmap_unfilled_gap_specs(
unfilled_specs=[(2, spec)],
major_steps_by_index={2: major},
steps=[{"exercise_id": 1, "title": "A"}, {"exercise_id": 2, "title": "B"}],
brief=brief,
goal_query="Mae Geri",
)
assert len(specs) == 1
assert specs[0]["source"] == "roadmap_unfilled"
assert specs[0]["phase"] == "anwendung"
def test_normalize_major_steps_reindexes():
majors = normalize_major_steps_for_override(
[
MajorStep(index=9, phase="einstieg", learning_goal="Einstieg", consolidates=[]),
MajorStep(index=8, phase="perfektion", learning_goal="Ziel", consolidates=[]),
],
max_steps=5,
)
assert len(majors) == 2
assert majors[0].index == 0
assert majors[1].index == 1
def test_roadmap_context_from_override():
brief = build_semantic_brief("Mae Geri Perfektion")
override = RoadmapOverridePayload(
major_steps=[
MajorStep(index=0, phase="einstieg", learning_goal="Mae Geri Einstieg", consolidates=[]),
MajorStep(index=1, phase="grundlage", learning_goal="Stand und Hüfte", consolidates=[]),
MajorStep(index=2, phase="perfektion", learning_goal="Präzision unter Belastung", consolidates=[]),
]
)
ctx = roadmap_context_from_override(
"Mae Geri Perfektion",
max_steps=5,
semantic_brief=brief,
override=override,
)
assert ctx.pipeline_phase == "roadmap_v1_edited"
assert len(ctx.roadmap.major_steps) == 3
assert len(ctx.stage_specs) == 3
assert ctx.stage_specs[1].learning_goal == "Stand und Hüfte"
def test_api_dict_exposes_prompt_slug_catalog():
ctx = run_progression_roadmap_pipeline("Mae Geri", max_steps=3, include_llm_roadmap=False)
api = progression_roadmap_to_api_dict(ctx)
assert api["prompt_slug_catalog"]["goal_analysis"] == PROMPT_SLUG_GOAL_ANALYSIS
assert api["prompt_slug_catalog"]["roadmap"] == PROMPT_SLUG_ROADMAP
assert api["prompt_slug_catalog"]["stage_spec"] == PROMPT_SLUG_STAGE_SPEC
assert api["prompt_slugs"] == []
def test_parse_start_target_kumite_beinarbeit():
start, target = parse_start_target_from_goal_query(KUMITE_GOAL)
assert start is not None
assert "Steppbewegung" in start
assert target is not None
assert "dynamischen" in target
assert "Angriff" in target
def test_build_goal_analysis_uses_parsed_start_target():
brief = build_semantic_brief(KUMITE_GOAL)
ga = build_goal_analysis(KUMITE_GOAL, brief)
assert "Kumite Beinarbeit" in ga.primary_topic
assert "Steppbewegung" in ga.start_assumption
assert "dynamischen" in ga.target_state
assert "Voraussetzungen der Zielgruppe werden im Progressionsgraphen nicht analysiert" not in ga.start_assumption
def test_build_goal_analysis_structured_fields_override():
brief = build_semantic_brief("Kumite Beinarbeit")
structured = RoadmapStructuredInput(
start_situation="statische Vorwärtsbewegung im Partnerdrill",
target_state="explosiver Gegenangriff nach unvorhersehbarer Beinarbeit",
roadmap_notes="Kindergruppe 1012 Jahre",
)
ga = build_goal_analysis("Kumite Beinarbeit", brief, structured=structured)
assert ga.start_assumption == structured.start_situation
assert ga.target_state == structured.target_state
assert any("Kindergruppe" in c for c in ga.success_criteria)
def test_develop_micro_objectives_start_target_kumite():
brief = build_semantic_brief(KUMITE_GOAL)
ga = build_goal_analysis(KUMITE_GOAL, brief)
micro = develop_micro_objectives(brief, goal_analysis=ga, min_count=6)
titles = [m.title for m in micro]
assert any("Ausgang" in t for t in titles)
assert any("Ziel" in t for t in titles)
assert not any("Einstieg und Orientierung zum Thema" in t for t in titles)
def test_pipeline_kumite_major_steps_not_generic_templates():
ctx = run_progression_roadmap_pipeline(KUMITE_GOAL, max_steps=5, include_llm_roadmap=False)
goals = [s.learning_goal for s in ctx.roadmap.major_steps]
joined = " ".join(goals).lower()
assert "kumite beinarbeit" in joined
assert "steppbewegung" in joined or "ausgang" in joined
assert "dynamisch" in joined or "ziel" in joined
assert not any(g == "Grundstellung und Basisbewegung" for g in goals)