From 069ac5a182ff6128f13b85c66aa690c19e914630 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 1 Jan 2021 11:01:51 +0100
Subject: [PATCH 1/8] removing print statements
---
rowers/dataprep.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 705e398c..baedc6c4 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -1478,7 +1478,6 @@ def checkbreakthrough(w, r):
def checkduplicates(r,workoutdate,workoutstartdatetime,workoutenddatetime):
- print(workoutdate,workoutstartdatetime,workoutenddatetime)
duplicate = False
ws = Workout.objects.filter(user=r,date=workoutdate,duplicate=False).exclude(
startdatetime__gt=workoutenddatetime
@@ -1487,13 +1486,11 @@ def checkduplicates(r,workoutdate,workoutstartdatetime,workoutenddatetime):
ws2 = []
for ww in ws:
- print(ww)
t = ww.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
enddatetime = ww.startdatetime+delta
print(enddatetime,workoutstartdatetime)
if enddatetime > workoutstartdatetime:
- print('ja')
ws2.append(ww)
From 0e9944044d499de696e934c5bddaff55f729bb2f Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 1 Jan 2021 16:24:42 +0100
Subject: [PATCH 2/8] fitness-fit has now right algo
---
rowers/interactiveplots.py | 66 +++++++++++++++++++++++++++++------
rowers/views/analysisviews.py | 8 ++---
2 files changed, 57 insertions(+), 17 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 4bc6cfd6..b7a9f4ac 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -25,6 +25,8 @@ import itertools
from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc
from bokeh.models import CustomJS,Slider, TextInput,BoxAnnotation, Band
+import arrow
+
from rowers.utils import myqueue, totaltime_sec_to_string
import django_rq
queue = django_rq.get_queue('default')
@@ -102,6 +104,16 @@ import rowers.datautils as datautils
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 build_goldmedalstandards(workouts,kfitness):
dates = []
testpower = []
@@ -113,20 +125,48 @@ def build_goldmedalstandards(workouts,kfitness):
goldmedalstandards = []
goldmedaldurations = []
ids = []
+ workoutdt = []
for w in workouts:
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
ids.append(w.id)
- goldmedalstandards.append(goldmedalstandard)
- goldmedaldurations.append(goldmedalseconds)
+ if w.workouttype in mytypes.otwtypes:
+ goldmedalstandard = goldmedalstandard / (1.-w.user.otwslack/100.)
+ 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({
'workout':ids,
+ 'workoutdt': workoutdt,
'goldmedalstandard':goldmedalstandards,
'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:
- 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)]
powerdf = df[df['workout'].isin(ids)]
@@ -134,9 +174,14 @@ def build_goldmedalstandards(workouts,kfitness):
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)
+
+ dates.append(arrow.get(w.date).datetime)
+ if powertest > 0:
+ testpower.append(powertest)
+ testduration.append(durationtest)
+ else:
+ testpower.append(np.nan)
+ testduration.append(np.nan)
fatigues.append(np.nan)
fitnesses.append(np.nan)
@@ -156,7 +201,7 @@ def get_testpower(workouts,fitnesstestsecs,kfitness):
try:
df = pd.read_parquet(cpfile)
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)
except:
strokesdf = dataprep.getsmallrowdata_db(['power','workoutid','time'],ids=[w.id])
@@ -203,7 +248,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)
testduration.append(fitnesstestsecs)
fatigues.append(np.nan)
@@ -1710,7 +1755,7 @@ def getfatigues(
fatigues.append(fatigue)
fitnesses.append(fitness)
- dates.append(datetime.datetime.combine(date,datetime.datetime.min.time()))
+ dates.append(arrow.get(date).datetime)
testpower.append(np.nan)
testduration.append(np.nan)
@@ -1978,8 +2023,7 @@ 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']) < 1 \
- else x['testpower'],axis=1)
+ df['testpower'] = df.apply(lambda x: newtestpower(x),axis=1)
try:
df['testpower'].iloc[-1] = df['testdup'].iloc[-1]
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 07e5e71c..e601d3fe 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -1678,13 +1678,9 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
workouts = Workout.objects.filter(user=therower,date__gte=startdate,
date__lte=enddate,
- workouttype__in=mytypes.otwtypes,
+ workouttype__in=mytypes.rowtypes,
duplicate=False)
- if mode == 'rower':
- workouts = Workout.objects.filter(user=therower,date__gte=startdate,
- date__lte=enddate,workouttype__in=mytypes.otetypes,
- duplicate=False)
-
+
script,thediv = fitnessfit_chart(
From 87b6684465c76f4ad0f50cd3fee72c734ac70ff3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Fri, 1 Jan 2021 16:51:00 +0100
Subject: [PATCH 3/8] Adding gold medal standard to performance chart
(optional)
---
rowers/forms.py | 3 +
rowers/interactiveplots.py | 71 ++++++++++++++++++++----
rowers/models.py | 1 +
rowers/templates/performancemanager.html | 5 ++
rowers/views/analysisviews.py | 7 ++-
5 files changed, 74 insertions(+), 13 deletions(-)
diff --git a/rowers/forms.py b/rowers/forms.py
index 5266bdc4..763d4fdb 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -739,6 +739,9 @@ class PerformanceManagerForm(forms.Form):
doform = forms.BooleanField(required=False,initial=False,
label='Freshness')
+ showtests = forms.BooleanField(required=False,initial=False,
+ label='Show my best workouts')
+
class FitnessFitForm(forms.Form):
startdate = forms.DateField(
initial=timezone.now()-datetime.timedelta(days=365),
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index b7a9f4ac..c4f5aeb5 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -120,6 +120,7 @@ def build_goldmedalstandards(workouts,kfitness):
testduration = []
fatigues = []
fitnesses = []
+ impulses = []
data = []
goldmedalstandards = []
@@ -185,8 +186,9 @@ def build_goldmedalstandards(workouts,kfitness):
fatigues.append(np.nan)
fitnesses.append(np.nan)
+ impulses.append(np.nan)
- return dates, testpower, testduration, fatigues, fitnesses
+ return dates, testpower, testduration, fatigues, fitnesses,impulses
def get_testpower(workouts,fitnesstestsecs,kfitness):
@@ -1762,24 +1764,55 @@ def getfatigues(
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):
+ metricchoice='trimp',doform=False,dofatigue=False,
+ showtests=False):
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
TOOLS2 = 'box_zoom,hover'
- fatigues = []
- fitnesses = []
- dates = []
- testpower = []
- testduration = []
-
modelchoice = 'coggan'
p0 = 0
k1 = 1
k2 = 1
+ dates = []
+ testpower = []
+ fatigues = []
+ fitnesses = []
+ testduration = []
+ 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 = build_goldmedalstandards(
+ workouts,kfitness
+ )
+
+ df = pd.DataFrame({
+ '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)
+
+ 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()
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues,
fitnesses,
@@ -1794,6 +1827,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
df = pd.DataFrame({
'date':dates,
'testpower':testpower,
+ 'testduration': testduration,
'fatigue':fatigues,
'fitness':fitnesses,
'impulse':impulses,
@@ -1819,6 +1853,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
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'],
@@ -1881,8 +1916,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
yaxlabel = 'Fitness'
- #plot.circle('date','testpower',source=source,fill_color='green',size=10,
- # legend_label=legend_label.format(fitnesstest=fitnesstest))
+ if showtests:
+ plot.circle('date','testpower',source=source,fill_color='green',size=10,
+ legend_label='Your best workouts')
plot.xaxis.axis_label = None
plot.yaxis.axis_label = yaxlabel
@@ -1937,6 +1973,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
linked_crosshair = CrosshairTool(dimensions='height')
+
hover.tooltips = OrderedDict([
#(legend_label,'@testpower'),
('Date','@fdate'),
@@ -1946,7 +1983,17 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
('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',
plot_width=900,plot_height=150,
@@ -2007,7 +2054,7 @@ def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None,
workouts,fitnesstestsecs,kfitness
)
else:
- dates,testpower, testduration,fatigues,fitnesses = build_goldmedalstandards(
+ dates,testpower, testduration,fatigues,fitnesses,impulses = build_goldmedalstandards(
workouts,kfitness
)
# create CP data
diff --git a/rowers/models.py b/rowers/models.py
index aa31934e..54ceb777 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -891,6 +891,7 @@ class Rower(models.Model):
kfatigue = models.IntegerField(default=7,verbose_name='Fatigue Time Decay Constant (days)')
showfit = models.BooleanField(default=False)
showfresh = models.BooleanField(default=False)
+ showtests = models.BooleanField(default=False)
pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html
index 4136e750..e4a8a0f3 100644
--- a/rowers/templates/performancemanager.html
+++ b/rowers/templates/performancemanager.html
@@ -112,6 +112,11 @@
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.
+
+ Optionally, the chart shows you workouts that represent your best performance for that period.
+ We automatically detect hard workout segments and tests and compare them to world class
+ ("Gold Medal") standards for your gender, weight and age category.
+
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
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index e601d3fe..787f85ad 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -1567,6 +1567,7 @@ def performancemanager_view(request,userid=0,mode='rower',
usegoldmedalstandard = False
doform = therower.showfresh
dofatigue = therower.showfit
+ showtests = therower.showtests
if request.method == 'POST':
form = PerformanceManagerForm(request.POST)
@@ -1576,13 +1577,16 @@ def performancemanager_view(request,userid=0,mode='rower',
metricchoice = form.cleaned_data['metricchoice']
dofatigue = form.cleaned_data['dofatigue']
doform = form.cleaned_data['doform']
+ showtests = form.cleaned_data['showtests']
therower.showfresh = doform
therower.showfatigue = dofatigue
+ therower.showtests = showtests
therower.save()
else:
form = PerformanceManagerForm(initial={
'doform':doform,
'dofatigue':dofatigue,
+ 'showtests':showtests,
})
script, thediv, endfitness, endfatigue, endform = performance_chart(
@@ -1592,6 +1596,7 @@ def performancemanager_view(request,userid=0,mode='rower',
metricchoice = metricchoice,
doform = doform,
dofatigue = dofatigue,
+ showtests = showtests,
)
breadcrumbs = [
@@ -1680,7 +1685,7 @@ def fitness_from_cp_view(request,userid=0,mode='rower',
date__lte=enddate,
workouttype__in=mytypes.rowtypes,
duplicate=False)
-
+
script,thediv = fitnessfit_chart(
From ff8737703776a3fb8cae34a7517d59c3c73c1273 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 2 Jan 2021 17:27:47 +0100
Subject: [PATCH 4/8] commit
---
rowers/templatetags/rowerfilters.py | 2 --
rowers/views/workoutviews.py | 24 ++++++++++++------------
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index 6fce2550..b6edc61f 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -215,7 +215,6 @@ def alertenddate(list,i):
def is_coach(rower,rowers):
for r in rowers:
if rower not in rower_get_managers(r):
- print(r,rower)
return False
return True
@@ -256,7 +255,6 @@ def hrmajorticks(maxval,minval):
for t in ticks:
newticks.append(100+t*20)
- print(newticks)
return newticks
def strfdeltah(tdelta):
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index c51bfd3c..2b03fc55 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -3502,19 +3502,19 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
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(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(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:
From 01ba4fe86e9fa9c02df242e13212e7341ad4e759 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 2 Jan 2021 19:03:22 +0100
Subject: [PATCH 5/8] saving state
---
rowers/interactiveplots.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index c4f5aeb5..dc004f32 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -1707,14 +1707,15 @@ def interactive_forcecurve(theworkouts,workstrokesonly=True,plottype='scatter'):
def getfatigues(
fatigues,fitnesses,dates,testpower,testduration,
+ impulses,
startdate,enddate,user,metricchoice,kfatigue,kfitness):
fatigue = 0
fitness = 0
- impulses = []
- for f in fatigues:
- impulses.append(0)
+ #impulses = []
+ #for f in fatigues:
+ # impulses.append(0)
lambda_a = 2/(kfatigue+1)
lambda_c = 2/(kfitness+1)
@@ -1813,11 +1814,13 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
fatigues = df['fatigue'].values.tolist()
fitnesses = df['fitness'].values.tolist()
testduration = df['testduration'].values.tolist()
+ impulses = df['impulse'].tolist()
fatigues,fitnesses,dates,testpower,testduration,impulses = getfatigues(fatigues,
fitnesses,
dates,
testpower,testduration,
+ impulses,
startdate,enddate,
user,metricchoice,
kfatigue,kfitness)
From ddc679b4c7828227fd84aa132be160f6d1537305 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 2 Jan 2021 22:06:00 +0100
Subject: [PATCH 6/8] fix bug in perf chart
---
rowers/dataprep.py | 3 +++
rowers/interactiveplots.py | 24 +++++++++++++++++-------
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 9aa856ac..0d3303b0 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -1092,6 +1092,9 @@ def workout_goldmedalstandard(workout):
return workout.goldmedalstandard,workout.goldmedalseconds
if workout.workouttype in rowtypes:
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.goldmedalseconds = goldmedalseconds
workout.save()
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index dc004f32..fb7200f5 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -130,8 +130,6 @@ def build_goldmedalstandards(workouts,kfitness):
for w in workouts:
goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
ids.append(w.id)
- if w.workouttype in mytypes.otwtypes:
- goldmedalstandard = goldmedalstandard / (1.-w.user.otwslack/100.)
if goldmedalseconds > 60:
goldmedalstandards.append(goldmedalstandard)
goldmedaldurations.append(goldmedalseconds)
@@ -1713,9 +1711,9 @@ def getfatigues(
fatigue = 0
fitness = 0
- #impulses = []
- #for f in fatigues:
- # impulses.append(0)
+ impulses = []
+ for f in fatigues:
+ impulses.append(0)
lambda_a = 2/(kfatigue+1)
lambda_c = 2/(kfitness+1)
@@ -1752,7 +1750,6 @@ def getfatigues(
impulses.append(weight)
-
fatigue = (1-lambda_a)*fatigue+weight*lambda_a
fitness = (1-lambda_c)*fitness+weight*lambda_c
@@ -1762,6 +1759,7 @@ def getfatigues(
testpower.append(np.nan)
testduration.append(np.nan)
+
return fatigues,fitnesses,dates,testpower,testduration,impulses
def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
@@ -1771,6 +1769,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
TOOLS2 = 'box_zoom,hover'
+ # to avoid data mess later on
+ startdate = arrow.get(startdate).datetime.replace(hour=0,minute=0,second=0,microsecond=0)
+
modelchoice = 'coggan'
p0 = 0
@@ -1782,6 +1783,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
fatigues = []
fitnesses = []
testduration = []
+ impulses = []
if showtests:
workouts = Workout.objects.filter(user=user.rower,date__gte=startdate,
@@ -1809,6 +1811,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
except IndexError:
pass
+
dates = [d for d in df['date']]
testpower = df['testpower'].values.tolist()
fatigues = df['fatigue'].values.tolist()
@@ -1836,6 +1839,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
'impulse':impulses,
})
+
endfitness = fitnesses[-1]
endfatigue = fatigues[-1]
endform = endfitness-endfatigue
@@ -1853,6 +1857,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
df = df.groupby(['date']).max()
df['date'] = df.index.values
+ #for row in df.iterrows():
+ # print(row)
+
source = ColumnDataSource(
data = dict(
testpower = df['testpower'],
@@ -1929,7 +1936,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
y2rangemin = df.loc[:,['form']].min().min()
y2rangemax = df.loc[:,['form']].max().max()
- if dofatigue:
+ if dofatigue and showtests:
+ y1rangemin = df.loc[:,['testpower','fitness','fatigue']].min().min()
+ y1rangemax = df.loc[:,['testpower','fitness','fatigue']].max().max()
+ elif dofatigue:
y1rangemin = df.loc[:,['fitness','fatigue']].min().min()
y1rangemax = df.loc[:,['fitness','fatigue']].max().max()
else:
From 254f4eeedbd1b8bca19107890c49971ba8d99fc3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 2 Jan 2021 22:13:40 +0100
Subject: [PATCH 7/8] small improvements on perf chartt
---
rowers/interactiveplots.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index fb7200f5..3ccc776d 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -1771,6 +1771,8 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
# 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)
modelchoice = 'coggan'
@@ -1938,13 +1940,16 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
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()
+ 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
elif dofatigue:
y1rangemin = df.loc[:,['fitness','fatigue']].min().min()
- y1rangemax = df.loc[:,['fitness','fatigue']].max().max()
+ y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02
else:
y1rangemin = df.loc[:,['fitness']].min().min()
- y1rangemax = df.loc[:,['fitness']].max().max()
+ y1rangemax = df.loc[:,['fitness']].max().max()*1.02
if doform:
plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin,end=y2rangemax)
From 545cfa0dec69ea3694974203b3b7c0a6b2912f16 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 3 Jan 2021 15:57:09 +0100
Subject: [PATCH 8/8] better representation of marker workouts
---
rowers/forms.py | 2 -
rowers/interactiveplots.py | 63 ++++++++++++++++--------
rowers/models.py | 1 -
rowers/templates/performancemanager.html | 40 +++++++++++++--
rowers/templatetags/rowerfilters.py | 1 +
rowers/views/analysisviews.py | 14 +++---
6 files changed, 89 insertions(+), 32 deletions(-)
diff --git a/rowers/forms.py b/rowers/forms.py
index 763d4fdb..f2d73740 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -739,8 +739,6 @@ class PerformanceManagerForm(forms.Form):
doform = forms.BooleanField(required=False,initial=False,
label='Freshness')
- showtests = forms.BooleanField(required=False,initial=False,
- label='Show my best workouts')
class FitnessFitForm(forms.Form):
startdate = forms.DateField(
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 3ccc776d..1809be10 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -114,6 +114,15 @@ def newtestpower(x):
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):
dates = []
testpower = []
@@ -125,11 +134,14 @@ def build_goldmedalstandards(workouts,kfitness):
data = []
goldmedalstandards = []
goldmedaldurations = []
- ids = []
workoutdt = []
+ ids = []
+
+ outids = []
+
for w in workouts:
- goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
ids.append(w.id)
+ goldmedalstandard,goldmedalseconds = dataprep.workout_goldmedalstandard(w)
if goldmedalseconds > 60:
goldmedalstandards.append(goldmedalstandard)
goldmedaldurations.append(goldmedalseconds)
@@ -170,6 +182,7 @@ def build_goldmedalstandards(workouts,kfitness):
powerdf = df[df['workout'].isin(ids)]
indexmax = powerdf['goldmedalstandard'].idxmax()
+ theid = powerdf.loc[indexmax,'workout']
powertest = powerdf['goldmedalstandard'].max()
durationtest = powerdf.loc[indexmax,'goldmedalduration']
@@ -178,15 +191,17 @@ def build_goldmedalstandards(workouts,kfitness):
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)
fitnesses.append(np.nan)
impulses.append(np.nan)
- return dates, testpower, testduration, fatigues, fitnesses,impulses
+ return dates, testpower, testduration, fatigues, fitnesses,impulses,outids
def get_testpower(workouts,fitnesstestsecs,kfitness):
@@ -1760,6 +1775,7 @@ def getfatigues(
testduration.append(np.nan)
+
return fatigues,fitnesses,dates,testpower,testduration,impulses
def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
@@ -1787,16 +1803,19 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
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 = build_goldmedalstandards(
+ dates,testpower,testduration,fatigues,fitnesses,impulses, outids = build_goldmedalstandards(
workouts,kfitness
)
df = pd.DataFrame({
+ 'id': outids,
'date':dates,
'testpower':testpower,
'testduration':testduration,
@@ -1807,11 +1826,12 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
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
+ #try:
+ # df['testpower'].iloc[-1] = df['testdup'].iloc[-1]
+ #except IndexError:
+ # pass
dates = [d for d in df['date']]
@@ -1820,6 +1840,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
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,
fitnesses,
@@ -1859,6 +1880,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
df = df.groupby(['date']).max()
df['date'] = df.index.values
+
#for row in df.iterrows():
# print(row)
@@ -1928,9 +1950,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
yaxlabel = 'Fitness'
- if showtests:
- plot.circle('date','testpower',source=source,fill_color='green',size=10,
- legend_label='Your best workouts')
+ #if showtests:
+ # plot.circle('date','testpower',source=source,fill_color='green',size=10,
+ # legend_label='Your best workouts')
plot.xaxis.axis_label = None
plot.yaxis.axis_label = yaxlabel
@@ -1938,13 +1960,13 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
y2rangemin = df.loc[:,['form']].min().min()
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
- elif dofatigue:
+ #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:
y1rangemin = df.loc[:,['fitness','fatigue']].min().min()
y1rangemax = df.loc[:,['fitness','fatigue']].max().max()*1.02
else:
@@ -2024,6 +2046,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
plot2.y_range = Range1d(0,df['impulse'].max())
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.yaxis.axis_label = 'Impulse'
@@ -2045,10 +2068,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7,
nrworkouts = workouts.count(),
nrdata = len(df),
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,
diff --git a/rowers/models.py b/rowers/models.py
index 54ceb777..aa31934e 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -891,7 +891,6 @@ class Rower(models.Model):
kfatigue = models.IntegerField(default=7,verbose_name='Fatigue Time Decay Constant (days)')
showfit = models.BooleanField(default=False)
showfresh = models.BooleanField(default=False)
- showtests = models.BooleanField(default=False)
pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html
index e4a8a0f3..06908470 100644
--- a/rowers/templates/performancemanager.html
+++ b/rowers/templates/performancemanager.html
@@ -113,9 +113,12 @@
make this chart shorter than a few months.
- Optionally, the chart shows you workouts that represent your best performance for that period.
- We automatically detect hard workout segments and tests and compare them to world class
- ("Gold Medal") standards for your gender, weight and age category.
+ 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.
For this chart to reflect your fitness and freshness, it is important to have all workouts on
@@ -148,6 +151,37 @@
+ {% if bestworkouts %}
+ Marker Workouts
+
+
+
+
+ | Date |
+ Workout |
+ Gold Medal Score |
+ Duration |
+
+
+
+ {% for w in bestworkouts %}
+
+ | {{ w.date }} |
+
+ {{ w.name }}
+ |
+
+ {{ w.goldmedalstandard|floatformat:"0" }} %
+ |
+
+ {{ w.goldmedalseconds|secondstotimestring }}
+ |
+
+ {% endfor %}
+
+
+
+ {% endif %}
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index b6edc61f..dd33bcf1 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -270,6 +270,7 @@ def strfdeltah(tdelta):
return res
+@register.filter
def secondstotimestring(tdelta):
hours, rest = divmod(tdelta,3600)
minutes,seconds = divmod(rest,60)
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 787f85ad..f879150c 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -1567,7 +1567,6 @@ def performancemanager_view(request,userid=0,mode='rower',
usegoldmedalstandard = False
doform = therower.showfresh
dofatigue = therower.showfit
- showtests = therower.showtests
if request.method == 'POST':
form = PerformanceManagerForm(request.POST)
@@ -1577,28 +1576,30 @@ def performancemanager_view(request,userid=0,mode='rower',
metricchoice = form.cleaned_data['metricchoice']
dofatigue = form.cleaned_data['dofatigue']
doform = form.cleaned_data['doform']
- showtests = form.cleaned_data['showtests']
therower.showfresh = doform
therower.showfatigue = dofatigue
- therower.showtests = showtests
therower.save()
else:
form = PerformanceManagerForm(initial={
'doform':doform,
'dofatigue':dofatigue,
- 'showtests':showtests,
})
- script, thediv, endfitness, endfatigue, endform = performance_chart(
+ script, thediv, endfitness, endfatigue, endform, ids = performance_chart(
theuser,startdate=startdate,enddate=enddate,
kfitness = kfitness,
kfatigue = kfatigue,
metricchoice = metricchoice,
doform = doform,
dofatigue = dofatigue,
- showtests = showtests,
+ showtests = True,
)
+ ids = pd.Series(ids).dropna().values
+
+ bestworkouts = Workout.objects.filter(id__in=ids).order_by('date')
+
+
breadcrumbs = [
{
'url':'/rowers/analysis',
@@ -1634,6 +1635,7 @@ def performancemanager_view(request,userid=0,mode='rower',
'endfitness':int(endfitness),
'endfatigue':int(endfatigue),
'endform':int(endform),
+ 'bestworkouts':bestworkouts,
})