Private
Public Access
1
0

Merge branch 'feature/banister' into develop

This commit is contained in:
Sander Roosendaal
2020-11-24 17:52:23 +01:00
5 changed files with 152 additions and 40 deletions

View File

@@ -703,6 +703,59 @@ class FitnessMetricForm(forms.Form):
class Meta:
fields = ['startdate','enddate','mode']
class FitnessFitForm(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')
modechoices = (
('rower','indoor rower'),
('water','on the water')
)
metricchoices = (
('trimp','TRIMP'),
('rscore','rScore')
)
fitnesstest = forms.IntegerField(required=True,initial=20,
label='Test Duration (minutes)')
kfitness = forms.IntegerField(initial=42,required=True,
label='Fitness Time Constant (days)')
kfatigue = forms.IntegerField(initial=7,required=True,
label='Fatigue Time Constant (days)')
metricchoice = forms.ChoiceField(required=True,
choices=metricchoices,
initial='rscore',
label='Workload Metric')
# temporary
k1 = forms.FloatField(required=True,initial=1.0,
label='k1')
k2 = forms.FloatField(required=True,initial=1.0,
label='k2')
p0 = forms.IntegerField(required=True,initial=100,label='Unfit Performance')
mode = forms.ChoiceField(required=True,
choices=modechoices,
initial='rower',
label='Workout Mode'
)
class Meta:
fields = ['startdate','enddate','mode','fitnesstest',
'kfitness','kfatigue','metricchoice',
'k1','k2','p0']
class SessionDateShiftForm(forms.Form):
shiftstartdate = forms.DateField(
initial=timezone.now(),

View File

@@ -1528,28 +1528,35 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'):
return [script,div,js_resources,css_resources]
def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
enddate=None,nrdays=42):
enddate=None,kfitness=42,kfatigue=7,fitnesstest=20,
metricchoice='rscore',
k1=1,k2=1,p0=100):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
dates = []
fourminpower = []
hourpower = []
testpower = []
fatigues = []
fitnesses = []
workouts = workouts.order_by('date')
data = []
fitnesstestsecs = fitnesstest*60
# 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:
pass
if len(data) == 0:
return pd.Series(),pd.Series(),0,pd.Series()
return '','Insufficient data'
if len(data)>1:
df = pd.concat(data,axis=0)
@@ -1557,7 +1564,8 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
for w in workouts:
ids = [w.id for w in workouts.filter(date__gte=w.date-datetime.timedelta(days=nrdays),
# 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)]
powerdf = df[df['workout'].isin(ids)]
@@ -1577,48 +1585,61 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
thesecs = powerdf['delta'].values
theavpower = powerdf['cp'].values
if thesecs.min() < 240 and thesecs.max() > 240:
ww = griddata(thesecs,theavpower,np.arange(239,241,1),method='linear',rescale=True)
powerfourmin = ww[1]
if thesecs.min() < fitnesstestsecs and thesecs.max() > fitnesstestsecs:
ww = griddata(thesecs,theavpower,np.array([fitnesstestsecs]),method='linear',rescale=True)
powertest = ww[0]
else:
powerfourmin = np.nan
powertest = np.nan
if thesecs.min() < 3600 and thesecs.max() > 3600:
ww = griddata(thesecs,theavpower,np.arange(3599,3601,1),method='linear',rescale=True)
powerhour = ww[1]
else:
powerhour = np.nan
dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time()))
fourminpower.append(powerfourmin)
hourpower.append(powerhour)
testpower.append(powertest)
# create Fitness and Fatigue number
fatigue = 0
fitness = 0
previousworkouts = workouts.filter(date__lte=w.date)
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)
fatigues.append(fatigue)
fitnesses.append(fitness)
df = pd.DataFrame({
'date':dates,
'fourminpower':fourminpower,
'hourpower':hourpower,
'testpower':testpower,
'fatigue':fatigues,
'fitness':fitnesses,
})
df['fatigue'] = k2*df['fatigue']
df['fitness'] = p0+k1*df['fitness']
df['form'] = df['fitness']-df['fatigue']
df.sort_values(['date'],inplace=True)
df['hourdup'] = df['hourpower'].shift(1)
df['fourmindup'] = df['fourminpower'].shift(1)
df['hourpower'] = df.apply(lambda x: np.nan if abs(x['hourpower'] - x['hourdup']) < 4 \
else x['hourpower'],axis=1)
df['fourminpower'] = df.apply(lambda x: np.nan if abs(x['fourminpower'] - x['fourmindup']) < 4 \
else x['fourminpower'],axis=1)
df['testdup'] = df['testpower'].shift(1)
df['testpower'] = df.apply(lambda x: np.nan if abs(x['testpower'] - x['testdup']) < 4 \
else x['testpower'],axis=1)
source = ColumnDataSource(
data = dict(
fourminpower = df['fourminpower'],
hourpower = df['hourpower'],
testpower = df['testpower'],
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'],
fatigue = df['fatigue'],
form = df['form'],
)
)
@@ -1656,14 +1677,21 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
y_range_name = "watermark",
)
plot.circle('date','fourminpower',source=source,fill_color='green',size=10,legend_label='4 min power')
plot.circle('date','hourpower',source=source,fill_color='blue',size=10,legend_label='60 min power')
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)'
plot.legend.location = "top_left"
plot.xaxis.formatter = DatetimeTickFormatter(
days=["%d %B %Y"],
months=["%d %B %Y"],
@@ -1673,7 +1701,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
plot.xaxis.major_label_orientation = pi/4
plot.sizing_mode = 'stretch_both'
plot.y_range = Range1d(0,1.5*max(fourminpower))
plot.y_range = Range1d(0,1.5*max(testpower))
startdate = datetime.datetime.combine(startdate,datetime.datetime.min.time())
enddate = datetime.datetime.combine(enddate,datetime.datetime.min.time())
@@ -1685,8 +1713,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
hover = plot.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
('Power 4 minutes','@fourminpower'),
('Power 1 hour','@hourpower'),
('Power {fitnesstest} minutes'.format(fitnesstest=fitnesstest),'@testpower'),
('Date','@fdate')
])

View File

@@ -72,11 +72,22 @@
</script>
{% if rower.user %}
<h1>Power Progress for {{ rower.user.first_name }} </h1>
<h1>Fitness Progress for {{ rower.user.first_name }} </h1>
{% else %}
<h1>Power Progress for {{ user.first_name }} </h1>
<h1>Fitness Progress for {{ user.first_name }} </h1>
{% endif %}
<p>
This is an experimental page. Using the form below you can set
a date range to compare a prediction of your form (from TRIMP or rScore values
representing your workload). The Fitness Time Constant is the
time constant describing your fitness decline when you stop training. The
Fatigue Time Constant (shorter than fitness time constant) is the time
constant describing fatigue decline after a session. You have to select
a test duration that represents your fitness goal and is a duration for which
you regularly do maximal or submaximal efforts during your regular rowing workouts.
</p>
<ul class="main-content">
<li class="grid_2">

View File

@@ -1551,17 +1551,33 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
therower = getrequestrower(request,userid=userid)
theuser = therower.user
kfitness = 42
kfatigue = 7
fitnesstest = 20
metricchoice = 'rscore'
# temp fit parameters
k1 = 1
k2 = 1
p0 = 100.
if request.method == 'POST':
form = FitnessMetricForm(request.POST)
form = FitnessFitForm(request.POST)
if form.is_valid():
startdate = form.cleaned_data['startdate']
enddate = form.cleaned_data['enddate']
mode = form.cleaned_data['mode']
kfitness = form.cleaned_data['kfitness']
kfatigue = form.cleaned_data['kfatigue']
fitnesstest = form.cleaned_data['fitnesstest']
metricchoice = form.cleaned_data['metricchoice']
# temporary manual "fit" parameters
k1 = form.cleaned_data['k1']
k2 = form.cleaned_data['k2']
p0 = form.cleaned_data['p0']
else:
form = FitnessMetricForm()
form = FitnessFitForm()
workouts = Workout.objects.filter(user=therower,date__gte=startdate,
date__lte=enddate,
@@ -1576,6 +1592,11 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
workouts,theuser,
workoutmode=mode,startdate=startdate,
enddate=enddate,
kfitness=kfitness,
kfatigue=kfatigue,
fitnesstest=fitnesstest,
metricchoice=metricchoice,
k1=k1,k2=k2,p0=p0
)
breadcrumbs = [

View File

@@ -76,7 +76,7 @@ from rowers.forms import (
disqualifiers,SearchForm,BillingForm,PlanSelectForm,
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
StravaChartForm,
StravaChartForm,FitnessFitForm
)
from django.urls import reverse, reverse_lazy