improved
This commit is contained in:
197
rowers/session_utils.py
Normal file
197
rowers/session_utils.py
Normal file
@@ -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)
|
||||
|
||||
@@ -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 <info@rowsandall.com>'
|
||||
|
||||
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
|
||||
|
||||
11
rowers/templates/sessionstats.html
Normal file
11
rowers/templates/sessionstats.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends "emailbase.html" %}
|
||||
{% block body %}
|
||||
<p>
|
||||
Attached the requested stats.
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
Best Regards, the Rowsandall Team
|
||||
</p>
|
||||
{% endblock %}
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user