diff --git a/rowers/forms.py b/rowers/forms.py index 77d94d08..cd2615ab 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -743,7 +743,7 @@ class FitnessFitForm(forms.Form): initial='rscore', label='Workload Metric') - modelchoices = forms.ChoiceField(required=True, + modelchoice = forms.ChoiceField(required=True, choices=modelchoices, initial='tsb', label='Model to use') diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 64d0cd0a..785c259a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1530,7 +1530,8 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'): def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, enddate=None,kfitness=42,kfatigue=7,fitnesstest=20, metricchoice='rscore', - k1=1,k2=1,p0=100): + k1=1,k2=1,p0=100, + modelchoice='tsb'): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' @@ -1598,10 +1599,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, fitnesses.append(np.nan) - allworkouts = Workout.objects.filter(user=user.rower,date__lte=enddate, - date__gte=startdate, - duplicate=False, - ) + df = pd.DataFrame({ 'date':dates, @@ -1613,9 +1611,13 @@ 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']) < 4 \ + df['testpower'] = df.apply(lambda x: np.nan if abs(x['testpower'] - x['testdup']) < 1 \ else x['testpower'],axis=1) + df['testpower'].iloc[-1] = df['testdup'].iloc[-1] + + + dates = [d for d in df['date']] testpower = df['testpower'].values.tolist() fatigues = df['fatigue'].values.tolist() @@ -1623,28 +1625,28 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, - for w in allworkouts: - # create Fitness and Fatigue number - fatigue = 0 - fitness = 0 - - previousworkouts = Workout.objects.filter(user=user.rower,date__lte=w.date, - date__gte=startdate, - duplicate=False) - for ww in previousworkouts: - delta = (w.date-ww.date).days - weight = getattr(ww,metricchoice) - fatigue += weight*math.exp(-delta/kfatigue) - fitness += weight*math.exp(-delta/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 modelchoice == 'tsb': + fatigue = (1-lambda_a)*fatigue+weight*lambda_a + fitness = (1-lambda_c)*fitness+weight*lambda_c + else: + fatigue = fatigue*math.exp(-1./kfatigue) + weight + fitness = fitness*math.exp(-1./kfitness) + weight fatigues.append(fatigue) fitnesses.append(fitness) + dates.append(datetime.datetime.combine(date,datetime.datetime.min.time())) testpower.append(np.nan) - dates.append( - datetime.datetime.combine( - w.date,datetime.datetime.min.time()) - ) - df = pd.DataFrame({ @@ -1654,8 +1656,13 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, 'fitness':fitnesses, }) - df['fatigue'] = k2*df['fatigue'] - df['fitness'] = p0+k1*df['fitness'] + + + if modelchoice == 'banister': + df['fatigue'] = k2*df['fatigue'] + df['fitness'] = p0+k1*df['fitness'] + + df['form'] = df['fitness']-df['fatigue'] @@ -1713,19 +1720,37 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, y_range_name = "watermark", ) + if modelchoice == 'banister': + fitlabel = 'PTE (fitness)' + fatiguelabel = 'NTE (fatigue)' + formlabel = 'Performance' + else: + fitlabel = 'CTL' + fatiguelabel = 'ATL' + formlabel = 'TSB' + plot.circle('date','testpower',source=source,fill_color='green',size=10, legend_label='{fitnesstest} min power'.format(fitnesstest=fitnesstest)) - plot.line('date','fitness',source=source,color='yellow', - legend_label='fitness') - plot.line('date','fatigue',source=source,color='red', - legend_label='fatigue') - plot.line('date','form',source=source,color='green', - legend_label='form') - plot.xaxis.axis_label = 'Date' plot.yaxis.axis_label = 'Power (W)' + + y2rangemin = df.loc[:,['fitness','fatigue','form']].min().min() + y2rangemax = df.loc[:,['fitness','fatigue','form']].max().max() + plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax) + plot.add_layout(LinearAxis(y_range_name="yax2",axis_label="Score"),"right") + + plot.line('date','fitness',source=source,color='blue', + legend_label=fitlabel,y_range_name="yax2") + plot.line('date','fatigue',source=source,color='red', + legend_label=fatiguelabel,y_range_name="yax2") + plot.line('date','form',source=source,color='green', + legend_label=formlabel,y_range_name="yax2") + + + + plot.legend.location = "top_left" plot.xaxis.formatter = DatetimeTickFormatter( diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index fa1f3dc3..efba7b2a 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1556,6 +1556,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', kfatigue = 7 fitnesstest = 20 metricchoice = 'rscore' + modelchoice = 'tsb' # temp fit parameters k1 = 1 @@ -1576,6 +1577,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', k1 = form.cleaned_data['k1'] k2 = form.cleaned_data['k2'] p0 = form.cleaned_data['p0'] + modelchoice = form.cleaned_data['modelchoice'] else: form = FitnessFitForm() @@ -1598,7 +1600,8 @@ def fitness_from_cp_view(request,userid=0,mode='rower', kfatigue=kfatigue, fitnesstest=fitnesstest, metricchoice=metricchoice, - k1=k1,k2=k2,p0=p0 + k1=k1,k2=k2,p0=p0, + modelchoice=modelchoice, ) breadcrumbs = [