From a70a1e98acb82a0f5b597d96bdc2b18724d68fbd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 16 Jun 2017 15:41:07 +0200 Subject: [PATCH] added notification to rowing physics --- rowers/dataprep.py | 125 ++++++-------------------------------------- rowers/datautils.py | 109 ++++++++++++++++++++++++++++++++++++++ rowers/models.py | 5 +- rowers/tasks.py | 11 +++- rowers/tests.py | 12 +++++ rowers/urls.py | 1 + rowers/utils.py | 1 + rowers/views.py | 27 +++++++++- 8 files changed, 177 insertions(+), 114 deletions(-) create mode 100644 rowers/datautils.py diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 8cf854f4..16babd4a 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -36,7 +36,7 @@ import pandas as pd import numpy as np import itertools import math -from tasks import handle_sendemail_unrecognized +from tasks import handle_sendemail_unrecognized,handle_sendemail_breakthrough from django.conf import settings from sqlalchemy import create_engine @@ -44,6 +44,7 @@ import sqlalchemy as sa import sys import utils +import datautils from utils import lbstoN from scipy.interpolate import griddata @@ -140,107 +141,6 @@ def filter_df(datadf,fieldname,value,largerthan=True): return datadf -def getsinglecp(df): - thesecs = df['TimeStamp (sec)'].max()-df['TimeStamp (sec)'].min() - if thesecs != 0: - maxt = 2*thesecs - else: - maxt = 1000. - - maxlog10 = np.log10(maxt) - logarr = np.arange(50)*maxlog10/50. - logarr = [int(10.**(la)) for la in logarr] - logarr = pd.Series(logarr) - logarr.drop_duplicates(keep='first',inplace=True) - - logarr = logarr.values - - - dfnew = pd.DataFrame({ - 'time':df['TimeStamp (sec)']-df.ix[0,'TimeStamp (sec)'], - 'power':df[' Power (watts)'] - }) - - dfnew['workoutid'] = 0 - - dfgrouped = dfnew.groupby(['workoutid']) - delta,cpvalue,avgpower = getcp(dfgrouped,logarr) - - return delta,cpvalue,avgpower - -def getcp(dfgrouped,logarr): - delta = [] - cpvalue = [] - avgpower = {} - #avgpower[0] = 0 - - for id,group in dfgrouped: - tt = group['time'].copy() - ww = group['power'].copy() - - tmax = tt.max() - newlen = int(tmax/5000.) - if newlen < len(tt): - newt = np.arange(newlen)*tmax/float(newlen) - ww = griddata(tt.values, - ww.values, - newt,method='linear', - rescale=True) - - tt = pd.Series(newt) - ww = pd.Series(ww) - - try: - avgpower[id] = int(ww.mean()) - except ValueError: - avgpower[id] = '---' - if not np.isnan(ww.mean()): - length = len(ww) - dt = [] - cpw = [] - for i in xrange(length-2): - deltat,wmax = getmaxwattinterval(tt,ww,i) - if not np.isnan(deltat) and not np.isnan(wmax): - dt.append(deltat) - cpw.append(wmax) - - - - dt = pd.Series(dt) - cpw = pd.Series(cpw) - if len(dt): - - cpvalues = griddata(dt.values, - cpw.values, - logarr,method='linear', - rescale=True) - - for cpv in cpvalues: - cpvalue.append(cpv) - for d in logarr: - delta.append(d) - - delta = pd.Series(delta,name='Delta') - cpvalue = pd.Series(cpvalue,name='CP') - return delta,cpvalue,avgpower - -def getmaxwattinterval(tt,ww,i): - w_roll = ww.rolling(i+2).mean().dropna() - if len(w_roll): - # now goes with # data points - should be fixed seconds - indexmax = w_roll.idxmax(axis=1) - try: - t_0 = tt.ix[indexmax] - t_1 = tt.ix[indexmax-i] - deltat = 1.0e-3*(t_0-t_1) - wmax = w_roll.ix[indexmax] - except KeyError: - pass - else: - wmax = 0 - deltat = 0 - - return deltat,wmax def df_resample(datadf): # time stamps must be in seconds @@ -527,7 +427,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', isbreakthrough = False if workouttype == 'water': - delta,cpvalues,avgpower = getsinglecp(row.df) + delta,cpvalues,avgpower = datautils.getsinglecp(row.df) if utils.isbreakthrough(delta,cpvalues,r.p0,r.p1,r.p2,r.p3): isbreakthrough = True @@ -689,16 +589,21 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', # submit email task to send email about breakthrough workout if isbreakthrough: a_messages.info(r.user,'It looks like you have a new breakthrough workout') - if settings.DEBUG: - res = handle_sendemail_breakthrough(w.id,r.user.email, + if settings.DEBUG and r.getemailnotifications: + res = handle_sendemail_breakthrough.delay(w.id,r.user.email, r.user.first_name, r.user.last_name) + elif r.getemailnotifications: + try: + res = queuehigh.enqueue( + handle_sendemail_breakthrough(w.id, + r.user.email, + r.user.first_name, + r.user.last_name)) + except AttributeError: + pass else: - res = queuehigh.enqueue( - handle_sendemail_breakthrough(w.id, - r.user.email, - r.user.first_name, - r.user.last_name)) + pass if privacy == 'visible': ts = Team.objects.filter(rower=r) diff --git a/rowers/datautils.py b/rowers/datautils.py new file mode 100644 index 00000000..9118dc2c --- /dev/null +++ b/rowers/datautils.py @@ -0,0 +1,109 @@ +import pandas as pd +import numpy as np +from scipy.interpolate import griddata + +def getsinglecp(df): + thesecs = df['TimeStamp (sec)'].max()-df['TimeStamp (sec)'].min() + if thesecs != 0: + maxt = 2*thesecs + else: + maxt = 1000. + + maxlog10 = np.log10(maxt) + logarr = np.arange(50)*maxlog10/50. + logarr = [int(10.**(la)) for la in logarr] + logarr = pd.Series(logarr) + logarr.drop_duplicates(keep='first',inplace=True) + + logarr = logarr.values + + + dfnew = pd.DataFrame({ + 'time':1000*(df['TimeStamp (sec)']-df.ix[0,'TimeStamp (sec)']), + 'power':df[' Power (watts)'] + }) + + dfnew['workoutid'] = 0 + + dfgrouped = dfnew.groupby(['workoutid']) + delta,cpvalue,avgpower = getcp(dfgrouped,logarr) + + return delta,cpvalue,avgpower + +def getcp(dfgrouped,logarr): + delta = [] + cpvalue = [] + avgpower = {} + #avgpower[0] = 0 + + for id,group in dfgrouped: + tt = group['time'].copy() + ww = group['power'].copy() + + tmax = tt.max() + if tmax > 500000: + newlen = int(tmax/5000.) + else: + newlen = len(tt) + if newlen < len(tt): + newt = np.arange(newlen)*tmax/float(newlen) + ww = griddata(tt.values, + ww.values, + newt,method='linear', + rescale=True) + + tt = pd.Series(newt) + ww = pd.Series(ww) + + + try: + avgpower[id] = int(ww.mean()) + except ValueError: + avgpower[id] = '---' + if not np.isnan(ww.mean()): + length = len(ww) + dt = [] + cpw = [] + for i in xrange(length-2): + deltat,wmax = getmaxwattinterval(tt,ww,i) + if not np.isnan(deltat) and not np.isnan(wmax): + dt.append(deltat) + cpw.append(wmax) + + + + dt = pd.Series(dt) + cpw = pd.Series(cpw) + if len(dt): + + cpvalues = griddata(dt.values, + cpw.values, + logarr,method='linear', + rescale=True) + + for cpv in cpvalues: + cpvalue.append(cpv) + for d in logarr: + delta.append(d) + + delta = pd.Series(delta,name='Delta') + cpvalue = pd.Series(cpvalue,name='CP') + return delta,cpvalue,avgpower + +def getmaxwattinterval(tt,ww,i): + w_roll = ww.rolling(i+2).mean().dropna() + if len(w_roll): + # now goes with # data points - should be fixed seconds + indexmax = w_roll.idxmax(axis=1) + try: + t_0 = tt.ix[indexmax] + t_1 = tt.ix[indexmax-i] + deltat = 1.0e-3*(t_0-t_1) + wmax = w_roll.ix[indexmax] + except KeyError: + pass + else: + wmax = 0 + deltat = 0 + + return deltat,wmax diff --git a/rowers/models.py b/rowers/models.py index bdde5013..2eb2ec3c 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -232,6 +232,9 @@ class Rower(models.Model): ('hidden','Hidden'), ) + getemailnotifications = models.BooleanField(default=True, + verbose_name='Receive email notifications') + rowerplan = models.CharField(default='basic',max_length=30, choices=plans) @@ -746,7 +749,7 @@ class RowerPowerZonesForm(ModelForm): class AccountRowerForm(ModelForm): class Meta: model = Rower - fields = ['weightcategory'] + fields = ['weightcategory','getemailnotifications'] class UserForm(ModelForm): class Meta: diff --git a/rowers/tasks.py b/rowers/tasks.py index 56e20c35..8663b845 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -69,6 +69,8 @@ def handle_sendemail_breakthrough(workoutid,useremail,userfirstname,userlastname message += str(workoutid) message += "/updatecp\n\n" + message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n" + message += "Best Regards, the Rowsandall Team" email = EmailMessage(subject, message, @@ -79,7 +81,6 @@ def handle_sendemail_breakthrough(workoutid,useremail,userfirstname,userlastname res = email.send() # remove tcx file - os.remove(unrecognizedfile) return 1 @@ -177,7 +178,7 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile): # Calculate wind and stream corrections for OTW rowing @app.task def handle_otwsetpower(f1,boattype,weightvalue, - first_name,last_name,email,workoutid, + first_name,last_name,email,workoutid,ps=[1,1,1,1], debug=False): try: rowdata = rdata(f1) @@ -219,6 +220,12 @@ def handle_otwsetpower(f1,boattype,weightvalue, rowdata.write_csv(f1,gzip=True) update_strokedata(workoutid,rowdata.df,debug=debug) + delta,cpvalues,avgpower = datautils.getsinglecp(rowdata.df) + if utils.isbreakthrough(delta,cpvalues,ps[0],ps[1],ps[2],ps[3]): + handle_sendemail_breakthrough(workoutid,email, + first_name, + last_name) + # send email fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "Your Rowsandall OTW calculations are ready" diff --git a/rowers/tests.py b/rowers/tests.py index 8ddbfaed..415154ef 100644 --- a/rowers/tests.py +++ b/rowers/tests.py @@ -68,6 +68,9 @@ class C2Objects(DjangoTestCase): u = User.objects.create_user('john', 'sander@ds.ds', 'koeinsloot') + u.first_name = 'John' + u.last_name = 'Sander' + u.save() r = Rower.objects.create(user=u) res = add_workout_from_strokedata(u,1,data,strokedata,source='c2') @@ -88,6 +91,9 @@ class C2Objects(DjangoTestCase): u = User.objects.create_user('john', 'sander@ds.ds', 'koeinsloot') + u.first_name = 'John' + u.last_name = 'Sander' + u.save() r = Rower.objects.create(user=u) res = add_workout_from_strokedata(u,1,data,strokedata,source='c2') @@ -162,6 +168,9 @@ class StravaObjects(DjangoTestCase): u = User.objects.create_user('john', 'sander@ds.ds', 'koeinsloot') + u.first_name = 'John' + u.last_name = 'Sander' + u.save() r = Rower.objects.create(user=u) res = add_workout_from_strokedata(u,1,workoutsummary,strokedata, @@ -235,6 +244,9 @@ class StravaObjects(DjangoTestCase): u = User.objects.create_user('john', 'sander@ds.ds', 'koeinsloot') + u.first_name = 'John' + u.last_name = 'Sander' + u.save() r = Rower.objects.create(user=u) res = add_workout_from_strokedata(u,1,workoutsummary,strokedata, diff --git a/rowers/urls.py b/rowers/urls.py index e850a4c6..844f9ae0 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -183,6 +183,7 @@ urlpatterns = [ url(r'^workout/compare/(?P\d+)/(?P\d+-\d+-\d+)/(?P\w+.*)$',views.workout_comparison_list), url(r'^workout/(?P\d+)/edit$',views.workout_edit_view), url(r'^workout/(?P\d+)/setprivate$',views.workout_setprivate_view), + url(r'^workout/(?P\d+)/updatecp$',views.workout_update_cp_view), url(r'^workout/(?P\d+)/makepublic$',views.workout_makepublic_view), url(r'^workout/(?P\d+)/geeky$',views.workout_geeky_view), url(r'^workout/(?P\d+)/advanced$',views.workout_advanced_view), diff --git a/rowers/utils.py b/rowers/utils.py index 7cc6c401..22c4680f 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -77,6 +77,7 @@ def geo_distance(lat1,lon1,lat2,lon2): def isbreakthrough(delta,cpvalues,p0,p1,p2,p3): + pwr = p0/(1+delta/p2) pwr += p1/(1+delta/p3) diff --git a/rowers/views.py b/rowers/views.py index 26e67f55..3ca3cffb 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -274,6 +274,8 @@ from utils import ( str2bool ) +import datautils + from rowers.models import checkworkoutuser # Check if a user is a Coach member @@ -2778,6 +2780,27 @@ def rankings_view(request,theuser=0, 'teams':get_my_teams(request.user), }) +@user_passes_test(ispromember,login_url="/",redirect_field_name=None) +def workout_update_cp_view(request,id=0): + try: + row = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + if (checkworkoutuser(request.user,row)==False): + message = "You are not allowed to edit this workout" + messages.error(request,message) + url = reverse(workouts_view) + + return HttpResponseRedirect(url) + + row.rankingpiece = True + row.save() + + url = reverse(otwrankings_view) + + return HttpResponseRedirect(url) + # Show ranking distances including predicted paces @user_passes_test(ispromember,login_url="/",redirect_field_name=None) def otwrankings_view(request,theuser=0, @@ -2919,7 +2942,7 @@ def otwrankings_view(request,theuser=0, dfgrouped = df.groupby(['workoutid']) - delta,cpvalue,avgpower = dataprep.getcp(dfgrouped,logarr) + delta,cpvalue,avgpower = datautils.getcp(dfgrouped,logarr) powerdf = pd.DataFrame({ @@ -7779,6 +7802,7 @@ def rower_edit_view(request,message=""): last_name = ucd['last_name'] email = ucd['email'] weightcategory = cd['weightcategory'] + getemailnotifications = cd['getemailnotifications'] u = request.user if len(first_name): u.first_name = first_name @@ -7788,6 +7812,7 @@ def rower_edit_view(request,message=""): u.save() r = getrower(u) r.weightcategory = weightcategory + r.getemailnotifications = getemailnotifications r.save() form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r)