Merge branch 'release/v16.4.0'
This commit is contained in:
@@ -468,7 +468,6 @@ def getfastest(df,thevalue,mode='distance'):
|
|||||||
starttime = griddata(restime,starttimes,[thevalue*60*1000],method='linear',rescale=True)
|
starttime = griddata(restime,starttimes,[thevalue*60*1000],method='linear',rescale=True)
|
||||||
duration = griddata(restime,restime,[thevalue*60*1000],method='linear',rescale=True)
|
duration = griddata(restime,restime,[thevalue*60*1000],method='linear',rescale=True)
|
||||||
endtime = starttime+duration
|
endtime = starttime+duration
|
||||||
print(distance,starttime,endtime )
|
|
||||||
return distance[0],starttime[0]/1000.,endtime[0]/1000.
|
return distance[0],starttime[0]/1000.,endtime[0]/1000.
|
||||||
|
|
||||||
return 0 # pragma: no cover
|
return 0 # pragma: no cover
|
||||||
|
|||||||
@@ -58,6 +58,24 @@ class FlexibleDecimalField(forms.DecimalField):
|
|||||||
value = value.replace('.', '').replace(',', '.')
|
value = value.replace('.', '').replace(',', '.')
|
||||||
return super(FlexibleDecimalField, self).to_python(value)
|
return super(FlexibleDecimalField, self).to_python(value)
|
||||||
|
|
||||||
|
class TrainingZonesForm(forms.Form):
|
||||||
|
zoneschoices = (
|
||||||
|
('power','Power Zones'),
|
||||||
|
('hr','Heart Rate Zones')
|
||||||
|
)
|
||||||
|
|
||||||
|
zones = forms.ChoiceField(initial='hr',label='Training Zones',choices=zoneschoices)
|
||||||
|
startdate = forms.DateField(
|
||||||
|
initial=timezone.now()-datetime.timedelta(days=365),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='Start Date')
|
||||||
|
enddate = forms.DateField(
|
||||||
|
initial=timezone.now(),
|
||||||
|
# widget=SelectDateWidget(years=range(1990,2050)),
|
||||||
|
widget=AdminDateWidget(), #format='%Y-%m-%d'),
|
||||||
|
label='End Date')
|
||||||
|
|
||||||
|
|
||||||
class InstantPlanSelectForm(forms.Form):
|
class InstantPlanSelectForm(forms.Form):
|
||||||
datechoices = (
|
datechoices = (
|
||||||
('startdate','start date'),
|
('startdate','start date'),
|
||||||
|
|||||||
@@ -715,13 +715,14 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l
|
|||||||
dd = w.date.strftime('%m/%d')
|
dd = w.date.strftime('%m/%d')
|
||||||
dd2 = w.date.strftime('%Y/%m/%d')
|
dd2 = w.date.strftime('%Y/%m/%d')
|
||||||
dd3 = w.date.strftime('%Y/%m')
|
dd3 = w.date.strftime('%Y/%m')
|
||||||
|
|
||||||
du = w.duration.hour*60+w.duration.minute
|
du = w.duration.hour*60+w.duration.minute
|
||||||
trimp = w.trimp
|
trimp = w.trimp
|
||||||
rscore = w.rscore
|
rscore = w.rscore
|
||||||
if rscore == 0: # pragma: no cover
|
if rscore == 0: # pragma: no cover
|
||||||
rscore = w.hrtss
|
rscore = w.hrtss
|
||||||
|
|
||||||
if totaldays<30: # pragma: no cover
|
if totaldays<=30: # pragma: no cover
|
||||||
dates.append(dd)
|
dates.append(dd)
|
||||||
dates_sorting.append(dd2)
|
dates_sorting.append(dd2)
|
||||||
else:
|
else:
|
||||||
@@ -759,7 +760,7 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l
|
|||||||
dd = d.strftime('%d')
|
dd = d.strftime('%d')
|
||||||
|
|
||||||
|
|
||||||
if totaldays<30:
|
if totaldays<=30:
|
||||||
dates.append(d.strftime('%m/%d'))
|
dates.append(d.strftime('%m/%d'))
|
||||||
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
||||||
else:
|
else:
|
||||||
@@ -6557,3 +6558,318 @@ def interactive_otw_advanced_pace_chart(id=0,promember=0):
|
|||||||
div = ''
|
div = ''
|
||||||
|
|
||||||
return [script,div]
|
return [script,div]
|
||||||
|
|
||||||
|
def get_zones_report(rower,startdate,enddate,trainingzones='hr'):
|
||||||
|
duration = enddate-startdate
|
||||||
|
|
||||||
|
totaldays = duration.total_seconds()/(24*3600)
|
||||||
|
|
||||||
|
dates = []
|
||||||
|
dates_sorting = []
|
||||||
|
minutes = []
|
||||||
|
hours = []
|
||||||
|
zones = []
|
||||||
|
|
||||||
|
workouts = Workout.objects.filter(
|
||||||
|
user=rower,
|
||||||
|
startdatetime__gte=startdate,
|
||||||
|
startdatetime__lte=enddate,
|
||||||
|
duplicate=False,
|
||||||
|
).order_by("-startdatetime")
|
||||||
|
|
||||||
|
ids = [w.id for w in workouts]
|
||||||
|
|
||||||
|
columns = ['workoutid','hr','power','time']
|
||||||
|
|
||||||
|
df = dataprep.getsmallrowdata_db(columns,ids=ids)
|
||||||
|
try:
|
||||||
|
df['deltat'] = df['time'].diff().clip(lower=0)
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
df = dataprep.clean_df_stats(df,workstrokesonly=False,
|
||||||
|
ignoreadvanced=True,ignorehr=False)
|
||||||
|
|
||||||
|
#totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts)
|
||||||
|
|
||||||
|
hrzones = rower.hrzones
|
||||||
|
powerzones = rower.powerzones
|
||||||
|
|
||||||
|
for w in workouts:
|
||||||
|
dd = w.date.strftime('%m/%d')
|
||||||
|
dd2 = w.date.strftime('%Y/%m/%d')
|
||||||
|
dd3 = w.date.strftime('%Y/%m')
|
||||||
|
dd4 = arrow.get(w.date).isocalendar()[1]
|
||||||
|
#print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar())
|
||||||
|
|
||||||
|
qryw = 'workoutid == {workoutid}'.format(workoutid=w.id)
|
||||||
|
|
||||||
|
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = 'power < {ut2}'.format(ut2=rower.pw_ut2)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append('<{ut2}'.format(ut2=hrzones[1]))
|
||||||
|
else:
|
||||||
|
zones.append('<{ut2}'.format(ut2=powerzones[1]))
|
||||||
|
#print(w,dd,timeinzone,'<UT2')
|
||||||
|
|
||||||
|
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{ut2} <= power < {ut2}'.format(ut1=rower.pw_ut1,ut2=rower.pw_ut2)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[1])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[1])
|
||||||
|
#print(w,dd,timeinzone,'UT2')
|
||||||
|
|
||||||
|
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{ut1} <= power < {at}'.format(ut1=rower.pw_ut1,at=rower.pw_at)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[2])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[2])
|
||||||
|
#print(w,dd,timeinzone,'UT1')
|
||||||
|
|
||||||
|
qry = '{at} <= hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{at} <= power < {tr}'.format(at=rower.pw_at,tr=rower.pw_tr)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[3])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[3])
|
||||||
|
#print(w,dd,timeinzone,'AT')
|
||||||
|
|
||||||
|
qry = '{tr} <= hr < {an}'.format(tr=rower.tr,an=rower.an)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr,an=rower.pw_an)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[4])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[4])
|
||||||
|
#print(w,dd,timeinzone,'TR')
|
||||||
|
|
||||||
|
qry = 'hr >= {an}'.format(an=rower.an)
|
||||||
|
if trainingzones == 'power':
|
||||||
|
qry = 'power >= {an}'.format(an=rower.pw_an)
|
||||||
|
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||||
|
if totaldays<=30:
|
||||||
|
dates.append(dd)
|
||||||
|
dates_sorting.append(dd2)
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else: # pragma: no cover
|
||||||
|
dates.append(dd3)
|
||||||
|
dates_sorting.append(dd3)
|
||||||
|
minutes.append(timeinzone)
|
||||||
|
hours.append(timeinzone/60.)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[5])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[5])
|
||||||
|
#print(w,dd,timeinzone,'AN')
|
||||||
|
|
||||||
|
try:
|
||||||
|
d = utc.localize(startdate)
|
||||||
|
except (ValueError,AttributeError): # pragma: no cover
|
||||||
|
d = startdate
|
||||||
|
|
||||||
|
try:
|
||||||
|
enddate = utc.localize(enddate)
|
||||||
|
except (ValueError,AttributeError): # pragma: no cover
|
||||||
|
pass
|
||||||
|
|
||||||
|
while d<=enddate:
|
||||||
|
dd = d.strftime('%d')
|
||||||
|
if totaldays <= 30:
|
||||||
|
dates.append(d.strftime('%m/%d'))
|
||||||
|
dates_sorting.append(d.strftime('%Y/%m/%d'))
|
||||||
|
elif totaldays<=121: # pragma: no cover
|
||||||
|
dd4 = arrow.get(d).isocalendar()[1]
|
||||||
|
dates.append(dd4)
|
||||||
|
dates_sorting.append(dd4)
|
||||||
|
else:
|
||||||
|
dates.append(d.strftime('%Y/%m'))
|
||||||
|
dates_sorting.append(d.strftime('%Y/%m'))
|
||||||
|
|
||||||
|
minutes.append(0)
|
||||||
|
hours.append(0)
|
||||||
|
if trainingzones == 'hr':
|
||||||
|
zones.append(hrzones[1])
|
||||||
|
else:
|
||||||
|
zones.append(powerzones[1])
|
||||||
|
|
||||||
|
d += datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# this should be renamed with rower zones
|
||||||
|
data = {
|
||||||
|
'date':dates,
|
||||||
|
'date_sorting':dates_sorting,
|
||||||
|
'minutes': minutes,
|
||||||
|
'zones':zones,
|
||||||
|
'hours':hours,
|
||||||
|
}
|
||||||
|
|
||||||
|
#print(pd.DataFrame(data).head())
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def interactive_zoneschart(rower,data,startdate,enddate,trainingzones='hr'):
|
||||||
|
duration = enddate-startdate
|
||||||
|
|
||||||
|
totaldays = duration.total_seconds()/(24*3600)
|
||||||
|
|
||||||
|
colors = ['gray','yellow','lime','blue','purple','red']
|
||||||
|
|
||||||
|
hrzones = rower.hrzones
|
||||||
|
powerzones = rower.powerzones
|
||||||
|
|
||||||
|
|
||||||
|
color_map = {
|
||||||
|
'<{ut2}'.format(ut2=hrzones[1]):'gray',
|
||||||
|
hrzones[1]:'lime',
|
||||||
|
hrzones[2]:'yellow',
|
||||||
|
hrzones[3]:'blue',
|
||||||
|
hrzones[4]:'purple',
|
||||||
|
hrzones[5]:'red',
|
||||||
|
}
|
||||||
|
if trainingzones == 'power':
|
||||||
|
color_map = {
|
||||||
|
'<{ut2}'.format(ut2=powerzones[1]):'gray',
|
||||||
|
powerzones[1]:'lime',
|
||||||
|
powerzones[2]:'yellow',
|
||||||
|
powerzones[3]:'blue',
|
||||||
|
powerzones[4]:'purple',
|
||||||
|
powerzones[5]:'red',
|
||||||
|
}
|
||||||
|
|
||||||
|
zones_order = [
|
||||||
|
'<{ut2}'.format(ut2=hrzones[1]),
|
||||||
|
hrzones[1],
|
||||||
|
hrzones[2],
|
||||||
|
hrzones[3],
|
||||||
|
hrzones[4],
|
||||||
|
hrzones[5]
|
||||||
|
]
|
||||||
|
|
||||||
|
if trainingzones == 'power':
|
||||||
|
zones_order = [
|
||||||
|
'<{ut2}'.format(ut2=powerzones[1]),
|
||||||
|
powerzones[1],
|
||||||
|
powerzones[2],
|
||||||
|
powerzones[3],
|
||||||
|
powerzones[4],
|
||||||
|
powerzones[5]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
if totaldays > 30:
|
||||||
|
df.drop('minutes',inplace=True,axis='columns')
|
||||||
|
else:
|
||||||
|
df.drop('hours',inplace=True,axis='columns')
|
||||||
|
|
||||||
|
|
||||||
|
source = ColumnDataSource(df)
|
||||||
|
|
||||||
|
df.sort_values('date_sorting',inplace=True)
|
||||||
|
df.drop('date_sorting',inplace=True,axis='columns')
|
||||||
|
|
||||||
|
hv.extension('bokeh')
|
||||||
|
|
||||||
|
|
||||||
|
bars = hv.Bars(df, kdims = ['date','zones']).aggregate(function=np.sum).redim.values(zones=zones_order)
|
||||||
|
|
||||||
|
#bars = table.to.bars(['date','zones'],['minutes'])
|
||||||
|
bars.opts(
|
||||||
|
opts.Bars(cmap=color_map,show_legend=True,stacked=True,
|
||||||
|
tools=['tap','hover'],width=550,padding=(0,(0,.1)),
|
||||||
|
legend_position='bottom',
|
||||||
|
show_frame=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
p = hv.render(bars)
|
||||||
|
|
||||||
|
p.title.text = 'Activity {d1} to {d2} for {r}'.format(
|
||||||
|
d1 = startdate.strftime("%Y-%m-%d"),
|
||||||
|
d2 = enddate.strftime("%Y-%m-%d"),
|
||||||
|
r = str(rower),
|
||||||
|
)
|
||||||
|
|
||||||
|
if totaldays >= 30:
|
||||||
|
p.xaxis.axis_label = 'Week'
|
||||||
|
if totaldays >= 121:
|
||||||
|
p.xaxis.axis_label = 'Month'
|
||||||
|
|
||||||
|
p.plot_width=550
|
||||||
|
p.plot_height=350
|
||||||
|
p.toolbar_location = 'above'
|
||||||
|
p.y_range.start = 0
|
||||||
|
p.sizing_mode = 'stretch_both'
|
||||||
|
|
||||||
|
script,div = components(p)
|
||||||
|
|
||||||
|
return script,div
|
||||||
|
|||||||
@@ -359,6 +359,8 @@ def get_todays_micro(plan,thedate=date.today()):
|
|||||||
enddate__gte = thedate
|
enddate__gte = thedate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if thismacro:
|
if thismacro:
|
||||||
thismeso = TrainingMesoCycle.objects.filter(
|
thismeso = TrainingMesoCycle.objects.filter(
|
||||||
plan=thismacro[0],
|
plan=thismacro[0],
|
||||||
@@ -366,6 +368,8 @@ def get_todays_micro(plan,thedate=date.today()):
|
|||||||
enddate__gte = thedate
|
enddate__gte = thedate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if thismeso:
|
if thismeso:
|
||||||
thismicro = TrainingMicroCycle.objects.filter(
|
thismicro = TrainingMicroCycle.objects.filter(
|
||||||
plan=thismeso[0],
|
plan=thismeso[0],
|
||||||
|
|||||||
@@ -2806,7 +2806,7 @@ def handle_update_wps(rid,types,ids,mode,debug=False,**kwargs):
|
|||||||
return 0
|
return 0
|
||||||
try:
|
try:
|
||||||
wps_median = int(df.loc[mask,'driveenergy'].median())
|
wps_median = int(df.loc[mask,'driveenergy'].median())
|
||||||
except ValueError:
|
except ValueError: # pragma: no cover
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if mode == 'water':
|
if mode == 'water':
|
||||||
@@ -3029,7 +3029,7 @@ def df_from_summary(data):
|
|||||||
weightclass = data['weight_class']
|
weightclass = data['weight_class']
|
||||||
try:
|
try:
|
||||||
title = data['name']
|
title = data['name']
|
||||||
except KeyError:
|
except KeyError: # pragma: no cover
|
||||||
title = ""
|
title = ""
|
||||||
try:
|
try:
|
||||||
t = data['comments'].split('\n', 1)[0]
|
t = data['comments'].split('\n', 1)[0]
|
||||||
@@ -3043,13 +3043,22 @@ def df_from_summary(data):
|
|||||||
|
|
||||||
startdatetime,starttime,workoutdate,duration,starttimeunix,timezone = utils.get_startdatetime_from_c2data(data)
|
startdatetime,starttime,workoutdate,duration,starttimeunix,timezone = utils.get_startdatetime_from_c2data(data)
|
||||||
|
|
||||||
splits = data['workout']['splits']
|
try:
|
||||||
|
splits = data['workout']['splits']
|
||||||
|
except KeyError:
|
||||||
|
splits = [0]
|
||||||
time = starttimeunix
|
time = starttimeunix
|
||||||
elapsed_distance = 0
|
elapsed_distance = 0
|
||||||
times = [0]
|
times = [0]
|
||||||
distances = [0]
|
distances = [0]
|
||||||
spms = [splits[0]['stroke_rate']]
|
try:
|
||||||
hrs = [splits[0]['heart_rate']['average']]
|
spms = [splits[0]['stroke_rate']]
|
||||||
|
except KeyError:
|
||||||
|
spms = [0]
|
||||||
|
try:
|
||||||
|
hrs = [splits[0]['heart_rate']['average']]
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
hrs = [0]
|
||||||
|
|
||||||
for split in splits:
|
for split in splits:
|
||||||
time += split['time']/10.
|
time += split['time']/10.
|
||||||
@@ -3057,7 +3066,10 @@ def df_from_summary(data):
|
|||||||
times.append(time)
|
times.append(time)
|
||||||
distances.append(elapsed_distance)
|
distances.append(elapsed_distance)
|
||||||
spms.append(split['stroke_rate'])
|
spms.append(split['stroke_rate'])
|
||||||
hrs.append(split['heart_rate']['average'])
|
try:
|
||||||
|
hrs.append(split['heart_rate']['average'])
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
hrs.append(0)
|
||||||
|
|
||||||
df = pd.DataFrame({
|
df = pd.DataFrame({
|
||||||
'TimeStamp (sec)': times,
|
'TimeStamp (sec)': times,
|
||||||
@@ -3090,7 +3102,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
has_strokedata = data['stroke_data']
|
has_strokedata = data['stroke_data']
|
||||||
except KeyError:
|
except KeyError: # pragma: no cover
|
||||||
has_strokedata = True
|
has_strokedata = True
|
||||||
|
|
||||||
|
|
||||||
@@ -3146,7 +3158,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone
|
|||||||
dologging('debuglog.log',s.text)
|
dologging('debuglog.log',s.text)
|
||||||
has_strokedata = False
|
has_strokedata = False
|
||||||
|
|
||||||
if not has_strokedata:
|
if not has_strokedata: # pragma: no cover
|
||||||
df = df_from_summary(data)
|
df = df_from_summary(data)
|
||||||
else:
|
else:
|
||||||
dologging('debuglog.log',json.dumps(s.json()))
|
dologging('debuglog.log',json.dumps(s.json()))
|
||||||
|
|||||||
@@ -41,24 +41,79 @@
|
|||||||
title="Jump to following workout"><em>Next</em></a>
|
title="Jump to following workout"><em>Next</em></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<table width=100%>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th><td>{{ workout.name }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Public link to this workout</th>
|
||||||
|
<td>
|
||||||
|
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
|
||||||
|
<td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
|
||||||
<h1>Edit Workout Interval Data</h1>
|
<h1>Edit Workout Interval Data</h1>
|
||||||
|
<p>
|
||||||
|
Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest
|
||||||
|
intervals. To save your work, press the Save button above the updated summary.
|
||||||
|
</p>
|
||||||
<ul class="main-content">
|
<ul class="main-content">
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<table width=100%>
|
<h1>Updated Summary</h1>
|
||||||
<tr>
|
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
|
||||||
<th>Name</th><td>{{ workout.name }}</td>
|
{% csrf_token %}
|
||||||
</tr><tr>
|
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
|
||||||
<th>Distance:</th><td>{{ workout.distance }}m</td>
|
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
|
||||||
</tr><tr>
|
{% for key,value in formvalues.items %}
|
||||||
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
|
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
|
||||||
</tr><tr>
|
{% endfor %}
|
||||||
<th>Public link to this workout</th>
|
<p>
|
||||||
<td>
|
<input type="submit" value="Save">
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
|
</p>
|
||||||
<td>
|
<span>
|
||||||
</tr>
|
<a href="">Reset to last saved</a>
|
||||||
</table>
|
|
||||||
|
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
|
||||||
|
</span>
|
||||||
|
</form>
|
||||||
|
<p>
|
||||||
|
<pre>
|
||||||
|
{{ intervalstats }}
|
||||||
|
</pre>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
||||||
|
<script async="true" type="text/javascript">
|
||||||
|
Bokeh.set_log_level("info");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ interactiveplot |safe }}
|
||||||
|
|
||||||
|
{{ the_div |safe }}
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
|
<h1>Feeling lucky?</h1>
|
||||||
|
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
|
||||||
|
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
|
||||||
|
section below, where you can edit it.
|
||||||
|
<form enctype="multipart/form-data" method="post">
|
||||||
|
<input type="hidden" name="ruptures" value="ruptures">
|
||||||
|
<table width=100%>
|
||||||
|
{{ ruptureform.as_table }}
|
||||||
|
</table>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input class="button" type="submit" value="I'm feeling lucky">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
<h1>Interval Shorthand</h1>
|
<h1>Interval Shorthand</h1>
|
||||||
<p>
|
<p>
|
||||||
See the how-to <a href="#howto">at the bottom of this page</a> for details on how to use this form.
|
See the how-to <a href="#howto">at the bottom of this page</a> for details on how to use this form.
|
||||||
@@ -70,6 +125,8 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input class="button" type="submit" value="Update">
|
<input class="button" type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
|
</li>
|
||||||
|
<li class="grid_2">
|
||||||
<h1>Intervals by Power/Pace</h1>
|
<h1>Intervals by Power/Pace</h1>
|
||||||
|
|
||||||
<p>With this form, you can specify a power or pace level. Everything faster/harder than the
|
<p>With this form, you can specify a power or pace level. Everything faster/harder than the
|
||||||
@@ -92,40 +149,6 @@
|
|||||||
<input class="button" type="submit" value="Submit">
|
<input class="button" type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li class="grid_2">
|
|
||||||
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
|
||||||
<script async="true" type="text/javascript">
|
|
||||||
Bokeh.set_log_level("info");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{ interactiveplot |safe }}
|
|
||||||
|
|
||||||
{{ the_div |safe }}
|
|
||||||
</li>
|
|
||||||
<li class="grid_2">
|
|
||||||
<h1>Updated Summary</h1>
|
|
||||||
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
|
|
||||||
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
|
|
||||||
{% for key,value in formvalues.items %}
|
|
||||||
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
|
|
||||||
{% endfor %}
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Save">
|
|
||||||
</p>
|
|
||||||
<span>
|
|
||||||
<a href="">Reset to last saved</a>
|
|
||||||
|
|
||||||
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
<p>
|
|
||||||
<pre>
|
|
||||||
{{ intervalstats }}
|
|
||||||
</pre>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
<li class="grid_2">
|
<li class="grid_2">
|
||||||
<h1 id="howto">Interval Shorthand How-To</h1>
|
<h1 id="howto">Interval Shorthand How-To</h1>
|
||||||
<p>This is a quick way to enter the intervals using a special mini-language.</p>
|
<p>This is a quick way to enter the intervals using a special mini-language.</p>
|
||||||
|
|||||||
69
rowers/templates/trainingzones.html
Normal file
69
rowers/templates/trainingzones.html
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
{% extends "newbase.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load rowerfilters %}
|
||||||
|
|
||||||
|
{% block title %}Rowsandall Training Plans{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<script src="https://cdn.pydata.org/bokeh/release/bokeh-2.2.3.min.js"></script>
|
||||||
|
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-2.2.3.min.js"></script>
|
||||||
|
<script async="true" type="text/javascript">
|
||||||
|
Bokeh.set_log_level("info");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Training Zones</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="id_script">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="main-content">
|
||||||
|
<li class="grid_4">
|
||||||
|
<div id="id_chart">
|
||||||
|
{{ the_div|safe }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="grid_4">
|
||||||
|
<p>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<input class="button" type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type='text/javascript'
|
||||||
|
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function($) {
|
||||||
|
console.log('loading script for chart');
|
||||||
|
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/trainingzones/user/{{ rower.user.id }}/data/?startdate={{ startdate|date:"Y-m-d" }}&enddate={{ enddate|date:"Y-m-d" }}&zones={{ zones }}', function(json) {
|
||||||
|
var script = json.script;
|
||||||
|
var div = json.div;
|
||||||
|
$("#id_sitready").remove();
|
||||||
|
$("#id_chart").append(div);
|
||||||
|
$("#id_script").append(script);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
{% include 'menu_analytics.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -1107,6 +1107,44 @@ class MarkerPerformanceTest(TestCase):
|
|||||||
|
|
||||||
self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200)
|
self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200)
|
||||||
|
|
||||||
|
@patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_uh)
|
||||||
|
def test_trainingzones_view(self,mocked_getsmallrowdata_db):
|
||||||
|
login = self.c.login(username=self.u.username,password=self.password)
|
||||||
|
self.assertTrue(login)
|
||||||
|
|
||||||
|
startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date()
|
||||||
|
enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date()
|
||||||
|
zones = 'hr'
|
||||||
|
|
||||||
|
url = reverse('trainingzones_view')
|
||||||
|
response = self.c.get(url)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
url = reverse('trainingzones_view_data')
|
||||||
|
response = self.c.get(url)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
url += '?startdate={startdate}&enddate={enddate}&zones={zones}'.format(
|
||||||
|
startdate = startdate.strftime("%Y-%m-%d"),
|
||||||
|
enddate = enddate.strftime("%Y-%m-%d"),
|
||||||
|
zones=zones,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.c.get(url)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
zones = 'power'
|
||||||
|
url = reverse('trainingzones_view_data')
|
||||||
|
url += '?startdate={startdate}&enddate={enddate}&zones={zones}'.format(
|
||||||
|
startdate = startdate.strftime("%Y-%m-%d"),
|
||||||
|
enddate = enddate.strftime("%Y-%m-%d"),
|
||||||
|
zones=zones,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.c.get(url)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
|
||||||
@patch('rowers.dataprep.create_engine')
|
@patch('rowers.dataprep.create_engine')
|
||||||
@patch('rowers.dataprep.getsmallrowdata_db')
|
@patch('rowers.dataprep.getsmallrowdata_db')
|
||||||
def test_performancemanager_view(self, mocked_sqlalchemy,
|
def test_performancemanager_view(self, mocked_sqlalchemy,
|
||||||
|
|||||||
@@ -820,6 +820,13 @@ class WorkoutViewTest(TestCase):
|
|||||||
response = self.c.get(url)
|
response = self.c.get(url)
|
||||||
self.assertEqual(response.status_code,200)
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
|
form_data = {
|
||||||
|
'ruptures':'ruptures',
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.c.post(url,form_data)
|
||||||
|
self.assertEqual(response.status_code,200)
|
||||||
|
|
||||||
form_data = {
|
form_data = {
|
||||||
'intervalstring':'4x2min/1min',
|
'intervalstring':'4x2min/1min',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import json
|
|||||||
|
|
||||||
import rowers.utils as utils
|
import rowers.utils as utils
|
||||||
|
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
import rowers.garmin_stuff as gs
|
import rowers.garmin_stuff as gs
|
||||||
|
|
||||||
@@ -342,6 +343,9 @@ class C2Objects(DjangoTestCase):
|
|||||||
|
|
||||||
self.assertEqual(str(timezone),'America/Los_Angeles')
|
self.assertEqual(str(timezone),'America/Los_Angeles')
|
||||||
|
|
||||||
|
df = tasks.df_from_summary(data)
|
||||||
|
self.assertEqual(len(df),8)
|
||||||
|
|
||||||
got = arrow.get(startdatetime).isoformat()
|
got = arrow.get(startdatetime).isoformat()
|
||||||
want = arrow.get('2021-05-23 09:11:37.100000-07:00').isoformat()
|
want = arrow.get('2021-05-23 09:11:37.100000-07:00').isoformat()
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ class PlannedSessionTests(TestCase):
|
|||||||
elif not sundays:
|
elif not sundays:
|
||||||
sundays = [cycle.enddate]
|
sundays = [cycle.enddate]
|
||||||
|
|
||||||
|
|
||||||
for i in range(len(sundays)):
|
for i in range(len(sundays)):
|
||||||
if i==0:
|
if i==0:
|
||||||
monday = cycle.startdate
|
monday = cycle.startdate
|
||||||
@@ -239,6 +240,7 @@ class PlannedSessionTests(TestCase):
|
|||||||
monday = sundays[i]-datetime.timedelta(days=6)
|
monday = sundays[i]-datetime.timedelta(days=6)
|
||||||
if monday < cycle.startdate:
|
if monday < cycle.startdate:
|
||||||
monday = cycle.startdate
|
monday = cycle.startdate
|
||||||
|
|
||||||
|
|
||||||
nextsunday = sundays[i]
|
nextsunday = sundays[i]
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,10 @@ urlpatterns = [
|
|||||||
re_path(r'^performancemanager/$',views.performancemanager_view,name='performancemanager_view'),
|
re_path(r'^performancemanager/$',views.performancemanager_view,name='performancemanager_view'),
|
||||||
re_path(r'^performancemanager/user/(?P<userid>\d+)/$',views.performancemanager_view,name='performancemanager_view'),
|
re_path(r'^performancemanager/user/(?P<userid>\d+)/$',views.performancemanager_view,name='performancemanager_view'),
|
||||||
re_path(r'^performancemanager/user/(?P<userid>\d+)/(?P<mode>\w+.*)/$',views.performancemanager_view,name='performancemanager_view'),
|
re_path(r'^performancemanager/user/(?P<userid>\d+)/(?P<mode>\w+.*)/$',views.performancemanager_view,name='performancemanager_view'),
|
||||||
|
re_path(r'^trainingzones/$',views.trainingzones_view,name='trainingzones_view'),
|
||||||
|
re_path(r'^trainingzones/user/(?P<userid>\d+)/$',views.trainingzones_view,name='trainingzones_view'),
|
||||||
|
re_path(r'^trainingzones/user/(?P<userid>\d+)/data/$',views.trainingzones_view_data,name="trainingzones_view_data"),
|
||||||
|
re_path(r'^trainingzones/data/$',views.trainingzones_view_data,name="trainingzones_view_data"),
|
||||||
re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',views.rankings_view2,name='rankings_view2'),
|
re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',views.rankings_view2,name='rankings_view2'),
|
||||||
re_path(r'^ote-bests2/$',views.rankings_view2,name='rankings_view2'),
|
re_path(r'^ote-bests2/$',views.rankings_view2,name='rankings_view2'),
|
||||||
re_path(r'^analysisdata/$',views.analysis_view_data,name='analysis_view_data'),
|
re_path(r'^analysisdata/$',views.analysis_view_data,name='analysis_view_data'),
|
||||||
|
|||||||
@@ -1049,6 +1049,89 @@ def goldmedalscores_view(request,userid=0,
|
|||||||
'bestworkouts':bestworkouts,
|
'bestworkouts':bestworkouts,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@user_passes_test(ispromember, login_url="/rowers/paidplans",
|
||||||
|
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
|
||||||
|
redirect_field_name=None)
|
||||||
|
@permission_required('rower.is_coach',fn=get_user_by_userid,raise_exception=True)
|
||||||
|
def trainingzones_view(request,userid=0,mode='rower',
|
||||||
|
startdate=timezone.now()-timezone.timedelta(days=365),
|
||||||
|
enddate=timezone.now()):
|
||||||
|
|
||||||
|
is_ajax = request_is_ajax(request)
|
||||||
|
|
||||||
|
r = getrequestrower(request,userid=userid)
|
||||||
|
|
||||||
|
|
||||||
|
enddate = timezone.now()
|
||||||
|
startdate = enddate-datetime.timedelta(days=365)
|
||||||
|
|
||||||
|
form = TrainingZonesForm()
|
||||||
|
zones = 'hr'
|
||||||
|
if request.method == 'POST': # pragma: no cover
|
||||||
|
form = TrainingZonesForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
startdate = form.cleaned_data['startdate']
|
||||||
|
enddate = form.cleaned_data['enddate']
|
||||||
|
zones = form.cleaned_data['zones']
|
||||||
|
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
div = get_call()
|
||||||
|
|
||||||
|
breadcrumbs = [
|
||||||
|
{
|
||||||
|
'url':'/rowers/analysis',
|
||||||
|
'name':'Analysis'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url':reverse('trainingzones_view'),
|
||||||
|
'name': 'Training Zones'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return render(request,'trainingzones.html',
|
||||||
|
{
|
||||||
|
'active':'nav-analysis',
|
||||||
|
'breadcrumbs':breadcrumbs,
|
||||||
|
'rower':r,
|
||||||
|
'the_script':script,
|
||||||
|
'the_div':div,
|
||||||
|
'form':form,
|
||||||
|
'startdate':startdate,
|
||||||
|
'enddate':enddate,
|
||||||
|
'zones':zones,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def trainingzones_view_data(request,userid=0):
|
||||||
|
r = getrequestrower(request,userid=userid)
|
||||||
|
|
||||||
|
startdate = timezone.now()-datetime.timedelta(days=365)
|
||||||
|
enddate = timezone.now()
|
||||||
|
zones = 'hr'
|
||||||
|
|
||||||
|
if request.GET.get('zones'):
|
||||||
|
zones = request.GET.get('zones')
|
||||||
|
|
||||||
|
if request.GET.get('startdate'):
|
||||||
|
startdate = datetime.datetime.strptime(request.GET.get('startdate'),"%Y-%m-%d")
|
||||||
|
startdate = arrow.get(startdate).datetime
|
||||||
|
|
||||||
|
if request.GET.get('enddate'):
|
||||||
|
enddate = datetime.datetime.strptime(request.GET.get('enddate'),"%Y-%m-%d")
|
||||||
|
enddate = arrow.get(enddate).datetime
|
||||||
|
|
||||||
|
data = get_zones_report(r,startdate,enddate,trainingzones=zones)
|
||||||
|
|
||||||
|
script, div = interactive_zoneschart(r,data,startdate,enddate,trainingzones=zones)
|
||||||
|
|
||||||
|
return JSONResponse({
|
||||||
|
'script': script,
|
||||||
|
'div': div,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@user_passes_test(ispromember, login_url="/rowers/paidplans",
|
@user_passes_test(ispromember, login_url="/rowers/paidplans",
|
||||||
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
|
message="This functionality requires a Pro plan or higher. If you are already a Pro user, please log in to access this functionality",
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ from rowers.forms import (
|
|||||||
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
VideoAnalysisCreateForm,WorkoutSingleSelectForm,
|
||||||
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
|
VideoAnalysisMetricsForm,SurveyForm,HistorySelectForm,
|
||||||
StravaChartForm,FitnessFitForm,PerformanceManagerForm,
|
StravaChartForm,FitnessFitForm,PerformanceManagerForm,
|
||||||
TrainingPlanBillingForm,InstantPlanSelectForm
|
TrainingPlanBillingForm,InstantPlanSelectForm,
|
||||||
|
TrainingZonesForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ from rowers.utils import intervals_to_string
|
|||||||
from urllib.parse import urlparse, parse_qs
|
from urllib.parse import urlparse, parse_qs
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
|
|
||||||
|
import ruptures as rpt
|
||||||
|
|
||||||
def default(o): # pragma: no cover
|
def default(o): # pragma: no cover
|
||||||
if isinstance(o, numpy.int64): return int(o)
|
if isinstance(o, numpy.int64): return int(o)
|
||||||
if isinstance(o, numpy.int32): return int(o)
|
if isinstance(o, numpy.int32): return int(o)
|
||||||
@@ -4913,7 +4915,7 @@ def workout_upload_api(request):
|
|||||||
w.timezone = timezone
|
w.timezone = timezone
|
||||||
w.save()
|
w.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if make_plot: # pragma: no cover
|
if make_plot: # pragma: no cover
|
||||||
@@ -6144,6 +6146,42 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
|
|||||||
|
|
||||||
powerupdateform = PowerIntervalUpdateForm(initial=data)
|
powerupdateform = PowerIntervalUpdateForm(initial=data)
|
||||||
|
|
||||||
|
# feeling lucky / ruptures
|
||||||
|
if request.method == 'POST' and "ruptures" in request.POST:
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'spm':rowdata.df[' Cadence (stokes/min)'],
|
||||||
|
'power':rowdata.df[' Power (watts)'],
|
||||||
|
'v':rowdata.df[' AverageBoatSpeed (m/s)']
|
||||||
|
})
|
||||||
|
algo = rpt.Pelt(model="rbf").fit(df.values)
|
||||||
|
result = algo.predict(pen=10)
|
||||||
|
|
||||||
|
|
||||||
|
df['time'] = rowdata.df['TimeStamp (sec)'].values
|
||||||
|
timeprev = int(df['time'].values[0])
|
||||||
|
timenext = int(df['time'].values[result[0]])
|
||||||
|
s = '{delta}sec'.format(delta=timenext-timeprev)
|
||||||
|
|
||||||
|
|
||||||
|
for i in range(len(result)-1):
|
||||||
|
timeprev = int(df['time'].values[result[i]-1])
|
||||||
|
timenext = int(df['time'].values[result[i+1]-1])
|
||||||
|
interval = '+{delta}sec'.format(delta=timenext-timeprev)
|
||||||
|
s += interval
|
||||||
|
|
||||||
|
try:
|
||||||
|
rowdata.updateinterval_string(s)
|
||||||
|
except: # pragma: no cover
|
||||||
|
messages.error(request,"Nope, you were not lucky")
|
||||||
|
|
||||||
|
intervalstats = rowdata.allstats()
|
||||||
|
itime, idist, itype = rowdata.intervalstats_values()
|
||||||
|
nrintervals = len(idist)
|
||||||
|
savebutton = 'savestringform'
|
||||||
|
intervalString = s
|
||||||
|
form = SummaryStringForm(initial={'intervalstring':intervalString})
|
||||||
|
|
||||||
|
|
||||||
# We have submitted the mini language interpreter
|
# We have submitted the mini language interpreter
|
||||||
if request.method == 'POST' and "intervalstring" in request.POST:
|
if request.method == 'POST' and "intervalstring" in request.POST:
|
||||||
form = SummaryStringForm(request.POST)
|
form = SummaryStringForm(request.POST)
|
||||||
|
|||||||
Reference in New Issue
Block a user