diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 5e7caf0f..d3e8c4ac 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1078,9 +1078,16 @@ def fitscore(rower,workout): wcpowers = fitfunc(p1wc,times) scores = 100.*powers/wcpowers - indexmax = scores.idxmax() + try: + indexmax = scores.idxmax() + delta = df.loc[indexmax,'delta'] + maxvalue = scores.max() + except ValueError: + indexmax = 0 + delta = 0 + maxvalue = 0 - return scores.max(),df.loc[indexmax,'delta'] + return maxvalue,delta def fetchcp_new(rower,workouts): diff --git a/rowers/forms.py b/rowers/forms.py index cd2615ab..bc05c8e7 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -732,6 +732,9 @@ class FitnessFitForm(forms.Form): fitnesstest = forms.IntegerField(required=True,initial=20, label='Test Duration (minutes)') + usefitscore = forms.BooleanField(required=False,initial=False, + label='Use best performance against world class') + kfitness = forms.IntegerField(initial=42,required=True, label='Fitness Time Constant (days)') diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8ede6c38..3709ddd7 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -102,6 +102,103 @@ import rowers.datautils as datautils from pandas.core.groupby.groupby import DataError +def get_fitscore(workouts,kfitness): + dates = [] + testpower = [] + fatigues = [] + fitnesses = [] + data = [] + fitscores = [] + ids = [] + for w in workouts: + fitscore,fitnesstestsecs = dataprep.fitscore(w.user,w) + ids.append(w.id) + fitscores.append(fitscore) + + df = pd.DataFrame({'workout':ids,'fitscore':fitscores}) + + 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() + + dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) + testpower.append(powertest) + fatigues.append(np.nan) + fitnesses.append(np.nan) + + return dates, testpower, fatigues, fitnesses + + +def get_testpower(workouts,fitnesstestsecs,kfitness): + dates = [] + testpower = [] + fatigues = [] + fitnesses = [] + data = [] + for w in workouts: + cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=w.id) + try: + df = pd.read_parquet(cpfile) + df['workout'] = w.id + df['workoutdate'] = w.date.strftime('%d-%m-%Y') + data.append(df) + except: + strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id]) + res = myqueue(queuelow, + handle_setcp, + strokesdf, + cpfile,w.id) + + if len(data)>1: + df = pd.concat(data,axis=0) + + fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + for w in workouts: + # Create CP data point for date range + ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness), + date__lte=w.date)] + + try: + powerdf = df[df['workout'].isin(ids)] + + powerdf = powerdf[powerdf['cp'] == powerdf.groupby(['delta'])['cp'].transform('max')] + powerdf = powerdf.sort_values(['delta']).reset_index() + + + powerdf = powerdf[powerdf['cp']>0] + powerdf.dropna(axis=0,inplace=True) + powerdf.sort_values(['delta','cp'],ascending=[1,0],inplace=True) + powerdf.drop_duplicates(subset='delta',keep='first',inplace=True) + except KeyError: + powerdf = pd.DataFrame() + + # p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) + if len(powerdf['delta'])>= 4: + thesecs = powerdf['delta'].values + theavpower = powerdf['cp'].values + + if thesecs.min() < fitnesstestsecs and thesecs.max() > fitnesstestsecs: + ww = griddata(thesecs,theavpower,np.array([fitnesstestsecs]),method='linear',rescale=True) + powertest = ww[0] + else: + powertest = np.nan + + + + dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) + testpower.append(powertest) + fatigues.append(np.nan) + fitnesses.append(np.nan) + + return dates,testpower,fatigues,fitnesses + + + def errorbar(fig, x, y, source=ColumnDataSource(), xerr=False, yerr=False, color='black', point_kwargs={}, error_kwargs={}): @@ -1538,81 +1635,27 @@ 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, - modelchoice='tsb'): + modelchoice='tsb', + usefitscore=False): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - dates = [] - testpower = [] - fatigues = [] - fitnesses = [] + workouts = workouts.order_by('date') - data = [] fitnesstestsecs = fitnesstest*60 df = pd.DataFrame() + if not usefitscore: + dates,testpower,fatigues,fitnesses = get_testpower( + workouts,fitnesstestsecs,kfitness + ) + else: + dates,testpower,fatigues,fitnesses = get_fitscore( + workouts,kfitness + ) # create CP data - for w in workouts: - cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=w.id) - try: - df = pd.read_parquet(cpfile) - df['workout'] = w.id - df['workoutdate'] = w.date.strftime('%d-%m-%Y') - data.append(df) - except: - strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id]) - res = myqueue(queuelow, - handle_setcp, - strokesdf, - cpfile,w.id) - - if len(data)>1: - df = pd.concat(data,axis=0) - - fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) - errfunc = lambda pars,x,y: fitfunc(pars,x)-y - - for w in workouts: - # Create CP data point for date range - ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=kfitness), - date__lte=w.date)] - - try: - powerdf = df[df['workout'].isin(ids)] - - powerdf = powerdf[powerdf['cp'] == powerdf.groupby(['delta'])['cp'].transform('max')] - powerdf = powerdf.sort_values(['delta']).reset_index() - - - powerdf = powerdf[powerdf['cp']>0] - powerdf.dropna(axis=0,inplace=True) - powerdf.sort_values(['delta','cp'],ascending=[1,0],inplace=True) - powerdf.drop_duplicates(subset='delta',keep='first',inplace=True) - except KeyError: - powerdf = pd.DataFrame() - - # p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) - if len(powerdf['delta'])>= 4: - thesecs = powerdf['delta'].values - theavpower = powerdf['cp'].values - - if thesecs.min() < fitnesstestsecs and thesecs.max() > fitnesstestsecs: - ww = griddata(thesecs,theavpower,np.array([fitnesstestsecs]),method='linear',rescale=True) - powertest = ww[0] - else: - powertest = np.nan - - - - dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time())) - testpower.append(powertest) - fatigues.append(np.nan) - fitnesses.append(np.nan) - - - df = pd.DataFrame({ 'date':dates, @@ -1654,7 +1697,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, 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())) @@ -1743,11 +1786,18 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, formlabel = 'TSB' rightaxlabel = 'Coggan CTL/ATL/TSB' + if usefitscore: + legend_label = 'Test Score' + yaxlabel = 'Test Score' + else: + legend_label = '{fitnesstest} min power' + yaxlabel = 'Test Power (Watt)' + plot.circle('date','testpower',source=source,fill_color='green',size=10, - legend_label='{fitnesstest} min power'.format(fitnesstest=fitnesstest)) + legend_label=legend_label.format(fitnesstest=fitnesstest)) plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = 'Test Power (Watt)' + plot.yaxis.axis_label = yaxlabel y2rangemin = df.loc[:,['fitness','fatigue','form']].min().min() diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index efba7b2a..e3402241 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1557,6 +1557,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', fitnesstest = 20 metricchoice = 'rscore' modelchoice = 'tsb' + usefitscore = False # temp fit parameters k1 = 1 @@ -1578,6 +1579,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'] else: form = FitnessFitForm() @@ -1602,6 +1604,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower', metricchoice=metricchoice, k1=k1,k2=k2,p0=p0, modelchoice=modelchoice, + usefitscore=usefitscore, ) breadcrumbs = [