f1tness_parser/tests/test_core/test_metrics.py
2026-10-22 23:46:29 +03:00

116 lines
3.8 KiB
Python

from datetime import date
import pytest
from app.core.dto.training import ApproachDTO, ExerciseSessionDTO
from app.core.metrics import estimate_1rm, session_metrics, summarize
def make_session(day: int, *sets: tuple, month: int = 1) -> ExerciseSessionDTO:
return ExerciseSessionDTO(
date=date(2025, month, day),
approaches=[ApproachDTO(weight=weight, reps=reps) for weight, reps in sets],
)
def test_estimate_1rm_uses_epley_formula():
assert estimate_1rm(140, 12) == pytest.approx(196)
assert estimate_1rm(100, 10) == pytest.approx(133.333, rel=1e-4)
def test_single_rep_is_the_weight_itself():
assert estimate_1rm(100, 1) == 100
def test_session_metrics_of_weighted_sets():
metrics = session_metrics(make_session(1, (100, 12), (110, 10)).approaches)
assert metrics.max_weight == 110
assert metrics.e1rm == pytest.approx(max(140.0, 110 * (1 + 10 / 30)))
assert metrics.volume == 100 * 12 + 110 * 10
assert metrics.total_reps == 22
assert metrics.best_reps == 12
def test_sets_without_weight_count_only_as_reps():
metrics = session_metrics(make_session(1, (0, 7), (47, 12)).approaches)
assert metrics.e1rm == pytest.approx(estimate_1rm(47, 12))
assert metrics.volume == 47 * 12
assert metrics.total_reps == 19
def test_session_without_weight_has_no_e1rm():
metrics = session_metrics(make_session(1, (0, 11), (0, 8)).approaches)
assert metrics.e1rm is None
assert metrics.volume == 0
assert metrics.total_reps == 19
assert metrics.best_reps == 11
def test_weighted_progress_records_and_change():
# newest first, like the repository returns
history = [
make_session(20, (100, 12)), # e1RM 140
make_session(10, (110, 12), (100, 12)), # e1RM 154 (record)
make_session(1, (80, 10)), # e1RM 106.67
]
progress = summarize(history)
assert progress.weighted is True
assert progress.max_weight == 110
assert progress.best_primary.value == pytest.approx(154)
assert progress.best_primary.date == date(2025, 1, 10)
assert progress.best_secondary.value == 110 * 12 + 100 * 12
assert progress.first_primary == pytest.approx(80 * (1 + 10 / 30))
assert progress.last_primary == pytest.approx(140)
assert progress.primary_change == pytest.approx(140 - 80 * (1 + 10 / 30))
assert progress.primary_change_percent == pytest.approx(
(140 - 80 * (1 + 10 / 30)) / (80 * (1 + 10 / 30)) * 100
)
def test_record_keeps_the_first_date_it_was_reached():
history = [make_session(20, (100, 12)), make_session(10, (100, 12))]
assert summarize(history).best_primary.date == date(2025, 1, 10)
def test_exercise_without_weight_is_tracked_by_reps():
history = [
make_session(20, (0, 8), (0, 7)), # best 8, total 15
make_session(10, (0, 10), (0, 5)), # best 10, total 15
make_session(1, (0, 6)), # best 6, total 6
]
progress = summarize(history)
assert progress.weighted is False
assert progress.max_weight == 0
assert progress.best_primary.value == 10
assert progress.best_primary.date == date(2025, 1, 10)
assert progress.best_secondary.value == 15
assert progress.best_secondary.date == date(2025, 1, 10)
assert (progress.first_primary, progress.last_primary) == (6, 8)
assert progress.primary_change == 2
def test_weighted_exercise_ignores_sessions_without_weight_for_primary_metric():
history = [
make_session(20, (0, 12)), # no e1RM
make_session(10, (100, 10)),
]
progress = summarize(history)
assert progress.weighted is True
assert progress.last_primary == pytest.approx(estimate_1rm(100, 10))
assert progress.primary_change == 0
def test_percent_change_is_undefined_when_the_first_value_is_zero():
progress = summarize([make_session(1, (0, 0))])
assert progress.primary_change_percent is None