Private
Public Access
1
0

Merge branch 'feature/fitnesschart' into develop

This commit is contained in:
Sander Roosendaal
2021-01-03 19:05:32 +01:00
7 changed files with 225 additions and 48 deletions

View File

@@ -1092,6 +1092,9 @@ def workout_goldmedalstandard(workout):
return workout.goldmedalstandard,workout.goldmedalseconds return workout.goldmedalstandard,workout.goldmedalseconds
if workout.workouttype in rowtypes: if workout.workouttype in rowtypes:
goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout) 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.goldmedalstandard = goldmedalstandard
workout.goldmedalseconds = goldmedalseconds workout.goldmedalseconds = goldmedalseconds
workout.save() workout.save()

View File

@@ -739,6 +739,7 @@ class PerformanceManagerForm(forms.Form):
doform = forms.BooleanField(required=False,initial=False, doform = forms.BooleanField(required=False,initial=False,
label='Freshness') label='Freshness')
class FitnessFitForm(forms.Form): class FitnessFitForm(forms.Form):
startdate = forms.DateField( startdate = forms.DateField(
initial=timezone.now()-datetime.timedelta(days=365), initial=timezone.now()-datetime.timedelta(days=365),

View File

@@ -25,6 +25,8 @@ import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band
import arrow
from rowers.utils import myqueue, totaltime_sec_to_string from rowers.utils import myqueue, totaltime_sec_to_string
import django_rq import django_rq
queue = django_rq.get_queue('default') queue = django_rq.get_queue('default')
@@ -102,46 +104,104 @@ import rowers.datautils as datautils
from pandas.core.groupby.groupby import DataError 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): def build_goldmedalstandards(workouts,kfitness):
dates = [] dates = []
testpower = [] testpower = []
testduration = [] testduration = []
fatigues = [] fatigues = []
fitnesses = [] fitnesses = []
impulses = []
data = [] data = []
goldmedalstandards = [] goldmedalstandards = []
goldmedaldurations = [] goldmedaldurations = []
workoutdt = []
ids = [] ids = []
outids = []
for w in workouts: for w in workouts:
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
ids.append(w.id) ids.append(w.id)
goldmedalstandards.append(goldmedalstandard) goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
goldmedaldurations.append(goldmedalseconds) 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({ df = pd.DataFrame({
'workout':ids, 'workout':ids,
'workoutdt': workoutdt,
'goldmedalstandard':goldmedalstandards, 'goldmedalstandard':goldmedalstandards,
'goldmedalduration':goldmedaldurations, '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: 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)] date__lte=w.date)]
powerdf = df[df['workout'].isin(ids)] powerdf = df[df['workout'].isin(ids)]
indexmax = powerdf['goldmedalstandard'].idxmax() indexmax = powerdf['goldmedalstandard'].idxmax()
theid = powerdf.loc[indexmax,'workout']
powertest = powerdf['goldmedalstandard'].max() powertest = powerdf['goldmedalstandard'].max()
durationtest = powerdf.loc[indexmax,'goldmedalduration'] durationtest = powerdf.loc[indexmax,'goldmedalduration']
dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time()))
testpower.append(powertest) dates.append(arrow.get(w.date).datetime)
testduration.append(durationtest) 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) fatigues.append(np.nan)
fitnesses.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): def get_testpower(workouts,fitnesstestsecs,kfitness):
@@ -156,7 +216,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness):
try: try:
df = pd.read_parquet(cpfile) df = pd.read_parquet(cpfile)
df['workout'] = w.id 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) data.append(df)
except: except:
strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id]) 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) testpower.append(powertest)
testduration.append(fitnesstestsecs) testduration.append(fitnesstestsecs)
fatigues.append(np.nan) fatigues.append(np.nan)
@@ -1660,6 +1720,7 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'):
def getfatigues( def getfatigues(
fatigues,fitnesses,dates,testpower,testduration, fatigues,fitnesses,dates,testpower,testduration,
impulses,
startdate,enddate,user,metricchoice,kfatigue,kfitness): startdate,enddate,user,metricchoice,kfatigue,kfitness):
fatigue = 0 fatigue = 0
@@ -1704,42 +1765,88 @@ def getfatigues(
impulses.append(weight) impulses.append(weight)
fatigue = (1-lambda_a)*fatigue+weight*lambda_a fatigue = (1-lambda_a)*fatigue+weight*lambda_a
fitness = (1-lambda_c)*fitness+weight*lambda_c fitness = (1-lambda_c)*fitness+weight*lambda_c
fatigues.append(fatigue) fatigues.append(fatigue)
fitnesses.append(fitness) fitnesses.append(fitness)
dates.append(datetime.datetime.combine(date,datetime.datetime.min.time())) dates.append(arrow.get(date).datetime)
testpower.append(np.nan) testpower.append(np.nan)
testduration.append(np.nan) testduration.append(np.nan)
return fatigues,fitnesses,dates,testpower,testduration,impulses return fatigues,fitnesses,dates,testpower,testduration,impulses
def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, 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' TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
TOOLS2 = 'box_zoom,hover' 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' modelchoice = 'coggan'
p0 = 0 p0 = 0
k1 = 1 k1 = 1
k2 = 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, fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues,
fitnesses, fitnesses,
dates, dates,
testpower,testduration, testpower,testduration,
impulses,
startdate,enddate, startdate,enddate,
user,metricchoice, user,metricchoice,
kfatigue,kfitness) kfatigue,kfitness)
@@ -1749,11 +1856,13 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
df = pd.DataFrame({ df = pd.DataFrame({
'date':dates, 'date':dates,
'testpower':testpower, 'testpower':testpower,
'testduration': testduration,
'fatigue':fatigues, 'fatigue':fatigues,
'fitness':fitnesses, 'fitness':fitnesses,
'impulse':impulses, 'impulse':impulses,
}) })
endfitness = fitnesses[-1] endfitness = fitnesses[-1]
endfatigue = fatigues[-1] endfatigue = fatigues[-1]
endform = endfitness-endfatigue 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 = df.groupby(['date']).max()
df['date'] = df.index.values df['date'] = df.index.values
#for row in df.iterrows():
# print(row)
source = ColumnDataSource( source = ColumnDataSource(
data = dict( data = dict(
testpower = df['testpower'], testpower = df['testpower'],
testduration = df['testduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
date = df['date'], date = df['date'],
fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')), fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')),
fitness = df['fitness'], fitness = df['fitness'],
@@ -1836,8 +1950,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
yaxlabel = 'Fitness' yaxlabel = 'Fitness'
#plot.circle('date','testpower',source=source,fill_color='green',size=10, #if showtests:
# legend_label=legend_label.format(fitnesstest=fitnesstest)) # plot.circle('date','testpower',source=source,fill_color='green',size=10,
# legend_label='Your best workouts')
plot.xaxis.axis_label = None plot.xaxis.axis_label = None
plot.yaxis.axis_label = yaxlabel 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() y2rangemin = df.loc[:,['form']].min().min()
y2rangemax = df.loc[:,['form']].max().max() 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: if dofatigue:
y1rangemin = df.loc[:,['fitness','fatigue']].min().min() y1rangemin = df.loc[:,['fitness','fatigue']].min().min()
y1rangemax = df.loc[:,['fitness','fatigue']].max().max() y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02
else: else:
y1rangemin = df.loc[:,['fitness']].min().min() y1rangemin = df.loc[:,['fitness']].min().min()
y1rangemax = df.loc[:,['fitness']].max().max() y1rangemax = df.loc[:,['fitness']].max().max()*1.02
if doform: if doform:
plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax) 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') linked_crosshair = CrosshairTool(dimensions='height')
hover.tooltips = OrderedDict([ hover.tooltips = OrderedDict([
#(legend_label,'@testpower'), #(legend_label,'@testpower'),
('Date','@fdate'), ('Date','@fdate'),
@@ -1901,7 +2023,17 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
('Impulse','@impulse{int}') ('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', plot2 = Figure(tools=TOOLS2,x_axis_type='datetime',
plot_width=900,plot_height=150, 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.y_range = Range1d(0,df['impulse'].max())
plot2.vbar(x = df['date'], top = df['impulse'],color='gray') 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.sizing_mode = 'scale_both'
plot2.yaxis.axis_label = 'Impulse' plot2.yaxis.axis_label = 'Impulse'
@@ -1935,10 +2068,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
nrworkouts = workouts.count(), nrworkouts = workouts.count(),
nrdata = len(df), nrdata = len(df),
e = e, 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, 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 workouts,fitnesstestsecs,kfitness
) )
else: else:
dates,testpower, testduration,fatigues,fitnesses = build_goldmedalstandards( dates,testpower, testduration,fatigues,fitnesses,impulses = build_goldmedalstandards(
workouts,kfitness workouts,kfitness
) )
# create CP data # create CP data
@@ -1978,8 +2111,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
df['testdup'] = df['testpower'].shift(1) df['testdup'] = df['testpower'].shift(1)
df['testpower'] = df.apply(lambda x: np.nan if abs(x['testpower'] - x['testdup']) < 1 \ df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1)
else x['testpower'],axis=1)
try: try:
df['testpower'].iloc[-1] = df['testdup'].iloc[-1] df['testpower'].iloc[-1] = df['testdup'].iloc[-1]

View File

@@ -112,6 +112,14 @@
on the left. The model balances out after a few weeks of regular training, so don't 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. make this chart shorter than a few months.
</p> </p>
<p>
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.
</p>
<p> <p>
For this chart to reflect your fitness and freshness, it is important to have all workouts on 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 Rowsandall.com. You can automatically import workouts from other fitness platforms. Change
@@ -143,6 +151,37 @@
</p> </p>
</div> </div>
</li> </li>
{% if bestworkouts %}
<h2>Marker Workouts</h2>
<li class="grid_4">
<table width="100%" class="listtable">
<thead>
<tr>
<th>Date</th>
<th>Workout</th>
<th>Gold Medal Score</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
{% for w in bestworkouts %}
<tr>
<td>{{ w.date }}</td>
<td>
<a href="/rowers/workout/{{ w.id|encode }}/">{{ w.name }}
</td>
<td>
{{ w.goldmedalstandard|floatformat:"0" }} %
</td>
<td>
{{ w.goldmedalseconds|secondstotimestring }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</li>
{% endif %}
</ul> </ul>

View File

@@ -215,7 +215,6 @@ def alertenddate(list,i):
def is_coach(rower,rowers): def is_coach(rower,rowers):
for r in rowers: for r in rowers:
if rower not in rower_get_managers(r): if rower not in rower_get_managers(r):
print(r,rower)
return False return False
return True return True
@@ -256,7 +255,6 @@ def hrmajorticks(maxval,minval):
for t in ticks: for t in ticks:
newticks.append(100+t*20) newticks.append(100+t*20)
print(newticks)
return newticks return newticks
def strfdeltah(tdelta): def strfdeltah(tdelta):
@@ -272,6 +270,7 @@ def strfdeltah(tdelta):
return res return res
@register.filter
def secondstotimestring(tdelta): def secondstotimestring(tdelta):
hours, rest = divmod(tdelta,3600) hours, rest = divmod(tdelta,3600)
minutes,seconds = divmod(rest,60) minutes,seconds = divmod(rest,60)

View File

@@ -1585,15 +1585,21 @@ def performancemanager_view(request,userid=0,mode='rower',
'dofatigue':dofatigue, 'dofatigue':dofatigue,
}) })
script, thediv, endfitness, endfatigue, endform = performance_chart( script, thediv, endfitness, endfatigue, endform, ids = performance_chart(
theuser,startdate=startdate,enddate=enddate, theuser,startdate=startdate,enddate=enddate,
kfitness = kfitness, kfitness = kfitness,
kfatigue = kfatigue, kfatigue = kfatigue,
metricchoice = metricchoice, metricchoice = metricchoice,
doform = doform, doform = doform,
dofatigue = dofatigue, dofatigue = dofatigue,
showtests = True,
) )
ids = pd.Series(ids).dropna().values
bestworkouts = Workout.objects.filter(id__in=ids).order_by('date')
breadcrumbs = [ breadcrumbs = [
{ {
'url':'/rowers/analysis', 'url':'/rowers/analysis',
@@ -1629,6 +1635,7 @@ def performancemanager_view(request,userid=0,mode='rower',
'endfitness':int(endfitness), 'endfitness':int(endfitness),
'endfatigue':int(endfatigue), 'endfatigue':int(endfatigue),
'endform':int(endform), '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, workouts = Workout.objects.filter(user=therower,date__gte=startdate,
date__lte=enddate, date__lte=enddate,
workouttype__in=mytypes.otwtypes, workouttype__in=mytypes.rowtypes,
duplicate=False) duplicate=False)
if mode == 'rower':
workouts = Workout.objects.filter(user=therower,date__gte=startdate,
date__lte=enddate,workouttype__in=mytypes.otetypes,
duplicate=False)

View File

@@ -3502,19 +3502,19 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w) goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
#if not np.isnan(goldmedalstandard) and goldmedalstandard > 0: if not np.isnan(goldmedalstandard) and goldmedalstandard > 0:
# otherstats['goldmedalstandard'] = { otherstats['goldmedalstandard'] = {
# 'verbose_name': 'Gold Medal Standard', 'verbose_name': 'Gold Medal Standard',
# 'value': int(goldmedalstandard), 'value': int(goldmedalstandard),
# 'unit': '%', 'unit': '%',
# } }
#if not np.isnan(goldmedalseconds) and goldmedalseconds > 0: if not np.isnan(goldmedalseconds) and goldmedalseconds > 0:
# otherstats['goldmedalseconds'] = { otherstats['goldmedalseconds'] = {
# 'verbose_name': 'Gold Medal Standard Duration', 'verbose_name': 'Gold Medal Standard Duration',
# 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True), 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True),
# 'unit': '', 'unit': '',
# } }
if not np.isnan(tss) and tss != 0: if not np.isnan(tss) and tss != 0: