diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 9326cf35..e51e6631 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -17,6 +17,7 @@ from django.utils import timezone from time import strftime, strptime, mktime, time, daylight import arrow from django.utils.timezone import get_current_timezone + thetimezone = get_current_timezone() from rowingdata import ( TCXParser, RowProParser, ErgDataParser, @@ -25,7 +26,7 @@ from rowingdata import ( MysteryParser, BoatCoachOTWParser, painsledDesktopParser, speedcoachParser, ErgStickParser, SpeedCoach2Parser, FITParser, fitsummarydata, - make_cumvalues, + make_cumvalues,cumcpdata, summarydata, get_file_type, ) @@ -40,7 +41,7 @@ import itertools import math from tasks import ( handle_sendemail_unrecognized, handle_sendemail_breakthrough, - handle_sendemail_hard + handle_sendemail_hard, handle_updatecp,handle_updateergcp ) from django.conf import settings @@ -425,6 +426,106 @@ def paceformatsecs(values): return out +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( + rower_id=rower_id, + table=table, + )) + connection = engine.raw_connection() + df = pd.read_sql_query(query, engine) + + return df + +def deletecpdata_sql(rower_id,table='cpdata'): + engine = create_engine(database_url, echo=False) + query = sa.text('DELETE from {table} WHERE user={rower_id};'.format( + rower_id=rower_id, + table=table, + )) + with engine.connect() as conn, conn.begin(): + try: + result = conn.execute(query) + except: + print "Database locked" + conn.close() + engine.dispose() + + + +def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=[]): + deletecpdata_sql(rower_id) + df = pd.DataFrame( + { + 'delta':delta, + 'cp':cp, + 'user':rower_id + } + ) + + if not distance.empty: + df['distance'] = distance + + engine = create_engine(database_url, echo=False) + with engine.connect() as conn, conn.begin(): + df.to_sql(table, engine, if_exists='append', index=False) + conn.close() + engine.dispose() + + +def runcpupdate(rower): + startdate = timezone.now()-datetime.timedelta(days=365) + enddate = timezone.now()+datetime.timedelta(days=5) + theworkouts = Workout.objects.filter(user=rower,rankingpiece=True, + workouttype='water', + startdatetime__gte=startdate, + startdatetime__lte=enddate) + + theids = [w.id for w in theworkouts] + + + if settings.DEBUG: + res = handle_updatecp.delay(rower.id,theids,debug=True) + else: + res = queue.enqueue(handle_updatecp,rower.id,theids) + +def fetchcperg(rower,theworkouts): + theids = [int(w.id) for w in theworkouts] + thefilenames = [w.csvfilename for w in theworkouts] + cpdf = getcpdata_sql(rower.id,table='ergcpdata') + + if settings.DEBUG: + res = handle_updateergcp.delay(rower.id,thefilenames,debug=True) + else: + res = queue.enqueue(handle_updateergcp,rower.id,thefilenames) + + return cpdf + + +def fetchcp(rower,theworkouts): + # get all power data from database (plus workoutid) + theids = [int(w.id) for w in theworkouts] + columns = ['power','workoutid','time'] + df = getsmallrowdata_db(columns,ids=theids) + + dfgrouped = df.groupby(['workoutid']) + avgpower2 = dict(dfgrouped.mean()['power'].astype(int)) + + cpdf = getcpdata_sql(rower.id) + + if not cpdf.empty: + return cpdf['delta'],cpdf['cp'],avgpower2 + else: + if settings.DEBUG: + res = handle_updatecp.delay(rower.id,theids,debug=True) + else: + res = queue.enqueue(handle_updatecp,rower.id,theids) + return [],[],avgpower2 + + + return [],[],avgpower2 + + # Processes painsled CSV file to database @@ -567,11 +668,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if dosummary: summary = row.allstats() - #summary = row.summary() - #summary += '\n' - #summary += row.intervalstats() - #workoutstartdatetime = row.rowdatetime timezone_str = 'UTC' try: workoutstartdatetime = timezone.make_aware(row.rowdatetime) @@ -649,24 +746,24 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', ishard = False if workouttype == 'water': df = getsmallrowdata_db(['power', 'workoutid', 'time'], ids=[w.id]) - # delta,cpvalues,avgpower = datautils.getsinglecp(row.df) - thesecs = totaltime - maxt = 1.05 * thesecs - if maxt > 0: - logarr = datautils.getlogarr(maxt) - dfgrouped = df.groupby(['workoutid']) - delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) + if df['power'].mean(): + thesecs = totaltime + maxt = 1.05 * thesecs + if maxt > 0: + logarr = datautils.getlogarr(maxt) + dfgrouped = df.groupby(['workoutid']) + delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) - res, btvalues, res2 = utils.isbreakthrough( - delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) - else: - res = 0 - res2 = 0 - if res: - isbreakthrough = True - res = datautils.updatecp(delta, cpvalues, r) - if res2 and not isbreakthrough: - ishard = True + res, btvalues, res2 = utils.isbreakthrough( + delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) + else: + res = 0 + res2 = 0 + if res: + isbreakthrough = True + res = datautils.updatecp(delta, cpvalues, r) + if res2 and not isbreakthrough: + ishard = True # submit email task to send email about breakthrough workout if isbreakthrough: diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index c2cd7f9c..767ace9f 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -489,9 +489,9 @@ def testdata(time,distance,pace,spm): return t1 and t2 and t3 and t4 -def getsmallrowdata_db(columns,ids=[]): +def getsmallrowdata_db(columns,ids=[],debug=False): - data = read_cols_df_sql(ids,columns) + data = read_cols_df_sql(ids,columns,debug=debug) return data @@ -548,8 +548,67 @@ def read_df_sql(id,debug=False): engine.dispose() return df +def getcpdata_sql(rower_id,table='cpdata',debug=False): + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + query = sa.text('SELECT * from {table} WHERE user={rower_id};'.format( + rower_id=rower_id, + table=table, + )) + connection = engine.raw_connection() + df = pd.read_sql_query(query, engine) + + return df + +def deletecpdata_sql(rower_id,table='cpdata',debug=False): + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + query = sa.text('DELETE from {table} WHERE user={rower_id};'.format( + rower_id=rower_id, + table=table, + )) + with engine.connect() as conn, conn.begin(): + try: + result = conn.execute(query) + except: + print "Database locked" + conn.close() + engine.dispose() + + + +def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=pd.Series([]),debug=False): + deletecpdata_sql(rower_id,table=table,debug=debug) + df = pd.DataFrame( + { + 'delta':delta, + 'cp':cp, + 'user':rower_id + } + ) + + if not distance.empty: + df['distance'] = distance + + if debug: + engine = create_engine(database_url_debug, echo=False) + else: + engine = create_engine(database_url, echo=False) + + with engine.connect() as conn, conn.begin(): + df.to_sql(table, engine, if_exists='append', index=False) + conn.close() + engine.dispose() + + def smalldataprep(therows,xparam,yparam1,yparam2): diff --git a/rowers/datautils.py b/rowers/datautils.py index f01aac3b..5d853157 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -107,6 +107,7 @@ def getsinglecp(df): return delta,cpvalue,avgpower + def getcp(dfgrouped,logarr): delta = [] cpvalue = [] @@ -206,3 +207,4 @@ def getmaxwattinterval(tt,ww,i): deltat = 0 return deltat,wmax + diff --git a/rowers/forms.py b/rowers/forms.py index 34b14e4d..4923a517 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -152,6 +152,11 @@ class WorkFlowMiddlePanelElement(forms.Form): # The form to indicate additional actions to be performed immediately # after a successful upload + +nextpages = list(landingpages) +nextpages.append(('workout_upload_view','Upload Another File')) +nextpages = tuple(nextpages) + class UploadOptionsForm(forms.Form): plotchoices = ( ('timeplot','Time Plot'), @@ -181,9 +186,9 @@ class UploadOptionsForm(forms.Form): makeprivate = forms.BooleanField(initial=False,required=False, label='Make Workout Private') - landingpage = forms.ChoiceField(choices=landingpages, + landingpage = forms.ChoiceField(choices=nextpages, initial='workout_edit_view', - label='Landing Page') + label='After Upload, go to') class Meta: fields = ['make_plot','plottype','upload_toc2','makeprivate'] diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index dff5c227..29edb78c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -41,6 +41,7 @@ import pandas as pd from pytz import timezone as tz,utc from django.utils.timezone import get_current_timezone from django.utils.timezone import activate +from django.utils import timezone activate(settings.TIME_ZONE) thetimezone = get_current_timezone() @@ -1065,10 +1066,9 @@ def interactive_otwcpchart(powerdf,promember=0): return [script,div,p1,ratio,message] -def interactive_cpchart(thedistances,thesecs,theavpower, +def interactive_cpchart(rower,thedistances,thesecs,theavpower, theworkouts,promember=0): - message = 0 # plot tools if (promember==1): @@ -1086,7 +1086,7 @@ def interactive_cpchart(thedistances,thesecs,theavpower, p = pd.Series(500./velo) p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - + source = ColumnDataSource( data = dict( dist = thedistances, @@ -1108,6 +1108,7 @@ def interactive_cpchart(thedistances,thesecs,theavpower, paulslope = 5.0/np.log10(2.0) paulintercept = p[0]-paulslope*np.log10(thedistances[0]) + fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.) fitp = paulslope*fitx+paulintercept @@ -1198,16 +1199,14 @@ def interactive_cpchart(thedistances,thesecs,theavpower, plot.xaxis.axis_label = "Duration (seconds)" plot.yaxis.axis_label = "Power (W)" - therows = [] - for workout in theworkouts: - f1 = workout.csvfilename - rowdata = rdata(f1) - if rowdata != 0: - therows.append(rowdata) - cpdata = cumcpdata(therows) + cpdata = dataprep.fetchcperg(rower, theworkouts) - velo = cpdata['Distance']/cpdata['Delta'] + if cpdata.empty: + message = 'Calculations are running in the background. Please refresh this page to see updated results' + return ['','',paulslope,paulintercept,p1,message] + + velo = cpdata['distance']/cpdata['delta'] p = 500./velo @@ -1215,12 +1214,12 @@ def interactive_cpchart(thedistances,thesecs,theavpower, source2 = ColumnDataSource( data = dict( - duration = cpdata['Delta'], - power = cpdata['CP'], + duration = cpdata['delta'], + power = cpdata['cp'], tim = niceformat( - cpdata['Delta'].fillna(method='ffill').apply(lambda x: timedeltaconv(x)) + cpdata['delta'].fillna(method='ffill').apply(lambda x: timedeltaconv(x)) ), - dist = cpdata['Distance'], + dist = cpdata['distance'], pace = nicepaceformat(p2), ) ) @@ -1252,6 +1251,7 @@ def interactive_cpchart(thedistances,thesecs,theavpower, script, div = components(plot) + return [script,div,paulslope,paulintercept,p1,message] def interactive_windchart(id=0,promember=0): diff --git a/rowers/metrics.py b/rowers/metrics.py index 4e741b08..9c1fcfa0 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -294,7 +294,7 @@ dropping to 8m for race pace in the single.""", 'yparam1':'driveenergy', 'yparam2':'None', 'xparam':'spm', - 'plottype':'line', + 'plottype':'scatter', 'workouttype':'ote', 'reststrokes':True, 'notes':"""This chart shows the Work per Stroke versus Stroke Rate. diff --git a/rowers/models.py b/rowers/models.py index 54002477..1b233404 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -641,7 +641,30 @@ attrs.update(strokedatafields) StrokeData = type(str('StrokeData'), (models.Model,), attrs ) - + +# Storing data for the OTW CP chart +class cpdata(models.Model): + delta = models.IntegerField(default=0) + cp = models.FloatField(default=0) + user = models.IntegerField(default=0) + + class Meta: + db_table = 'cpdata' + index_together = ['user'] + app_label = 'rowers' + +# Storing data for the OTW CP chart +class ergcpdata(models.Model): + delta = models.IntegerField(default=0) + cp = models.FloatField(default=0) + distance = models.FloatField(default=0) + user = models.IntegerField(default=0) + + class Meta: + db_table = 'ergcpdata' + index_together = ['user'] + app_label = 'rowers' + # A wrapper around the png files class GraphImage(models.Model): filename = models.CharField(default='',max_length=150,blank=True,null=True) diff --git a/rowers/tasks.py b/rowers/tasks.py index bea8eddf..372dc1cb 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -26,7 +26,7 @@ from utils import deserialize_list from rowers.dataprepnodjango import ( update_strokedata, new_workout_from_file, - getsmallrowdata_db, + getsmallrowdata_db, updatecpdata_sql ) from django.core.mail import send_mail, EmailMessage @@ -400,9 +400,45 @@ def handle_otwsetpower(f1, boattype, weightvalue, return 1 -# This function generates all the static (PNG image) plots + +@app.task +def handle_updateergcp(rower_id,workoutfilenames,debug=False): + therows = [] + for f1 in workoutfilenames: + rowdata = rdata(f1) + if rowdata != 0: + therows.append(rowdata) + + cpdata = rowingdata.cumcpdata(therows) + cpdata.columns = cpdata.columns.str.lower() + + updatecpdata_sql(rower_id,cpdata['delta'],cpdata['cp'], + table='ergcpdata',distance=cpdata['distance'], + debug=debug) + + return 1 +@app.task +def handle_updatecp(rower_id,workoutids,debug=False): + columns = ['power','workoutid','time'] + df = getsmallrowdata_db(columns,ids=workoutids,debug=debug) + dfgrouped = df.groupby(['workoutid']) + + if not df.empty: + maxt = 1.05*df['time'].max()/1000. + else: + maxt = 1000. + + logarr = datautils.getlogarr(maxt) + + + delta,cpvalue,avgpower = datautils.getcp(dfgrouped,logarr) + + updatecpdata_sql(rower_id,delta,cpvalue,debug=debug) + + return 1 + @app.task def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename): diff --git a/rowers/templates/flexchart3otw.html b/rowers/templates/flexchart3otw.html index 02e698d3..c7f2517a 100644 --- a/rowers/templates/flexchart3otw.html +++ b/rowers/templates/flexchart3otw.html @@ -189,7 +189,12 @@ {% else %} > {% endif %} - + + {% if favoritechartnotes %} +
Chart {{ favoritenr|add:1 }}:{{ favoritechartnotes }}
+