diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 45cfd3d4..f99a9468 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -555,6 +555,71 @@ def fetchcp(rower,theworkouts,table='cpdata'):
return [],[],avgpower2
+# create a new workout from manually entered data
+def create_row_df(r,distance,duration,startdatetime,
+ title = 'Manually added workout',notes='',
+ workouttype='rower'):
+
+
+ nr_strokes = int(distance/10.)
+
+ unixstarttime = arrow.get(startdatetime).timestamp
+
+ totalseconds = duration.hour*3600.
+ totalseconds += duration.minute*60.
+ totalseconds += duration.second
+ totalseconds += duration.microsecond/1.e6
+
+
+ spm = 60.*nr_strokes/totalseconds
+
+ step = totalseconds/float(nr_strokes)
+
+ elapsed = np.arange(0,totalseconds+step,step)
+
+ dstep = distance/float(nr_strokes)
+
+ d = np.arange(0,distance+dstep,dstep)
+
+ unixtime = unixstarttime + elapsed
+
+ pace = 500.*totalseconds/distance
+
+ if workouttype in ['rower','slides','dynamic']:
+ velo = distance/totalseconds
+ power = 2.8*velo**3
+ else:
+ power = 0
+
+ df = pd.DataFrame({
+ 'TimeStamp (sec)': unixtime,
+ ' Horizontal (meters)': d,
+ ' Cadence (stokes/min)': spm,
+ ' Stroke500mPace (sec/500m)':pace,
+ ' ElapsedTime (sec)':elapsed,
+ ' Power (watts)':power,
+ })
+
+ timestr = strftime("%Y%m%d-%H%M%S")
+
+ csvfilename = 'media/df_' + timestr + '.csv'
+ df[' ElapsedTime (sec)'] = df['TimeStamp (sec)']
+
+ row = rrdata(df=df)
+
+ row.write_csv(csvfilename, gzip = True)
+
+ id, message = save_workout_database(csvfilename, r,
+ title=title,
+ notes=notes,
+ dosmooth=False,
+ workouttype=workouttype,
+ consistencychecks=False,
+ totaltime=totalseconds)
+
+ return (id, message)
+
+
# Processes painsled CSV file to database
diff --git a/rowers/templates/manualadd.html b/rowers/templates/manualadd.html
new file mode 100644
index 00000000..60540df9
--- /dev/null
+++ b/rowers/templates/manualadd.html
@@ -0,0 +1,38 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+{% load tz %}
+
+
+{% get_current_timezone as TIME_ZONE %}
+
+{% block content %}
+
+
Add Workout Manually
+
+ {% if form.errors %}
+
+ Please correct the error{{ form.errors|pluralize }} below.
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/oterankings.html b/rowers/templates/oterankings.html
new file mode 100644
index 00000000..9fdc910d
--- /dev/null
+++ b/rowers/templates/oterankings.html
@@ -0,0 +1,232 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block scripts %}
+{% include "monitorjobs.html" %}
+{% endblock %}
+
+{% 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 %}
+
+
+
+
+
+
+
+
+
+
+
Critical Power Plot
+
+ {{ the_div|safe }}
+
+
+
+
+
+
Ranking Piece Results
+
+ {% if rankingworkouts %}
+
+
+
+
+ Distance
+ Duration
+ Avg Power
+ Date
+ Avg HR
+ Max HR
+ Edit
+
+
+
+ {% for workout in rankingworkouts %}
+
+ {{ workout.distance }} m
+ {{ workout.duration |durationprint:"%H:%M:%S.%f" }}
+ {{ avgpower|lookup:workout.id }} W
+ {{ workout.date }}
+ {{ workout.averagehr }}
+ {{ workout.maxhr }}
+
+ {{ workout.name }}
+
+
+
+ {% endfor %}
+
+
+ {% else %}
+
No ranking workouts found
+ {% endif %}
+
+
+
+
+
Pace predictions for Ranking Pieces
+
+
Add non-ranking piece using the form. The piece will be added in the prediction tables below.
+
+
+
+
+
+
+
+ Duration
+ Distance
+ Pace (upper)
+ Power
+ Power (upper)
+
+
+
+ {% for pred in cpredictions %}
+
+ {% for key, value in pred.items %}
+ {% if key == "power" or key == "upper" %}
+ {{ value }} W
+ {% endif %}
+ {% if key == "duration" %}
+ {{ value |deltatimeprint }}
+ {% endif %}
+ {% if key == "distance" %}
+ {{ value }} m
+ {% endif %}
+ {% if key == 'pace' %}
+ {{ value|paceprint }}
+ {% endif %}
+ {% endfor %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+ minutes
+
+
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/rankings.html b/rowers/templates/rankings.html
index e5625f95..741f5a41 100644
--- a/rowers/templates/rankings.html
+++ b/rowers/templates/rankings.html
@@ -72,7 +72,7 @@
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.
+
diff --git a/rowers/urls.py b/rowers/urls.py
index b31c2be7..46406980 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -126,6 +126,7 @@ urlpatterns = [
url(r'^list-workouts/team/(?P\d+)/$',views.workouts_view),
url(r'^list-workouts/(?P\w+.*)/(?P\w+.*)$',views.workouts_view),
url(r'^list-workouts/$',views.workouts_view),
+ url(r'^addmanual/$',views.addmanual_view),
url(r'^team-compare-select/team/(?P\d+)/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select),
url(r'^team-compare-select/team/(?P\d+)/$',views.team_comparison_select),
url(r'^team-compare-select/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select),
diff --git a/rowers/views.py b/rowers/views.py
index 8fe5e197..58cbdee0 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -3096,6 +3096,82 @@ def histo(request,theuser=0,
'teams':get_my_teams(request.user),
})
+# add a workout manually
+@login_required()
+def addmanual_view(request):
+ r = Rower.objects.get(user=request.user)
+
+ if request.method == 'POST':
+ # Form was submitted
+ form = WorkoutForm(request.POST)
+ if form.is_valid():
+ # Get values from form
+ name = form.cleaned_data['name']
+ date = form.cleaned_data['date']
+ starttime = form.cleaned_data['starttime']
+ workouttype = form.cleaned_data['workouttype']
+ duration = form.cleaned_data['duration']
+ distance = form.cleaned_data['distance']
+ notes = form.cleaned_data['notes']
+ thetimezone = form.cleaned_data['timezone']
+ try:
+ boattype = request.POST['boattype']
+ except KeyError:
+ boattype = '1x'
+ try:
+ privacy = request.POST['privacy']
+ except KeyError:
+ privacy = 'visible'
+ try:
+ rankingpiece = form.cleaned_data['rankingpiece']
+ except KeyError:
+ rankingpiece =- Workout.objects.get(id=id).rankingpiece
+
+ startdatetime = (str(date) + ' ' + str(starttime))
+ startdatetime = datetime.datetime.strptime(startdatetime,
+ "%Y-%m-%d %H:%M:%S")
+ startdatetime = timezone.make_aware(startdatetime)
+ startdatetime = startdatetime.astimezone(
+ pytz.timezone(thetimezone)
+ )
+
+
+ print name
+ id,message = dataprep.create_row_df(r,
+ distance,
+ duration,startdatetime,
+ title = name,
+ notes=notes,
+ workouttype=workouttype)
+
+
+
+ if message:
+ messages.error(request,message)
+
+ if id:
+ w = Workout.objects.get(id=id)
+ w.rankingpiece = rankingpiece
+ w.notes = notes
+ w.save()
+ messages.info(request,'New workout created')
+
+
+ initial = {
+ 'workouttype':'rower',
+ 'date':datetime.date.today(),
+ 'starttime':timezone.now(),
+ 'timezone':r.defaulttimezone,
+ 'duration':datetime.timedelta(minutes=2),
+ 'distance':500,
+
+ }
+ form = WorkoutForm(initial=initial)
+
+ return render(request,'manualadd.html',
+ {'form':form,
+ })
+
# Show ranking distances including predicted paces
@login_required()
def rankings_view(request,theuser=0,
@@ -3779,6 +3855,8 @@ def oterankings_view(request,theuser=0,
rankingdurations.append(datetime.time(hour=1))
rankingdurations.append(datetime.time(hour=1,minute=15))
+ rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
+
thedistances = []
theworkouts = []
thesecs = []
@@ -3874,21 +3952,29 @@ def oterankings_view(request,theuser=0,
form = PredictedPieceForm(request.POST)
clean = form.is_valid()
value = form.cleaned_data['value']
- hourvalue,value = divmod(value,60)
+ hourvalue,tvalue = divmod(value,60)
hourvalue = int(hourvalue)
- minutevalue = int(value)
- value = int(60*(value-minutevalue))
+ minutevalue = int(tvalue)
+ tvalue = int(60*(tvalue-minutevalue))
if hourvalue >= 24:
hourvalue = 23
- rankingdurations.append(datetime.time(minute=minutevalue,
- hour=hourvalue,
- second=value))
+ pieceunit = form.cleaned_data['pieceunit']
+ if pieceunit == 'd':
+ rankingdistances.append(value)
+ else:
+ rankingdurations.append(datetime.time(
+ minute=minutevalue,
+ hour=hourvalue,
+ second=tvalue
+ ))
else:
form = PredictedPieceForm()
cpredictions = []
+
+
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
@@ -3900,6 +3986,9 @@ def oterankings_view(request,theuser=0,
pwr = p1[0]/(1+t/p1[2])
pwr += p1[1]/(1+t/p1[3])
+ velo = (pwr/2.8)**(1./3.)
+ p = 500./velo
+ d = t*velo
if pwr <= 0:
pwr = 50.
@@ -3912,16 +4001,78 @@ def oterankings_view(request,theuser=0,
pwr2 = pwr
a = {
+ 'distance':int(d),
'duration':timedeltaconv(t),
'power':int(pwr),
- 'upper':int(pwr2)}
+ 'upper':int(pwr2),
+ 'pace':timedeltaconv(p)}
+
cpredictions.append(a)
- del form.fields["pieceunit"]
+ # initiation - get 10 min power, then use Paul's law
+
+ t_10 = 600.
+ power_10 = p1[0]/(1+t_10/p1[2])
+ power_10 += p1[1]/(1+t_10/p1[3])
+
+ velo_10 = (power_10/2.8)**(1./3.)
+ pace_10 = 500./velo_10
+ distance_10 = t_10*velo_10
+
+ paulslope = 5.
+
+ for rankingdistance in rankingdistances:
+
+ delta = paulslope * np.log(rankingdistance/distance_10)/np.log(2)
+
+
+ p = pace_10+delta
+ velo = 500./p
+ t = rankingdistance/velo
+
+ pwr2 = p1[0]/(1+t/p1[2])
+ pwr2 += p1[1]/(1+t/p1[3])
+ pwr2 *= ratio
+
+ if pwr2 <= 0:
+ pwr2 = 50.
+
+ velo2 = (pwr2/2.8)**(1./3.)
+
+ if np.isnan(velo2) or velo2 <= 0:
+ velo2 = 1.0
+
+ t2 = rankingdistance/velo2
+
+ pwr3 = p1[0]/(1+t2/p1[2])
+ pwr3 += p1[1]/(1+t2/p1[3])
+ pwr3 *= ratio
+
+
+ if pwr3 <= 0:
+ pwr3 = 50.
+
+ velo3 = (pwr3/2.8)**(1./3.)
+ if np.isnan(velo3) or velo3 <= 0:
+ velo3 = 1.0
+
+ t3 = rankingdistance/velo3
+ p3 = 500./velo3
+
+ a = {
+ 'distance':rankingdistance,
+ 'duration':timedeltaconv(t3),
+ 'power':'--',
+ 'upper':int(pwr3),
+ 'pace':timedeltaconv(p3)}
+
+ cpredictions.append(a)
+
+ # del form.fields["pieceunit"]
messages.error(request,message)
- return render(request, 'otwrankings.html',
+ return render(request, 'oterankings.html',
{'rankingworkouts':theworkouts,
'interactiveplot':script,
'the_div':div,
diff --git a/static/img/image.png b/static/img/image.png
new file mode 100644
index 00000000..d6acd417
Binary files /dev/null and b/static/img/image.png differ