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'