From 2dea905b3cef2a4ad1413b3a17f3767c9fda9265 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 26 Jul 2024 16:31:29 +0200 Subject: [PATCH] improved --- rowers/session_utils.py | 197 ++++++++++++++++++++++++++ rowers/tasks.py | 48 ++++++- rowers/templates/sessionstats.html | 11 ++ rowers/tests/testdata/testdata.tcx.gz | Bin 4002 -> 4001 bytes rowers/views/otherviews.py | 39 +---- 5 files changed, 262 insertions(+), 33 deletions(-) create mode 100644 rowers/session_utils.py create mode 100644 rowers/templates/sessionstats.html diff --git a/rowers/session_utils.py b/rowers/session_utils.py new file mode 100644 index 00000000..d5e79403 --- /dev/null +++ b/rowers/session_utils.py @@ -0,0 +1,197 @@ +from django.utils import timezone +from rowers.models import Workout, VirtualRaceResult, CourseTestResult + +def timefield_to_seconds_duration(t): + duration = t.hour*3600. + duration += t.minute * 60. + duration += t.second + duration += t.microsecond/1.e6 + + return duration + + + +def is_session_complete_ws(ws, ps): + ws = ws.order_by("date") + if ws.count() == 0: + today = timezone.now() + if today.date() > ps.enddate: + verdict = 'missed' + ratio = 0 + return ratio, verdict, None + else: + return 0, 'not done', None + + value = ps.sessionvalue + if ps.sessionunit == 'min': + value *= 60. + elif ps.sessionunit == 'km': # pragma: no cover + value *= 1000. + + cratiomin = 1 + # cratiomax = 1 + + cratios = { + 'better than nothing': 0, + 'partial': 0.6, + 'on target': 0.8, + 'over target': 1.2, + 'way over target': 1.5 + } + + if ps.criterium == 'none': + if ps.sessiontype == 'session': + cratiomin = 0.8 + # cratiomax = 1.2 + else: + cratios['on target'] = 0.9167 + cratios['over target'] = 1.0833 + cratiomin = 0.9167 + # cratiomax = 1.0833 + + score = 0 + completiondate = None + for w in ws: + if ps.sessionmode == 'distance': + score += w.distance + elif ps.sessionmode == 'time': + durationseconds = timefield_to_seconds_duration(w.duration) + score += durationseconds + elif ps.sessionmode == 'TRIMP': + score += w.trimp + elif ps.sessionmode == 'rScore': + score += wrscore + if not completiondate and score >= cratiomin*value: + completiondate = w.date + + try: + ratio = score/float(int(value)) + except ZeroDivisionError: # pragma: no cover + ratio = 0 + + verdict = 'better than nothing' + + if ps.sessiontype in ['session', 'cycletarget']: + if ps.criterium == 'exact': + if ratio == 1.0: + return ratio, 'on target', completiondate + else: + if not completiondate: # pragma: no cover + completiondate = ws.reverse()[0].date + return ratio, 'partial', completiondate + elif ps.criterium == 'minimum': # pragma: no cover + if ratio >= 1.0: + return ratio, 'on target', completiondate + else: + if not completiondate: + completiondate = ws.reverse()[0].date + + return ratio, 'partial', completiondate + else: + thevalue = 0 + for key, value in cratios.items(): + if ratio > value and value > thevalue: + verdict = key + thevalue = value + + completiondate = ws.reverse()[0].date + return ratio, verdict, completiondate + elif ps.sessiontype == 'test': + if ratio == 1.0: + return ratio, 'on target', completiondate + else: + return ratio, 'partial', completiondate + elif ps.sessiontype == 'challenge': + if ps.criterium == 'exact': + if ratio == 1.0: + return ratio, 'on target', completiondate + else: + return ratio, 'partial', completiondate + elif ps.criterium == 'minimum': # pragma: no cover + if ratio > 1.0: + return ratio, 'on target', completiondate + else: + if not completiondate: + completiondate = ws.reverse()[0].date + return ratio, 'partial', completiondate + else: + if not completiondate: # pragma: no cover + completiondate = ws.reverse()[0].date + return ratio, 'partial', completiondate + elif ps.sessiontype == 'race': # pragma: no cover + vs = VirtualRaceResult.objects.filter(race=ps) + wids = [w.id for w in ws] + for record in vs: + if record.workoutid in wids: + if record.coursecompleted: + ratio = record.distance/ps.sessionvalue + return ratio, 'on target', completiondate + else: + ratio = record.distance/ps.sessionvalue + return ratio, 'partial', completiondate + return (0, 'partial', None) + elif ps.sessiontype in ['fastest_time', 'fastest_distance']: # pragma: no cover + vs = CourseTestResult.objects.filter( + plannedsession=ps, userid=ws[0].user.user.id) + completiondate = ws.reverse()[0].date + wids = [w.id for w in ws] + for record in vs: + if record.workoutid in wids: + if record.coursecompleted: + ratio = 1 + return ratio, 'on target', completiondate + else: + return 0, 'partial', completiondate + if ws: + record = CourseTestResult( + userid=ws[0].user.id, + plannedsession=ps, + workoutid=ws[0].id, + duration=dt.time(0, 0), + coursecompleted=False + ) + record.save() + return (0, 'not done', None) + elif ps.sessiontype == 'coursetest': # pragma: no cover + vs = CourseTestResult.objects.filter(plannedsession=ps) + wids = [w.id for w in ws] + for record in vs: + if record.workoutid in wids: + if record.coursecompleted: + ratio = record.distance/float(ps.sessionvalue) + return ratio, 'on target', completiondate + else: + ratio = record.distance/float(ps.sessionvalue) + return ratio, 'partial', completiondate + + # we're still here - no record, need to create one + if ws: + record = CourseTestResult( + userid=ws[0].user.id, + plannedsession=ps, + workoutid=ws[0].id, + duration=dt.time(0, 0), + coursecompleted=False, + ) + record.save() + _ = myqueue(queue, handle_check_race_course, ws[0].csvfilename, + ws[0].id, ps.course.id, record.id, + ws[0].user.user.email, ws[0].user.user.first_name, + mode='coursetest') + + return (0, 'not done', None) + + else: # pragma: no cover + if not completiondate: + completiondate = ws.reverse()[0].date + return ratio, verdict, completiondate + + +def is_session_complete(r, ps): + if r not in ps.rower.all(): # pragma: no cover + return 0, 'not assigned', None + + ws = Workout.objects.filter(user=r, plannedsession=ps) + + return is_session_complete_ws(ws, ps) + diff --git a/rowers/tasks.py b/rowers/tasks.py index a9feb7a3..94dea01b 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -17,7 +17,7 @@ from rowers.models import ( VirtualRaceResult, CourseTestResult, Rower, GraphImage ) - +from rowers.session_utils import is_session_complete import math from rowers.courseutils import ( coursetime_paths, coursetime_first, time_in_path, @@ -441,6 +441,52 @@ def uploadactivity(access_token, filename, description='', return 0, 0, 0, 0 # pragma: no cover +@app.task +def send_session_stats(user, debug=False, **kwargs): + ws = Workout.objects.filter(plannedsession__isnull=False) + + results = [] + + for w in ws: + ps = w.plannedsession + r = w.user + ratio, status, cdate = is_session_complete(r, ps) + d = { + 'date':w.date, + 'session_id':ps.id, + 'session_name':ps.name, + 'complete': ratio, + 'status': status, + 'rscore': w.rscore, + 'duration': w.duration, + } + results.append(d) + + df = pd.DataFrame(results) + + code = str(uuid4()) + filename = code+'.csv' + + df.to_csv(filename) + + subject = "Session Stats" + + from_email = 'Rowsandall ' + + useremail = user.email + + _ = send_template_email( + from_email, [useremail], + subject, + 'sessionstats.html', + d, + attach_file=filename, + ) + + os.remove(filename) + + return 1 + @app.task def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): # pragma: no cover diff --git a/rowers/templates/sessionstats.html b/rowers/templates/sessionstats.html new file mode 100644 index 00000000..061424c4 --- /dev/null +++ b/rowers/templates/sessionstats.html @@ -0,0 +1,11 @@ +{% extends "emailbase.html" %} +{% block body %} +

+ Attached the requested stats. +

+ + +

+ Best Regards, the Rowsandall Team +

+{% endblock %} diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 8690018423549240cefe5684373ba19ba736dd7f..415f6b2fdb76b5355391909d39e10fc3e883ebd8 100644 GIT binary patch literal 4001 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 H#DD<+d$}9Y literal 4002 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 ID#U;R020PPRR910 diff --git a/rowers/views/otherviews.py b/rowers/views/otherviews.py index 79442881..3fef3dd5 100644 --- a/rowers/views/otherviews.py +++ b/rowers/views/otherviews.py @@ -2,6 +2,7 @@ from rowers.views.statements import * from rowers.interactiveplots import sleep from rowers.plannedsessions import is_session_complete +from rowers.tasks import send_session_stats from rq import Queue from redis import Redis @@ -61,41 +62,15 @@ def sessions_stats(request): if not request.user.is_staff: # pragma: no cover raise PermissionDenied("Not Allowed") - ws = Workout.objects.filter(plannedsession__isnull=False) + myqueue(queuelow, + send_session_stats, + request.user) - results = [] + r = getrower(request.user) + url = reverse('workouts_view') - for w in ws: - ps = w.plannedsession - r = w.user - ratio, status, cdate = is_session_complete(r, ps) - d = { - 'date':w.date, - 'session_id':ps.id, - 'session_name':ps.name, - 'complete': ratio, - 'status': status, - 'rscore': w.rscore, - 'duration': w.duration, - } - results.append(d) + return HttpResponseRedirect(url) - df = pd.DataFrame(results) - - code = str(uuid4()) - filename = code+'.csv' - - df.to_csv(filename) - - with open(filename,'r') as f: - response = HttpResponse(f) - response['Content-Disposition'] = 'attachment; filename="%s"' % filename - response['Content-Type'] = 'application/octet-stream' - - os.remove(filename) - return response - - @login_required()