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/forms.py b/rowers/forms.py index 5266bdc4..f2d73740 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -739,6 +739,7 @@ class PerformanceManagerForm(forms.Form): doform = forms.BooleanField(required=False,initial=False, label='Freshness') + 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 4bc6cfd6..1809be10 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,46 +104,104 @@ 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 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 = [] testduration = [] fatigues = [] fitnesses = [] + impulses = [] data = [] goldmedalstandards = [] goldmedaldurations = [] + workoutdt = [] ids = [] + + outids = [] + for w in workouts: - goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) ids.append(w.id) - goldmedalstandards.append(goldmedalstandard) - goldmedaldurations.append(goldmedalseconds) + goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) + 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)] indexmax = powerdf['goldmedalstandard'].idxmax() + theid = powerdf.loc[indexmax,'workout'] 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) + 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 + return dates, testpower, testduration, fatigues, fitnesses,impulses,outids def get_testpower(workouts,fitnesstestsecs,kfitness): @@ -156,7 +216,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 +263,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) @@ -1660,6 +1720,7 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'): def getfatigues( fatigues,fitnesses,dates,testpower,testduration, + impulses, startdate,enddate,user,metricchoice,kfatigue,kfitness): fatigue = 0 @@ -1704,42 +1765,88 @@ def getfatigues( impulses.append(weight) - fatigue = (1-lambda_a)*fatigue+weight*lambda_a fitness = (1-lambda_c)*fitness+weight*lambda_c 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) + + 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' + # 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) - fatigues = [] - fitnesses = [] - dates = [] - testpower = [] - testduration = [] modelchoice = 'coggan' p0 = 0 k1 = 1 k2 = 1 + dates = [] + testpower = [] + fatigues = [] + fitnesses = [] + 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, outids = build_goldmedalstandards( + workouts,kfitness + ) + + df = pd.DataFrame({ + 'id': outids, + '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) + df['id'] = df.apply(lambda x: newtestpowerid(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() + impulses = df['impulse'].tolist() + outids = df['id'].unique() fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues, fitnesses, dates, testpower,testduration, + impulses, startdate,enddate, user,metricchoice, kfatigue,kfitness) @@ -1749,11 +1856,13 @@ 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, }) + endfitness = fitnesses[-1] endfatigue = fatigues[-1] endform = endfitness-endfatigue @@ -1771,9 +1880,14 @@ 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'], + 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'], @@ -1836,8 +1950,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 @@ -1845,12 +1960,18 @@ 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 if 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) @@ -1892,6 +2013,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'), @@ -1901,7 +2023,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, @@ -1914,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' @@ -1935,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, @@ -1962,7 +2095,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 @@ -1978,8 +2111,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/templates/performancemanager.html b/rowers/templates/performancemanager.html index 4136e750..06908470 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -112,6 +112,14 @@ 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.

+

+ 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 Rowsandall.com. You can automatically import workouts from other fitness platforms. Change @@ -143,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 6fce2550..dd33bcf1 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): @@ -272,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 07e5e71c..f879150c 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1585,15 +1585,21 @@ def performancemanager_view(request,userid=0,mode='rower', 'dofatigue':dofatigue, }) - 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 = True, ) + ids = pd.Series(ids).dropna().values + + bestworkouts = Workout.objects.filter(id__in=ids).order_by('date') + + breadcrumbs = [ { 'url':'/rowers/analysis', @@ -1629,6 +1635,7 @@ def performancemanager_view(request,userid=0,mode='rower', 'endfitness':int(endfitness), 'endfatigue':int(endfatigue), 'endform':int(endform), + 'bestworkouts':bestworkouts, }) @@ -1678,12 +1685,8 @@ 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) 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: