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 86900184..415f6b2f 100644 Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ 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()