shinkan-jinkendo/backend/tests/test_planning_path_rematch.py
Lars 713a344d17
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 52s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 41s
Test Suite / playwright-tests (push) Successful in 1m51s
Enhance Roadmap Step Handling and Off-Topic Logic
- Improved off-topic step handling by incorporating roadmap major step indices for better indexing and detection.
- Refactored `collect_gap_fill_specs` to streamline the insertion logic for off-topic steps, ensuring correct placement based on major step indices.
- Introduced `_normalize_roadmap_steps_coverage` function to standardize roadmap steps coverage, enhancing the handling of missing slots.
- Added `prune_stripped_after_rematch` function to clean up stripped off-topic steps after rematching, improving the overall rematching process.
- Updated tests to validate new rematching and off-topic handling features, ensuring robustness against edge cases.
- Incremented application version to reflect these updates.
2026-06-11 10:40:25 +02:00

173 lines
4.7 KiB
Python

"""Tests Auto-Rematch nach Pfad-QS (Phase A)."""
from planning_path_rematch import collect_rematch_slot_indices, rematch_roadmap_slots
from planning_progression_roadmap import ProgressionRoadmapContext, StageSpecArtifact
def _stage_specs():
return [
StageSpecArtifact(major_step_index=0, learning_goal="Grundlage"),
StageSpecArtifact(major_step_index=1, learning_goal="Vertiefung"),
StageSpecArtifact(major_step_index=2, learning_goal="Anwendung"),
]
def test_collect_rematch_slot_indices_from_stripped_with_major_index():
specs = _stage_specs()
stripped = [
{
"step_index": 1,
"roadmap_major_step_index": 1,
"issue": "technique_scope",
"reasons": ["Passt nicht zur Haupttechnik"],
}
]
indices, reasons = collect_rematch_slot_indices(
stripped_off_topic=stripped,
off_topic_steps=[],
optimization_hints=[],
stage_specs=specs,
)
assert indices == {1}
assert "Haupttechnik" in reasons[1]
def test_collect_rematch_slot_indices_resolves_step_index_to_major():
specs = _stage_specs()
off_topic = [
{
"step_index": 2,
"issue": "stage_mismatch",
"reasons": ["Ziel passt nicht"],
}
]
indices, reasons = collect_rematch_slot_indices(
stripped_off_topic=[],
off_topic_steps=off_topic,
optimization_hints=[],
stage_specs=specs,
)
assert indices == {2}
assert reasons[2] == "Ziel passt nicht"
def test_collect_rematch_slot_indices_from_optimization_hints():
specs = _stage_specs()
hints = [
{
"action": "rematch_slot",
"roadmap_major_step_index": 0,
"reason": "QS-Tier-1",
}
]
indices, _ = collect_rematch_slot_indices(
stripped_off_topic=[],
off_topic_steps=[],
optimization_hints=hints,
stage_specs=specs,
)
assert indices == {0}
def test_rematch_roadmap_slots_replaces_only_target_slot():
specs = _stage_specs()
ctx = ProgressionRoadmapContext(
goal_query="Mawashi Geri",
max_steps=3,
stage_specs=specs,
)
steps = [
{
"exercise_id": 10,
"title": "Slot 0 OK",
"roadmap_major_step_index": 0,
},
{
"exercise_id": 20,
"title": "Mae Geri falsch",
"roadmap_major_step_index": 1,
},
{
"exercise_id": 30,
"title": "Slot 2 OK",
"roadmap_major_step_index": 2,
},
]
def _fake_match(cur, *, stage_spec, used, **kwargs):
assert stage_spec.major_step_index == 1
assert 20 in used
assert 10 in used
assert 30 in used
return (
{
"exercise_id": 21,
"title": "Sprungkraft Mawashi",
"roadmap_major_step_index": 1,
},
None,
)
ordered, log, unfilled = rematch_roadmap_slots(
None,
tenant=None,
body=None,
goal_query="Mawashi Geri",
max_steps=3,
semantic_brief=None,
path_target_profile=None,
path_intent="",
roadmap_ctx=ctx,
steps=steps,
slot_indices={1},
rematch_reasons={1: "technique_scope"},
match_slot_fn=_fake_match,
)
assert len(ordered) == 3
assert ordered[0]["exercise_id"] == 10
assert ordered[1]["exercise_id"] == 21
assert ordered[2]["exercise_id"] == 30
assert len(log) == 1
assert log[0]["action"] == "replaced"
assert log[0]["replaced_exercise_id"] == 20
assert log[0]["new_exercise_id"] == 21
assert not unfilled
def test_rematch_excludes_replaced_exercise_from_used():
specs = _stage_specs()
ctx = ProgressionRoadmapContext(
goal_query="Mawashi Geri",
max_steps=3,
stage_specs=specs,
)
steps = [
{"exercise_id": 10, "title": "OK", "roadmap_major_step_index": 0},
{"exercise_id": 99, "title": "Mae Geri", "roadmap_major_step_index": 1},
]
seen_used = []
def _fake_match(cur, *, used, stage_spec, **kwargs):
seen_used.append(set(used))
return (
{"exercise_id": 42, "title": "Neu", "roadmap_major_step_index": stage_spec.major_step_index},
None,
)
rematch_roadmap_slots(
None,
tenant=None,
body=None,
goal_query="Mawashi",
max_steps=3,
semantic_brief=None,
path_target_profile=None,
path_intent="",
roadmap_ctx=ctx,
steps=steps,
slot_indices={1},
rematch_reasons={1: "technique_scope"},
match_slot_fn=_fake_match,
)
assert 99 in seen_used[0]