# Python from django.utils import timezone from datetime import datetime import datetime as dt from datetime import timedelta from datetime import date import time from django.db import IntegrityError import uuid from django.conf import settings import pytz from utils import myqueue,calculate_age,totaltime_sec_to_string import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') from rowers.models import ( Rower, Workout,Team, GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle, TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult ) import metrics import numpy as np import dataprep import courses from rowers.tasks import handle_check_race_course # Low Level functions - to be called by higher level methods def add_workouts_plannedsession(ws,ps,r): result = 0 comments = [] errors = [] # check if all sessions have same date dates = [w.date for w in ws] if (not all(d == dates[0] for d in dates)) and ps.sessiontype not in ['challenge','cycletarget']: errors.append('For tests and training sessions, selected workouts must all be done on the same date') return result,comments,errors if len(ws)>1 and ps.sessiontype == 'test': errors.append('For tests, you can only attach one workout') return result,comments,errors wold = Workout.objects.filter(plannedsession=ps,user=r) ids = [w.id for w in wold] + [w.id for w in ws] ids = list(set(ids)) if len(ids)>1 and ps.sessiontype in ['test','coursetest','race']: errors.append('For tests, you can only attach one workout') return result,comments,errors # start adding sessions for w in ws: if w.date>=ps.startdate and w.date<=ps.enddate: w.plannedsession = ps w.save() result += 1 comments.append('Attached workout %i to session' % w.id) if ps.sessiontype == 'coursetest': record = CourseTestResult( userid=w.user.id, plannedsession=ps, duration=dt.time(0,0), coursecompleted=False, ) record.save() job = myqueue(queue,handle_check_race_course,w.csvfilename, w.id,ps.course.id,record.id, mode='coursetest') else: errors.append('Workout %i did not match session dates' % w.id) return result,comments,errors def remove_workout_plannedsession(w,ps): if w.plannedsession == ps: w.plannedsession = None w.save() return 1 return 0 def clone_planned_session(ps): ps.save() ps.pk = None # creates new instance ps.save() 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 get_virtualrace_times(virtualrace): geocourse = GeoCourse.objects.get(id = virtualrace.course.id) timezone_str = courses.get_course_timezone(geocourse) startdatetime = datetime.datetime.combine( virtualrace.startdate,virtualrace.start_time) enddatetime = datetime.datetime.combine( virtualrace.enddate,virtualrace.end_time) startdatetime = pytz.timezone(timezone_str).localize( startdatetime ) enddatetime = pytz.timezone(timezone_str).localize( enddatetime ) return { 'startdatetime':startdatetime, 'enddatetime':enddatetime, 'evaluation_closure':virtualrace.evaluation_closure, 'registration_closure':virtualrace.registration_closure, } def get_session_metrics(ps): rowers = ps.rower.all() rscore = [] trimp = [] duration = [] distance = [] firstname = [] lastname = [] completedate = [] status = [] for r in rowers: rscorev = 0 trimpv = 0 durationv = 0 distancev = 0 completedatev = '' statusv = 0 ws = Workout.objects.filter(user=r,plannedsession=ps).order_by("date") if len(ws) != 0: for w in ws: distancev += w.distance durationv += timefield_to_seconds_duration(w.duration) thetrimp,hrtss = dataprep.workout_trimp(w) trimpv += thetrimp tss = dataprep.workout_rscore(w)[0] if not np.isnan(tss) and tss != 0: rscorev += tss elif tss == 0: rscorev += hrtss ratio,statusv,completiondate = is_session_complete_ws(ws,ps) try: completedatev = completiondate.strftime('%Y-%m-%d') except AttributeError: completedatev = '' durationv /= 60. trimp.append(int(trimpv)) duration.append(int(durationv)) distance.append(int(distancev)) rscore.append(int(rscorev)) firstname.append(r.user.first_name) lastname.append(r.user.last_name) status.append(statusv) completedate.append(completedatev) thedict = { 'first_name':firstname, 'last_name':lastname, 'duration':duration, 'distance':distance, 'rscore':rscore, 'trimp':trimp, 'completedate':completedate, 'status':status, } return thedict def is_session_complete_ws(ws,ps): ws = ws.order_by("date") if len(ws)==0: today = date.today() if today > ps.enddate: status = 'missed' ratio = 0 return ratio,status,None else: return 0,'not done',None value = ps.sessionvalue if ps.sessionunit == 'min': value *= 60. elif ps.sessionunit == 'km': value *= 1000. cratiomin = 1 cratiomax = 1 if ps.criterium == 'none': if ps.sessiontype == 'session': cratiomin = 0.8 cratiomax = 1.2 else: 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': trimp,hrtss = dataprep.workout_trimp(w) score += trimp elif ps.sessionmode == 'rScore': rscore = dataprep.workout_rscore(w)[0] if not np.isnan(rscore) and rscore != 0: score += rscore elif rscore == 0: trimp,hrtss = dataprep.workout_trimp(w) score += hrtss if not completiondate and score>=cratiomin*value: completiondate = w.date ratio = score/float(value) status = 'partial' if ps.sessiontype in ['session','cycletarget']: if ps.criterium == 'exact': if ratio == 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate elif ps.criterium == 'minimum': if ratio >= 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate else: if ratio>cratiomin and ratio 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate elif ps.sessiontype == 'race': 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,'completed',completiondate if ps.course: ( coursetime, coursemeters, coursecompleted ) = courses.get_time_course(ws,ps.course) if coursecompleted: return 1.0,'completed',completiondate else: return ratio,'partial',completiondate else: if ps.criterium == 'exact': if ratio == 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate elif ps.criterium == 'minimum': if ratio >= 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate else: if ratio>cratiomin and ratio= 1.0: return ratio,'completed',completiondate else: if not completiondate: completiondate = ws.reverse()[0].date return ratio,'partial',completiondate else: if ratio>cratiomin and ratio startdatetime and timezone.now() < evaluation_closure: is_complete,has_registered = race_rower_status(r,race) if is_complete == False: return True else: return False else: return False return False def race_can_resubmit(r,race): if r not in race.rower.all(): return False start_time = race.start_time start_date = race.startdate startdatetime = datetime.combine(start_date,start_time) startdatetime = pytz.timezone(race.timezone).localize( startdatetime ) evaluation_closure = race.evaluation_closure if timezone.now() > startdatetime and timezone.now() < evaluation_closure: is_complete,has_registered = race_rower_status(r,race) return is_complete else: return False return False def race_can_withdraw(r,race): if r not in race.rower.all(): return False start_time = race.start_time start_date = race.startdate startdatetime = datetime.combine(start_date,start_time) startdatetime = pytz.timezone(race.timezone).localize( startdatetime ) registration_closure = race.registration_closure if registration_closure is None or registration_closure == '': registration_closure = startdatetime if timezone.now() > registration_closure: return False elif timezone.now() > startdatetime: return False return True def race_can_register(r,race): if r in race.rower.all(): return False start_time = race.start_time start_date = race.startdate startdatetime = datetime.combine(start_date,start_time) startdatetime = pytz.timezone(race.timezone).localize( startdatetime ) registration_closure = race.registration_closure if registration_closure is None or registration_closure == '': registration_closure = startdatetime if timezone.now() > registration_closure: return False return True def add_rower_race(r,race): race.rower.add(r) race.save() return 1 def remove_rower_race(r,race): race.rower.remove(r) records = VirtualRaceResult.objects.filter(userid=r.id, workoutid__isnull=True, race=race) for r in records: r.delete() return 1 # Low Level functions - to be called by higher level methods def add_workout_race(ws,race,r): result = 0 comments = [] errors = [] start_time = race.start_time start_date = race.startdate startdatetime = datetime.combine(start_date,start_time) startdatetime = pytz.timezone(race.timezone).localize( startdatetime ) end_time = race.end_time end_date = race.enddate enddatetime = datetime.combine(end_date,end_time) enddatetime = pytz.timezone(race.timezone).localize( enddatetime ) # check if all sessions have same date dates = [w.date for w in ws] if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']: errors.append('For tests and training sessions, selected workouts must all be done on the same date') return result,comments,errors,0 if len(ws)>1 and race.sessiontype == 'test': errors.append('For tests, you can only attach one workout') return result,comments,errors,0 ids = [w.id for w in ws] ids = list(set(ids)) if len(ids)>1 and race.sessiontype in ['test','coursetest','race']: errors.append('For tests, you can only attach one workout') return result,comments,errors,0 # start adding sessions for w in ws: if w.startdatetime>=startdatetime and w.startdatetime<=enddatetime: w.plannedsession = race w.save() result += 1 else: errors.append('Workout %i did not match the race window' % w.id) return result,comments,errors,0 if result>0: username = r.user.first_name+' '+r.user.last_name if r.birthdate: age = calculate_age(r.birthdate) else: age = None records = VirtualRaceResult.objects.filter( userid=r.id, race=race ) record = records[0] if ws[0].boattype != record.boattype: errors.append('Your workout boat type did not match the boat type you registered') return result,comments,errors,0 if ws[0].weightcategory != record.weightcategory: errors.append('Your workout weight category did not match the weight category you registered') return result,comments, errors,0 job = myqueue(queue,handle_check_race_course,ws[0].csvfilename, ws[0].id,race.course.id,record.id) add_workouts_plannedsession(ws,race,r) return result,comments,errors,job.id def delete_race_result(workout,race): results = VirtualRaceResult.objects.filter(workoutid=workout.id,race=race) for r in results: r.workoutid = None r.save()