From 0a18100b89315e5009537f112e404dc0f2e3fca1 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 1 May 2018 08:34:47 +0200
Subject: [PATCH 1/6] better physics recipe
---
rowers/templates/physics.html | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/rowers/templates/physics.html b/rowers/templates/physics.html
index 4ed2bd7f..efc5757d 100644
--- a/rowers/templates/physics.html
+++ b/rowers/templates/physics.html
@@ -107,16 +107,27 @@ GPS. For now, export the data to Strava and then import them here.
- Click on the workout. This will bring you to the workout Edit view
- Click on the "Advanced" button
- - Click on Edit Wind Data (optional) to edit wind data. Go back to Advanced when you're done.
- - Click on Edit Stream Data (optional) to edit stream data. Go back to Advanced when you're done.
+ - Click on "Geeky Stuff"
+ - Look for three buttons labelled "Edit Wind Data", "Edit Stream
+ Data" and "OTW Power"
+ - If you have wind or stream data, click on the appropriate
+ button and enter your data.
+ - If you have both wind and stream, click the shortcut button
+ on the respective page to take you to the other parameter
- Click on OTW Power
- - Select the boat type and enter the average weight per crew member.
- Do not use the crew total weight.
+ - Select the boat type and enter the average weight
+ per crew member.
+ Do not use the crew total weight.
- Click "Update & Run"
- Go do something else. You will receive an email when the calculations are finished. The calculation itself will take about 10 minutes for an
hour long row, but there may be other people's calculations in the queue, so
-it may take longer.
- - From the "Advanced" view, click on "Corrected Pace Plot" to see the result. From here, you can re-run the calculation with different parameters.
+ it may take longer.
+ - Progress can be monitored by clicking on "here" in the message
+ at the top of the page advising that the calculation has
+ begun.
+ -
+ When the calculation is complete, go back to the "Geeky Stuff" page
+ and click on "Corrected Pace Plot" to see the result. From here, you can re-run the calculation with different parameters.
@@ -152,4 +163,4 @@ well validated. For that, I need to collect data, so keep the workouts coming!
- {% endblock content %}
\ No newline at end of file
+ {% endblock content %}
From 39ff62e560831e78e59ba25a86a7924726614ea8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Tue, 1 May 2018 10:04:53 +0200
Subject: [PATCH 2/6] now uses NK summary for NK files
---
rowers/dataprep.py | 2 +-
rowers/dataprepnodjango.py | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 61116cea..3a1debc8 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -1308,7 +1308,7 @@ def new_workout_from_file(r, f2,
message = 'Something went wrong: ' + errorstring
return (0, message, '')
- dosummary = (fileformat != 'fit')
+ dosummary = (fileformat != 'fit' and fileformat != 'speedcoach2')
id, message = save_workout_database(
f2, r,
workouttype=workouttype,
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index 49a3e6dc..11c8e1e1 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -623,7 +623,8 @@ def new_workout_from_file(r,f2,
- dosummary = (fileformat != 'fit')
+ dosummary = (fileformat != 'fit' and fileformat != 'speedcoach2')
+
id,message = save_workout_database(f2,r,
workouttype=workouttype,
makeprivate=makeprivate,
From 379626f802a8ca2173f77bacd81e925b7a5c1b54 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 2 May 2018 09:41:59 +0200
Subject: [PATCH 3/6] correctly recognizes Empower version on new files
---
rowers/dataprep.py | 36 +++++++++++++++++++++++-------------
rowers/models.py | 2 +-
2 files changed, 24 insertions(+), 14 deletions(-)
diff --git a/rowers/dataprep.py b/rowers/dataprep.py
index 3a1debc8..237edf1e 100644
--- a/rowers/dataprep.py
+++ b/rowers/dataprep.py
@@ -10,7 +10,9 @@ from rowingdata import rower as rrower
from shutil import copyfile
-from rowingdata import get_file_type, get_empower_rigging
+from rowingdata import (
+ get_file_type, get_empower_rigging,get_empower_firmware
+ )
from rowers.tasks import handle_sendemail_unrecognized
from rowers.tasks import handle_zip_file
@@ -1153,11 +1155,15 @@ def parsenonpainsled(fileformat,f2,summary):
if (fileformat == 'speedcoach2'):
row = SpeedCoach2Parser(f2)
hasrecognized = True
- try:
- oarlength, inboard = get_empower_rigging(f2)
- summary = row.allstats()
- except:
- pass
+
+ oarlength, inboard = get_empower_rigging(f2)
+ empowerfirmware = get_empower_firmware(f2)
+ if empowerfirmware != '':
+ fileformat = fileformat+'v'+str(empowerfirmware)
+ else:
+ fileformat = 'speedcoach2v0'
+ summary = row.allstats()
+
# handle ErgStick
if (fileformat == 'ergstick'):
@@ -1176,7 +1182,7 @@ def parsenonpainsled(fileformat,f2,summary):
pass
hasrecognized = True
- return row,hasrecognized,summary
+ return row,hasrecognized,summary,fileformat
def handle_nonpainsled(f2, fileformat, summary=''):
oarlength = 2.89
@@ -1184,7 +1190,7 @@ def handle_nonpainsled(f2, fileformat, summary=''):
hasrecognized = False
try:
- row,hasrecognized,summary = parsenonpainsled(fileformat,f2,summary)
+ row,hasrecognized,summary,fileformat = parsenonpainsled(fileformat,f2,summary)
except:
pass
@@ -1211,7 +1217,7 @@ def handle_nonpainsled(f2, fileformat, summary=''):
except:
os.remove(f_to_be_deleted + '.gz')
- return (f2, summary, oarlength, inboard)
+ return (f2, summary, oarlength, inboard, fileformat)
# Create new workout from file and store it in the database
# This routine should be used everywhere in views.py and mailprocessing.py
@@ -1297,9 +1303,11 @@ def new_workout_from_file(r, f2,
# handle non-Painsled by converting it to painsled compatible CSV
if (fileformat != 'csv'):
try:
- f2, summary, oarlength, inboard = handle_nonpainsled(f2,
- fileformat,
- summary=summary)
+ f2, summary, oarlength, inboard, fileformat = handle_nonpainsled(
+ f2,
+ fileformat,
+ summary=summary
+ )
if not f2:
message = 'Something went wrong'
return (0, message, '')
@@ -1308,7 +1316,9 @@ def new_workout_from_file(r, f2,
message = 'Something went wrong: ' + errorstring
return (0, message, '')
- dosummary = (fileformat != 'fit' and fileformat != 'speedcoach2')
+ dosummary = (fileformat != 'fit' and 'speedcoach2' not in fileformat)
+
+
id, message = save_workout_database(
f2, r,
workouttype=workouttype,
diff --git a/rowers/models.py b/rowers/models.py
index b8c70042..75afa570 100644
--- a/rowers/models.py
+++ b/rowers/models.py
@@ -1221,7 +1221,7 @@ class Workout(models.Model):
name = models.CharField(max_length=150)
date = models.DateField()
workouttype = models.CharField(choices=workouttypes,max_length=50)
- workoutsource = models.CharField(choices=workoutsources,max_length=100,
+ workoutsource = models.CharField(max_length=100,
default='unknown')
boattype = models.CharField(choices=boattypes,max_length=50,
default='1x',
From 592077419716eb9a27fab4bdad0b047a1ded1f23 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 2 May 2018 14:20:20 +0200
Subject: [PATCH 4/6] empower fix done
---
rowers/dataprepnodjango.py | 49 ++++++++++++
rowers/tasks.py | 85 +++++++++++++++++++-
rowers/templates/empower_fix.html | 125 ++++++++++++++++++++++++++++++
rowers/templates/multiflex.html | 2 +-
rowers/urls.py | 1 +
rowers/views.py | 105 +++++++++++++++++++++++++
6 files changed, 365 insertions(+), 2 deletions(-)
create mode 100644 rowers/templates/empower_fix.html
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index 11c8e1e1..f17cbb8b 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -4,6 +4,7 @@ from rowingdata import rowingdata as rrdata
from rowingdata import make_cumvalues
from rowingdata import rower as rrower
from rowingdata import main as rmain
+from rowingdata import empower_bug_correction,get_empower_rigging
from time import strftime
from pandas import DataFrame,Series
@@ -659,7 +660,55 @@ def update_strokedata(id,df,debug=False):
debug=debug)
return rowdata
+
+def update_empower(id, inboard, oarlength, boattype, df, f1, debug=False):
+
+ corr_factor = 1.0
+ if 'x' in boattype:
+ # sweep
+ a = 0.06
+ b = 0.275
+ else:
+ # scull
+ a = 0.15
+ b = 0.275
+
+ corr_factor = empower_bug_correction(oarlength,inboard,a,b)
+
+ success = False
+
+ print df[' Power (watts)'].mean()
+ try:
+ df['power empower old'] = df[' Power (watts)']
+ df[' Power (watts)'] = df[' Power (watts)'] * corr_factor
+ df['driveenergy empower old'] = df['driveenergy']
+ df['driveenergy'] = df['driveenergy'] * corr_factor
+ success = True
+ except KeyError:
+ pass
+
+ if success:
+ delete_strokedata(id,debug=debug)
+ if debug:
+ print "updated ",id
+ print "correction ",corr_factor
+ else:
+ if debug:
+ print "not updated ",id
+
+ print df[' Power (watts)'].mean()
+
+
+ rowdata = dataprep(df,id=id,bands=True,barchart=True,otwpower=True,
+ debug=debug)
+
+ row = rrdata(df=df)
+ row.write_csv(f1,gzip=True)
+
+ return success
+
+
def testdata(time,distance,pace,spm):
t1 = np.issubdtype(time,np.number)
t2 = np.issubdtype(distance,np.number)
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 08f48c49..96c400a2 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -41,7 +41,7 @@ from rowers.dataprepnodjango import (
getsmallrowdata_db, updatecpdata_sql,
update_agegroup_db,fitnessmetric_to_sql,
add_c2_stroke_data_db,totaltime_sec_to_string,
- create_c2_stroke_data_db
+ create_c2_stroke_data_db,update_empower
)
@@ -265,6 +265,89 @@ def handle_new_workout_from_file(r, f2,
# process and update workouts
+@app.task(bind=True)
+def handle_update_empower(self,
+ useremail,
+ workoutdicts,
+ debug=False, **kwargs):
+
+ job = self.request
+ job_id = job.id
+
+ if 'jobkey' in kwargs:
+ job_id = kwargs.pop('jobkey')
+
+ aantal = len(workoutdicts)
+ counter = 0
+
+ for workoutdict in workoutdicts:
+ wid = workoutdict['id']
+ inboard = workoutdict['inboard']
+ oarlength = workoutdict['oarlength']
+ boattype = workoutdict['boattype']
+ f1 = workoutdict['filename']
+
+ print wid
+
+ # oarlength consistency checks will be done in view
+
+ havedata = 1
+ try:
+ rowdata = rdata(f1)
+ except IOError:
+ try:
+ rowdata = rdata(f1 + '.csv')
+ except IOError:
+ try:
+ rowdata = rdata(f1 + '.gz')
+ except IOError:
+ havedata = 0
+
+ progressurl = SITE_URL
+ siteurl = SITE_URL
+ if debug:
+ progressurl = SITE_URL_DEV
+ siteurl = SITE_URL_DEV
+ secret = PROGRESS_CACHE_SECRET
+
+ kwargs['job_id'] = job_id
+
+ progressurl += "/rowers/record-progress/"
+ progressurl += job_id
+
+ if havedata:
+ success = update_empower(wid, inboard, oarlength, boattype,
+ rowdata.df, f1, debug=debug)
+
+ counter += 1
+
+ progress = 100.*float(counter)/float(aantal)
+
+ post_data = {
+ "secret":secret,
+ "value":progress,
+ }
+
+ s = requests.post(progressurl, data=post_data)
+ status_code = s.status_code
+
+ subject = "Rowsandall.com Your Old Empower Oarlock data have been corrected"
+ message = """
+We have updated Power and Work per Stroke data according to the instructions by Nielsen-Kellerman.
+"""
+
+ email = EmailMessage(subject, message,
+ 'Rowsandall ',
+ [useremail])
+
+ if 'emailbounced' in kwargs:
+ emailbounced = kwargs['emailbounced']
+ else:
+ emailbounced = False
+
+ if not emailbounced:
+ res = email.send()
+ return 1
@app.task
def handle_updatedps(useremail, workoutids, debug=False,**kwargs):
diff --git a/rowers/templates/empower_fix.html b/rowers/templates/empower_fix.html
new file mode 100644
index 00000000..c817d02e
--- /dev/null
+++ b/rowers/templates/empower_fix.html
@@ -0,0 +1,125 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block title %}Workouts{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+ {% include "teambuttons.html" with teamid=team.id team=team %}
+
+
+
Empower Workouts
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/rowers/templates/multiflex.html b/rowers/templates/multiflex.html
index 375602ea..2ab83e20 100644
--- a/rowers/templates/multiflex.html
+++ b/rowers/templates/multiflex.html
@@ -37,7 +37,7 @@
-
Multi Flex Chart
+
Trend Flex Chart
{{ the_div|safe }}
diff --git a/rowers/urls.py b/rowers/urls.py
index d37bf26b..b207fa47 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -122,6 +122,7 @@ urlpatterns = [
url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'),
url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'),
url(r'^exportallworkouts/?$',views.workouts_summaries_email_view),
+ url(r'^update_empower$',views.rower_update_empower_view),
url(r'^agegroupcp/(?P
\d+)$',views.agegroupcpview),
url(r'^agegroupcp/(?P\d+)/(?P\d+)$',views.agegroupcpview),
url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$',
diff --git a/rowers/views.py b/rowers/views.py
index ccb2b10c..9607f740 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -128,6 +128,7 @@ from rowers.tasks import (
handle_updatecp,long_test_task,long_test_task2,
handle_zip_file,handle_getagegrouprecords,
handle_updatefitnessmetric,
+ handle_update_empower,
handle_sendemail_userdeleted,
)
@@ -369,6 +370,7 @@ verbose_job_status = {
'make_plot': 'Create static chart',
'long_test_task': 'Long Test Task',
'long_test_task2': 'Long Test Task 2',
+ 'update_empower': 'Correct Empower Inflacted Power Bug',
}
def get_job_status(jobid):
@@ -11812,6 +11814,109 @@ def rower_calcdps_view(request):
url = reverse(workouts_view)
return HttpResponseRedirect(url)
+@login_required()
+def rower_update_empower_view(
+ request,
+ startdate=timezone.now()-datetime.timedelta(days=365),
+ enddate=timezone.now()
+):
+ try:
+ r = getrower(request.user)
+ except Rower.DoesNotExist:
+ raise Http404("Rower doesn't exist")
+
+ if 'startdate' in request.session:
+ startdate = iso8601.parse_date(request.session['startdate'])
+
+
+ if 'enddate' in request.session:
+ enddate = iso8601.parse_date(request.session['enddate'])
+
+ if request.method == 'POST' and 'daterange' in request.POST:
+ dateform = DateRangeForm(request.POST)
+ if dateform.is_valid():
+ startdate = dateform.cleaned_data['startdate']
+ enddate = dateform.cleaned_data['enddate']
+ startdatestring = startdate.strftime('%Y-%m-%d')
+ enddatestring = enddate.strftime('%Y-%m-%d')
+ request.session['startdate'] = startdatestring
+ request.session['enddate'] = enddatestring
+ else:
+ dateform = DateRangeForm(initial={
+ 'startdate':startdate,
+ 'enddate':enddate,
+ })
+
+
+ if request.method == 'POST' and 'workouts' in request.POST:
+ form = WorkoutMultipleCompareForm(request.POST)
+ if form.is_valid():
+ cd = form.cleaned_data
+ workouts = cd['workouts']
+ workoutdicts = []
+
+ for w in workouts:
+ if 'x' in w.boattype and w.oarlength is not None and w.oarlength > 3.30:
+ message = "Oarlength and boat type mismatch for workout "+str(w.id)+". Skipping workout"
+ messages.error(request,message)
+ elif 'x' not in w.boattype and w.oarlength is not None and w.oarlength <= 3.30:
+ message = "Oarlength and boat type mismatch for workout "+str(w.id)+". Skipping workout"
+ messages.error(request,message)
+ elif w.oarlength is None:
+ message = "Incorrect oarlength in workout "+str(w.id)+". Skipping workout"
+ messages.error(request,message)
+ else:
+
+
+ workoutdict = {
+ 'id':w.id,
+ 'boattype':w.boattype,
+ 'filename':w.csvfilename,
+ 'inboard':w.inboard,
+ 'oarlength':w.oarlength
+ }
+
+ workoutdicts.append(workoutdict)
+
+ w.workoutsource = 'speedcoach2corrected'
+ w.save()
+
+
+ job = myqueue(queuelow,handle_update_empower,
+ request.user.email,workoutdicts,
+ emailbounced=r.emailbounced)
+
+ try:
+ request.session['async_tasks'] += [(job.id,'update_empower')]
+ except KeyError:
+ request.session['async_tasks'] = [(job.id,'update_empower')]
+
+ successmessage = 'Your workouts are being updated in the background. You will receive email when this is done. You can check the status of your calculations here'
+
+ messages.info(request,successmessage)
+
+ url = reverse(workouts_view)
+ return HttpResponseRedirect(url)
+ else:
+
+ workouts = Workout.objects.filter(
+ startdatetime__gte=startdate,
+ startdatetime__lte=enddate,
+ workoutsource='speedcoach2',
+ user=r,
+ ).order_by("-date","-starttime")
+
+ form = WorkoutMultipleCompareForm()
+ form.fields["workouts"].queryset = workouts
+ # GET request = prepare form
+
+ return render(request, 'empower_fix.html',
+ {'workouts':workouts,
+ 'dateform':dateform,
+ 'form':form,
+ 'rower':r
+ })
+
@login_required()
def team_leave_view(request,id=0):
r = getrower(request.user)
From dc480c9e8c68be32b0db9a8332c36a9a86b780c8 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Wed, 2 May 2018 18:45:58 +0200
Subject: [PATCH 5/6] explanation on page
---
rowers/templates/empower_fix.html | 21 ++++++++++++++++++++-
rowers/templates/list_workouts.html | 9 ++++++---
rowers/views.py | 6 +++++-
3 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/rowers/templates/empower_fix.html b/rowers/templates/empower_fix.html
index c817d02e..8e059f80 100644
--- a/rowers/templates/empower_fix.html
+++ b/rowers/templates/empower_fix.html
@@ -69,7 +69,26 @@
{% include "teambuttons.html" with teamid=team.id team=team %}
-
Empower Workouts
+
Empower Workouts
+
This functionality is aimed at users who have uploaded workouts from
+ the Nielsen-Kellerman Empower Oarlock/SpeedCoach combination before the
+ power inflation bug was known (May 4, 2018).
+
+
Workouts recorded with a SpeedCoach running NK Firmware version 2.17 or
+ lower have Power and Work per Stroke values that are approximately
+ 9% (sculling) or 5% too high. The exact value of the error depends on your
+ inboard and oar length.
+
+
Currently, we autocorrect workouts recorded with old Firmware upon
+ their upload, but workouts that were present on the site before
+ the bug was known still have incorrect values for Power and Work per Stroke.
+
+
+
+ You can use this page to correct those workouts.
+
+
+
diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html
index 93c148e1..a3e07cbb 100644
--- a/rowers/templates/list_workouts.html
+++ b/rowers/templates/list_workouts.html
@@ -266,21 +266,24 @@
{% if rankingonly and not team %}
-