From c12a2c8fe3ee117dffa9ab932dcb6ce1d1f5daed Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 5 Oct 2019 14:35:06 +0200 Subject: [PATCH] first version actual/executed chart for plan --- rowers/interactiveplots.py | 33 ++++- rowers/models.py | 2 +- rowers/plannedsessions.py | 169 ++++++++++++++++++++++- rowers/templates/trainingplan_chart.html | 35 +++++ rowers/urls.py | 4 + rowers/views/planviews.py | 153 ++++++++------------ 6 files changed, 299 insertions(+), 97 deletions(-) create mode 100644 rowers/templates/trainingplan_chart.html diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 5b30d4c4..3aaa69b6 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -232,6 +232,37 @@ 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() + + yrange1 = Range1d(start=0,end=1.1*yaxmaximum) + + + tidy_df = data.melt(id_vars=['startdate'],value_vars=['planned','executed']) + 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 +383,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..697a63a0 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,170 @@ 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") + 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 + return(0,'no plan found') + 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/planviews.py b/rowers/views/planviews.py index 1e2f47b4..8868e0e7 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,63 @@ 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,enddate = get_dates_timeperiod(request) + + try: + plan = TrainingPlan.objects.get(id=id) + except TrainingPlan.DoesNotExist: + raise Http4040("Training Plan Does Not Exist") + + r = getrequestrower(request,userid=userid) + + if not checkaccessuser(request.user,plan.manager): + if request.user.rower not in plan.rowers.all(): + raise PermissionDenied("Access denied") + + data,message = get_execution_report(r,plan.startdate,plan.enddate,plan=plan) + + script, div = interactive_planchart(data,plan.startdate,plan.enddate) + + 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' + } + ] + + return render(request,'trainingplan_chart.html', + { + 'plan':plan, + '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 +2416,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")