From 4e94a9e2e5d73d257bb68aee29ceb8987bfabbd4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Jun 2017 20:54:24 -0500 Subject: [PATCH] otw-bests somehow working --- rowers/interactiveplots.py | 88 +++++++++++++ rowers/templates/otwrankings.html | 199 ++++++++++++++++++++++++++++++ rowers/views.py | 48 ++----- 3 files changed, 298 insertions(+), 37 deletions(-) create mode 100644 rowers/templates/otwrankings.html diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 7f640cee..0fdb77c1 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -596,6 +596,94 @@ def googlemap_chart(lat,lon,name=""): return [script,div] +def interactive_otwcpchart(powerdf,promember=0): + powerdf = powerdf[~(powerdf == 0).any(axis=1)] + # plot tools + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + + x_axis_type = 'log' + y_axis_type = 'linear' + + source = ColumnDataSource( + data = powerdf + ) + + # there is no Paul's law for OTW + + # Fit the data to thee parameter CP model + fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) + errfunc = lambda pars,x,y: fitfunc(pars,x)-y + + p0 = [500,350,10,8000] + + p1 = p0 + + thesecs = powerdf['Delta'] + theavpower = powerdf['CP'] + + if len(thesecs)>=4: + p1, success = optimize.leastsq(errfunc, p0[:], args = (thesecs,theavpower)) + else: + factor = fitfunc(p0,thesecs.mean())/theavpower.mean() + p1 = [p0[0]/factor,p0[1]/factor,p0[2],p0[3]] + + + fitt = pd.Series(10**(4*np.arange(100)/100.)) + + fitpower = fitfunc(p1,fitt) + + message = "" + #if len(fitpower[fitpower<0]) > 0: + # message = "CP model fit didn't give correct results" + + + sourcecomplex = ColumnDataSource( + data = dict( + power = fitpower, + duration = fitt + ) + ) + + # making the plot + plot = Figure(tools=TOOLS,x_axis_type=x_axis_type, + plot_width=900, + toolbar_location="above", + toolbar_sticky=False) + + # add watermark + plot.extra_y_ranges = {"watermark": watermarkrange} + + plot.image_url([watermarkurl],1.8*max(thesecs),watermarky, + watermarkw,watermarkh, + global_alpha=watermarkalpha, + w_units='screen', + h_units='screen', + anchor=watermarkanchor, + dilate=True, + y_range_name = "watermark", + ) + + plot.circle('Delta','CP',source=source,fill_color='red',size=15, + legend='Power') + plot.xaxis.axis_label = "Duration (seconds)" + plot.yaxis.axis_label = "Power (W)" + + plot.y_range = Range1d(0,1.5*max(theavpower)) + plot.x_range = Range1d(1,2*max(thesecs)) + plot.legend.orientation = "vertical" + + + plot.line('duration','power',source=sourcecomplex,legend="CP Model", + color='green') + + script, div = components(plot) + + return [script,div,p1,message] + def interactive_cpchart(thedistances,thesecs,theavpower, theworkouts,promember=0): diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html new file mode 100644 index 00000000..35b35865 --- /dev/null +++ b/rowers/templates/otwrankings.html @@ -0,0 +1,199 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Workouts{% endblock %} + +{% block content %} + + + + + {{ interactiveplot |safe }} + + + + + +
+
+ {% if theuser %} +

{{ theuser.first_name }}'s Ranking Pieces

+ {% else %} +

{{ user.first_name }}'s Ranking Pieces

+ {% endif %} +
+
+ {% if user.is_authenticated and user|is_manager %} + +
+ +
+

Summary for {{ theuser.first_name }} {{ theuser.last_name }} + between {{ startdate|date }} and {{ enddate|date }}

+ +

Direct link for other users: + https://rowsandall.com/rowers/{{ id }}/otw-bests/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }} +

+ +

The table gives the best efforts achieved on the official Concept2 ranking pieces in the selected date range.

+ +

This page will evolve and try to give you guidance on where to improve.

+
+
+

Use this form to select a different date range:

+

+ Select start and end date for a date range: +

+ +
+ + + {{ dateform.as_table }} +
+ {% csrf_token %} +
+
+ +
+
+
+ Or use the last {{ deltaform }} days. +
+
+ {% csrf_token %} + + +
+
+ +
+ +

Ranking Piece Results

+ + {% if rankingworkouts %} + + + + + + + + + + + + + + {% for workout in rankingworkouts %} + + + + + + + + + + + {% endfor %} + +
Distance Duration Date Avg HR Max HR Edit
{{ workout.distance }} {{ workout.duration |durationprint:"%H:%M:%S.%f" }} {{ workout.date }} {{ workout.averagehr }} {{ workout.maxhr }} + {{ workout.name }}
+ {% else %} +

No ranking workouts found

+ {% endif %} + +
+ + +
+ +

Critical Power Plot

+ + {{ the_div|safe }} + +
+ +
+

Pace predictions for Ranking Pieces

+ +

Add non-ranking piece using the form. The piece will be added in the prediction tables below.

+
+
+ {{ form.value }} {{ form.pieceunit }} + + {% csrf_token %} +
+
+ + +
+ + + +
+ No Paul Data +
+
+

CP Model

+ + + + + + + + + {% for pred in cpredictions %} + + {% for key, value in pred.items %} + {% if key == "power" %} + + {% endif %} + {% if key == "duration" %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
Duration Power
{{ value }} W {{ value |deltatimeprint }}
+ +
+
+ +{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index 922ba101..fc15ea5e 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2938,15 +2938,14 @@ def otwrankings_view(request,theuser=0, # create interactive plot - if len(thedistances) !=0 : - res = interactive_cpchart(thedistances,thesecs,theavpower, - theworkouts,promember=promember) + if len(powerdf) !=0 : + res = interactive_otwcpchart(powerdf) script = res[0] div = res[1] - paulslope = res[2] - paulintercept = res[3] - p1 = res[4] - message = res[5] + p1 = res[2] + paulslope = 1 + paulintercept = 1 + message = res[3] else: script = '' div = '

No ranking pieces found.

' @@ -2983,23 +2982,6 @@ def otwrankings_view(request,theuser=0, t += rankingduration.second t += rankingduration.microsecond/1.e6 - # Paul's model - ratio = paulintercept/paulslope - - u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope - - d = 500*t*np.log(10.) - d = d/(paulslope*lambertw(u)) - d = d.real - - velo = d/t - p = 500./velo - pwr = 2.8*(velo**3) - a = {'distance':int(d), - 'duration':timedeltaconv(t), - 'pace':timedeltaconv(p), - 'power':int(pwr)} - predictions.append(a) # CP model pwr = p1[0]/(1+t/p1[2]) @@ -3008,28 +2990,20 @@ def otwrankings_view(request,theuser=0, if pwr <= 0: pwr = 50. - velo = (pwr/2.8)**(1./3.) - - if np.isnan(velo) or velo <=0: - velo = 1.0 - - d = t*velo - p = 500./velo - a = {'distance':int(d), - 'duration':timedeltaconv(t), - 'pace':timedeltaconv(p), - 'power':int(pwr)} + a = { + 'duration':timedeltaconv(t), + 'power':int(pwr)} cpredictions.append(a) + print cpredictions messages.error(request,message) - return render(request, 'rankings.html', + return render(request, 'otwrankings.html', {'rankingworkouts':theworkouts, 'interactiveplot':script, 'the_div':div, 'predictions':predictions, 'cpredictions':cpredictions, - 'nrdata':len(thedistances), 'form':form, 'dateform':dateform, 'deltaform':deltaform,