diff --git a/rowers/forms.py b/rowers/forms.py index bc05c8e7..26defb73 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -703,6 +703,36 @@ class FitnessMetricForm(forms.Form): class Meta: fields = ['startdate','enddate','mode'] +class PerformanceManagerForm(forms.Form): + startdate = forms.DateField( + initial=timezone.now()-datetime.timedelta(days=365), + # widget=SelectDateWidget(years=range(1990,2050)), + widget=AdminDateWidget(), + label='Start Date') + enddate = forms.DateField( + initial=timezone.now(), + widget=AdminDateWidget(), + label='End Date') + + metricchoices = ( + ('trimp','Use Heart Rate Data'), + ('rscore','Use Power and Heart Rate Data'), + ) + + metricchoice = forms.ChoiceField( + required=True, + choices=metricchoices, + initial='trimp', + label='Data', + widget=forms.RadioSelect + ) + + dofatigue = forms.BooleanField(required=False,initial=False, + label='Fatigue') + + 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 6ef69641..3150b026 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1631,6 +1631,238 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'): return [script,div,js_resources,css_resources] +def getfatigues( + fatigues,fitnesses,dates,testpower, + startdate,enddate,user,metricchoice,kfatigue,kfitness): + + fatigue = 0 + fitness = 0 + + lambda_a = 2/(kfatigue+1) + lambda_c = 2/(kfitness+1) + nrdays = (enddate-startdate).days + for i in range(nrdays): + date = startdate+datetime.timedelta(days=i) + ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False) + weight = 0 + for w in ws: + weight += getattr(w,metricchoice) + if getattr(w,metricchoice) == 0: + if metricchoice == 'rscore' and w.hrtss != 0: + weight+= w.hrtss + else: + trimp,hrtss = dataprep.workout_trimp(w) + rscore,normp = dataprep.workout_rscore(w) + + + 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())) + testpower.append(np.nan) + + return fatigues,fitnesses,dates,testpower + +def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, + metricchoice='trimp',doform=False,dofatigue=False): + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + fatigues = [] + fitnesses = [] + dates = [] + testpower = [] + + modelchoice = 'coggan' + p0 = 0 + k1 = 1 + k2 = 1 + + + + + fatigues,fitnesses,dates,testpower = getfatigues(fatigues, + fitnesses, + dates, + testpower, + startdate,enddate, + user,metricchoice, + kfatigue,kfitness) + + + + df = pd.DataFrame({ + 'date':dates, + 'testpower':testpower, + 'fatigue':fatigues, + 'fitness':fitnesses, + }) + + + + if modelchoice == 'banister': + df['fatigue'] = k2*df['fatigue'] + df['fitness'] = p0+k1*df['fitness'] + + + df['form'] = df['fitness']-df['fatigue'] + + + + df.sort_values(['date'],inplace=True) + df = df.groupby(['date']).max() + df['date'] = df.index.values + + source = ColumnDataSource( + data = dict( + testpower = df['testpower'], + date = df['date'], + fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')), + fitness = df['fitness'], + fatigue = df['fatigue'], + form = df['form'], + ) + ) + + + plot = Figure(tools=TOOLS,x_axis_type='datetime', + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + + # add watermark + watermarkurl = "/static/img/logo7.png" + watermarksource = ColumnDataSource(dict( + url = [watermarkurl],)) + + watermarkrange = Range1d(start=0,end=1) + watermarkalpha = 0.6 + watermarkx = 0.99 + watermarky = 0.01 + watermarkw = 184 + watermarkh = 35 + watermarkanchor = 'bottom_right' + plot.extra_y_ranges = {"watermark": watermarkrange} + plot.extra_x_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],watermarkx,watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + x_range_name = "watermark", + y_range_name = "watermark", + ) + + if modelchoice == 'banister': + fitlabel = 'PTE (fitness)' + fatiguelabel = 'NTE (fatigue)' + formlabel = 'Performance' + rightaxlabel = 'NTE' + if doform: + yaxlabel = 'PTE/Performance' + else: + yaxlabel = 'PTE' + else: + fitlabel = 'Fitness' + fatiguelabel = 'Fatigue' + formlabel = 'Freshness' + rightaxlabel = 'Fatigue' + if doform: + yaxlabel = 'Fitness/Freshness' + else: + yaxlabel = 'Fitness' + + + #plot.circle('date','testpower',source=source,fill_color='green',size=10, + # legend_label=legend_label.format(fitnesstest=fitnesstest)) + + plot.xaxis.axis_label = 'Date' + plot.yaxis.axis_label = yaxlabel + + + y2rangemin = df.loc[:,['form']].min().min() + y2rangemax = df.loc[:,['form']].max().max() + if dofatigue: + y1rangemin = df.loc[:,['fitness','fatigue']].min().min() + y1rangemax = df.loc[:,['fitness','fatigue']].max().max() + else: + y1rangemin = df.loc[:,['fitness']].min().min() + y1rangemax = df.loc[:,['fitness']].max().max() + + if doform: + plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax) + plot.add_layout(LinearAxis(y_range_name="yax2",axis_label=rightaxlabel),"right") + + plot.line('date','fitness',source=source,color='blue', + legend_label=fitlabel) + band = Band(base='date', upper='fitness', source=source, level='underlay', + fill_alpha=0.2, fill_color='blue') + plot.add_layout(band) + + + if dofatigue: + plot.line('date','fatigue',source=source,color='red', + legend_label=fatiguelabel) + if doform: + plot.line('date','form',source=source,color='green', + legend_label=formlabel,y_range_name="yax2") + + plot.legend.location = "top_left" + + plot.xaxis.formatter = DatetimeTickFormatter( + days=["%d %B %Y"], + months=["%d %B %Y"], + years=["%d %B %Y"], + ) + + plot.xaxis.major_label_orientation = pi/4 + plot.sizing_mode = 'stretch_both' + + #plot.y_range = Range1d(0,1.5*max(df['testpower'])) + startdate = datetime.datetime.combine(startdate,datetime.datetime.min.time()) + enddate = datetime.datetime.combine(enddate,datetime.datetime.min.time()) + + plot.x_range = Range1d( + startdate,enddate+datetime.timedelta(days=5), + ) + plot.y_range = Range1d( + start=0,end=y1rangemax, + ) + plot.title.text = 'Performance Manager '+user.first_name + + hover = plot.select(dict(type=HoverTool)) + + hover.tooltips = OrderedDict([ + #(legend_label,'@testpower'), + ('Date','@fdate'), + (fitlabel,'@fitness'), + (fatiguelabel,'@fatigue'), + (formlabel,'@form') + ]) + + try: + script,div = components(plot) + except Exception as e: + df.dropna(inplace=True,axis=0,how='any') + return ( + '', + 'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format( + nrworkouts = workouts.count(), + nrdata = len(df), + e = e, + ) + ) + + return [script,div] + + def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, enddate=None,kfitness=42,kfatigue=7,fitnesstest=20, metricchoice='rscore', @@ -1681,34 +1913,10 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, fatigues = df['fatigue'].values.tolist() fitnesses = df['fitness'].values.tolist() - - - fatigue = 0 - fitness = 0 - lambda_a = 2/(kfatigue+1) - lambda_c = 2/(kfitness+1) - nrdays = (enddate-startdate).days - for i in range(nrdays): - date = startdate+datetime.timedelta(days=i) - ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False) - weight = 0 - for w in ws: - weight += getattr(w,metricchoice) - if getattr(w,metricchoice) == 0: - if metricchoice == 'rscore' and w.hrtss != 0: - weight+= w.hrtss - else: - trimp,hrtss = dataprep.workout_trimp(w) - rscore,normp = dataprep.workout_rscore(w) - - - 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())) - testpower.append(np.nan) + fatigues,fitnesses,dates,testpower = getfatigues( + fatigues,fitnesses,dates,testpower, + startdate,enddate,user,metricchoice,kfatigue,kfitness + ) df = pd.DataFrame({ diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index 12a5d364..d0684f55 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -11,6 +11,18 @@