diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 263f9e3e..3d177657 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 @@ -509,6 +515,23 @@ def paceformatsecs(values): return out +def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False): + 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 + + 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/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/datautils.py b/rowers/datautils.py index 5d853157..1358e0d6 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') @@ -201,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/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'}), diff --git a/rowers/middleware.py b/rowers/middleware.py new file mode 100644 index 00000000..70837ada --- /dev/null +++ b/rowers/middleware.py @@ -0,0 +1,70 @@ +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: + 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: + 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( + 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] + 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, + user.id,mode,theids, + ) + + + return 1 + +class PowerTimeFitnessMetricMiddleWare(object): + def process_request(self, request): + # Code to be executed before the view is called + if request.user.is_authenticated(): + 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 0a9a7eda..e37e35e9 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -241,6 +241,23 @@ class CalcAgePerformance(models.Model): class Meta: db_table = 'calcagegrouprecords' + +class PowerTimeFitnessMetric(models.Model): + modechoices = ( + ('rower','Rower'), + ('water','On the water') + ) + + 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) + 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/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/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 %}