Private
Public Access
1
0

Merge branch 'feature/goldmedalscore' into develop

This commit is contained in:
Sander Roosendaal
2020-12-10 21:17:36 +01:00
10 changed files with 194 additions and 53 deletions
+80 -14
View File
@@ -8,6 +8,7 @@ from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rowers.models import (
Workout, Team, CalcAgePerformance,C2WorldClassAgePerformance,
User
)
import pytz
@@ -325,10 +326,13 @@ def workout_summary_to_df(
startdate=datetime.datetime(1970,1,1),
enddate=timezone.now()+timezone.timedelta(days=1)):
ws = Workout.objects.filter(user=rower).order_by("startdatetime")
ws = Workout.objects.filter(
user=rower,date__gte=startdate,date__lte=enddate
).order_by("startdatetime")
types = []
names = []
ids = []
startdatetimes = []
timezones = []
distances = []
@@ -339,12 +343,19 @@ def workout_summary_to_df(
notes = []
tcx_links = []
csv_links = []
workout_links = []
goldstandards = []
goldstandarddurations = []
rscores = []
hrtss = []
trimps = []
rankingpieces = []
boattypes = []
for w in ws:
types.append(w.workouttype)
names.append(w.name)
ids.append(encoder.encode_hex(w.id))
startdatetimes.append(w.startdatetime)
timezones.append(w.timezone)
distances.append(w.distance)
@@ -352,6 +363,7 @@ def workout_summary_to_df(
weightcategories.append(w.weightcategory)
adaptivetypes.append(w.adaptiveclass)
weightvalues.append(w.weightvalue)
boattypes.append(w.boattype)
notes.append(w.notes)
tcx_link = SITE_URL+'/rowers/workout/{id}/emailtcx'.format(
id=encoder.encode_hex(w.id)
@@ -361,25 +373,41 @@ def workout_summary_to_df(
id=encoder.encode_hex(w.id)
)
csv_links.append(csv_link)
workout_link = SITE_URL+'/rowers/workout/{id}/'.format(
id=encoder.encode_hex(w.id)
)
workout_links.append(workout_link)
trimps.append(workout_trimp(w)[0])
rscore = workout_rscore(w)
rscores.append(int(rscore[0]))
hrtss.append(int(w.hrtss))
goldstandard,goldstandardduration = workout_goldmedalstandard(w)
goldstandards.append(int(goldstandard))
goldstandarddurations.append(int(goldstandardduration))
rankingpieces.append(w.rankingpiece)
df = pd.DataFrame({
'name':names,
'ID': ids,
'date':startdatetimes,
'name':names,
'link':workout_links,
'timezone':timezones,
'type':types,
'boat type':boattypes,
'distance (m)':distances,
'duration ':durations,
'ranking piece':rankingpieces,
'weight category':weightcategories,
'adaptive classification':adaptivetypes,
'weight (kg)':weightvalues,
'notes':notes,
'Stroke Data TCX':tcx_links,
'Stroke Data CSV':csv_links,
'TRIMP Training Load':trimps,
'TSS Training Load':rscores,
'hrTSS Training Load':hrtss,
'GS':goldstandards,
'GS_secs':goldstandarddurations,
'notes':notes,
})
return df
@@ -1026,28 +1054,58 @@ from rowers.datautils import p0
from rowers.utils import calculate_age
from scipy import optimize
def fitscore(rower,workout):
def get_workoutsummaries(userid,startdate):
u = User.objects.get(id=userid)
r = u.rower
df = workout_summary_to_df(r,startdate=startdate)
df.drop(['Stroke Data TCX','Stroke Data CSV'],axis=1,inplace=True)
df = df.sort_values('date',ascending=False)
return df
def workout_goldmedalstandard(workout):
if workout.goldmedalstandard > 0:
return workout.goldmedalstandard,workout.goldmedalseconds
if workout.workouttype in rowtypes:
goldmedalstandard,goldmedalseconds = calculate_goldmedalstandard(workout.user,workout)
workout.goldmedalstandard = goldmedalstandard
workout.goldmedalseconds = goldmedalseconds
workout.save()
return goldmedalstandard, goldmedalseconds
else:
return 0,0
def calculate_goldmedalstandard(rower,workout):
cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id)
try:
df = pd.read_parquet(cpfile)
except:
df, delta, cpvalues = setcp(workout)
if df.empty:
df, delta, cpvalues = setcp(workout)
age = calculate_age(rower.birthdate,today=workout.date)
agerecords = CalcAgePerformance.objects.filter(
age=age,
sex=rower.sex,
weightcategory = rower.weightcategory
)
wcdurations = []
wcpower = []
getrecords = len(agerecords) == 0
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
if record.power > 0:
wcdurations.append(record.duration)
wcpower.append(record.power)
else:
getrecords = True
if len(agerecords)==0:
durations = [1,4,10,20,30,60]
distances = []
if getrecords:
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
df2 = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
@@ -1066,12 +1124,13 @@ def fitscore(rower,workout):
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
if len(wcdurations)>4:
if len(wcdurations)>=4:
p1wc, success = optimize.leastsq(errfunc, p0[:],args=(wcdurations,wcpower))
else:
factor = fitfunc(p0,wcdurations.mean()/wcpower.mean())
p1wc = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]]
success = 0
return 0,0
times = df['delta']
@@ -1079,11 +1138,12 @@ def fitscore(rower,workout):
wcpowers = fitfunc(p1wc,times)
scores = 100.*powers/wcpowers
try:
indexmax = scores.idxmax()
delta = df.loc[indexmax,'delta']
delta = int(df.loc[indexmax,'delta'])
maxvalue = scores.max()
except ValueError:
except (ValueError,TypeError):
indexmax = 0
delta = 0
maxvalue = 0
@@ -1127,7 +1187,6 @@ def setcp(workout,background=False):
return job.id
if not strokesdf.empty:
totaltime = strokesdf['time'].max()
try:
@@ -1150,6 +1209,10 @@ def setcp(workout,background=False):
'id':workout.id,
})
df.to_parquet(filename,engine='fastparquet',compression='GZIP')
goldmedalstandard, goldmedalduration = calculate_goldmedalstandard(workout.user,workout)
workout.goldmedalstandard = goldmedalstandard
workout.goldmedalduration = goldmedalduration
workout.save()
return df,delta,cpvalues
return pd.DataFrame({'delta':[],'cp':[]}),pd.Series(),pd.Series()
@@ -2580,7 +2643,10 @@ def read_df_sql(id):
rowdata,row = getrowdata(id=id)
if rowdata and len(rowdata.df):
data = dataprep(rowdata.df,id=id,bands=True,otwpower=True,barchart=True)
df = pd.read_parquet(f)
try:
df = pd.read_parquet(f)
except OSError:
df = data
else:
df = pd.DataFrame()
+1 -1
View File
@@ -768,7 +768,7 @@ class FitnessFitForm(forms.Form):
fitnesstest = forms.IntegerField(required=True,initial=20,
label='Test Duration (minutes)')
usefitscore = forms.BooleanField(required=False,initial=False,
usegoldmedalstandard = forms.BooleanField(required=False,initial=False,
label='Use best performance against world class')
kfitness = forms.IntegerField(initial=42,required=True,
+42 -22
View File
@@ -25,7 +25,7 @@ import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band
from rowers.utils import myqueue
from rowers.utils import myqueue, totaltime_sec_to_string
import django_rq
queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
@@ -102,39 +102,52 @@ import rowers.datautils as datautils
from pandas.core.groupby.groupby import DataError
def get_fitscore(workouts,kfitness):
def build_goldmedalstandards(workouts,kfitness):
dates = []
testpower = []
testduration = []
fatigues = []
fitnesses = []
data = []
fitscores = []
goldmedalstandards = []
goldmedaldurations = []
ids = []
for w in workouts:
fitscore,fitnesstestsecs = dataprep.fitscore(w.user,w)
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
ids.append(w.id)
fitscores.append(fitscore)
goldmedalstandards.append(goldmedalstandard)
goldmedaldurations.append(goldmedalseconds)
df = pd.DataFrame({'workout':ids,'fitscore':fitscores})
df = pd.DataFrame({
'workout':ids,
'goldmedalstandard':goldmedalstandards,
'goldmedalduration':goldmedaldurations,
})
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()
indexmax = powerdf['goldmedalstandard'].idxmax()
powertest = powerdf['goldmedalstandard'].max()
durationtest = powerdf.loc[indexmax,'goldmedalduration']
dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time()))
testpower.append(powertest)
testduration.append(durationtest)
fatigues.append(np.nan)
fitnesses.append(np.nan)
return dates, testpower, fatigues, fitnesses
return dates, testpower, testduration, fatigues, fitnesses
def get_testpower(workouts,fitnesstestsecs,kfitness):
dates = []
testpower = []
testduration = []
fatigues = []
fitnesses = []
data = []
@@ -192,10 +205,11 @@ def get_testpower(workouts,fitnesstestsecs,kfitness):
dates.append(datetime.datetime.combine(w.date,datetime.datetime.min.time()))
testpower.append(powertest)
testduration.append(fitnesstestsecs)
fatigues.append(np.nan)
fitnesses.append(np.nan)
return dates,testpower,fatigues,fitnesses
return dates,testpower, testduration,fatigues,fitnesses
@@ -1632,7 +1646,7 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'):
return [script,div,js_resources,css_resources]
def getfatigues(
fatigues,fitnesses,dates,testpower,
fatigues,fitnesses,dates,testpower,testduration,
startdate,enddate,user,metricchoice,kfatigue,kfitness):
fatigue = 0
@@ -1685,8 +1699,9 @@ def getfatigues(
fitnesses.append(fitness)
dates.append(datetime.datetime.combine(date,datetime.datetime.min.time()))
testpower.append(np.nan)
testduration.append(np.nan)
return fatigues,fitnesses,dates,testpower,impulses
return fatigues,fitnesses,dates,testpower,testduration,impulses
def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
metricchoice='trimp',doform=False,dofatigue=False):
@@ -1699,6 +1714,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
fitnesses = []
dates = []
testpower = []
testduration = []
modelchoice = 'coggan'
p0 = 0
@@ -1707,11 +1723,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
fatigues,fitnesses,dates,testpower,impulses = getfatigues(fatigues,
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues,
fitnesses,
dates,
testpower,
testpower,testduration,
startdate,enddate,
user,metricchoice,
kfatigue,kfitness)
@@ -1918,7 +1933,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
metricchoice='rscore',
k1=1,k2=1,p0=100,
modelchoice='tsb',
usefitscore=False):
usegoldmedalstandard=False):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
@@ -1929,12 +1944,12 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
fitnesstestsecs = fitnesstest*60
df = pd.DataFrame()
if not usefitscore:
dates,testpower,fatigues,fitnesses = get_testpower(
if not usegoldmedalstandard:
dates,testpower,testduration, fatigues,fitnesses = get_testpower(
workouts,fitnesstestsecs,kfitness
)
else:
dates,testpower,fatigues,fitnesses = get_fitscore(
dates,testpower, testduration,fatigues,fitnesses = build_goldmedalstandards(
workouts,kfitness
)
# create CP data
@@ -1942,6 +1957,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
df = pd.DataFrame({
'date':dates,
'testpower':testpower,
'testduration':testduration,
'fatigue':fatigues,
'fitness':fitnesses,
})
@@ -1962,9 +1978,10 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
testpower = df['testpower'].values.tolist()
fatigues = df['fatigue'].values.tolist()
fitnesses = df['fitness'].values.tolist()
testduration = df['testduration'].values.tolist()
fatigues,fitnesses,dates,testpower,impulses = getfatigues(
fatigues,fitnesses,dates,testpower,
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(
fatigues,fitnesses,dates,testpower,testduration,
startdate,enddate,user,metricchoice,kfatigue,kfitness
)
@@ -1972,6 +1989,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
df = pd.DataFrame({
'date':dates,
'testpower':testpower,
'testduration':testduration,
'fatigue':fatigues,
'fitness':fitnesses,
})
@@ -1997,6 +2015,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
source = ColumnDataSource(
data = dict(
testpower = df['testpower'],
testduration = df['testduration'].apply(lambda x:totaltime_sec_to_string(x,shorten=True)),
date = df['date'],
fdate = df['date'].map(lambda x: x.strftime('%d-%m-%Y')),
fitness = df['fitness'],
@@ -2051,7 +2070,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
formlabel = 'TSB'
rightaxlabel = 'Coggan CTL/ATL/TSB'
if usefitscore:
if usegoldmedalstandard:
legend_label = 'Test Score'
yaxlabel = 'Test Score'
else:
@@ -2106,7 +2125,8 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
hover = plot.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
(legend_label,'@testpower'),
(legend_label,'@testpower{int}'),
('Test', '@testduration'),
('Date','@fdate'),
(fitlabel,'@fitness'),
(fatiguelabel,'@fatigue'),
+1
View File
@@ -2953,6 +2953,7 @@ class Workout(models.Model):
normv = models.FloatField(default=-1,blank=True)
normw = models.FloatField(default=-1,blank=True)
goldmedalstandard = models.FloatField(default=-1,blank=True,verbose_name='Gold Medal Standard')
goldmedalseconds = models.IntegerField(default=0,blank=True,verbose_name='Gold Medal Seconds')
rpe = models.IntegerField(default=0,blank=True,choices=rpechoices,
verbose_name='Rate of Perceived Exertion')
+6 -1
View File
@@ -16,6 +16,7 @@ import json
from scipy import optimize
from scipy.signal import savgol_filter
from scipy.interpolate import griddata
import rowingdata
from rowingdata import make_cumvalues
@@ -333,7 +334,11 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt',
power = 0.5*(np.abs(power)+power)
else:
power = 0
new_age = np.range([age])
ww = griddata(ages.values,
powers.values,
new_age,method='linear',rescale=True)
power = 0.5*(np.abs(power)+power)
else:
power = 0
+7
View File
@@ -49,6 +49,13 @@
</table>
</li>
<li class="grid_2">
<p>Gold Medal Standard: For rowing workouts, the best performance, relative to world class rowers
of your age, gender and weight category,
found in this workout. This metric uses your power data over time and
compares them with the power that the best rowers of your age, gender and weight category
can hold over time.</p>
<p>Gold Medal Standard Duration: The time interval over which your best
performance in this workout was achieved.</p>
<p>rPower: Equivalent steady state power for the duration of the workout.</p>
<p>Heart Rate Drift: Comparing heart rate normalized for average power for the first and second half of the workout</p>
<p>TRIMP: TRaining IMPact. A way to combine duration and heart rate into a single number.</p>
+30 -8
View File
@@ -327,7 +327,10 @@ def calculate_age(born,today=None):
if not today:
today = date.today()
if born:
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
try:
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
except AttributeError:
return None
else:
return None
@@ -373,7 +376,9 @@ def wavg(group, avg_name, weight_name):
except ZeroDivisionError:
return d.mean()
def totaltime_sec_to_string(totaltime):
def totaltime_sec_to_string(totaltime,shorten=False):
if np.isnan(totaltime):
return ''
hours = int(totaltime / 3600.)
if hours > 23:
message = 'Warning: The workout duration was longer than 23 hours. '
@@ -397,12 +402,29 @@ def totaltime_sec_to_string(totaltime):
if not message:
message = 'Warning: there is something wrong with the workout duration'
duration = "{hours:02d}:{minutes:02d}:{seconds:02d}.{tenths}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
tenths=tenths
)
duration = ""
if not shorten:
duration = "{hours:02d}:{minutes:02d}:{seconds:02d}.{tenths}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
tenths=tenths
)
else:
if hours != 0:
duration = "{hours}:{minutes:02d}:{seconds:02d}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
tenths=tenths
)
else:
duration = "{minutes}:{seconds:02d}".format(
hours=hours,
minutes=minutes,
seconds=seconds,
tenths=tenths
)
return duration
+4 -4
View File
@@ -1561,7 +1561,7 @@ def performancemanager_view(request,userid=0,mode='rower',
fitnesstest = 20
metricchoice = 'trimp'
modelchoice = 'tsb'
usefitscore = False
usegoldmedalstandard = False
doform = therower.showfresh
dofatigue = therower.showfit
@@ -1647,7 +1647,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
fitnesstest = 20
metricchoice = 'trimp'
modelchoice = 'tsb'
usefitscore = False
usegoldmedalstandard = False
# temp fit parameters
k1 = 1
@@ -1669,7 +1669,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']
usegoldmedalstandard = form.cleaned_data['usegoldmedalstandard']
else:
form = FitnessFitForm()
@@ -1694,7 +1694,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
metricchoice=metricchoice,
k1=k1,k2=k2,p0=p0,
modelchoice=modelchoice,
usefitscore=usefitscore,
usegoldmedalstandard=usegoldmedalstandard,
)
breadcrumbs = [
+4 -1
View File
@@ -554,7 +554,10 @@ def getrequestplanrower(request,rowerid=0,userid=0,notpermanent=False):
if rowerid != 0:
r = Rower.objects.get(id=rowerid)
elif userid != 0:
u = User.objects.get(id=userid)
try:
u = User.objects.get(id=userid)
except User.DoesNotExist:
raise Http404("User does not exist")
r = getrower(u)
else:
r = getrower(request.user)
+17
View File
@@ -12,6 +12,7 @@ import rowers.mytypes as mytypes
import numpy
from rowers.mailprocessing import send_confirm
import rowers.uploads as uploads
import rowers.utils as utils
from urllib.parse import urlparse, parse_qs
from json.decoder import JSONDecodeError
@@ -3497,6 +3498,22 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
# Normalized power & TSS
tss,normp = dataprep.workout_rscore(w)
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
#if not np.isnan(goldmedalstandard) and goldmedalstandard > 0:
# otherstats['goldmedalstandard'] = {
# 'verbose_name': 'Gold Medal Standard',
# 'value': int(goldmedalstandard),
# 'unit': '%',
# }
#if not np.isnan(goldmedalseconds) and goldmedalseconds > 0:
# otherstats['goldmedalseconds'] = {
# 'verbose_name': 'Gold Medal Standard Duration',
# 'value': utils.totaltime_sec_to_string(goldmedalseconds,shorten=True),
# 'unit': '',
# }
if not np.isnan(tss) and tss != 0: