shinkan-jinkendo/backend/tests/test_planning_exercise_retrieval.py
Lars d1d8539b42
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
Refactor Planning Exercise Retrieval and Suggestion Logic
- Updated the planning exercise retrieval process to implement a multistage approach, ranking the entire visible library deterministically against the expectation profile.
- Removed the previous profile OR pool mechanism, simplifying the retrieval logic and ensuring full-text search is only used as a scoring signal.
- Adjusted the `compose_retrieval_phase` function to accommodate the new full library ranking strategy.
- Incremented version to 0.8.177 and updated changelog to reflect these changes in planning exercise capabilities.
2026-05-23 06:35:45 +02:00

110 lines
3.4 KiB
Python

"""Tests Planungs-Retrieval Phase A (Voll-Library-Ranking)."""
from planning_exercise_profiles import ExerciseMatchProfile, PlanningTargetProfile
from planning_exercise_retrieval import (
fetch_all_visible_exercise_rows,
rank_visible_library_hits,
)
def test_fetch_all_visible_has_no_profile_or_pool_filter():
captured = {}
class _Cur:
def execute(self, sql, params):
captured["sql"] = sql
captured["params"] = list(params)
def fetchall(self):
return []
fetch_all_visible_exercise_rows(
_Cur(),
vis_sql="(e.visibility = 'official' OR (e.visibility = 'private' AND e.created_by = %s))",
vis_params=[42],
query="Kime Partner",
exercise_kind_any=["simple"],
)
sql = captured["sql"].lower()
assert "exercise_skills" not in sql
assert "@@ plainto_tsquery" not in sql
assert " exists " not in sql.replace("primary_focus_name", "")
params = captured["params"]
assert params[0] == "Kime Partner"
assert params[1] == 42
assert params[2] == "archived"
assert params[3] == "simple"
assert params[-1] == 8000
def test_rank_visible_library_prefers_profile_over_untagged_pool_miss():
"""Übung ohne Pool-Tags, aber hoher Profil-Match, muss vor schwachem Treffer ranken."""
target = PlanningTargetProfile(
focus_area_ids={10: 1.0},
skill_weights={5: 1.0},
sources=["framework_catalog"],
)
rows = [
{"id": 1, "title": "Schwach", "summary": "", "primary_focus_name": "X", "ft_rank": 0.9},
{"id": 2, "title": "Stark", "summary": "", "primary_focus_name": "Karate", "ft_rank": 0.0},
]
profiles = {
1: ExerciseMatchProfile(exercise_id=1, focus_area_ids={99: 1.0}),
2: ExerciseMatchProfile(exercise_id=2, focus_area_ids={10: 1.0}, skill_weights={5: 1.0}),
}
class _Cur:
def execute(self, sql, params=None):
return None
def fetchall(self):
return []
def _fake_load(cur, exercise_ids, *, batch=400):
_ = (cur, batch)
return {eid: profiles[eid] for eid in exercise_ids if eid in profiles}
def _fake_skills(cur, exercise_ids, *, batch=400):
_ = (cur, batch)
return {1: {99}, 2: {5, 10}}
import planning_exercise_retrieval as mod
orig_profiles = mod._load_match_profiles_chunked
orig_skills = mod._load_skill_sets_chunked
try:
mod._load_match_profiles_chunked = _fake_load
mod._load_skill_sets_chunked = _fake_skills
hits, _ = rank_visible_library_hits(
_Cur(),
rows,
query="",
intent="suggest_next",
intent_weights={
"fulltext": 0.08,
"progression": 0.28,
"skill": 0.12,
"plan": 0.10,
"profile": 0.25,
"repeat_unit": -0.30,
"repeat_group": -0.15,
},
target=target,
pack={
"planned_exercise_ids": [],
"group_recent_exercise_ids": [],
"progression_successor_ids": [],
"anchor_skill_ids": [],
"anchor_exercise_id": None,
"progression_edge_notes": {},
},
)
finally:
mod._load_match_profiles_chunked = orig_profiles
mod._load_skill_sets_chunked = orig_skills
assert hits[0]["id"] == 2
assert hits[0]["score"] > hits[1]["score"]