Private
Public Access
1
0

Merge branch 'feature/empowerbug' into develop

This commit is contained in:
Sander Roosendaal
2018-05-03 08:25:23 +02:00
11 changed files with 432 additions and 31 deletions

View File

@@ -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
@@ -1094,11 +1096,12 @@ def parsenonpainsled(fileformat,f2,summary):
if (fileformat == 'rp'):
row = RowProParser(f2)
hasrecognized = True
# handle TCX
# handle TCX
if (fileformat == 'tcx'):
row = TCXParser(f2)
hasrecognized = True
# handle Mystery
if (fileformat == 'mystery'):
row = MysteryParser(f2)
@@ -1153,11 +1156,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 +1183,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,17 +1191,17 @@ 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
# Handle c2log
if (fileformat == 'c2log' or fileformat == 'rowprolog'):
return (0,0,0,0)
return (0,0,0,0,0)
if not hasrecognized:
return (0,0,0,0)
return (0,0,0,0,0)
f_to_be_deleted = f2
# should delete file
@@ -1203,7 +1210,7 @@ def handle_nonpainsled(f2, fileformat, summary=''):
row2 = rrdata(df = row.df)
row2.write_csv(f2, gzip=True)
except:
return (0,0,0,0)
return (0,0,0,0,0)
# os.remove(f2)
try:
@@ -1211,7 +1218,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,18 +1304,23 @@ 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, '')
except:
except Exception as e:
errorstring = str(sys.exc_info()[0])
message = 'Something went wrong: ' + errorstring
print e.message
message = 'Something went wrong: ' + e.message
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,

View File

@@ -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)

View File

@@ -121,7 +121,7 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0,testing=False):
# handle non-Painsled
if fileformat != 'csv':
filename_mediadir, summary, oarlength, inboard = dataprep.handle_nonpainsled(
filename_mediadir, summary, oarlength, inboard,fileformat = dataprep.handle_nonpainsled(
'media/' + datafilename, fileformat, summary)
if not filename_mediadir:
return 0
@@ -148,7 +148,7 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0,testing=False):
pass
row.write_csv(datafilename, gzip=True)
dosummary = (fileformat != 'fit')
dosummary = (fileformat != 'fit' and 'speedcoach2' not in fileformat)
if name == '':
name = 'Workout from Background Queue'

View File

@@ -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',

View File

@@ -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 <info@rowsandall.com>',
[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):

View File

@@ -0,0 +1,144 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Workouts{% endblock %}
{% block content %}
<script>
function toggle(source) {
checkboxes = document.querySelectorAll("input[name='workouts']");
for(var i=0, n=checkboxes.length;i<n;i++) {
checkboxes[i].checked = source.checked;
}
}
</script>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(function() {
// Get the form fields and hidden div
var modality = $("#id_modality");
var hidden = $("#id_waterboattype");
// Hide the fields.
// Use JS to do this in case the user doesn't have JS
// enabled.
hidden.hide();
// Setup an event listener for when the state of the
// checkbox changes.
modality.change(function() {
// Check to see if the checkbox is checked.
// If it is, show the fields and populate the input.
// If not, hide the fields.
var Value = modality.val();
if (Value=='water') {
// Show the hidden fields.
hidden.show();
} else {
// Make sure that the hidden fields are indeed
// hidden.
hidden.hide();
// You may also want to clear the value of the
// hidden fields here. Just in case somebody
// shows the fields, enters data to them and then
// unticks the checkbox.
//
// This would do the job:
//
// $("#hidden_field").val("");
}
});
});
</script>
<div class="grid_12 alpha">
{% include "teambuttons.html" with teamid=team.id team=team %}
</div>
<div class="grid_12 alpha">
<h2>Empower Workouts</h2>
<p>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). </p>
<p>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.</p>
<p>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.
</p>
<p>
You can use this page to correct those workouts.
</p>
</div>
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<form enctype="multipart/form-data" action="" method="post">
<div class="grid_4 alpha">
<table>
{{ dateform.as_table }}
</table>
{% csrf_token %}
</div>
<div class="grid_2 omega">
<input name='daterange' class="button green" type="submit" value="Submit">
</div>
</form>
</div>
</div>
<form enctype="multipart/form-data" action="" method="post">
<div id="workouts_table" class="grid_8 alpha">
{% if workouts %}
<input type="checkbox" onClick="toggle(this)" /> Toggle All<br/>
<table width="100%" class="listtable">
{{ form.as_table }}
</table>
{% else %}
<p> No workouts found </p>
{% endif %}
</div>
<div id="form_settings" class="grid_4 alpha">
<p>Select workouts on the left,
and press submit</p>
<div class="grid_1 prefix_2 suffix_1">
<p>
{% csrf_token %}
<input name='workoutselectform' class="button green" type="submit" value="Submit">
</p>
</div>
<div class="grid_4">
<p>You can use the date form above to reduce the selection</p>
</div>
</div>
</form>
{% endblock %}

View File

@@ -266,21 +266,24 @@
<div class="grid_6 alpha">
{% if rankingonly and not team %}
<div class="grid_2 prefix_1 alpha">
<div class="grid_2 alpha">
<a class="button small green" href="/rowers/list-workouts">All Workouts</a>
</div>
{% elif not team %}
<div class="grid_2 prefix_1 alpha">
<div class="grid_2 alpha">
<a class="button small green" href="/rowers/list-workouts/ranking">Ranking Pieces Only</a>
</div>
{% endif %}
<div class="grid_2 suffix_1 omega">
<div class="grid_2">
{% if user|is_promember %}
<a class="button small gray" href="/rowers/workouts-join-select">Glue Workouts</a>
{% else %}
<a class="button blue small" href="/rowers/promembership">Glue</a>
{% endif %}
</div>
<div class="grid_2 omega">
<a class="button small gray" href="/rowers/update_empower">Empower Repair</a>
</div>
<p>&nbsp;</p>
{% if team %}
<form id="searchform" action="/rowers/list-workouts/team/{{ team.id }}/{{ startdate|date:"Y-m-d" }}/{{ enddate|date:"Y-m-d" }}"

View File

@@ -37,7 +37,7 @@
<div class="grid_12 alpha">
<h1>Multi Flex Chart</h1>
<h1>Trend Flex Chart</h1>
<div id="workouts" class="grid_8 alpha">
<div id="id_chart" class="grid_8 alpha flexplot">
{{ the_div|safe }}

View File

@@ -846,7 +846,7 @@ class ViewTest(TestCase):
f_to_be_deleted = w.csvfilename
os.remove(f_to_be_deleted+'.gz')
def test_upload_view_TCX_SpeedCoach2(self):
def test_upload_view_TCX_SpeedCoach2a(self):
self.c.login(username='john',password='koeinsloot')
filename = 'rowers/testdata/Speedcoach2example.csv'
@@ -876,7 +876,7 @@ class ViewTest(TestCase):
f_to_be_deleted = w.csvfilename
os.remove(f_to_be_deleted+'.gz')
def test_upload_view_TCX_SpeedCoach2(self):
def test_upload_view_TCX_SpeedCoach2b(self):
self.c.login(username='john',password='koeinsloot')
filename = 'rowers/testdata/Speedcoach2example.csv'
@@ -908,7 +908,7 @@ class ViewTest(TestCase):
def test_upload_view_TCX_SpeedCoach2(self):
def test_upload_view_TCX_SpeedCoach2c(self):
self.c.login(username='john',password='koeinsloot')
filename = 'rowers/testdata/speedcoach3test3.csv'

View File

@@ -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<age>\d+)$',views.agegroupcpview),
url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)$',views.agegroupcpview),
url(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)$',

View File

@@ -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 Inflated Power Bug',
}
def get_job_status(jobid):
@@ -11812,6 +11814,113 @@ 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 w.user != r:
message = "You can only alter your own workouts"
messages.error(request,message)
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,
debug=False,
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 <a href="/rowers/jobs-status" target="_blank">here</a>'
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)