From 1dd0c0cff9449d843cfb6e0916f0a59420ae92e9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 5 Dec 2020 13:31:02 +0100 Subject: [PATCH 01/10] passing tests - saving goldmedal score --- rowers/dataprep.py | 5 ++++- rowers/utils.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 1f6a27ba..ced2f2ac 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1083,7 +1083,7 @@ def fitscore(rower,workout): indexmax = scores.idxmax() delta = df.loc[indexmax,'delta'] maxvalue = scores.max() - except ValueError: + except (ValueError,TypeError): indexmax = 0 delta = 0 maxvalue = 0 @@ -1150,6 +1150,9 @@ def setcp(workout,background=False): 'id':workout.id, }) df.to_parquet(filename,engine='fastparquet',compression='GZIP') + goldmedalstandard, goldmedalduration = fitscore(workout.user,workout) + workout.goldmedalstandard = goldmedalstandard + workout.save() return df,delta,cpvalues return pd.DataFrame({'delta':[],'cp':[]}),pd.Series(),pd.Series() diff --git a/rowers/utils.py b/rowers/utils.py index cf76d3ca..6e7b45a2 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -327,7 +327,10 @@ def calculate_age(born,today=None): if not today: today = date.today() if born: - return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + try: + return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + except AttributeError: + return None else: return None From 0bb0237aeeb62d726d8b71dd9bbb1479a1667c50 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 5 Dec 2020 14:21:37 +0100 Subject: [PATCH 02/10] better calculation of world class record --- rowers/dataprep.py | 32 +++++++++++++++++++++++++------- rowers/tasks.py | 7 ++++++- rowers/views/workoutviews.py | 9 +++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index ced2f2ac..d916934b 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1026,6 +1026,14 @@ from rowers.datautils import p0 from rowers.utils import calculate_age from scipy import optimize +def workout_goldmedalstandard(workout): + if workout.goldmedalstandard > 0: + return workout.goldmedalstandard + goldmedalstandard,goldmedalduration = fitscore(workout.user,workout) + workout.goldmedalstandard = goldmedalstandard + workout.save() + return goldmedalstandard + def fitscore(rower,workout): cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: @@ -1033,21 +1041,30 @@ def fitscore(rower,workout): except: df, delta, cpvalues = setcp(workout) + if df.empty: + df, delta, cpvalues = setcp(workout) + age = calculate_age(rower.birthdate,today=workout.date) + agerecords = CalcAgePerformance.objects.filter( age=age, sex=rower.sex, weightcategory = rower.weightcategory ) + wcdurations = [] wcpower = [] + getrecords = len(agerecords) == 0 for record in agerecords: - wcdurations.append(record.duration) - wcpower.append(record.power) + if record.power > 0: + wcdurations.append(record.duration) + wcpower.append(record.power) + else: + getrecords = True - if len(agerecords)==0: - durations = [1,4,10,20,30,60] - distances = [] + if getrecords: + durations = [1,4,30,60] + distances = [100,500,1000,2000,5000,6000,10000,21097,42195] df2 = pd.DataFrame( list( C2WorldClassAgePerformance.objects.filter( @@ -1066,12 +1083,13 @@ def fitscore(rower,workout): fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) errfunc = lambda pars,x,y: fitfunc(pars,x)-y - if len(wcdurations)>4: + if len(wcdurations)>=4: p1wc, success = optimize.leastsq(errfunc, p0[:],args=(wcdurations,wcpower)) else: factor = fitfunc(p0,wcdurations.mean()/wcpower.mean()) p1wc = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] success = 0 + return 0,0 times = df['delta'] @@ -1079,6 +1097,7 @@ def fitscore(rower,workout): wcpowers = fitfunc(p1wc,times) scores = 100.*powers/wcpowers + try: indexmax = scores.idxmax() delta = df.loc[indexmax,'delta'] @@ -1127,7 +1146,6 @@ def setcp(workout,background=False): return job.id - if not strokesdf.empty: totaltime = strokesdf['time'].max() try: diff --git a/rowers/tasks.py b/rowers/tasks.py index 103f09fe..57cbdbae 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -16,6 +16,7 @@ import json from scipy import optimize from scipy.signal import savgol_filter +from scipy.interpolate import griddata import rowingdata from rowingdata import make_cumvalues @@ -333,7 +334,11 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt', power = 0.5*(np.abs(power)+power) else: - power = 0 + new_age = np.range([age]) + ww = griddata(ages.values, + powers.values, + new_age,method='linear',rescale=True) + power = 0.5*(np.abs(power)+power) else: power = 0 diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index fe44f91d..ac877885 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3497,6 +3497,15 @@ def workout_stats_view(request,id=0,message="",successmessage=""): # Normalized power & TSS tss,normp = dataprep.workout_rscore(w) + goldmedalstandard = dataprep.workout_goldmedalstandard(w) + + + if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: + otherstats['goldmedalstandard'] = { + 'verbose_name': 'Gold Medal Standard', + 'value': int(goldmedalstandard), + 'unit': '%', + } if not np.isnan(tss) and tss != 0: From 939a3e27c03fbd4714484bd6ef94051131df6bca Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 7 Dec 2020 08:54:50 +0100 Subject: [PATCH 03/10] adding gold medal durations --- rowers/dataprep.py | 26 +++++++++++++++++--------- rowers/forms.py | 2 +- rowers/interactiveplots.py | 23 +++++++++++++---------- rowers/models.py | 1 + rowers/utils.py | 31 ++++++++++++++++++++++++------- rowers/views/analysisviews.py | 8 ++++---- rowers/views/workoutviews.py | 12 ++++++++++-- 7 files changed, 70 insertions(+), 33 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index d916934b..12a7acea 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1028,13 +1028,17 @@ from scipy import optimize def workout_goldmedalstandard(workout): if workout.goldmedalstandard > 0: - return workout.goldmedalstandard - goldmedalstandard,goldmedalduration = fitscore(workout.user,workout) - workout.goldmedalstandard = goldmedalstandard - workout.save() - return goldmedalstandard + return workout.goldmedalstandard,workout.goldmedalseconds + if workout.workouttype in rowtypes: + goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout) + workout.goldmedalstandard = goldmedalstandard + workout.goldmedalseconds = goldmedalseconds + workout.save() + return goldmedalstandard, goldmedalseconds + else: + return 0,0 -def fitscore(rower,workout): +def calculate_goldmedalstandard(rower,workout): cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: df = pd.read_parquet(cpfile) @@ -1100,7 +1104,7 @@ def fitscore(rower,workout): try: indexmax = scores.idxmax() - delta = df.loc[indexmax,'delta'] + delta = int(df.loc[indexmax,'delta']) maxvalue = scores.max() except (ValueError,TypeError): indexmax = 0 @@ -1168,8 +1172,9 @@ def setcp(workout,background=False): 'id':workout.id, }) df.to_parquet(filename,engine='fastparquet',compression='GZIP') - goldmedalstandard, goldmedalduration = fitscore(workout.user,workout) + goldmedalstandard, goldmedalduration = calculate_goldmedalstandard(workout.user,workout) workout.goldmedalstandard = goldmedalstandard + workout.goldmedalduration = goldmedalduration workout.save() return df,delta,cpvalues @@ -2601,7 +2606,10 @@ def read_df_sql(id): rowdata,row = getrowdata(id=id) if rowdata and len(rowdata.df): data = dataprep(rowdata.df,id=id,bands=True,otwpower=True,barchart=True) - df = pd.read_parquet(f) + try: + df = pd.read_parquet(f) + except OSError: + df = data else: df = pd.DataFrame() diff --git a/rowers/forms.py b/rowers/forms.py index 6d074114..5266bdc4 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -768,7 +768,7 @@ class FitnessFitForm(forms.Form): fitnesstest = forms.IntegerField(required=True,initial=20, label='Test Duration (minutes)') - usefitscore = forms.BooleanField(required=False,initial=False, + usegoldmedalstandard = forms.BooleanField(required=False,initial=False, label='Use best performance against world class') kfitness = forms.IntegerField(initial=42,required=True, diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 14711cc9..1ed3e6ad 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -102,27 +102,30 @@ import rowers.datautils as datautils from pandas.core.groupby.groupby import DataError -def get_fitscore(workouts,kfitness): +def build_goldmedalstandards(workouts,kfitness): dates = [] testpower = [] fatigues = [] fitnesses = [] + data = [] - fitscores = [] + goldmedalstandards = [] + goldmedaldurations = [] ids = [] for w in workouts: - fitscore,fitnesstestsecs = dataprep.fitscore(w.user,w) + goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) ids.append(w.id) - fitscores.append(fitscore) + goldmedalstandards.append(goldmedalstandard) + goldmedaldurations.append(goldmedalseconds) - df = pd.DataFrame({'workout':ids,'fitscore':fitscores}) + df = pd.DataFrame({'workout':ids,'goldmedalstandard':goldmedalstandards}) for w in workouts: ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness), date__lte=w.date)] powerdf = df[df['workout'].isin(ids)] - powertest = powerdf['fitscore'].max() + powertest = powerdf['goldmedalstandard'].max() dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) testpower.append(powertest) @@ -1918,7 +1921,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, metricchoice='rscore', k1=1,k2=1,p0=100, modelchoice='tsb', - usefitscore=False): + usegoldmedalstandard=False): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' @@ -1929,12 +1932,12 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, fitnesstestsecs = fitnesstest*60 df = pd.DataFrame() - if not usefitscore: + if not usegoldmedalstandard: dates,testpower,fatigues,fitnesses = get_testpower( workouts,fitnesstestsecs,kfitness ) else: - dates,testpower,fatigues,fitnesses = get_fitscore( + dates,testpower,fatigues,fitnesses = build_goldmedalstandards( workouts,kfitness ) # create CP data @@ -2051,7 +2054,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, formlabel = 'TSB' rightaxlabel = 'Coggan CTL/ATL/TSB' - if usefitscore: + if usegoldmedalstandard: legend_label = 'Test Score' yaxlabel = 'Test Score' else: diff --git a/rowers/models.py b/rowers/models.py index 8a6663a3..aa31934e 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2953,6 +2953,7 @@ class Workout(models.Model): normv = models.FloatField(default=-1,blank=True) normw = models.FloatField(default=-1,blank=True) goldmedalstandard = models.FloatField(default=-1,blank=True,verbose_name='Gold Medal Standard') + goldmedalseconds = models.IntegerField(default=0,blank=True,verbose_name='Gold Medal Seconds') rpe = models.IntegerField(default=0,blank=True,choices=rpechoices, verbose_name='Rate of Perceived Exertion') diff --git a/rowers/utils.py b/rowers/utils.py index 6e7b45a2..954a611a 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -376,7 +376,7 @@ def wavg(group, avg_name, weight_name): except ZeroDivisionError: return d.mean() -def totaltime_sec_to_string(totaltime): +def totaltime_sec_to_string(totaltime,shorten=False): hours = int(totaltime / 3600.) if hours > 23: message = 'Warning: The workout duration was longer than 23 hours. ' @@ -400,12 +400,29 @@ def totaltime_sec_to_string(totaltime): if not message: message = 'Warning: there is something wrong with the workout duration' - duration = "{hours:02d}:{minutes:02d}:{seconds:02d}.{tenths}".format( - hours=hours, - minutes=minutes, - seconds=seconds, - tenths=tenths - ) + duration = "" + if not shorten: + duration = "{hours:02d}:{minutes:02d}:{seconds:02d}.{tenths}".format( + hours=hours, + minutes=minutes, + seconds=seconds, + tenths=tenths + ) + else: + if hours != 0: + duration = "{hours}:{minutes:02d}:{seconds:02d}".format( + hours=hours, + minutes=minutes, + seconds=seconds, + tenths=tenths + ) + else: + duration = "{minutes}:{seconds:02d}".format( + hours=hours, + minutes=minutes, + seconds=seconds, + tenths=tenths + ) return duration diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 5ccc4562..477d7147 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1561,7 +1561,7 @@ def performancemanager_view(request,userid=0,mode='rower', fitnesstest = 20 metricchoice = 'trimp' modelchoice = 'tsb' - usefitscore = False + usegoldmedalstandard = False doform = therower.showfresh dofatigue = therower.showfit @@ -1647,7 +1647,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', fitnesstest = 20 metricchoice = 'trimp' modelchoice = 'tsb' - usefitscore = False + usegoldmedalstandard = False # temp fit parameters k1 = 1 @@ -1669,7 +1669,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', k2 = form.cleaned_data['k2'] p0 = form.cleaned_data['p0'] modelchoice = form.cleaned_data['modelchoice'] - usefitscore = form.cleaned_data['usefitscore'] + usegoldmedalstandard = form.cleaned_data['usegoldmedalstandard'] else: form = FitnessFitForm() @@ -1694,7 +1694,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', metricchoice=metricchoice, k1=k1,k2=k2,p0=p0, modelchoice=modelchoice, - usefitscore=usefitscore, + usegoldmedalstandard=usegoldmedalstandard, ) breadcrumbs = [ diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index ac877885..0b004435 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -12,6 +12,7 @@ import rowers.mytypes as mytypes import numpy from rowers.mailprocessing import send_confirm import rowers.uploads as uploads +import rowers.utils as utils from urllib.parse import urlparse, parse_qs from json.decoder import JSONDecodeError @@ -3497,8 +3498,8 @@ def workout_stats_view(request,id=0,message="",successmessage=""): # Normalized power & TSS tss,normp = dataprep.workout_rscore(w) - goldmedalstandard = dataprep.workout_goldmedalstandard(w) - + goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) + if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: otherstats['goldmedalstandard'] = { @@ -3507,6 +3508,13 @@ def workout_stats_view(request,id=0,message="",successmessage=""): 'unit': '%', } + if not np.isnan(goldmedalseconds) and goldmedalseconds > 0: + otherstats['goldmedalseconds'] = { + 'verbose_name': 'Gold Medal Standard Duration', + 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True), + 'unit': '', + } + if not np.isnan(tss) and tss != 0: otherstats['tss'] = { From 78b8a2065385ccc3359d7543412f062eb61c47c2 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 7 Dec 2020 09:00:49 +0100 Subject: [PATCH 04/10] adding explanation --- rowers/templates/workoutstats.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rowers/templates/workoutstats.html b/rowers/templates/workoutstats.html index a7eaf737..335466b2 100644 --- a/rowers/templates/workoutstats.html +++ b/rowers/templates/workoutstats.html @@ -49,6 +49,13 @@
  • +

    Gold Medal Standard: The best performance, relative to world class rowers + of your age, gender and weight category, + found in this workout. This metric uses your power data over time and + compares them with the power that the best rowers of your age, gender and weight category + can hold over time.

    +

    Gold Medal Standard Duration: The time interval over which your best + performance in this workout was achieved.

    rPower: Equivalent steady state power for the duration of the workout.

    Heart Rate Drift: Comparing heart rate normalized for average power for the first and second half of the workout

    TRIMP: TRaining IMPact. A way to combine duration and heart rate into a single number.

    @@ -69,7 +76,7 @@
  • - + {% if stats %}
  • Statistics

    @@ -164,7 +171,7 @@ {% endfor %} - +
  • {% endif %} From 6426f8513dac0a1c19e255c522e5558a4c3787e0 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 7 Dec 2020 09:01:48 +0100 Subject: [PATCH 05/10] adding more explanation --- rowers/templates/workoutstats.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/templates/workoutstats.html b/rowers/templates/workoutstats.html index 335466b2..088b6af3 100644 --- a/rowers/templates/workoutstats.html +++ b/rowers/templates/workoutstats.html @@ -49,7 +49,7 @@
  • -

    Gold Medal Standard: The best performance, relative to world class rowers +

    Gold Medal Standard: For rowing workouts, the best performance, relative to world class rowers of your age, gender and weight category, found in this workout. This metric uses your power data over time and compares them with the power that the best rowers of your age, gender and weight category From 5df1a2272ab1435fc3252f112cc4d0250ee9ae8c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 7 Dec 2020 15:00:27 +0100 Subject: [PATCH 06/10] adding test duration --- rowers/interactiveplots.py | 39 +++++++++++++++++++++++++++----------- rowers/utils.py | 2 ++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 1ed3e6ad..0c183790 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -25,7 +25,7 @@ import itertools from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band -from rowers.utils import myqueue +from rowers.utils import myqueue, totaltime_sec_to_string import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') @@ -105,9 +105,10 @@ from pandas.core.groupby.groupby import DataError def build_goldmedalstandards(workouts,kfitness): dates = [] testpower = [] + testduration = [] fatigues = [] fitnesses = [] - + data = [] goldmedalstandards = [] goldmedaldurations = [] @@ -118,26 +119,35 @@ def build_goldmedalstandards(workouts,kfitness): goldmedalstandards.append(goldmedalstandard) goldmedaldurations.append(goldmedalseconds) - df = pd.DataFrame({'workout':ids,'goldmedalstandard':goldmedalstandards}) + df = pd.DataFrame({ + 'workout':ids, + 'goldmedalstandard':goldmedalstandards, + 'goldmedalduration':goldmedaldurations, + }) for w in workouts: ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness), date__lte=w.date)] powerdf = df[df['workout'].isin(ids)] + indexmax = powerdf['goldmedalstandard'].idxmax() powertest = powerdf['goldmedalstandard'].max() + durationtest = powerdf.loc[indexmax,'goldmedalduration'] dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) testpower.append(powertest) + testduration.append(durationtest) + fatigues.append(np.nan) fitnesses.append(np.nan) - return dates, testpower, fatigues, fitnesses + return dates, testpower, testduration, fatigues, fitnesses def get_testpower(workouts,fitnesstestsecs,kfitness): dates = [] testpower = [] + testduration = [] fatigues = [] fitnesses = [] data = [] @@ -195,6 +205,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness): dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) testpower.append(powertest) + testduration.append(fitnesstestsecs) fatigues.append(np.nan) fitnesses.append(np.nan) @@ -1635,7 +1646,7 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'): return [script,div,js_resources,css_resources] def getfatigues( - fatigues,fitnesses,dates,testpower, + fatigues,fitnesses,dates,testpower,testduration, startdate,enddate,user,metricchoice,kfatigue,kfitness): fatigue = 0 @@ -1688,8 +1699,9 @@ def getfatigues( fitnesses.append(fitness) dates.append(datetime.datetime.combine(date,datetime.datetime.min.time())) testpower.append(np.nan) + testduration.append(np.nan) - return fatigues,fitnesses,dates,testpower,impulses + return fatigues,fitnesses,dates,testpower,testduration,impulses def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, metricchoice='trimp',doform=False,dofatigue=False): @@ -1933,11 +1945,11 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, df = pd.DataFrame() if not usegoldmedalstandard: - dates,testpower,fatigues,fitnesses = get_testpower( + dates,testpower,testduration, fatigues,fitnesses = get_testpower( workouts,fitnesstestsecs,kfitness ) else: - dates,testpower,fatigues,fitnesses = build_goldmedalstandards( + dates,testpower, testduration,fatigues,fitnesses = build_goldmedalstandards( workouts,kfitness ) # create CP data @@ -1945,6 +1957,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, df = pd.DataFrame({ 'date':dates, 'testpower':testpower, + 'testduration':testduration, 'fatigue':fatigues, 'fitness':fitnesses, }) @@ -1965,9 +1978,10 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, testpower = df['testpower'].values.tolist() fatigues = df['fatigue'].values.tolist() fitnesses = df['fitness'].values.tolist() + testduration = df['testduration'].values.tolist() - fatigues,fitnesses,dates,testpower,impulses = getfatigues( - fatigues,fitnesses,dates,testpower, + fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues( + fatigues,fitnesses,dates,testpower,testduration, startdate,enddate,user,metricchoice,kfatigue,kfitness ) @@ -1975,6 +1989,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, df = pd.DataFrame({ 'date':dates, 'testpower':testpower, + 'testduration':testduration, 'fatigue':fatigues, 'fitness':fitnesses, }) @@ -2000,6 +2015,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, source = ColumnDataSource( data = dict( testpower = df['testpower'], + testduration = df['testduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)), date = df['date'], fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')), fitness = df['fitness'], @@ -2109,7 +2125,8 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, hover = plot.select(dict(type=HoverTool)) hover.tooltips = OrderedDict([ - (legend_label,'@testpower'), + (legend_label,'@testpower{int}'), + ('Test', '@testduration'), ('Date','@fdate'), (fitlabel,'@fitness'), (fatiguelabel,'@fatigue'), diff --git a/rowers/utils.py b/rowers/utils.py index 954a611a..c7573b69 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -377,6 +377,8 @@ def wavg(group, avg_name, weight_name): return d.mean() def totaltime_sec_to_string(totaltime,shorten=False): + if np.isnan(totaltime): + return '' hours = int(totaltime / 3600.) if hours > 23: message = 'Warning: The workout duration was longer than 23 hours. ' From 98a62e6019a33ebfe7fdce0f89580bd8a22a9807 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 7 Dec 2020 22:06:04 +0100 Subject: [PATCH 07/10] bug fixes and adding data download --- rowers/dataprep.py | 26 +++++++++++++++++++++++++- rowers/interactiveplots.py | 8 ++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 12a7acea..1d18c90a 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals from __future__ import unicode_literals, absolute_import from rowers.models import ( Workout, Team, CalcAgePerformance,C2WorldClassAgePerformance, + User ) import pytz @@ -325,7 +326,9 @@ def workout_summary_to_df( startdate=datetime.datetime(1970,1,1), enddate=timezone.now()+timezone.timedelta(days=1)): - ws = Workout.objects.filter(user=rower).order_by("startdatetime") + ws = Workout.objects.filter( + user=rower,date__gte=startdate,date__lte=enddate + ).order_by("startdatetime") types = [] names = [] @@ -339,6 +342,9 @@ def workout_summary_to_df( notes = [] tcx_links = [] csv_links = [] + workout_links = [] + goldstandards = [] + goldstandarddurations = [] rscores = [] trimps = [] @@ -361,12 +367,20 @@ def workout_summary_to_df( id=encoder.encode_hex(w.id) ) csv_links.append(csv_link) + workout_link = SITE_URL+'/rowers/workout/{id}/'.format( + id=encoder.encode_hex(w.id) + ) + workout_links.append(workout_link) trimps.append(workout_trimp(w)[0]) rscore = workout_rscore(w) rscores.append(int(rscore[0])) + goldstandard,goldstandardduration = workout_goldmedalstandard(w) + goldstandards.append(int(goldstandard)) + goldstandarddurations.append(int(goldstandardduration)) df = pd.DataFrame({ 'name':names, + 'link':workout_links, 'date':startdatetimes, 'timezone':timezones, 'type':types, @@ -380,6 +394,8 @@ def workout_summary_to_df( 'Stroke Data CSV':csv_links, 'TRIMP Training Load':trimps, 'TSS Training Load':rscores, + 'GS':goldstandards, + 'GS_secs':goldstandarddurations, }) return df @@ -1026,6 +1042,14 @@ from rowers.datautils import p0 from rowers.utils import calculate_age from scipy import optimize +def get_workoutsummaries(userid,startdate): + u = User.objects.get(id=userid) + r = u.rower + df = workout_summary_to_df(r,startdate=startdate) + df = df.sort_values('date') + + return df + def workout_goldmedalstandard(workout): if workout.goldmedalstandard > 0: return workout.goldmedalstandard,workout.goldmedalseconds diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 0c183790..5012ba5b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -209,7 +209,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness): fatigues.append(np.nan) fitnesses.append(np.nan) - return dates,testpower,fatigues,fitnesses + return dates,testpower, testduration,fatigues,fitnesses @@ -1714,6 +1714,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, fitnesses = [] dates = [] testpower = [] + testduration = [] modelchoice = 'coggan' p0 = 0 @@ -1722,11 +1723,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, - - fatigues,fitnesses,dates,testpower,impulses = getfatigues(fatigues, + fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues, fitnesses, dates, - testpower, + testpower,testduration, startdate,enddate, user,metricchoice, kfatigue,kfitness) From 5639a88052035e0881ebe9ae1d4e2e7bc4dc4d96 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 8 Dec 2020 08:24:48 +0100 Subject: [PATCH 08/10] improving data export --- rowers/dataprep.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 1d18c90a..844693e7 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -332,6 +332,7 @@ def workout_summary_to_df( types = [] names = [] + ids = [] startdatetimes = [] timezones = [] distances = [] @@ -346,11 +347,15 @@ def workout_summary_to_df( goldstandards = [] goldstandarddurations = [] rscores = [] + hrtss = [] trimps = [] + rankingpieces = [] + boattypes = [] for w in ws: types.append(w.workouttype) names.append(w.name) + ids.append(encoder.encode_hex(w.id)) startdatetimes.append(w.startdatetime) timezones.append(w.timezone) distances.append(w.distance) @@ -358,6 +363,7 @@ def workout_summary_to_df( weightcategories.append(w.weightcategory) adaptivetypes.append(w.adaptiveclass) weightvalues.append(w.weightvalue) + boattypes.append(w.boattype) notes.append(w.notes) tcx_link = SITE_URL+'/rowers/workout/{id}/emailtcx'.format( id=encoder.encode_hex(w.id) @@ -374,28 +380,34 @@ def workout_summary_to_df( trimps.append(workout_trimp(w)[0]) rscore = workout_rscore(w) rscores.append(int(rscore[0])) + hrtss.append(int(w.hrtss)) goldstandard,goldstandardduration = workout_goldmedalstandard(w) goldstandards.append(int(goldstandard)) goldstandarddurations.append(int(goldstandardduration)) + rankingpieces.append(w.rankingpiece) df = pd.DataFrame({ + 'ID': ids, + 'date':startdatetimes, 'name':names, 'link':workout_links, - 'date':startdatetimes, 'timezone':timezones, 'type':types, + 'boat type':boattypes, 'distance (m)':distances, 'duration ':durations, + 'ranking piece':rankingpieces, 'weight category':weightcategories, 'adaptive classification':adaptivetypes, 'weight (kg)':weightvalues, - 'notes':notes, 'Stroke Data TCX':tcx_links, 'Stroke Data CSV':csv_links, 'TRIMP Training Load':trimps, 'TSS Training Load':rscores, + 'hrTSS Training Load':hrtss, 'GS':goldstandards, 'GS_secs':goldstandarddurations, + 'notes':notes, }) return df @@ -1046,7 +1058,8 @@ def get_workoutsummaries(userid,startdate): u = User.objects.get(id=userid) r = u.rower df = workout_summary_to_df(r,startdate=startdate) - df = df.sort_values('date') + df.drop(['Stroke Data TCX','Stroke Data CSV'],axis=1,inplace=True) + df = df.sort_values('date',ascending=False) return df From 05a97ed9d1e7bef93984820bc1a549dda4a5427d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 10 Dec 2020 21:11:59 +0100 Subject: [PATCH 09/10] bug fix --- rowers/views/statements.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 13f82819..c69b1d94 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -554,7 +554,10 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False): if rowerid != 0: r = Rower.objects.get(id=rowerid) elif userid != 0: - u = User.objects.get(id=userid) + try: + u = User.objects.get(id=userid) + except User.DoesNotExist: + raise Http404("User does not exist") r = getrower(u) else: r = getrower(request.user) From 2c65482993c1e4bbc4ccd17dc3f82eadb6282009 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 10 Dec 2020 21:12:58 +0100 Subject: [PATCH 10/10] removing gs for release --- rowers/views/workoutviews.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 0b004435..e113b27c 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3501,19 +3501,19 @@ def workout_stats_view(request,id=0,message="",successmessage=""): goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) - if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: - otherstats['goldmedalstandard'] = { - 'verbose_name': 'Gold Medal Standard', - 'value': int(goldmedalstandard), - 'unit': '%', - } + #if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: + # otherstats['goldmedalstandard'] = { + # 'verbose_name': 'Gold Medal Standard', + # 'value': int(goldmedalstandard), + # 'unit': '%', + # } - if not np.isnan(goldmedalseconds) and goldmedalseconds > 0: - otherstats['goldmedalseconds'] = { - 'verbose_name': 'Gold Medal Standard Duration', - 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True), - 'unit': '', - } + #if not np.isnan(goldmedalseconds) and goldmedalseconds > 0: + # otherstats['goldmedalseconds'] = { + # 'verbose_name': 'Gold Medal Standard Duration', + # 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True), + # 'unit': '', + # } if not np.isnan(tss) and tss != 0: