diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 9e099622..1aade283 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -537,8 +537,39 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True,
return datadf
-
def getstatsfields():
+ fielddict = {name:d['verbose_name'] for name,d in rowingmetrics}
+
+# fielddict.pop('ergpace')
+# fielddict.pop('hr_an')
+# fielddict.pop('hr_tr')
+# fielddict.pop('hr_at')
+# fielddict.pop('hr_ut2')
+# fielddict.pop('hr_ut1')
+ fielddict.pop('time')
+ fielddict.pop('distance')
+# fielddict.pop('nowindpace')
+# fielddict.pop('fnowindpace')
+# fielddict.pop('fergpace')
+# fielddict.pop('equivergpower')
+# fielddict.pop('workoutstate')
+# fielddict.pop('fpace')
+# fielddict.pop('pace')
+# fielddict.pop('id')
+# fielddict.pop('ftime')
+# fielddict.pop('x_right')
+# fielddict.pop('hr_max')
+# fielddict.pop('hr_bottom')
+ fielddict.pop('cumdist')
+
+ try:
+ fieldlist = [field for field, value in fielddict.iteritems()]
+ except AttributeError:
+ fieldlist = [field for field, value in fielddict.items()]
+
+ return fieldlist, fielddict
+
+def getstatsfields_old():
# Get field names and remove those that are not useful in stats
fields = StrokeData._meta.get_fields()
diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py
index 5b30d4c4..c2e921db 100644
--- a/rowers/interactiveplots.py
+++ b/rowers/interactiveplots.py
@@ -232,6 +232,40 @@ def interactive_boxchart(datadf,fieldname,extratitle='',
return script,div
+
+def interactive_planchart(data,startdate,enddate):
+ source = ColumnDataSource(data)
+
+ hv.extension('bokeh')
+
+
+ yaxmaximum = data['executed'].max()
+ if data['planned'].max() > yaxmaximum:
+ yaxmaximum = data['planned'].max()
+
+ if yaxmaximum == 0:
+ yaxmaximum = 250
+
+ yrange1 = Range1d(start=0,end=1.1*yaxmaximum)
+
+
+ tidy_df = data.melt(id_vars=['startdate'],value_vars=['executed','planned'])
+ bars = hv.Bars(tidy_df,['startdate','variable'],['value'])
+ bars.opts(
+ opts.Bars(show_legend=True,tools=['tap','hover'],legend_position='bottom',show_frame=True))
+
+ p = hv.render(bars)
+
+ p.plot_width=550
+ p.plot_height=350
+ p.y_range = yrange1
+ p.toolbar_location = 'above'
+ p.sizing_mode = 'scale_width'
+
+ script,div = components(p)
+
+ return script,div
+
def interactive_activitychart(workouts,startdate,enddate,stack='type'):
dates = []
@@ -352,7 +386,7 @@ def interactive_activitychart(workouts,startdate,enddate,stack='type'):
p.plot_width=550
p.plot_height=350
- p.toolbar_location = None
+ p.toolbar_location = 'above'
p.sizing_mode = 'scale_width'
url = "http://rowsandall.com/rowers/workout/@duration/"
taptool = p.select(type=TapTool)
diff --git a/rowers/models.py b/rowers/models.py
index e3e23ada..0c69aecc 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1981,7 +1981,7 @@ class TrainingMicroCycle(models.Model):
if self.startdate < self.plan.startdate:
self.startdate = self.plan.startdate
- othercycles = TrainingMicroCycle.objects.filter(
+ othercycles = TrainingMicroCycle.objects.filter(
plan=self.plan).exclude(pk=self.pk).order_by("-startdate")
for othercycle in othercycles:
diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py
index f3c33b4e..e2a96bc5 100644
--- a/rowers/plannedsessions.py
+++ b/rowers/plannedsessions.py
@@ -22,11 +22,14 @@ queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('low')
+import pandas as pd
+
from rowers.models import (
Rower, Workout,Team,
GeoCourse, TrainingMicroCycle,TrainingMesoCycle,TrainingMacroCycle,
TrainingPlan,PlannedSession,VirtualRaceResult,CourseTestResult,
- get_course_timezone, IndoorVirtualRaceResult,VirtualRace
+ get_course_timezone, IndoorVirtualRaceResult,VirtualRace,createmacrofillers,
+ createmesofillers,createmicrofillers,
)
from rowers.courses import get_time_course
@@ -47,6 +50,181 @@ from rowers.tasks import (
)
from rowers.utils import totaltime_sec_to_string
+def checkscores(r,macrocycles):
+ for m in macrocycles:
+ createmesofillers(m)
+ m.plantime = 0
+ m.actualtime = 0
+ m.plandistance = 0
+ m.actualdistance = 0
+ m.planrscore = 0
+ m.actualrscore = 0
+ m.plantrimp = 0
+ m.actualtrimp = 0
+
+
+ mesocycles = TrainingMesoCycle.objects.filter(
+ plan=m,
+ type='userdefined').order_by("startdate")
+
+ for me in mesocycles:
+ me.plantime = 0
+ me.actualtime = 0
+ me.plandistance = 0
+ me.actualdistance = 0
+ me.planrscore = 0
+ me.actualrscore = 0
+ me.plantrimp = 0
+ me.actualtrimp = 0
+
+ microcycles = TrainingMicroCycle.objects.filter(
+ plan=me,
+ type='userdefined').order_by("startdate")
+
+ for mm in microcycles:
+ sps = get_sessions(r,startdate=mm.startdate,enddate=mm.enddate)
+
+ # sps = PlannedSession.objects.filter(
+ # rower = r,
+ # startdate__lte=mm.enddate,
+ # enddate__gte=mm.startdate)
+
+
+ mm.plantime = 0
+ mm.actualtime = 0
+ mm.plandistance = 0
+ mm.actualdistance = 0
+ mm.planrscore = 0
+ mm.actualrscore = 0
+ mm.plantrimp = 0
+ mm.actualtrimp = 0
+
+
+ if mm.type == 'userdefined':
+ for ps in sps:
+ ratio, status, cdate = is_session_complete(r,ps)
+ if ps.sessionmode == 'time':
+ mm.plantime += ps.sessionvalue
+ mm.actualtime += int(ps.sessionvalue*ratio)
+ elif ps.sessionmode == 'distance' and ps.sessiontype != 'race':
+ mm.plandistance += ps.sessionvalue
+ mm.actualdistance += int(ps.sessionvalue*ratio)
+ elif ps.sessionmode == 'rScore':
+ mm.planrscore += ps.sessionvalue
+ mm.actualrscore += int(ps.sessionvalue*ratio)
+ elif ps.sessionmode == 'TRIMP':
+ mm.plantrimp += ps.sessionvalue
+ mm.actualtrimp += int(ps.sessionvalue*ratio)
+
+ mm.save()
+
+ me.plantime += mm.plantime
+ me.actualtime += mm.actualtime
+ me.plandistance += mm.plandistance
+ me.actualdistance += mm.actualdistance
+ me.planrscore += mm.planrscore
+ me.actualrscore += mm.actualrscore
+ me.plantrimp += mm.plantrimp
+ me.actualtrimp += mm.actualtrimp
+
+ if me.type == 'userdefined':
+ me.save()
+
+ m.plantime += me.plantime
+ m.actualtime += me.actualtime
+ m.plandistance += me.plandistance
+ m.actualdistance += me.actualdistance
+ m.planrscore += me.planrscore
+ m.actualrscore += me.actualrscore
+ m.plantrimp += me.plantrimp
+ m.actualtrimp += me.actualtrimp
+
+
+
+ if m.type == 'userdefined':
+ m.save()
+
+
+def get_execution_report(rower,startdate,enddate,plan=None):
+ if plan:
+ macros = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate")
+ checkscores(rower,macros)
+ mesos = TrainingMesoCycle.objects.filter(plan__in=macros).order_by("startdate")
+ micros = TrainingMicroCycle.objects.filter(plan__in=mesos).order_by("startdate")
+ micros = micros.exclude(enddate__lte=startdate).exclude(startdate__gte=enddate)
+ else:
+ plans = TrainingPlan.objects.filter(startdate__lte=startdate,enddate__gte=startdate)
+ plans2 = TrainingPlan.objects.filter(enddate__lte=enddate,startdate__lte=enddate)
+ plans = plans | plans2
+
+ plans = plans.exclude(status=False).order_by("-enddate")
+
+ if not plans:
+ # make week cycles here
+ # get monday before startdate
+ startdate += timedelta(days=1-startdate.isoweekday())
+ startdate = startdate-timedelta(days=7)
+ micros = []
+ while startdate <= enddate:
+ micro = type('micros',(object,),
+ {
+ 'startdate':startdate,
+ 'enddate':startdate+timedelta(days=7)
+ })
+ micros.append(micro)
+ startdate += timedelta(days=7)
+ else:
+ plan = plans[0]
+ macros = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate")
+ checkscores(rower,macros)
+ mesos = TrainingMesoCycle.objects.filter(plan__in=macros).order_by("startdate")
+ micros = TrainingMicroCycle.objects.filter(plan__in=mesos).order_by("startdate")
+
+ # we've got micros, now get sessions
+ startdates = []
+ planned = []
+ executed = []
+
+ for mm in micros:
+ plannedscore = 0
+ actualscore = 0
+ sps = get_sessions(rower,startdate=mm.startdate,enddate=mm.enddate)
+ for ps in sps:
+ if ps.sessionmode == 'rscore':
+ plannedscore += ps.planrscore
+ actualscore += ps.actualrscore
+ else:
+ ratio, status, cdate = is_session_complete(rower,ps)
+ ws = Workout.objects.filter(user=rower,plannedsession=ps)
+
+ for w in ws:
+ if w.rscore != 0:
+ plannedscore += w.rscore/ratio
+ actualscore += w.rscore
+ elif w.hrtss != 0:
+ plannedscore += w.hrtss/ratio
+ actualscore += w.hrtss
+ else:
+ minutes = w.duration.hour*60+w.duration.minute
+ plannedscore += minutes/ratio
+ actualscore += minutes
+
+ actualscore = int(actualscore)
+ plannedscore = int(plannedscore)
+
+ startdates += [mm.startdate]
+ planned += [plannedscore]
+ executed += [actualscore]
+
+
+ data = pd.DataFrame({
+ 'startdate':startdates,
+ 'planned':planned,
+ 'executed':executed,
+ })
+
+ return(data,'ok')
+
def get_indoorraces(workout):
races1 = VirtualRace.objects.filter(
sessiontype='indoorrace',
diff --git a/rowers/templates/trainingplan_chart.html b/rowers/templates/trainingplan_chart.html
new file mode 100644
index 00000000..030658cb
--- /dev/null
+++ b/rowers/templates/trainingplan_chart.html
@@ -0,0 +1,35 @@
+{% extends "newbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Rowsandall Training Plans{% endblock %}
+
+
+{% block main %}
+
+
+
+
+
+
Training Plan - {{ plan.name }}
+
+{{ the_script | safe }}
+
+
+ -
+
+ {{ the_div|safe }}
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_plan.html' %}
+{% endblock %}
diff --git a/rowers/urls.py b/rowers/urls.py
index 599dbac8..b6bcc91b 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -587,6 +587,10 @@ urlpatterns = [
name='rower_trainingplan_view'),
re_path(r'^plan/(?P\d+)/macro/(?P\d+)/user/(?P\d+)/$',views.rower_trainingplan_view,
name='rower_trainingplan_view'),
+ re_path(r'^plan/(?P\d+)/execution/$',views.rower_trainingplan_execution_view,
+ name='rower_trainingplan_execution_view'),
+ re_path(r'^plan/(?P\d+)/execution/user/(?P\d+)/$',views.rower_trainingplan_execution_view,
+ name='rower_trainingplan_execution_view'),
re_path(r'^macrocycle/(?P\d+)/$',login_required(
views.TrainingMacroCycleUpdate.as_view()),
name='macrocycle_update_view'),
diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py
index 60dea044..9cb21f11 100644
--- a/rowers/views/analysisviews.py
+++ b/rowers/views/analysisviews.py
@@ -523,8 +523,8 @@ def statsdata(workouts, options):
# Create stats
stats = {}
- fielddict.pop('workoutstate')
- fielddict.pop('workoutid')
+ # fielddict.pop('workoutstate')
+ # fielddict.pop('workoutid')
for field,verbosename in fielddict.items():
@@ -544,15 +544,15 @@ def statsdata(workouts, options):
cor = datadf.corr(method='spearman')
cor.fillna(value=0,inplace=True)
cordict = {}
- for field1,verbosename in fielddict.items():
+ for field1,verbosename1 in fielddict.items():
thedict = {}
- for field2,verbosename in fielddict.items():
+ for field2,verbosename2 in fielddict.items():
try:
- thedict[field2] = cor.loc[field1,field2]
+ thedict[verbosename2] = cor.loc[field1,field2]
except KeyError:
- thedict[field2] = 0
+ thedict[verbosename2] = 0
- cordict[field1] = thedict
+ cordict[verbosename1] = thedict
context = {
'stats':stats,
@@ -4208,8 +4208,10 @@ def cumstats(request,theuser=0,
# Create stats
stats = {}
- fielddict.pop('workoutstate')
- fielddict.pop('workoutid')
+ try:
+ fielddict.pop('pace')
+ except KeyError:
+ pass
for field,verbosename in fielddict.items():
thedict = {
@@ -4228,15 +4230,15 @@ def cumstats(request,theuser=0,
cor = datadf.corr(method='spearman')
cor.fillna(value=0,inplace=True)
cordict = {}
- for field1,verbosename in fielddict.items():
+ for field1,verbosename1 in fielddict.items():
thedict = {}
- for field2,verbosename in fielddict.items():
+ for field2,verbosename2 in fielddict.items():
try:
- thedict[field2] = cor.loc[field1,field2]
+ thedict[verbosename2] = cor.loc[field1,field2]
except KeyError:
- thedict[field2] = 0
+ thedict[verbosename2] = 0
- cordict[field1] = thedict
+ cordict[verbosename1] = thedict
# set options form correctly
initial = {}
diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py
index 1e2f47b4..4809c0fe 100644
--- a/rowers/views/planviews.py
+++ b/rowers/views/planviews.py
@@ -1053,7 +1053,7 @@ def plannedsessions_coach_view(request,
}
)
-from rowers.plannedsessions import cratiocolors
+from rowers.plannedsessions import cratiocolors,checkscores
@login_required()
def plannedsessions_view(request,
@@ -2329,6 +2329,97 @@ class MacroCycleDelete(DeleteView):
return obj
+@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
+ message="This functionality requires a Coach or Self-Coach plan",
+ redirect_field_name=None)
+def rower_trainingplan_execution_view(request,
+ id=0,
+ userid=0):
+
+ startdate = request.GET.get('startdate')
+ enddate = request.GET.get('enddate')
+
+ r = getrequestrower(request,userid=userid)
+
+
+ if int(id):
+ try:
+ plan = TrainingPlan.objects.get(id=id)
+ except TrainingPlan.DoesNotExist:
+ raise Http404("Training Plan Does Not Exist")
+ if not checkaccessuser(request.user,plan.manager):
+ if request.user.rower not in plan.rowers.all():
+ raise PermissionDenied("Access denied")
+
+ if not startdate or not enddate:
+ if int(id):
+ startdate = plan.startdate
+ enddate = plan.enddate
+ else:
+ startdate,enddate = get_dates_timeperiod(request)
+ else:
+ startdate,enddate = get_dates_timeperiod(request)
+
+
+ if int(id):
+ data,message = get_execution_report(r,startdate,enddate,plan=plan)
+ else:
+ data,message = get_execution_report(r,startdate,enddate)
+
+ if not data.empty:
+ script, div = interactive_planchart(data,startdate,enddate)
+ else:
+ script = ''
+ div = ''
+ messages.error(request,'The plan does not cover this time range')
+
+ if int(id):
+ breadcrumbs = [
+ {
+ 'url':reverse(plannedsessions_view,
+ kwargs={'userid':userid}),
+ 'name': 'Plan'
+ },
+ {
+ 'url':reverse(rower_trainingplan_view,
+ kwargs={'userid':userid,
+ 'id':id}),
+ 'name': plan.name
+ },
+ {
+ 'url':reverse(rower_trainingplan_execution_view,
+ kwargs={'userid':userid,
+ 'id':id}),
+ 'name': 'Execution'
+ }
+ ]
+ else:
+ breadcrumbs = [
+ {
+ 'url':reverse(plannedsessions_view,
+ kwargs={'userid':userid}),
+ 'name': 'Plan'
+ },
+ {
+ 'url':reverse(rower_trainingplan_execution_view,
+ kwargs={'userid':userid,
+ 'id':id}),
+ 'name': 'Execution'
+ }
+ ]
+
+ return render(request,'trainingplan_chart.html',
+ {
+ 'todays_date': timezone.now().date(),
+ 'active': 'nav-plan',
+ 'breadcrumbs':breadcrumbs,
+ 'rower':r,
+ 'the_script':script,
+ 'the_div':div
+ }
+ )
+
+
@user_passes_test(hasplannedsessions,login_url="/rowers/paidplans",
message="This functionality requires a Coach or Self-Coach plan",
redirect_field_name=None)
@@ -2359,99 +2450,7 @@ def rower_trainingplan_view(request,
plan=plan,
type='userdefined').order_by("startdate")
-
- for m in macrocycles:
- createmesofillers(m)
- m.plantime = 0
- m.actualtime = 0
- m.plandistance = 0
- m.actualdistance = 0
- m.planrscore = 0
- m.actualrscore = 0
- m.plantrimp = 0
- m.actualtrimp = 0
-
-
- mesocycles = TrainingMesoCycle.objects.filter(
- plan=m,
- type='userdefined').order_by("startdate")
-
- for me in mesocycles:
- me.plantime = 0
- me.actualtime = 0
- me.plandistance = 0
- me.actualdistance = 0
- me.planrscore = 0
- me.actualrscore = 0
- me.plantrimp = 0
- me.actualtrimp = 0
-
- microcycles = TrainingMicroCycle.objects.filter(
- plan=me,
- type='userdefined').order_by("startdate")
-
- for mm in microcycles:
- sps = get_sessions(r,startdate=mm.startdate,enddate=mm.enddate)
-
- # sps = PlannedSession.objects.filter(
- # rower = r,
- # startdate__lte=mm.enddate,
- # enddate__gte=mm.startdate)
-
-
- mm.plantime = 0
- mm.actualtime = 0
- mm.plandistance = 0
- mm.actualdistance = 0
- mm.planrscore = 0
- mm.actualrscore = 0
- mm.plantrimp = 0
- mm.actualtrimp = 0
-
-
- if mm.type == 'userdefined':
- for ps in sps:
- ratio, status, cdate = is_session_complete(r,ps)
- if ps.sessionmode == 'time':
- mm.plantime += ps.sessionvalue
- mm.actualtime += int(ps.sessionvalue*ratio)
- elif ps.sessionmode == 'distance' and ps.sessiontype != 'race':
- mm.plandistance += ps.sessionvalue
- mm.actualdistance += int(ps.sessionvalue*ratio)
- elif ps.sessionmode == 'rScore':
- mm.planrscore += ps.sessionvalue
- mm.actualrscore += int(ps.sessionvalue*ratio)
- elif ps.sessionmode == 'TRIMP':
- mm.plantrimp += ps.sessionvalue
- mm.actualtrimp += int(ps.sessionvalue*ratio)
-
- mm.save()
-
- me.plantime += mm.plantime
- me.actualtime += mm.actualtime
- me.plandistance += mm.plandistance
- me.actualdistance += mm.actualdistance
- me.planrscore += mm.planrscore
- me.actualrscore += mm.actualrscore
- me.plantrimp += mm.plantrimp
- me.actualtrimp += mm.actualtrimp
-
- if me.type == 'userdefined':
- me.save()
-
- m.plantime += me.plantime
- m.actualtime += me.actualtime
- m.plandistance += me.plandistance
- m.actualdistance += me.actualdistance
- m.planrscore += me.planrscore
- m.actualrscore += me.actualrscore
- m.plantrimp += me.plantrimp
- m.actualtrimp += me.actualtrimp
-
-
-
- if m.type == 'userdefined':
- m.save()
+ checkscores(r,macrocycles)
createmacrofillers(plan)
macrocycles = TrainingMacroCycle.objects.filter(plan=plan).order_by("startdate")
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 55321bc6..0293bfea 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -2537,9 +2537,13 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
stats = {}
fieldlist,fielddict = dataprep.getstatsfields()
- fielddict.pop('workoutstate')
- fielddict.pop('workoutid')
-
+ # fielddict.pop('workoutstate')
+ # fielddict.pop('workoutid')
+ try:
+ fielddict.pop('pace')
+ except KeyError:
+ pass
+
for field,verbosename in fielddict.items():
thedict = {
'mean':datadf[field].mean(),
@@ -2558,15 +2562,15 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
cor = datadf.corr(method='spearman')
cor.fillna(value=0,inplace=True)
cordict = {}
- for field1,verbosename in fielddict.items():
+ for field1,verbosename1 in fielddict.items():
thedict = {}
- for field2,verbosename in fielddict.items():
+ for field2,verbosename2 in fielddict.items():
try:
- thedict[field2] = cor.loc[field1,field2]
+ thedict[verbosename2] = cor.loc[field1,field2]
except KeyError:
- thedict[field2] = 0
+ thedict[verbosename2] = 0
- cordict[field1] = thedict
+ cordict[verbosename1] = thedict
# additional non-automated stats
otherstats = {}
diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css
index 28aa8f1b..f096204a 100644
--- a/static/css/rowsandall2.css
+++ b/static/css/rowsandall2.css
@@ -124,14 +124,14 @@ th {
th.rotate {
/* Something you can count on */
- height: 78px;
+ height: 120px;
white-space: nowrap;
}
th.rotate > div {
transform:
/* Magic Numbers */
- translate(18px, 51px)
+ translate(28px, 91px)
/* 45 is really 360 - 45 */
rotate(315deg);
width: 30px;