Use rir in the 1 RM calculation, if available

This commit is contained in:
Roland Geider
2026-01-10 12:42:51 +01:00
parent db362bf94d
commit 65a0108657
5 changed files with 50 additions and 60 deletions

View File

@@ -15,6 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Standard Library
import logging
from decimal import Decimal
from typing import Optional
# wger
@@ -26,6 +28,9 @@ from wger.trophies.models.user_trophy import UserTrophy
from .base import BaseTrophyChecker
logger = logging.getLogger(__name__)
class PersonalRecordChecker(BaseTrophyChecker):
"""
Checker for Personal Record (PR) repeatable trophy.
@@ -37,37 +42,33 @@ class PersonalRecordChecker(BaseTrophyChecker):
"""
def _estimate_one_rep_max(self):
def _estimate_one_rep_max(self) -> float:
"""
Brzycki's formula: 1RM = weight * (36 / (37 - repetitions))
"""
log: WorkoutLog | None = self.params.get('log', None)
Estimates the user's one-rep max (1RM) using Brzycki's formula:
1RM = weight * (36 / (37 - repetitions))
Note: returning float because of serialization issues with Decimal in JSON.
"""
log: WorkoutLog | None = self.params.get('log')
if not log:
raise ValueError('Log should not be None')
weight = getattr(log, 'weight', None)
repetitions = getattr(log, 'repetitions', None)
weight = log.weight
repetitions = log.repetitions
rir = log.rir
if weight is None:
raise ValueError('Weight should not be None')
if repetitions is None:
raise ValueError('Repetitions should not be None')
if rir is not None:
repetitions += rir
try:
reps = int(repetitions)
except (TypeError, ValueError):
raise ValueError('Repetitions must be an integer')
if reps == 37:
if repetitions == 37:
raise ValueError("In Brzycki's formula, repetitions cannot be equal to 37.")
try:
w = float(weight)
except (TypeError, ValueError):
raise ValueError('Weight must be a number')
return round(w * (36.0 / float(37 - reps)), 2)
result = weight * (Decimal('36') / (Decimal('37') - repetitions))
return round(float(result), 2)
def check(self) -> bool:
"""Check if user has beaten Personal Record."""
@@ -109,43 +110,41 @@ class PersonalRecordChecker(BaseTrophyChecker):
return 100.0 if self.check() else 0.0
def get_context_data(self) -> Optional[dict]:
log = self.params.get('log', None)
log: WorkoutLog | None = self.params.get('log', None)
if not log:
return None
session = getattr(log, 'session', None)
exercise = getattr(log, 'exercise', None)
repetitions_unit = getattr(log, 'repetitions_unit', None)
weight_unit = getattr(log, 'weight_unit', None)
repetitions = getattr(log, 'repetitions', None)
weight = getattr(log, 'weight', None)
session = log.session
exercise = log.exercise
repetitions_unit = log.repetitions_unit
weight_unit = log.weight_unit
repetitions = log.repetitions
weight = log.weight
try:
one_rm_estimate = self._estimate_one_rep_max()
except Exception as e:
print(f'PR estimation failed : {e}')
logger.warning(f'PR estimation failed : {e}')
one_rm_estimate = None
return {
'log_id': getattr(log, 'id', None),
'date': getattr(log, 'date', None).isoformat(),
'session_id': getattr(session, 'id', None) if session else None,
'exercise_id': getattr(exercise, 'id', None) if exercise else None,
'repetitions_unit_id': getattr(repetitions_unit, 'id', None)
if repetitions_unit
else None,
'log_id': log.id,
'date': log.date.isoformat(),
'session_id': session.id if session else None,
'exercise_id': exercise.id if exercise else None,
'repetitions_unit_id': repetitions_unit.id if repetitions_unit else None,
'repetitions': float(repetitions) if repetitions else None,
'weight_unit_id': getattr(weight_unit, 'id', None) if weight_unit else None,
'weight_unit_id': weight_unit.id if weight_unit else None,
'weight': float(weight) if weight else None,
'iteration': getattr(log, 'iteration', None),
'iteration': log.iteration,
'one_rep_max_estimate': one_rm_estimate,
}
def get_target_value(self) -> int:
def get_target_value(self) -> str:
return 'N/A'
def get_current_value(self) -> int:
def get_current_value(self) -> str:
return 'N/A'
def get_progress_display(self) -> str:

View File

@@ -35,6 +35,7 @@ from wger.trophies.models import (
UserTrophy,
)
logger = logging.getLogger(__name__)
# Trophy settings from WGER_SETTINGS (defined in settings_global.py)

View File

@@ -26,6 +26,7 @@ from wger.exercises.models.category import ExerciseCategory
from wger.manager.models.log import WorkoutLog
from wger.trophies.checkers.date_based import DateBasedChecker
from wger.trophies.checkers.inactivity_return import InactivityReturnChecker
from wger.trophies.checkers.personal_record import PersonalRecordChecker
from wger.trophies.checkers.streak import StreakChecker
from wger.trophies.checkers.time_based import TimeBasedChecker
from wger.trophies.checkers.volume import VolumeChecker
@@ -509,16 +510,10 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
)
def test_check_returns_false_with_no_logs(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
checker = PersonalRecordChecker(self.user, self.trophy, {})
self.assertFalse(checker.check())
def test_first_log_counts_as_pr(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100)
checker = PersonalRecordChecker(self.user, self.trophy, {'log': log})
@@ -528,9 +523,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
self.assertIsNotNone(context)
def test_improvement_detected_and_context_values(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
log1 = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100)
checker1 = PersonalRecordChecker(self.user, self.trophy, {'log': log1})
self.assertTrue(checker1.check())
@@ -543,9 +535,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
self.assertIsNotNone(context)
def no_award_if_not_an_improvement(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
log1 = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100)
checker1 = PersonalRecordChecker(self.user, self.trophy, {'log': log1})
self.assertTrue(checker1.check())
@@ -566,9 +555,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
self.assertFalse(checker4.check())
def test_estimate_1rm(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100)
one_rm = round(100 * (36.0 / (37 - 10)), 2)
@@ -577,10 +563,16 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
self.assertEqual(estimate, one_rm)
self.assertEqual(checker.get_context_data().get('one_rep_max_estimate'), one_rm)
def test_estimate_1rm_raises_on_missing_values(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
def test_estimate_1rm_with_rir(self):
log = WorkoutLog(user=self.user, exercise=self.exercise, repetitions=10, weight=100, rir=2)
one_rm = round(100 * (36.0 / (37 - 12)), 2)
checker = PersonalRecordChecker(self.user, self.trophy, {'log': log})
estimate = checker._estimate_one_rep_max()
self.assertEqual(estimate, one_rm)
self.assertEqual(checker.get_context_data().get('one_rep_max_estimate'), one_rm)
def test_estimate_1rm_raises_on_missing_values(self):
# No log
checker = PersonalRecordChecker(self.user, self.trophy, {})
with self.assertRaises(ValueError):
@@ -602,9 +594,6 @@ class PersonalRecordCheckerTestCase(WgerTestCase):
self.assertIsNone(context.get('one_rep_max_estimate'))
def test_estimate_1rm_raises_on_repetitions_37(self):
# wger
from wger.trophies.checkers.personal_record import PersonalRecordChecker
log = WorkoutLog(user=self.user, exercise=self.exercise, weight=100, repetitions=37)
checker = PersonalRecordChecker(self.user, self.trophy, {'log': log})
with self.assertRaises(ValueError):

View File

@@ -18,6 +18,7 @@ import logging
# wger
from wger.core.tests.base_testcase import WgerAccessTestCase
logger = logging.getLogger(__name__)
@@ -25,6 +26,7 @@ class TrophiesOverviewTestCase(WgerAccessTestCase):
"""
Test case for the trophies overview page
"""
url = 'trophies:admin-overview'
anonymous_fail = True
user_success = 'admin'
@@ -41,5 +43,3 @@ class TrophiesOverviewTestCase(WgerAccessTestCase):
'member4',
'member5',
)

View File

@@ -25,6 +25,7 @@ from django.views.generic import ListView
# wger
from wger.trophies.models import Trophy
logger = logging.getLogger(__name__)