first commit

This commit is contained in:
vanalmsick 2025-09-27 18:19:06 +01:00
commit e7f627801f
152 changed files with 35352 additions and 0 deletions

View file

@ -0,0 +1,177 @@
import datetime
from django.db.models import Min
from django.core.cache import cache
from workout_challenge.celery import app, is_task_already_executing
from django.apps import apps
from django.contrib.auth import get_user_model
def trigger_recalc_points():
last_recalc = cache.get('last_recalc_points', None)
if last_recalc is None or last_recalc < datetime.datetime.now() - datetime.timedelta(seconds=30):
cache.set('last_recalc_points', datetime.datetime.now(), 60 * 10)
eta = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=10)
recalc_points.apply_async(eta=eta)
else:
print('Recalc points task skipped because it was triggered less than 30 seconds ago')
@app.task(bind=True, time_limit=60 * 30, max_retries=3) # 30 min time limit
def recalc_points(self):
if is_task_already_executing('recalc_points'):
print('Recalc points task skipped because it is already running')
return 'Skipped because it is already running.'
print('Recalculating points...')
ActivityGoal = apps.get_model('competition', 'ActivityGoal')
Points = apps.get_model('competition', 'Points')
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
all_tasks = RecalcRequest.objects.filter(done=False)
grouped_tasks = all_tasks.values('user', 'goal').annotate(start_datetime=Min('start_datetime'))
for task_group in grouped_tasks:
points_lst = Points.objects.filter(goal=task_group['goal'], workout__user=task_group['user'], workout__start_datetime__gte=task_group['start_datetime']).order_by('workout__start_datetime')
goal = ActivityGoal.objects.get(pk=task_group['goal'])
scorer = Scorer()
scorer.set_goal(goal)
for points in points_lst:
earned_points = scorer.calculate_points(points)
setattr(points, 'points_capped', earned_points)
points.save()
all_tasks.delete()
print('All points recalculated.')
return [{k: str(v) for k, v in i.items()} for i in grouped_tasks]
class Scorer:
def __init__(self):
self.memory_today = None
self.memory_today_points_raw = 0
self.memory_today_points_capped = 0
self.memory_this_week = None
self.memory_week_points_raw = 0
self.memory_week_points_capped = 0
def set_goal(self, goal):
self.goal = goal
self.floor_workout = 0 if goal.min_per_workout is None else goal.min_per_workout / goal.goal * 100
self.cap_workout = None if goal.max_per_workout is None else goal.max_per_workout / goal.goal * 100
self.floor_day = 0 if goal.min_per_day is None else goal.min_per_day / goal.goal * 100
self.cap_day = None if goal.max_per_day is None else goal.max_per_day / goal.goal * 100
self.floor_week = 0 if goal.min_per_week is None else goal.min_per_week / goal.goal * 100
self.cap_week = None if goal.max_per_week is None else goal.max_per_week / goal.goal * 100
def calculate_points(self, points):
# potentially reset the memory if new day / week
if points.workout.start_datetime.date() != self.memory_today:
self.memory_today = points.workout.start_datetime.date()
self.memory_today_points_raw = 0
self.memory_today_points_capped = 0
if points.workout.start_datetime.isocalendar()[1] != self.memory_this_week:
self.memory_this_week = points.workout.start_datetime.isocalendar()[1]
self.memory_week_points_raw = 0
self.memory_week_points_capped = 0
earned_points = points.points_raw
self.memory_today_points_raw += earned_points
self.memory_week_points_raw += earned_points
earned_points = points.points_raw
# workout floor
earned_points = max(earned_points - self.floor_workout, 0)
# workout cap
if self.cap_workout is not None:
adjusted_cap = self.cap_workout - self.floor_workout
earned_points = min(earned_points, adjusted_cap)
# day floor
earned_points = max(min(earned_points, self.memory_today_points_raw - self.floor_day), 0)
# day cap
if self.cap_day is not None:
max_points_to_earn_today = self.cap_day - self.floor_day
earned_points = max(min(earned_points, max_points_to_earn_today - self.memory_today_points_capped),0)
# week floor
earned_points = max(min(earned_points, self.memory_week_points_raw - self.floor_week), 0)
# week cap
if self.cap_week is not None:
max_points_to_earn_week = self.cap_week - self.floor_week
earned_points = max(min(earned_points, max_points_to_earn_week - self.memory_week_points_capped),0)
self.memory_today_points_capped += earned_points
self.memory_week_points_capped += earned_points
return earned_points
class DummyObject:
def __init__(self, **kwargs):
self.min_per_workout = None
self.max_per_workout = None
self.min_per_day = None
self.max_per_day = None
self.min_per_week = None
self.max_per_week = None
for key, value in kwargs.items():
setattr(self, key, value)
def test_scorer():
for goal_kwargs, points, expected_result in (
({'goal': 100, 'min_per_workout': 10}, [10], 0),
({'goal': 100, 'min_per_workout': 10}, [20], 10),
({'goal': 100, 'max_per_workout': 30}, [30], 30),
({'goal': 100, 'max_per_workout': 30}, [40], 30),
({'goal': 100, 'min_per_workout': 10, 'max_per_workout': 30}, [40], 20),
({'goal': 100, 'min_per_day': 10}, [10], 0),
({'goal': 100, 'min_per_day': 10}, [20], 10),
({'goal': 100, 'min_per_day': 10}, [8, 8], 6),
({'goal': 100, 'max_per_day': 30}, [20], 20),
({'goal': 100, 'max_per_day': 30}, [20, 20], 30),
({'goal': 100, 'min_per_day': 10, 'max_per_day': 30}, [8, 12, 8, 8, 14], 20),
({'goal': 100, 'min_per_week': 10}, [10], 0),
({'goal': 100, 'min_per_week': 10}, [20], 10),
({'goal': 100, 'max_per_week': 30}, [20], 20),
({'goal': 100, 'max_per_week': 30}, [20, 20], 30),
({'goal': 100, 'min_per_week': 10, 'max_per_week': 30}, [8, 12, 8, 8, 14], 20),
({'goal': 100, 'min_per_workout': 10, 'min_per_day': 20}, [5, 20, 5, 20], 15),
({'goal': 100, 'min_per_workout': 20, 'min_per_day': 10}, [5, 30, 30], 20),
({'goal': 100, 'max_per_workout': 20, 'max_per_day': 30}, [20, 25, 25, 25], 30),
({'goal': 100, 'max_per_workout': 30, 'max_per_day': 20}, [20, 25, 25, 25], 20),
({'goal': 100, 'min_per_workout': 10, 'max_per_day': 15}, [5, 5, 5, 5], 0),
({'goal': 100, 'min_per_workout': 10, 'max_per_day': 30}, [15, 35, 5, 15], 30),
):
workout = DummyObject(start_datetime=datetime.datetime.fromisoformat('2023-01-01T00:00:00'))
goal = DummyObject(**goal_kwargs)
scorer = Scorer()
scorer.set_goal(goal)
earned_points = 0
for point in points:
point_obj = DummyObject(points_raw=point, workout = workout)
earned_points += scorer.calculate_points(point_obj)
assert earned_points == expected_result, f'Expected {expected_result}, got {earned_points} for goal {goal_kwargs}'
if __name__ == '__main__':
test_scorer()