From 069ac5a182ff6128f13b85c66aa690c19e914630 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 1 Jan 2021 11:01:51 +0100 Subject: [PATCH 1/8] removing print statements --- rowers/dataprep.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 705e398c..baedc6c4 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1478,7 +1478,6 @@ def checkbreakthrough(w, r): def checkduplicates(r,workoutdate,workoutstartdatetime,workoutenddatetime): - print(workoutdate,workoutstartdatetime,workoutenddatetime) duplicate = False ws = Workout.objects.filter(user=r,date=workoutdate,duplicate=False).exclude( startdatetime__gt=workoutenddatetime @@ -1487,13 +1486,11 @@ def checkduplicates(r,workoutdate,workoutstartdatetime,workoutenddatetime): ws2 = [] for ww in ws: - print(ww) t = ww.duration delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second) enddatetime = ww.startdatetime+delta print(enddatetime,workoutstartdatetime) if enddatetime > workoutstartdatetime: - print('ja') ws2.append(ww) From 0e9944044d499de696e934c5bddaff55f729bb2f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 1 Jan 2021 16:24:42 +0100 Subject: [PATCH 2/8] fitness-fit has now right algo --- rowers/interactiveplots.py | 66 +++++++++++++++++++++++++++++------ rowers/views/analysisviews.py | 8 ++--- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 4bc6cfd6..b7a9f4ac 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -25,6 +25,8 @@ import itertools from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band +import arrow + from rowers.utils import myqueue, totaltime_sec_to_string import django_rq queue = django_rq.get_queue('default') @@ -102,6 +104,16 @@ import rowers.datautils as datautils from pandas.core.groupby.groupby import DataError +def newtestpower(x): + try: + if abs(x['testpower'] - x['testdup']) < 1: + return np.nan + except (AttributeError,TypeError): + return np.nan + + + return x['testpower'] + def build_goldmedalstandards(workouts,kfitness): dates = [] testpower = [] @@ -113,20 +125,48 @@ def build_goldmedalstandards(workouts,kfitness): goldmedalstandards = [] goldmedaldurations = [] ids = [] + workoutdt = [] for w in workouts: goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) ids.append(w.id) - goldmedalstandards.append(goldmedalstandard) - goldmedaldurations.append(goldmedalseconds) + if w.workouttype in mytypes.otwtypes: + goldmedalstandard = goldmedalstandard / (1.-w.user.otwslack/100.) + if goldmedalseconds > 60: + goldmedalstandards.append(goldmedalstandard) + goldmedaldurations.append(goldmedalseconds) + else: + goldmedalstandards.append(0) + goldmedaldurations.append(0) + workoutdt.append(arrow.get(w.startdatetime).datetime) df = pd.DataFrame({ 'workout':ids, + 'workoutdt': workoutdt, 'goldmedalstandard':goldmedalstandards, 'goldmedalduration':goldmedaldurations, }) + + + df.sort_values(['workoutdt'],inplace=True) + + #for id, row in df.iterrows(): + # d = row['workoutdt'] + # dd = d-datetime.timedelta(days=90) + # mask = df['workoutdt']>dd + # mask2 = df['workoutdt']<=d + # df2 = df.where(mask & mask2) + # powertest = df2['goldmedalstandard'].max() + # idx = df2['goldmedalstandard'].argmax() + # durationtest = df2['goldmedalduration'].values[idx] + # dates.append(d) + # testpower.append(powertest) + # testduration.append(durationtest) + # fatigues.append(np.nan) + # fitnesses.append(np.nan) + for w in workouts: - ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness), + ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=90), date__lte=w.date)] powerdf = df[df['workout'].isin(ids)] @@ -134,9 +174,14 @@ def build_goldmedalstandards(workouts,kfitness): 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) + + dates.append(arrow.get(w.date).datetime) + if powertest > 0: + testpower.append(powertest) + testduration.append(durationtest) + else: + testpower.append(np.nan) + testduration.append(np.nan) fatigues.append(np.nan) fitnesses.append(np.nan) @@ -156,7 +201,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness): try: df = pd.read_parquet(cpfile) df['workout'] = w.id - df['workoutdate'] = w.date.strftime('%d-%m-%Y') + df['workoutdate'] = arrow.get(w.date.strftime('%d-%m-%Y')).datetime data.append(df) except: strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id]) @@ -203,7 +248,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness): - dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) + dates.append(arrow.get(w.date).datetime) testpower.append(powertest) testduration.append(fitnesstestsecs) fatigues.append(np.nan) @@ -1710,7 +1755,7 @@ def getfatigues( fatigues.append(fatigue) fitnesses.append(fitness) - dates.append(datetime.datetime.combine(date,datetime.datetime.min.time())) + dates.append(arrow.get(date).datetime) testpower.append(np.nan) testduration.append(np.nan) @@ -1978,8 +2023,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, df['testdup'] = df['testpower'].shift(1) - df['testpower'] = df.apply(lambda x: np.nan if abs(x['testpower'] - x['testdup']) < 1 \ - else x['testpower'],axis=1) + df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1) try: df['testpower'].iloc[-1] = df['testdup'].iloc[-1] diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 07e5e71c..e601d3fe 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1678,13 +1678,9 @@ def fitness_from_cp_view(request,userid=0,mode='rower', workouts = Workout.objects.filter(user=therower,date__gte=startdate, date__lte=enddate, - workouttype__in=mytypes.otwtypes, + workouttype__in=mytypes.rowtypes, duplicate=False) - if mode == 'rower': - workouts = Workout.objects.filter(user=therower,date__gte=startdate, - date__lte=enddate,workouttype__in=mytypes.otetypes, - duplicate=False) - + script,thediv = fitnessfit_chart( From 87b6684465c76f4ad0f50cd3fee72c734ac70ff3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 1 Jan 2021 16:51:00 +0100 Subject: [PATCH 3/8] Adding gold medal standard to performance chart (optional) --- rowers/forms.py | 3 + rowers/interactiveplots.py | 71 ++++++++++++++++++++---- rowers/models.py | 1 + rowers/templates/performancemanager.html | 5 ++ rowers/views/analysisviews.py | 7 ++- 5 files changed, 74 insertions(+), 13 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 5266bdc4..763d4fdb 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -739,6 +739,9 @@ class PerformanceManagerForm(forms.Form): doform = forms.BooleanField(required=False,initial=False, label='Freshness') + showtests = forms.BooleanField(required=False,initial=False, + label='Show my best workouts') + class FitnessFitForm(forms.Form): startdate = forms.DateField( initial=timezone.now()-datetime.timedelta(days=365), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index b7a9f4ac..c4f5aeb5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -120,6 +120,7 @@ def build_goldmedalstandards(workouts,kfitness): testduration = [] fatigues = [] fitnesses = [] + impulses = [] data = [] goldmedalstandards = [] @@ -185,8 +186,9 @@ def build_goldmedalstandards(workouts,kfitness): fatigues.append(np.nan) fitnesses.append(np.nan) + impulses.append(np.nan) - return dates, testpower, testduration, fatigues, fitnesses + return dates, testpower, testduration, fatigues, fitnesses,impulses def get_testpower(workouts,fitnesstestsecs,kfitness): @@ -1762,24 +1764,55 @@ def getfatigues( 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): + metricchoice='trimp',doform=False,dofatigue=False, + showtests=False): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' TOOLS2 = 'box_zoom,hover' - fatigues = [] - fitnesses = [] - dates = [] - testpower = [] - testduration = [] - modelchoice = 'coggan' p0 = 0 k1 = 1 k2 = 1 + dates = [] + testpower = [] + fatigues = [] + fitnesses = [] + testduration = [] + if showtests: + workouts = Workout.objects.filter(user=user.rower,date__gte=startdate, + date__lte=enddate, + workouttype__in=mytypes.rowtypes, + duplicate=False) + dates,testpower,testduration,fatigues,fitnesses,impulses = build_goldmedalstandards( + workouts,kfitness + ) + + df = pd.DataFrame({ + 'date':dates, + 'testpower':testpower, + 'testduration':testduration, + 'fatigue':fatigues, + 'fitness':fitnesses, + 'impulse':impulses, + }) + df.sort_values(['date'],inplace=True) + df['testdup'] = df['testpower'].shift(1) + df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1) + + try: + df['testpower'].iloc[-1] = df['testdup'].iloc[-1] + except IndexError: + pass + + dates = [d for d in df['date']] + testpower = df['testpower'].values.tolist() + fatigues = df['fatigue'].values.tolist() + fitnesses = df['fitness'].values.tolist() + testduration = df['testduration'].values.tolist() fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues, fitnesses, @@ -1794,6 +1827,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, df = pd.DataFrame({ 'date':dates, 'testpower':testpower, + 'testduration': testduration, 'fatigue':fatigues, 'fitness':fitnesses, 'impulse':impulses, @@ -1819,6 +1853,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, 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'], @@ -1881,8 +1916,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, yaxlabel = 'Fitness' - #plot.circle('date','testpower',source=source,fill_color='green',size=10, - # legend_label=legend_label.format(fitnesstest=fitnesstest)) + if showtests: + plot.circle('date','testpower',source=source,fill_color='green',size=10, + legend_label='Your best workouts') plot.xaxis.axis_label = None plot.yaxis.axis_label = yaxlabel @@ -1937,6 +1973,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, linked_crosshair = CrosshairTool(dimensions='height') + hover.tooltips = OrderedDict([ #(legend_label,'@testpower'), ('Date','@fdate'), @@ -1946,7 +1983,17 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, ('Impulse','@impulse{int}') ]) - + if showtests: + hover.tooltips = OrderedDict([ + #(legend_label,'@testpower'), + ('Date','@fdate'), + (fitlabel,'@fitness{int}'), + (fatiguelabel,'@fatigue{int}'), + (formlabel,'@form{int}'), + ('Impulse','@impulse{int}'), + ('Gold Medal Score','@testpower{int}'), + ('Test', '@testduration'), + ]) plot2 = Figure(tools=TOOLS2,x_axis_type='datetime', plot_width=900,plot_height=150, @@ -2007,7 +2054,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, workouts,fitnesstestsecs,kfitness ) else: - dates,testpower, testduration,fatigues,fitnesses = build_goldmedalstandards( + dates,testpower, testduration,fatigues,fitnesses,impulses = build_goldmedalstandards( workouts,kfitness ) # create CP data diff --git a/rowers/models.py b/rowers/models.py index aa31934e..54ceb777 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -891,6 +891,7 @@ class Rower(models.Model): kfatigue = models.IntegerField(default=7,verbose_name='Fatigue Time Decay Constant (days)') showfit = models.BooleanField(default=False) showfresh = models.BooleanField(default=False) + showtests = models.BooleanField(default=False) pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power") pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power") diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html index 4136e750..e4a8a0f3 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -112,6 +112,11 @@ on the left. The model balances out after a few weeks of regular training, so don't make this chart shorter than a few months.

+

+ Optionally, the chart shows you workouts that represent your best performance for that period. + We automatically detect hard workout segments and tests and compare them to world class + ("Gold Medal") standards for your gender, weight and age category. +

For this chart to reflect your fitness and freshness, it is important to have all workouts on Rowsandall.com. You can automatically import workouts from other fitness platforms. Change diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index e601d3fe..787f85ad 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1567,6 +1567,7 @@ def performancemanager_view(request,userid=0,mode='rower', usegoldmedalstandard = False doform = therower.showfresh dofatigue = therower.showfit + showtests = therower.showtests if request.method == 'POST': form = PerformanceManagerForm(request.POST) @@ -1576,13 +1577,16 @@ def performancemanager_view(request,userid=0,mode='rower', metricchoice = form.cleaned_data['metricchoice'] dofatigue = form.cleaned_data['dofatigue'] doform = form.cleaned_data['doform'] + showtests = form.cleaned_data['showtests'] therower.showfresh = doform therower.showfatigue = dofatigue + therower.showtests = showtests therower.save() else: form = PerformanceManagerForm(initial={ 'doform':doform, 'dofatigue':dofatigue, + 'showtests':showtests, }) script, thediv, endfitness, endfatigue, endform = performance_chart( @@ -1592,6 +1596,7 @@ def performancemanager_view(request,userid=0,mode='rower', metricchoice = metricchoice, doform = doform, dofatigue = dofatigue, + showtests = showtests, ) breadcrumbs = [ @@ -1680,7 +1685,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', date__lte=enddate, workouttype__in=mytypes.rowtypes, duplicate=False) - + script,thediv = fitnessfit_chart( From ff8737703776a3fb8cae34a7517d59c3c73c1273 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 2 Jan 2021 17:27:47 +0100 Subject: [PATCH 4/8] commit --- rowers/templatetags/rowerfilters.py | 2 -- rowers/views/workoutviews.py | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index 6fce2550..b6edc61f 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -215,7 +215,6 @@ def alertenddate(list,i): def is_coach(rower,rowers): for r in rowers: if rower not in rower_get_managers(r): - print(r,rower) return False return True @@ -256,7 +255,6 @@ def hrmajorticks(maxval,minval): for t in ticks: newticks.append(100+t*20) - print(newticks) return newticks def strfdeltah(tdelta): diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index c51bfd3c..2b03fc55 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -3502,19 +3502,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: From 01ba4fe86e9fa9c02df242e13212e7341ad4e759 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 2 Jan 2021 19:03:22 +0100 Subject: [PATCH 5/8] saving state --- rowers/interactiveplots.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c4f5aeb5..dc004f32 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1707,14 +1707,15 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'): def getfatigues( fatigues,fitnesses,dates,testpower,testduration, + impulses, startdate,enddate,user,metricchoice,kfatigue,kfitness): fatigue = 0 fitness = 0 - impulses = [] - for f in fatigues: - impulses.append(0) + #impulses = [] + #for f in fatigues: + # impulses.append(0) lambda_a = 2/(kfatigue+1) lambda_c = 2/(kfitness+1) @@ -1813,11 +1814,13 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, fatigues = df['fatigue'].values.tolist() fitnesses = df['fitness'].values.tolist() testduration = df['testduration'].values.tolist() + impulses = df['impulse'].tolist() fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues, fitnesses, dates, testpower,testduration, + impulses, startdate,enddate, user,metricchoice, kfatigue,kfitness) From ddc679b4c7828227fd84aa132be160f6d1537305 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 2 Jan 2021 22:06:00 +0100 Subject: [PATCH 6/8] fix bug in perf chart --- rowers/dataprep.py | 3 +++ rowers/interactiveplots.py | 24 +++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 9aa856ac..0d3303b0 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1092,6 +1092,9 @@ def workout_goldmedalstandard(workout): return workout.goldmedalstandard,workout.goldmedalseconds if workout.workouttype in rowtypes: goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout) + if workout.workouttype in otwtypes: + factor = 100./(100.-workout.user.otwslack) + goldmedalstandard = goldmedalstandard*factor workout.goldmedalstandard = goldmedalstandard workout.goldmedalseconds = goldmedalseconds workout.save() diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index dc004f32..fb7200f5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -130,8 +130,6 @@ def build_goldmedalstandards(workouts,kfitness): for w in workouts: goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) ids.append(w.id) - if w.workouttype in mytypes.otwtypes: - goldmedalstandard = goldmedalstandard / (1.-w.user.otwslack/100.) if goldmedalseconds > 60: goldmedalstandards.append(goldmedalstandard) goldmedaldurations.append(goldmedalseconds) @@ -1713,9 +1711,9 @@ def getfatigues( fatigue = 0 fitness = 0 - #impulses = [] - #for f in fatigues: - # impulses.append(0) + impulses = [] + for f in fatigues: + impulses.append(0) lambda_a = 2/(kfatigue+1) lambda_c = 2/(kfitness+1) @@ -1752,7 +1750,6 @@ def getfatigues( impulses.append(weight) - fatigue = (1-lambda_a)*fatigue+weight*lambda_a fitness = (1-lambda_c)*fitness+weight*lambda_c @@ -1762,6 +1759,7 @@ def getfatigues( testpower.append(np.nan) testduration.append(np.nan) + return fatigues,fitnesses,dates,testpower,testduration,impulses def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, @@ -1771,6 +1769,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' TOOLS2 = 'box_zoom,hover' + # to avoid data mess later on + startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0) + modelchoice = 'coggan' p0 = 0 @@ -1782,6 +1783,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, fatigues = [] fitnesses = [] testduration = [] + impulses = [] if showtests: workouts = Workout.objects.filter(user=user.rower,date__gte=startdate, @@ -1809,6 +1811,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, except IndexError: pass + dates = [d for d in df['date']] testpower = df['testpower'].values.tolist() fatigues = df['fatigue'].values.tolist() @@ -1836,6 +1839,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, 'impulse':impulses, }) + endfitness = fitnesses[-1] endfatigue = fatigues[-1] endform = endfitness-endfatigue @@ -1853,6 +1857,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, df = df.groupby(['date']).max() df['date'] = df.index.values + #for row in df.iterrows(): + # print(row) + source = ColumnDataSource( data = dict( testpower = df['testpower'], @@ -1929,7 +1936,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, y2rangemin = df.loc[:,['form']].min().min() y2rangemax = df.loc[:,['form']].max().max() - if dofatigue: + if dofatigue and showtests: + y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min() + y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max() + elif dofatigue: y1rangemin = df.loc[:,['fitness','fatigue']].min().min() y1rangemax = df.loc[:,['fitness','fatigue']].max().max() else: From 254f4eeedbd1b8bca19107890c49971ba8d99fc3 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 2 Jan 2021 22:13:40 +0100 Subject: [PATCH 7/8] small improvements on perf chartt --- rowers/interactiveplots.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index fb7200f5..3ccc776d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1771,6 +1771,8 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, # to avoid data mess later on startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0) + enddate = enddate+datetime.timedelta(days=1) + enddate = arrow.get(enddate).datetime.replace(hour=0,minute=0,second=0,microsecond=0) modelchoice = 'coggan' @@ -1938,13 +1940,16 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, y2rangemax = df.loc[:,['form']].max().max() if dofatigue and showtests: y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min() - y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max() + y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max()*1.02 + elif showtests: + y1rangemin = df.loc[:,['testpower','fitness']].min().min() + y1rangemax = df.loc[:,['testpower','fitness']].max().max()*1.02 elif dofatigue: y1rangemin = df.loc[:,['fitness','fatigue']].min().min() - y1rangemax = df.loc[:,['fitness','fatigue']].max().max() + y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02 else: y1rangemin = df.loc[:,['fitness']].min().min() - y1rangemax = df.loc[:,['fitness']].max().max() + y1rangemax = df.loc[:,['fitness']].max().max()*1.02 if doform: plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax) From 545cfa0dec69ea3694974203b3b7c0a6b2912f16 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 3 Jan 2021 15:57:09 +0100 Subject: [PATCH 8/8] better representation of marker workouts --- rowers/forms.py | 2 - rowers/interactiveplots.py | 63 ++++++++++++++++-------- rowers/models.py | 1 - rowers/templates/performancemanager.html | 40 +++++++++++++-- rowers/templatetags/rowerfilters.py | 1 + rowers/views/analysisviews.py | 14 +++--- 6 files changed, 89 insertions(+), 32 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 763d4fdb..f2d73740 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -739,8 +739,6 @@ class PerformanceManagerForm(forms.Form): doform = forms.BooleanField(required=False,initial=False, label='Freshness') - showtests = forms.BooleanField(required=False,initial=False, - label='Show my best workouts') class FitnessFitForm(forms.Form): startdate = forms.DateField( diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 3ccc776d..1809be10 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -114,6 +114,15 @@ def newtestpower(x): return x['testpower'] +def newtestpowerid(x): + try: + if np.isnan(x['testpower']): + return np.nan + except (AttributeError,TypeError): + return np.nan + + return x['id'] + def build_goldmedalstandards(workouts,kfitness): dates = [] testpower = [] @@ -125,11 +134,14 @@ def build_goldmedalstandards(workouts,kfitness): data = [] goldmedalstandards = [] goldmedaldurations = [] - ids = [] workoutdt = [] + ids = [] + + outids = [] + for w in workouts: - goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) ids.append(w.id) + goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) if goldmedalseconds > 60: goldmedalstandards.append(goldmedalstandard) goldmedaldurations.append(goldmedalseconds) @@ -170,6 +182,7 @@ def build_goldmedalstandards(workouts,kfitness): powerdf = df[df['workout'].isin(ids)] indexmax = powerdf['goldmedalstandard'].idxmax() + theid = powerdf.loc[indexmax,'workout'] powertest = powerdf['goldmedalstandard'].max() durationtest = powerdf.loc[indexmax,'goldmedalduration'] @@ -178,15 +191,17 @@ def build_goldmedalstandards(workouts,kfitness): if powertest > 0: testpower.append(powertest) testduration.append(durationtest) + outids.append(theid) else: testpower.append(np.nan) testduration.append(np.nan) + outids.append(np.nan) fatigues.append(np.nan) fitnesses.append(np.nan) impulses.append(np.nan) - return dates, testpower, testduration, fatigues, fitnesses,impulses + return dates, testpower, testduration, fatigues, fitnesses,impulses,outids def get_testpower(workouts,fitnesstestsecs,kfitness): @@ -1760,6 +1775,7 @@ def getfatigues( testduration.append(np.nan) + return fatigues,fitnesses,dates,testpower,testduration,impulses def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, @@ -1787,16 +1803,19 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, testduration = [] impulses = [] + outids = [] + if showtests: workouts = Workout.objects.filter(user=user.rower,date__gte=startdate, date__lte=enddate, workouttype__in=mytypes.rowtypes, duplicate=False) - dates,testpower,testduration,fatigues,fitnesses,impulses = build_goldmedalstandards( + dates,testpower,testduration,fatigues,fitnesses,impulses, outids = build_goldmedalstandards( workouts,kfitness ) df = pd.DataFrame({ + 'id': outids, 'date':dates, 'testpower':testpower, 'testduration':testduration, @@ -1807,11 +1826,12 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, df.sort_values(['date'],inplace=True) df['testdup'] = df['testpower'].shift(1) df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1) + df['id'] = df.apply(lambda x: newtestpowerid(x),axis=1) - try: - df['testpower'].iloc[-1] = df['testdup'].iloc[-1] - except IndexError: - pass + #try: + # df['testpower'].iloc[-1] = df['testdup'].iloc[-1] + #except IndexError: + # pass dates = [d for d in df['date']] @@ -1820,6 +1840,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, fitnesses = df['fitness'].values.tolist() testduration = df['testduration'].values.tolist() impulses = df['impulse'].tolist() + outids = df['id'].unique() fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues, fitnesses, @@ -1859,6 +1880,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, df = df.groupby(['date']).max() df['date'] = df.index.values + #for row in df.iterrows(): # print(row) @@ -1928,9 +1950,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, yaxlabel = 'Fitness' - if showtests: - plot.circle('date','testpower',source=source,fill_color='green',size=10, - legend_label='Your best workouts') + #if showtests: + # plot.circle('date','testpower',source=source,fill_color='green',size=10, + # legend_label='Your best workouts') plot.xaxis.axis_label = None plot.yaxis.axis_label = yaxlabel @@ -1938,13 +1960,13 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, y2rangemin = df.loc[:,['form']].min().min() y2rangemax = df.loc[:,['form']].max().max() - if dofatigue and showtests: - y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min() - y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max()*1.02 - elif showtests: - y1rangemin = df.loc[:,['testpower','fitness']].min().min() - y1rangemax = df.loc[:,['testpower','fitness']].max().max()*1.02 - elif dofatigue: + #if dofatigue and showtests: + # y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min() + # y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max()*1.02 + #elif showtests: + # y1rangemin = df.loc[:,['testpower','fitness']].min().min() + # y1rangemax = df.loc[:,['testpower','fitness']].max().max()*1.02 + if dofatigue: y1rangemin = df.loc[:,['fitness','fatigue']].min().min() y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02 else: @@ -2024,6 +2046,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, plot2.y_range = Range1d(0,df['impulse'].max()) plot2.vbar(x = df['date'], top = df['impulse'],color='gray') + plot2.vbar(x = df['date'], top = 0*df['testpower']+df['impulse'], color='red') plot2.sizing_mode = 'scale_both' plot2.yaxis.axis_label = 'Impulse' @@ -2045,10 +2068,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, nrworkouts = workouts.count(), nrdata = len(df), e = e, - ) + ),0,0,0,[] ) - return [script,div,endfitness,endfatigue,endform] + return [script,div,endfitness,endfatigue,endform,outids] def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, diff --git a/rowers/models.py b/rowers/models.py index 54ceb777..aa31934e 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -891,7 +891,6 @@ class Rower(models.Model): kfatigue = models.IntegerField(default=7,verbose_name='Fatigue Time Decay Constant (days)') showfit = models.BooleanField(default=False) showfresh = models.BooleanField(default=False) - showtests = models.BooleanField(default=False) pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power") pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power") diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html index e4a8a0f3..06908470 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -113,9 +113,12 @@ make this chart shorter than a few months.

- Optionally, the chart shows you workouts that represent your best performance for that period. - We automatically detect hard workout segments and tests and compare them to world class - ("Gold Medal") standards for your gender, weight and age category. + The bottom chart shows the training impulse of each individual workout. A gray bar + denotes a regular workout. The red bars denote workouts that stand out in terms + of your power/time performance for that period. This is only available for workouts + where Power (Watts) is measured. How well you performed is expressed as a + Gold Medal Score, where 100 means you are as good as the world class + athletes of your gender, weight and age category.

For this chart to reflect your fitness and freshness, it is important to have all workouts on @@ -148,6 +151,37 @@

+ {% if bestworkouts %} +

Marker Workouts

+
  • + + + + + + + + + + + {% for w in bestworkouts %} + + + + + + + {% endfor %} + +
    DateWorkoutGold Medal ScoreDuration
    {{ w.date }} + {{ w.name }} + + {{ w.goldmedalstandard|floatformat:"0" }} % + + {{ w.goldmedalseconds|secondstotimestring }} +
    +
  • + {% endif %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index b6edc61f..dd33bcf1 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -270,6 +270,7 @@ def strfdeltah(tdelta): return res +@register.filter def secondstotimestring(tdelta): hours, rest = divmod(tdelta,3600) minutes,seconds = divmod(rest,60) diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 787f85ad..f879150c 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1567,7 +1567,6 @@ def performancemanager_view(request,userid=0,mode='rower', usegoldmedalstandard = False doform = therower.showfresh dofatigue = therower.showfit - showtests = therower.showtests if request.method == 'POST': form = PerformanceManagerForm(request.POST) @@ -1577,28 +1576,30 @@ def performancemanager_view(request,userid=0,mode='rower', metricchoice = form.cleaned_data['metricchoice'] dofatigue = form.cleaned_data['dofatigue'] doform = form.cleaned_data['doform'] - showtests = form.cleaned_data['showtests'] therower.showfresh = doform therower.showfatigue = dofatigue - therower.showtests = showtests therower.save() else: form = PerformanceManagerForm(initial={ 'doform':doform, 'dofatigue':dofatigue, - 'showtests':showtests, }) - script, thediv, endfitness, endfatigue, endform = performance_chart( + script, thediv, endfitness, endfatigue, endform, ids = performance_chart( theuser,startdate=startdate,enddate=enddate, kfitness = kfitness, kfatigue = kfatigue, metricchoice = metricchoice, doform = doform, dofatigue = dofatigue, - showtests = showtests, + showtests = True, ) + ids = pd.Series(ids).dropna().values + + bestworkouts = Workout.objects.filter(id__in=ids).order_by('date') + + breadcrumbs = [ { 'url':'/rowers/analysis', @@ -1634,6 +1635,7 @@ def performancemanager_view(request,userid=0,mode='rower', 'endfitness':int(endfitness), 'endfatigue':int(endfatigue), 'endform':int(endform), + 'bestworkouts':bestworkouts, })