proof of concept for HR, as a sstart
This commit is contained in:
@@ -6557,3 +6557,200 @@ def interactive_otw_advanced_pace_chart(id=0,promember=0):
|
||||
div = ''
|
||||
|
||||
return [script,div]
|
||||
|
||||
def get_zones_report(rower,startdate,enddate):
|
||||
duration = enddate-startdate
|
||||
|
||||
totaldays = duration.total_seconds()/(24*3600)
|
||||
|
||||
dates = []
|
||||
dates_sorting = []
|
||||
minutes = []
|
||||
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:
|
||||
pass
|
||||
|
||||
df = dataprep.clean_df_stats(df,workstrokesonly=False,
|
||||
ignoreadvanced=True,ignorehr=False)
|
||||
|
||||
#totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts)
|
||||
|
||||
hrzones = rower.hrzones
|
||||
|
||||
for w in workouts:
|
||||
dd = w.date.strftime('%m/%d')
|
||||
dd2 = w.date.strftime('%Y/%m/%d')
|
||||
dd3 = w.date.strftime('%Y/%m')
|
||||
|
||||
qryw = 'workoutid == {workoutid}'.format(workoutid=w.id)
|
||||
|
||||
qry = 'hr < {ut2}'.format(ut2=rower.ut2)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('<UT2')
|
||||
#print(w,dd,timeinzone,'<UT2')
|
||||
|
||||
qry = '{ut2} <= hr < {ut1}'.format(ut1=rower.ut1,ut2=rower.ut2)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('UT2')
|
||||
#print(w,dd,timeinzone,'UT2')
|
||||
|
||||
qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1,at=rower.at)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('UT1')
|
||||
#print(w,dd,timeinzone,'UT1')
|
||||
|
||||
qry = '{at} <= hr < {tr}'.format(at=rower.at,tr=rower.tr)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('AT')
|
||||
#print(w,dd,timeinzone,'AT')
|
||||
|
||||
qry = '{tr} <= hr < {an}'.format(tr=rower.tr,an=rower.an)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('TR')
|
||||
#print(w,dd,timeinzone,'TR')
|
||||
|
||||
qry = 'hr >= {an}'.format(an=rower.an)
|
||||
timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3)
|
||||
if totaldays<30:
|
||||
dates.append(dd)
|
||||
dates_sorting.append(dd2)
|
||||
else: # pragma: no cover
|
||||
dates.append(dd3)
|
||||
dates_sorting.append(dd3)
|
||||
minutes.append(timeinzone)
|
||||
zones.append('AN')
|
||||
#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'))
|
||||
else:
|
||||
dates.append(d.strftime('%Y/%m'))
|
||||
dates_sorting.append(d.strftime('%Y/%m'))
|
||||
|
||||
minutes.append(0)
|
||||
zones.append('<UT2')
|
||||
|
||||
d += datetime.timedelta(days=1)
|
||||
|
||||
# this should be renamed with rower zones
|
||||
data = {
|
||||
'date':dates,
|
||||
'date_sorting':dates_sorting,
|
||||
'minutes': minutes,
|
||||
'zones':zones,
|
||||
}
|
||||
|
||||
#print(pd.DataFrame(data).head())
|
||||
|
||||
return data
|
||||
|
||||
def interactive_zoneschart(data,startdate,enddate):
|
||||
duration = enddate-startdate
|
||||
|
||||
totaldays = duration.total_seconds()/(24*3600)
|
||||
|
||||
colors = ['gray','yellow','lime','blue','purple','red']
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
source = ColumnDataSource(df)
|
||||
|
||||
df.sort_values('date_sorting',inplace=True)
|
||||
|
||||
hv.extension('bokeh')
|
||||
|
||||
|
||||
table = hv.Table(df,[
|
||||
('date','Date'),
|
||||
('minutes','minutes'),
|
||||
('zones','zones')]
|
||||
)
|
||||
|
||||
bars = hv.Bars(df, kdims = ['date','zones']).aggregate(function=np.sum)
|
||||
|
||||
#bars = table.to.bars(['date','zones'],['minutes'])
|
||||
bars.opts(
|
||||
opts.Bars(cmap=colors,show_legend=True,stacked=True,
|
||||
tools=['tap','hover'],width=550,xrotation=45,padding=(0,(0,.1)),
|
||||
legend_position='bottom',show_frame=False)
|
||||
)
|
||||
|
||||
p = hv.render(bars)
|
||||
|
||||
p.title.text = 'Activity {d1} to {d2}'.format(
|
||||
d1 = startdate.strftime("%Y-%m-%d"),
|
||||
d2 = enddate.strftime("%Y-%m-%d"),
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
35
rowers/templates/trainingzones.html
Normal file
35
rowers/templates/trainingzones.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% 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>
|
||||
|
||||
{{ the_script | safe }}
|
||||
|
||||
<ul class="main-content">
|
||||
<li class="grid_4">
|
||||
<div id="theplot" class="flexplot">
|
||||
{{ the_div|safe }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% include 'menu_plan.html' %}
|
||||
{% endblock %}
|
||||
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
BIN
rowers/tests/testdata/testdata.tcx.gz
vendored
Binary file not shown.
@@ -352,6 +352,8 @@ urlpatterns = [
|
||||
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+)/(?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'^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'^analysisdata/$',views.analysis_view_data,name='analysis_view_data'),
|
||||
|
||||
@@ -1049,6 +1049,48 @@ def goldmedalscores_view(request,userid=0,
|
||||
'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)
|
||||
|
||||
startdate,enddate = get_dates_timeperiod(request)
|
||||
enddate = timezone.now()
|
||||
startdate = enddate-datetime.timedelta(days=29)
|
||||
|
||||
data = get_zones_report(r,startdate,enddate)
|
||||
|
||||
|
||||
script, div = interactive_zoneschart(data,startdate,enddate)
|
||||
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@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",
|
||||
|
||||
Reference in New Issue
Block a user