From a72e1525f787a5750ec770975d984589ab1abeed Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Dec 2017 10:35:56 +0100 Subject: [PATCH 01/10] added alternative updatecp but it is slower ... --- rowers/datautils.py | 101 +++++++++++++++++++++++++++++++++++++++++++- rowers/views.py | 14 ++++-- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/rowers/datautils.py b/rowers/datautils.py index 5d853157..5aba0141 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -107,7 +107,104 @@ def getsinglecp(df): return delta,cpvalue,avgpower +def getcp_new(dfgrouped,logarr): + delta = [] + cpvalue = [] + avgpower = {} + + for id, group in dfgrouped: + tt = group['time'].copy() + + ww = group['power'].copy() + + try: + avgpower[id] = int(ww.mean()) + except ValueError: + avgpower[id] = '---' + + tmax = tt.max() + + if tmax > 500000: + newlen = int(tmax/2000.) + newt = np.arange(newlen)*tmax/float(newlen) + deltat = newt[1]-newt[0] + else: + newt = np.arange(0,tmax,10.) + deltat = 10. + + ww = griddata(tt.values, + ww.values, + newt,method='linear', + rescale=True) + + tt = pd.Series(newt) + ww = pd.Series(ww) + + G = pd.Series(ww.cumsum()) + G = pd.concat([pd.Series([0]),G]) + + h = np.mgrid[0:len(tt)+1:1,0:len(tt)+1:1] + + distances = pd.DataFrame(h[1]-h[0]) + + ones = 1+np.zeros(len(G)) + + Ghor = np.outer(ones,G) + Gver = np.outer(G,ones) + + Gdif = Ghor - Gver + + Gdif = np.tril(Gdif.T).T + + Gdif = pd.DataFrame(Gdif) + + F = Gdif/distances + + F.fillna(inplace=True,method='ffill',axis=1) + F.fillna(inplace=True,value=0) + + restime = [] + power = [] + + for i in np.arange(0,len(tt)+1,1): + restime.append(deltat*i) + cp = np.diag(F,i).max() + power.append(cp) + + power[0] = power[1] + + restime = np.array(restime) + power = np.array(power) + + + #power[0] = power[1] + + cpvalues = griddata(restime,power, + logarr,method='linear', fill_value=0) + + + + for cpv in cpvalues: + cpvalue.append(cpv) + for d in logarr: + delta.append(d) + + df = pd.DataFrame({ + 'delta':delta, + 'cpvalue':cpvalue + }) + + df.dropna(axis=0, how='any',inplace=True) + df = df.sort_values(['delta','cp'], ascending=[1, 0]) + df = df.drop_duplicates(subset='Delta', keep='first') + + delta = df['delta'] + cpvalue = df['cpvalue'] + + return delta,cpvalue,avgpower + + def getcp(dfgrouped,logarr): delta = [] cpvalue = [] @@ -165,7 +262,9 @@ def getcp(dfgrouped,logarr): cpvalue.append(cpv) for d in logarr: delta.append(d) - + + + delta = pd.Series(delta,name='Delta') cpvalue = pd.Series(cpvalue,name='CP') diff --git a/rowers/views.py b/rowers/views.py index a6cda5e9..1b98e8a9 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -4013,8 +4013,11 @@ def otwrankings_view(request,theuser=0, remove_asynctask(request,task['id']) elif 'fail' in task['status'].lower(): runningjob = 0 - remove_asynctask(request,task[id]) - messages.error(request,'Oh, your task failed') + try: + remove_asynctask(request,task[id]) + messages.error(request,'Oh, your task failed') + except KeyError: + pass elif 'started' in task['status'].lower(): messages.info(request,'Busy updating CP chart data') runningjob = 1 @@ -4273,8 +4276,11 @@ def oterankings_view(request,theuser=0, remove_asynctask(request,task['id']) elif 'fail' in task['status'].lower(): runningjob = 0 - remove_asynctask(request,task[id]) - messages.error(request,'Oh, your task failed') + try: + remove_asynctask(request,task[id]) + messages.error(request,'Oh, your task failed') + except KeyError: + pass elif 'started' in task['status'].lower(): messages.info(request,'Busy updating CP chart data') runningjob = 1 From 22eb1c0f9a3733f06fe7355d9513253cbbe1432d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 9 Jan 2018 08:48:10 +0100 Subject: [PATCH 02/10] added filtering on OTW efficiency < 200 --- rowers/dataprep.py | 6 ++++++ rowers/metrics.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 6fa013b4..393c5fc1 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -295,6 +295,12 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, except KeyError: pass + try: + mask = datadf['efficiency'] > 200. + datadf.loc[mask, 'efficiency'] = np.nan + except KeyError: + pass + try: mask = datadf['spm'] < 10 datadf.loc[mask, 'spm'] = np.nan diff --git a/rowers/metrics.py b/rowers/metrics.py index cc9f8644..bfac6b1f 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -65,7 +65,7 @@ rowingmetrics = ( 'null':True, 'verbose_name': 'Average Drive Force (N)', 'ax_min': 0, - 'ax_max': 900, + 'ax_max': 1200, 'mode':'both', 'type': 'pro'}), @@ -74,7 +74,7 @@ rowingmetrics = ( 'null':True, 'verbose_name': 'Peak Drive Force (N)', 'ax_min': 0, - 'ax_max': 900, + 'ax_max': 1500, 'mode':'both', 'type': 'pro'}), From da4f2e32dcb7217653e1ec59cf3e9eeed42e4c5b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Jan 2018 15:29:25 +0100 Subject: [PATCH 03/10] fixed unicodedecode error in workout split --- rowers/templates/list_workouts.html | 3 +-- rowers/views.py | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index d82399c7..4a1963a9 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -133,8 +133,7 @@ {% else %} - No Name + No Name {% endif %} {% else %} diff --git a/rowers/views.py b/rowers/views.py index b354215b..5d13e31e 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -10176,7 +10176,13 @@ def workout_split_view(request,id=id): else: url = reverse(workouts_view) - qdict = {'q':row.name} + rowname = row.name + if isinstance(rowname,unicode): + rowname = rowname.encode('utf8') + elif isinstance(rowname, str): + rowname = rowname.decode('utf8') + + qdict = {'q':rowname} url+='?'+urllib.urlencode(qdict) return HttpResponseRedirect(url) From ed8c5328849155204cc2f425b56696d230bf4bfa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Jan 2018 15:56:53 +0100 Subject: [PATCH 04/10] added PowerTimeFitnessMetric model --- rowers/models.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rowers/models.py b/rowers/models.py index 0a9a7eda..db2fb086 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -241,6 +241,20 @@ class CalcAgePerformance(models.Model): class Meta: db_table = 'calcagegrouprecords' + +class PowerTimeFitnessMetric(models.Model): + modechoices = ( + ('rower','Rower'), + ('water','On the water') + ) + + data = models.DateField(default=timezone.now) + user = models.ForeignKey(User) + PowerFourMin = models.FloatField(default=0) + PowerTwoK = models.FloatField(default=0) + PowerOneHour = models.FloatField(default=0) + workoutmode = models.CharField(default='rower',choices=modechoices, + max_length=40) class C2WorldClassAgePerformance(models.Model): weightcategories = ( From dd48d66c3aa73dc337ce42ee76cab43df2a92931 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Jan 2018 17:04:30 +0100 Subject: [PATCH 05/10] first attempt --- rowers/datautils.py | 3 +- rowers/models.py | 4 ++- rowers/urls.py | 2 ++ rowers/views.py | 80 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/rowers/datautils.py b/rowers/datautils.py index 5aba0141..1358e0d6 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -300,7 +300,8 @@ def getmaxwattinterval(tt,ww,i): wmax = 0 deltat = 0 except KeyError: - pass + wmax = 0 + deltat = 0 else: wmax = 0 deltat = 0 diff --git a/rowers/models.py b/rowers/models.py index db2fb086..f96ff624 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -248,13 +248,15 @@ class PowerTimeFitnessMetric(models.Model): ('water','On the water') ) - data = models.DateField(default=timezone.now) + date = models.DateField(default=timezone.now) user = models.ForeignKey(User) PowerFourMin = models.FloatField(default=0) PowerTwoK = models.FloatField(default=0) PowerOneHour = models.FloatField(default=0) workoutmode = models.CharField(default='rower',choices=modechoices, max_length=40) + class Meta: + db_table = 'powertimefitnessmetric' class C2WorldClassAgePerformance(models.Model): weightcategories = ( diff --git a/rowers/urls.py b/rowers/urls.py index 7d345028..b43cc09d 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -125,6 +125,8 @@ urlpatterns = [ url(r'^agegroupcp/(?P\d+)/(?P\d+)$',views.agegroupcpview), url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$', views.ajax_agegrouprecords), + url(r'^updatefitness/(?P\w+.*)/(?P\d+)$',views.fitness_metric_view), + url(r'^updatefitness$',views.fitness_metric_view), url(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)m$', views.agegrouprecordview), url(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)min$', diff --git a/rowers/views.py b/rowers/views.py index 09571a41..61aa9644 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -238,6 +238,7 @@ from io import BytesIO from scipy.special import lambertw from dataprep import timedeltaconv +from dataprep import getsmallrowdata_db from scipy.interpolate import griddata @@ -3206,6 +3207,85 @@ def addmanual_view(request): {'form':form, }) +@login_required() +def fitness_metric_view(request,mode='rower',days=42): + r = getrower(request.user) + startdate = timezone.now()-datetime.timedelta(days=days) + if mode == 'rower': + workouts = Workout.objects.filter( + user=r, + workouttype__in=['rower','dynamic','slides'], + startdatetime__gte=startdate) + else: + workouts = Workout.objects.filter( + user=r, + workouttype__in=['water','coastal'], + startdatetime__gte=startdate) + + theids = [int(w.id) for w in workouts] + columns = ['power','workoutid','time'] + df = getsmallrowdata_db(columns,ids=theids) + df.dropna(inplace=True,axis=0) + + if df.empty: + # change this + return 'aap' + + # df is not empty. We continue + dfgrouped = df.groupby(['workoutid']) + maxt = 1.05*df['time'].max()/1000. + + logarr = datautils.getlogarr(maxt) + + delta,cpvalue,avgpower = datautils.getcp(dfgrouped,logarr) + + powerdf = pd.DataFrame({ + 'Delta':delta, + 'CP':cpvalue, + }) + + powerdf = powerdf[powerdf['CP']>0] + powerdf.dropna(axis=0,inplace=True) + powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True) + powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True) + + p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) + # This is code duplication from datautils -- correct asap + fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) + + powerfourmin = fitfunc(p1,240.) + powerhour = fitfunc(p1,3600.) + + # 2k power + velofourmin = (powerfourmin/2.8)**(1./3.) + dfourmin = 240.*velofourmin + dratio = 2000./dfourmin + pacefourmin = 500./velofourmin + + # assume 5 sec per doubling drop + pace2k = pacefourmin + 5.*np.log10(dratio)/np.log10(2.) + velo2k = 500./pace2k + t2k = 2000./velo2k + pwr2k = fitfunc(p1,t2k) + velo2 = (pwr2k/2.8)**(1./3.) + if np.isnan(velo2) or velo2 <= 0: + velo2 = 1.0 + + t2 = 2000./velo2 + + pwr2k = fitfunc(p1,t2) + + velo3 = (pwr2k/2.8)**(1./3.) + + t3 = 2000./velo3 + + power2k = fitfunc(p1,t3) + + print powerfourmin,power2k,powerhour + + return HttpResponse("success") + + # Show ranking distances including predicted paces @login_required() def rankings_view(request,theuser=0, From fd88b8f375512fe79dbd186f2e6b606ed7fe7a71 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 16 Jan 2018 17:42:47 +0100 Subject: [PATCH 06/10] added saving to db --- rowers/views.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 61aa9644..0029b7fd 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -56,7 +56,7 @@ from rowers.models import ( RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData, Team,TeamForm,TeamInviteForm,TeamInvite,TeamRequest, WorkoutComment,WorkoutCommentForm,RowerExportForm, - CalcAgePerformance + CalcAgePerformance,PowerTimeFitnessMetric, ) from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement from rowers.metrics import rowingmetrics,defaultfavoritecharts @@ -3281,8 +3281,21 @@ def fitness_metric_view(request,mode='rower',days=42): power2k = fitfunc(p1,t3) + + print powerfourmin,power2k,powerhour + m = PowerTimeFitnessMetric( + user=request.user, + PowerFourMin = powerfourmin, + PowerTwoK = power2k, + PowerOneHour = powerhour, + workoutmode = mode, + date = timezone.now() + ) + + m.save() + return HttpResponse("success") From 40813c4a0ea39e48e5925eefee71ca9c4c0db99e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Jan 2018 16:54:28 +0100 Subject: [PATCH 07/10] db insertion through sql now working --- rowers/dataprep.py | 17 +++++++++++++++++ rowers/urls.py | 1 + rowers/views.py | 12 ++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 8e907486..10f76da1 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -515,6 +515,23 @@ def paceformatsecs(values): return out +def fitnessmetric_to_sql(m,table='powertimefitnessmetric'): + engine = create_engine(database_url, echo=False) + columns = ', '.join(m.keys()) + placeholders = ", ".join(["?"] * len(m)) + + query = "INSERT into %s ( %s ) Values (%s)" % (table, columns, placeholders) + + values = tuple(m[key] for key in m.keys()) + with engine.connect() as conn, conn.begin(): + result = conn.execute(query,values) + + conn.close() + engine.dispose() + + return result + + def getcpdata_sql(rower_id,table='cpdata'): engine = create_engine(database_url, echo=False) query = sa.text('SELECT * from {table} WHERE user={rower_id};'.format( diff --git a/rowers/urls.py b/rowers/urls.py index b43cc09d..0b93f9b2 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -126,6 +126,7 @@ urlpatterns = [ url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$', views.ajax_agegrouprecords), url(r'^updatefitness/(?P\w+.*)/(?P\d+)$',views.fitness_metric_view), + url(r'^updatefitness/(?P\w+.*)$',views.fitness_metric_view), url(r'^updatefitness$',views.fitness_metric_view), url(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)m$', views.agegrouprecordview), diff --git a/rowers/views.py b/rowers/views.py index 0029b7fd..f31575a5 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -3282,9 +3282,17 @@ def fitness_metric_view(request,mode='rower',days=42): power2k = fitfunc(p1,t3) - - print powerfourmin,power2k,powerhour + mdict = { + 'user_id': request.user.id, + 'PowerFourMin': powerfourmin, + 'PowerTwoK': power2k, + 'PowerOneHour': powerhour, + 'workoutmode': mode, + 'date': timezone.now().strftime('%Y-%m-%d'), + } + result = dataprep.fitnessmetric_to_sql(mdict) + m = PowerTimeFitnessMetric( user=request.user, PowerFourMin = powerfourmin, From 30a325d0f5908a69a5d3bc4f394c2ad6e2fdbf59 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Jan 2018 18:27:59 +0100 Subject: [PATCH 08/10] updatefitnessmetric as middleware --- rowers/dataprep.py | 4 +- rowers/dataprepnodjango.py | 20 +++++++ rowers/middleware.py | 61 +++++++++++++++++++ rowers/models.py | 1 + rowers/tasks.py | 79 ++++++++++++++++++++++++- rowers/utils.py | 1 + rowers/views.py | 117 ++++++++++++------------------------- rowsandall_app/settings.py | 1 + 8 files changed, 202 insertions(+), 82 deletions(-) create mode 100644 rowers/middleware.py diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 10f76da1..3d177657 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -515,7 +515,7 @@ def paceformatsecs(values): return out -def fitnessmetric_to_sql(m,table='powertimefitnessmetric'): +def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False): engine = create_engine(database_url, echo=False) columns = ', '.join(m.keys()) placeholders = ", ".join(["?"] * len(m)) @@ -529,7 +529,7 @@ def fitnessmetric_to_sql(m,table='powertimefitnessmetric'): conn.close() engine.dispose() - return result + return 1 def getcpdata_sql(rower_id,table='cpdata'): diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index 3ce3e6fb..56da1378 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -495,6 +495,26 @@ def getsmallrowdata_db(columns,ids=[],debug=False): return data +def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False): + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + columns = ', '.join(m.keys()) + placeholders = ", ".join(["?"] * len(m)) + + query = "INSERT into %s ( %s ) Values (%s)" % (table, columns, placeholders) + + values = tuple(m[key] for key in m.keys()) + with engine.connect() as conn, conn.begin(): + result = conn.execute(query,values) + + conn.close() + engine.dispose() + + return 1 + diff --git a/rowers/middleware.py b/rowers/middleware.py new file mode 100644 index 00000000..e3284aed --- /dev/null +++ b/rowers/middleware.py @@ -0,0 +1,61 @@ +from django.utils import timezone +from rowers.models import Workout, PowerTimeFitnessMetric, Rower +import datetime + +def getrower(user): + try: + r = Rower.objects.get(user=user) + except Rower.DoesNotExist: + r = Rower(user=user) + r.save() + + return r + +def do_update(user,mode='rower',days=42): + r = getrower(user) + + startdate = timezone.now()-datetime.timedelta(days=days) + + # test if not something already done + now_date = timezone.now().strftime('%Y-%m-%d') + ms = PowerTimeFitnessMetric.objects.filter( + user=user, + workoutmode=mode) + + if len(ms) == 0: + return 0 + + max_workout_id = max([m.last_workout for m in ms]) + last_update_date = max([m.date.strftime('%Y-%m-%d') for m in ms]) + + if mode == 'rower': + workouts = Workout.objects.filter( + user=r, + workouttype__in=['rower','dynamic','slides'], + startdatetime__gte=startdate) + else: + workouts = Workout.objects.filter( + user=r, + workouttype__in=['water','coastal'], + startdatetime__gte=startdate) + + theids = [int(w.id) for w in workouts] + max_id = max(theids) + + if last_update_date < now_date and max_workout_id < max_id: + job = myqueue(queue, + handle_updatefitnessmetric, + request.user.id,mode,theids, + ) + + return 1 + +class PowerTimeFitnessMetricMiddleWare(object): + def process_request(self, request): + # Code to be executed before the view is called + + result = do_update(request.user,mode='rower') + result = do_update(request.user,mode='water') + + + diff --git a/rowers/models.py b/rowers/models.py index f96ff624..e37e35e9 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -249,6 +249,7 @@ class PowerTimeFitnessMetric(models.Model): ) date = models.DateField(default=timezone.now) + last_workout = models.IntegerField(default=0) user = models.ForeignKey(User) PowerFourMin = models.FloatField(default=0) PowerTwoK = models.FloatField(default=0) diff --git a/rowers/tasks.py b/rowers/tasks.py index 6cddaa6e..9a5d90a3 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -25,13 +25,14 @@ from rowsandall_app.settings import PROGRESS_CACHE_SECRET import pandas as pd from django_rq import job +from django.utils import timezone from utils import deserialize_list from rowers.dataprepnodjango import ( update_strokedata, new_workout_from_file, getsmallrowdata_db, updatecpdata_sql, - update_agegroup_db, + update_agegroup_db,fitnessmetric_to_sql ) from django.core.mail import send_mail, EmailMessage @@ -612,6 +613,82 @@ def handle_updateergcp(rower_id,workoutfilenames,debug=False,**kwargs): return 1 +@app.task +def handle_updatefitnessmetric(user_id,mode,workoutids,debug=False, + **kwargs): + + columns = ['power','workoutid','time'] + df = getsmallrowdata_db(columns,ids=workoutids,debug=debug) + df.dropna(inplace=True,axis=0) + + if df.empty: + # change this + return 2 + + # df is not empty. We continue + dfgrouped = df.groupby(['workoutid']) + maxt = 1.05*df['time'].max()/1000. + + logarr = datautils.getlogarr(maxt) + + delta,cpvalue,avgpower = datautils.getcp(dfgrouped,logarr) + + powerdf = pd.DataFrame({ + 'Delta':delta, + 'CP':cpvalue, + }) + + powerdf = powerdf[powerdf['CP']>0] + powerdf.dropna(axis=0,inplace=True) + powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True) + powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True) + + p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) + # This is code duplication from datautils -- correct asap + fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) + + powerfourmin = fitfunc(p1,240.) + powerhour = fitfunc(p1,3600.) + + # 2k power + velofourmin = (powerfourmin/2.8)**(1./3.) + dfourmin = 240.*velofourmin + dratio = 2000./dfourmin + pacefourmin = 500./velofourmin + + # assume 5 sec per doubling drop + pace2k = pacefourmin + 5.*np.log10(dratio)/np.log10(2.) + velo2k = 500./pace2k + t2k = 2000./velo2k + pwr2k = fitfunc(p1,t2k) + velo2 = (pwr2k/2.8)**(1./3.) + if np.isnan(velo2) or velo2 <= 0: + velo2 = 1.0 + + t2 = 2000./velo2 + + pwr2k = fitfunc(p1,t2) + + velo3 = (pwr2k/2.8)**(1./3.) + + t3 = 2000./velo3 + + power2k = fitfunc(p1,t3) + + + mdict = { + 'user_id': user_id, + 'PowerFourMin': powerfourmin, + 'PowerTwoK': power2k, + 'PowerOneHour': powerhour, + 'workoutmode': mode, + 'last_workout': max(workoutids), + 'date': timezone.now().strftime('%Y-%m-%d'), + } + + result = fitnessmetric_to_sql(mdict,debug=debug) + + return result @app.task def handle_updatecp(rower_id,workoutids,debug=False,table='cpdata',**kwargs): diff --git a/rowers/utils.py b/rowers/utils.py index 26f74d3d..300e95fa 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -15,6 +15,7 @@ landingpages = ( ) + workflowmiddlepanel = ( ('panel_statcharts.html','Static Charts'), ('flexthumbnails.html','Flex Charts'), diff --git a/rowers/views.py b/rowers/views.py index f31575a5..2e3b967e 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -109,7 +109,8 @@ from rowers.tasks import ( handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemailnewresponse, handle_updatedps, handle_updatecp,long_test_task,long_test_task2, - handle_zip_file,handle_getagegrouprecords + handle_zip_file,handle_getagegrouprecords, + handle_updatefitnessmetric ) from scipy.signal import savgol_filter @@ -159,6 +160,20 @@ class JSONResponse(HttpResponse): kwargs['content_type'] = 'application/json' super(JSONResponse, self).__init__(content, **kwargs) + +def getrower(user): + try: + r = Rower.objects.get(user=user) + except Rower.DoesNotExist: + r = Rower(user=user) + r.save() + + return r + + + + + def getvalue(data): perc = 0 total = 1 @@ -737,6 +752,8 @@ def iscoachmember(user): return result + + def getrower(user): try: r = Rower.objects.get(user=user) @@ -746,6 +763,8 @@ def getrower(user): return r + + # Check if a user is a Pro member def ispromember(user): if not user.is_anonymous(): @@ -3211,6 +3230,16 @@ def addmanual_view(request): def fitness_metric_view(request,mode='rower',days=42): r = getrower(request.user) startdate = timezone.now()-datetime.timedelta(days=days) + + # test if not something already done + ms = PowerTimeFitnessMetric.objects.filter(user=request.user) + max_workout_id = max([m.last_workout for m in ms]) + last_update_date = max([m.date.strftime('%Y-%m-%d') for m in ms]) + + + now_date = timezone.now().strftime('%Y-%m-%d') + + if mode == 'rower': workouts = Workout.objects.filter( user=r, @@ -3223,88 +3252,18 @@ def fitness_metric_view(request,mode='rower',days=42): startdatetime__gte=startdate) theids = [int(w.id) for w in workouts] - columns = ['power','workoutid','time'] - df = getsmallrowdata_db(columns,ids=theids) - df.dropna(inplace=True,axis=0) + max_id = max(theids) - if df.empty: - # change this - return 'aap' + if last_update_date >= now_date or max_workout_id >= max_id: + return HttpResponse("already done today or no new workouts") - # df is not empty. We continue - dfgrouped = df.groupby(['workoutid']) - maxt = 1.05*df['time'].max()/1000. - - logarr = datautils.getlogarr(maxt) - - delta,cpvalue,avgpower = datautils.getcp(dfgrouped,logarr) - - powerdf = pd.DataFrame({ - 'Delta':delta, - 'CP':cpvalue, - }) - - powerdf = powerdf[powerdf['CP']>0] - powerdf.dropna(axis=0,inplace=True) - powerdf.sort_values(['Delta','CP'],ascending=[1,0],inplace=True) - powerdf.drop_duplicates(subset='Delta',keep='first',inplace=True) - - p1,fitt,fitpower,ratio = datautils.cpfit(powerdf) - # This is code duplication from datautils -- correct asap - fitfunc = lambda pars,x: abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) - - powerfourmin = fitfunc(p1,240.) - powerhour = fitfunc(p1,3600.) - - # 2k power - velofourmin = (powerfourmin/2.8)**(1./3.) - dfourmin = 240.*velofourmin - dratio = 2000./dfourmin - pacefourmin = 500./velofourmin - - # assume 5 sec per doubling drop - pace2k = pacefourmin + 5.*np.log10(dratio)/np.log10(2.) - velo2k = 500./pace2k - t2k = 2000./velo2k - pwr2k = fitfunc(p1,t2k) - velo2 = (pwr2k/2.8)**(1./3.) - if np.isnan(velo2) or velo2 <= 0: - velo2 = 1.0 - - t2 = 2000./velo2 - - pwr2k = fitfunc(p1,t2) - - velo3 = (pwr2k/2.8)**(1./3.) - - t3 = 2000./velo3 - - power2k = fitfunc(p1,t3) + job = myqueue(queue, + handle_updatefitnessmetric, + request.user.id,mode,theids, + ) - mdict = { - 'user_id': request.user.id, - 'PowerFourMin': powerfourmin, - 'PowerTwoK': power2k, - 'PowerOneHour': powerhour, - 'workoutmode': mode, - 'date': timezone.now().strftime('%Y-%m-%d'), - } - - result = dataprep.fitnessmetric_to_sql(mdict) - - m = PowerTimeFitnessMetric( - user=request.user, - PowerFourMin = powerfourmin, - PowerTwoK = power2k, - PowerOneHour = powerhour, - workoutmode = mode, - date = timezone.now() - ) - - m.save() - - return HttpResponse("success") + return HttpResponse("job queued") # Show ranking distances including predicted paces diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index f8da172b..400f4cc1 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -93,6 +93,7 @@ MIDDLEWARE_CLASSES = [ 'async_messages.middleware.AsyncMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'tz_detect.middleware.TimezoneMiddleware', + 'rowers.middleware.PowerTimeFitnessMetricMiddleWare', ] ROOT_URLCONF = 'rowsandall_app.urls' From 5917e3c5b8c94dbdbb4511b72e26b704bc939d3c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Jan 2018 18:39:02 +0100 Subject: [PATCH 09/10] some tweaks to make middleware working --- rowers/middleware.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/rowers/middleware.py b/rowers/middleware.py index e3284aed..db511b02 100644 --- a/rowers/middleware.py +++ b/rowers/middleware.py @@ -1,6 +1,10 @@ from django.utils import timezone from rowers.models import Workout, PowerTimeFitnessMetric, Rower import datetime +from utils import myqueue +import django_rq +queue = django_rq.get_queue('default') +from rowers.tasks import handle_updatefitnessmetric def getrower(user): try: @@ -23,10 +27,11 @@ def do_update(user,mode='rower',days=42): workoutmode=mode) if len(ms) == 0: - return 0 - - max_workout_id = max([m.last_workout for m in ms]) - last_update_date = max([m.date.strftime('%Y-%m-%d') for m in ms]) + max_workout_id = 0 + last_update_date = '1972-01-01' + else: + max_workout_id = max([m.last_workout for m in ms]) + last_update_date = max([m.date.strftime('%Y-%m-%d') for m in ms]) if mode == 'rower': workouts = Workout.objects.filter( @@ -40,14 +45,18 @@ def do_update(user,mode='rower',days=42): startdatetime__gte=startdate) theids = [int(w.id) for w in workouts] - max_id = max(theids) + try: + max_id = max(theids) + except ValueError: + max_id = 0 if last_update_date < now_date and max_workout_id < max_id: job = myqueue(queue, handle_updatefitnessmetric, - request.user.id,mode,theids, + user.id,mode,theids, ) + return 1 class PowerTimeFitnessMetricMiddleWare(object): From eb6625845ff7e93ff65267d6b0aba8a297e9cc24 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 17 Jan 2018 21:29:05 +0100 Subject: [PATCH 10/10] fixing bug middleware not working when user not logged in --- rowers/middleware.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rowers/middleware.py b/rowers/middleware.py index db511b02..70837ada 100644 --- a/rowers/middleware.py +++ b/rowers/middleware.py @@ -62,9 +62,9 @@ def do_update(user,mode='rower',days=42): class PowerTimeFitnessMetricMiddleWare(object): def process_request(self, request): # Code to be executed before the view is called - - result = do_update(request.user,mode='rower') - result = do_update(request.user,mode='water') + if request.user.is_authenticated(): + result = do_update(request.user,mode='rower') + result = do_update(request.user,mode='water')