mitai-jinkendo/backend/tests/test_training_profile_resolver.py
Lars f0e6fd04fb
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 15s
feat: Add personal reference values management in settings and API
- Introduced new routes and API endpoints for managing personal reference values.
- Updated the SettingsPage to include a section for reference values with navigation to manage them.
- Enhanced the backend to support reference values in the data layer and versioning.
- Added necessary imports and UI components for a seamless user experience.
2026-04-06 19:45:06 +02:00

139 lines
4.7 KiB
Python

"""
Unit tests: Layer 1 training profile resolver scaffold.
No database; pure template + algorithm + resolver behavior.
"""
import pytest
from data_layer.training_profile import (
CalculationTemplate,
DimensionSpec,
FocusAreaMapping,
TrainingEvaluationResult,
resolve_for_base_profile,
resolve_training_evaluation,
)
from data_layer.training_profile.algorithms.registry import (
get_algorithm,
list_algorithm_ids,
register_algorithm,
)
from data_layer.training_profile.models import AlgorithmRunResult
from data_layer.training_profile.profiles.registry import get_training_base_profile
from data_layer.training_profile.templates.registry import get_calculation_template
class TestAlgorithmRegistry:
def test_builtin_algorithms_registered(self):
ids = list_algorithm_ids()
assert "threshold_band" in ids
assert "linear_range" in ids
def test_get_algorithm_runs_threshold(self):
fn = get_algorithm("threshold_band")
r = fn(
inputs={"avg_hr": 130.0},
params={
"value_key": "avg_hr",
"bands": [
{"max": 120, "score": 0.2},
{"max": 150, "score": 0.8},
{"max": None, "score": 1.0},
],
},
)
assert r.normalized_score == 0.8
def test_duplicate_register_raises(self):
def dummy(*, inputs, params):
return AlgorithmRunResult(0.0, 0.0, [])
with pytest.raises(ValueError, match="already registered"):
register_algorithm("threshold_band", dummy)
class TestResolver:
def test_example_template_resolves(self):
tpl = get_calculation_template("scaffold_example_aerobic_v1")
result = resolve_training_evaluation(
activity_inputs={
"avg_hr": 135.0,
"duration_min": 45.0,
"distance_km": 10.0,
},
template=tpl,
)
assert isinstance(result, TrainingEvaluationResult)
assert result.template_id == "scaffold_example_aerobic_v1"
assert result.confidence == "high"
assert "aerobic_endurance" in result.focus_area_contributions
assert len(result.dimension_results) == 2
for dr in result.dimension_results:
assert dr.missing_inputs == []
def test_missing_required_input_skips_dimension(self):
tpl = get_calculation_template("scaffold_example_aerobic_v1")
result = resolve_training_evaluation(
activity_inputs={"avg_hr": 135.0},
template=tpl,
)
assert result.confidence in ("medium", "low", "insufficient")
skipped = [d for d in result.dimension_results if d.evidence.get("skipped")]
assert len(skipped) >= 1
def test_base_profile_filters_dimensions(self):
profile = get_training_base_profile("scaffold_strength_base")
tpl = get_calculation_template(profile.default_template_id)
result = resolve_training_evaluation(
activity_inputs={"duration_min": 50.0},
template=tpl,
base_profile=profile,
)
assert len(result.dimension_results) == 1
assert result.dimension_results[0].dimension_key == "effort"
def test_resolve_for_base_profile_convenience(self):
result = resolve_for_base_profile(
activity_inputs={"duration_min": 40.0},
base_profile_key="scaffold_strength_base",
include_trace=True,
)
assert result.base_profile_key == "scaffold_strength_base"
assert result.trace is not None
assert "effort" in result.trace
def test_to_serializable(self):
tpl = get_calculation_template("scaffold_example_strength_v1")
r = resolve_training_evaluation(
activity_inputs={"duration_min": 45.0},
template=tpl,
)
d = r.to_serializable()
assert d["template_id"] == tpl.id
assert "focus_area_contributions" in d
assert isinstance(d["dimension_results"], list)
class TestCustomTemplate:
def test_unknown_algorithm_raises(self):
bad = CalculationTemplate(
id="bad",
version="1",
label="bad",
dimensions=(
DimensionSpec(
key="x",
algorithm_id="does_not_exist",
inputs=("a",),
params={},
maps_to=(FocusAreaMapping("strength", 1.0),),
),
),
)
with pytest.raises(KeyError):
resolve_training_evaluation(
activity_inputs={"a": 1.0},
template=bad,
)