diff --git a/rowers/alerts.py b/rowers/alerts.py index ef74ddf0..2beeca2e 100644 --- a/rowers/alerts.py +++ b/rowers/alerts.py @@ -1,6 +1,6 @@ from rowers.models import Alert, Condition, User, Rower, Workout from rowers.teams import coach_getcoachees -from rowers.dataprep import getsmallrowdata_db, getrowdata_db +from rowers.dataprep import getrowdata_db, read_data, remove_nulls_pl import datetime import numpy as np import math @@ -101,8 +101,10 @@ def alert_get_stats(alert, nperiod=0): # pragma: no cover ids = [w.id for w in workouts] try: - df = getsmallrowdata_db(columns, ids=ids, doclean=True, + df = getsmallrowdata_pd(columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly) + df.dropna(axis=1,how='all',inplace=True) + df.dropna(axis=0,how='all',inplace=True) except: return { 'workouts': workouts.count(), diff --git a/rowers/courses.py b/rowers/courses.py index d3eab241..060d15ae 100644 --- a/rowers/courses.py +++ b/rowers/courses.py @@ -1,5 +1,4 @@ from rowers.courseutils import coursetime_paths, coursetime_first, time_in_path -import pandas as pd from rowers.models import ( Rower, Workout, GeoPoint, GeoPolygon, GeoCourse, @@ -420,62 +419,6 @@ def createcourse( return c -def get_time_course(ws, course): # pragma: no cover - coursetimeseconds = 0.0 - coursecompleted = False - - w = ws[0] - columns = ['time', ' latitude', ' longitude', 'cum_dist'] - rowdata = dataprep.getsmallrowdata_db( - columns, - ids=[w.id], - doclean=False, - workstrokesonly=False - ) - - rowdata.rename(columns={ - ' latitude': 'latitude', - ' longitude': 'longitude', - }, inplace=True) - - rowdata['time'] = rowdata['time']/1000. - - rowdata.fillna(method='backfill', inplace=True) - - rowdata['time'] = rowdata['time']-rowdata.ix[0, 'time'] - # we may want to expand the time (interpolate) - rowdata['dt'] = rowdata['time'].apply( - lambda x: timedelta(seconds=x) - ) - rowdata = rowdata.resample('100ms', on='dt').mean() - rowdata = rowdata.interpolate() - - # create path - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - paths = [] - for polygon in polygons: - path = polygon_to_path(polygon) - paths.append(path) - - ( - coursetimeseconds, - coursemeters, - coursecompleted, - - ) = coursetime_paths(rowdata, paths) - ( - coursetimefirst, - coursemetersfirst, - firstcompleted - ) = coursetime_first( - rowdata, paths) - - coursetimeseconds = coursetimeseconds-coursetimefirst - coursemeters = coursemeters-coursemetersfirst - - return coursetimeseconds, coursemeters, coursecompleted - def replacecourse(course1, course2): ps = PlannedSession.objects.filter(course=course1) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index cca3b186..4c8e94a9 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -8,6 +8,7 @@ from rowers.datautils import p0 from scipy import optimize from rowers.utils import calculate_age import datetime +import gzip from scipy.signal import savgol_filter from rowers.opaque import encoder from rowers.database import * @@ -27,6 +28,8 @@ from fitparse import FitFile import itertools import numpy as np import pandas as pd +import polars as pl +from polars.exceptions import ColumnNotFoundError from zipfile import BadZipFile import zipfile import os @@ -73,6 +76,7 @@ import pytz import collections import pendulum from rowingdata import rowingdata as rrdata +from rowingdata import rowingdata_pl as rrdata_pl from rowingdata import rower as rrower @@ -88,10 +92,9 @@ from rowers.dataroutines import * from rowers.tasks import ( handle_sendemail_newftp, - handle_sendemail_unrecognized, handle_setcp, + handle_sendemail_unrecognized, handle_getagegrouprecords, handle_update_wps, handle_request_post, handle_calctrimp, - handle_updatecp, handle_updateergcp, handle_sendemail_breakthrough, handle_sendemail_hard, ) @@ -123,6 +126,10 @@ from rq import Queue from rowers.datautils import rpetotss def rscore_approx(row): + if isinstance(row, pl.DataFrame): + row = {'rscore': row['rscore'][0]} + if isinstance(row, pl.Series): + row = {'rscore': row['rscore'][0]} if row['rscore'] > 0: return row['rscore'] if row['rscore'] == 0: # pragma: no cover @@ -215,18 +222,18 @@ def check_marker(workout): ids.append(w.id) gms.append(gmstandard) - df = pd.DataFrame({ + df = pl.DataFrame({ 'id': ids, 'gms': gms, }) - if df.empty: # pragma: no cover + if df.is_empty(): # pragma: no cover workout.ranking = True workout.save() return workout - indexmax = df['gms'].idxmax() - theid = df.loc[indexmax, 'id'] + theid = df.filter(pl.col("gms") == pl.col("gms").max())['id'][0] + wmax = Workout.objects.get(id=theid) # gms_max = wmax.goldmedalstandard @@ -322,7 +329,7 @@ def workout_summary_to_df( goldstandarddurations.append(int(goldstandardduration)) rankingpieces.append(w.rankingpiece) - df = pd.DataFrame({ + df = pl.DataFrame({ 'ID': ids, 'date': startdatetimes, 'name': names, @@ -392,7 +399,7 @@ def resample(id, r, parent, overwrite='copy'): row.write_csv(parent.csvfilename, gzip=True) - _ = dataprep(row.df, id=parent.id, bands=True, barchart=True, + _ = dataplep(row.df, id=parent.id, bands=True, barchart=True, otwpower=True, empower=True, inboard=parent.inboard) isbreakthrough, ishard = checkbreakthrough(parent, r) _ = check_marker(parent) @@ -414,18 +421,23 @@ def resample(id, r, parent, overwrite='copy'): def calculate_goldmedalstandard(rower, workout, recurrance=True): cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: - df = pd.read_parquet(cpfile) + df = pl.read_parquet(cpfile) except: + df = read_data(['power'], ids=[workout.id]) + df = remove_nulls_pl(df) background = True if settings.TESTING: background = False - df, delta, cpvalues = setcp(workout, background=background) - if df.empty: - return 0, 0 + if recurrance: + df, delta, cpvalues = setcp(workout, background=background) + if df.is_empty(): + return 0, 0 + else: + return 0,0 - if df.empty and recurrance: # pragma: no cover + if df.is_empty() and recurrance: # pragma: no cover df, delta, cpvalues = setcp(workout, recurrance=False, background=True) - if df.empty: + if df.is_empty(): return 0, 0 age = calculate_age(rower.birthdate, today=workout.date) @@ -453,7 +465,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): if getrecords: # pragma: no cover durations = [1, 4, 30, 60] distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - df2 = pd.DataFrame( + df2 = pl.DataFrame( list( C2WorldClassAgePerformance.objects.filter( sex=rower.sex, @@ -461,7 +473,7 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): ).values() ) ) - jsondf = df2.to_json() + jsondf = df2.write_json() _ = myqueue(queuelow, handle_getagegrouprecords, jsondf, distances, durations, age, rower.sex, rower.weightcategory) @@ -489,9 +501,10 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): scores = 100.*powers/wcpowers try: - indexmax = scores.idxmax() - delta = int(df.loc[indexmax, 'delta']) - maxvalue = scores.max() + df = pl.DataFrame({'times': times, 'scores': scores}) + df = df.filter(pl.col("scores") == pl.col("scores").max()) + delta = df[0, "times"] + maxvalue = df[0, "scores"] except (ValueError, TypeError, KeyError): # pragma: no cover indexmax = 0 delta = 0 @@ -504,21 +517,22 @@ def calculate_goldmedalstandard(rower, workout, recurrance=True): def setcp(workout, background=False, recurrance=True): try: filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) - df = pd.read_parquet(filename) + df = pl.read_parquet(filename) - if not df.empty: + if not df.is_empty(): # check dts tarr = datautils.getlogarr(4000) if df['delta'][0] in tarr: return(df, df['delta'], df['cp']) - except: + except Exception as e: pass - strokesdf = getsmallrowdata_db( + strokesdf = read_data( ['power', 'workoutid', 'time'], ids=[workout.id]) + strokesdf = remove_nulls_pl(strokesdf) - if strokesdf.empty: - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + if strokesdf.is_empty(): + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) totaltime = strokesdf['time'].max() maxt = totaltime/1000. @@ -533,7 +547,7 @@ def setcp(workout, background=False, recurrance=True): elif os.path.exists(csvfilename+'.gz'): # pragma: no cover csvfile = csvfilename+'.gz' else: # pragma: no cover - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) csvfile = os.path.abspath(csvfile) @@ -547,7 +561,7 @@ def setcp(workout, background=False, recurrance=True): grpc.channel_ready_future(channel).result(timeout=10) except grpc.FutureTimeoutError: # pragma: no cover dologging('metrics.log','grpc channel time out in setcp') - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) stub = metrics_pb2_grpc.MetricsStub(channel) req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr) @@ -556,25 +570,29 @@ def setcp(workout, background=False, recurrance=True): response = stub.GetCP(req, timeout=60) except Exception as e: dologging('metrics.log', traceback.format_exc()) - return pd.DataFrame({'delta': [], 'cp': []}), pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) - delta = pd.Series(np.array(response.delta)) - cpvalues = pd.Series(np.array(response.power)) + delta = pl.Series(np.array(response.delta)) + cpvalues = pl.Series(np.array(response.power)) powermean = response.avgpower - - - df = pd.DataFrame({ + df = pl.DataFrame({ 'delta': delta, 'cp': cpvalues, 'id': workout.id, }) - df.to_parquet(filename, engine='fastparquet', compression='GZIP') + df = df.drop_nulls() + + with gzip.open(filename, 'w') as f: + df.write_parquet(f) + + + #df.to_parquet(filename, engine='fastparquet', compression='GZIP') if recurrance: goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( - workout.user, workout) + workout.user, workout, recurrance=False) workout.goldmedalstandard = goldmedalstandard workout.goldmedalduration = goldmedalduration workout.save() @@ -602,14 +620,10 @@ def update_wps(r, types, mode='water', asynchron=True): mode ) - df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids) + df = read_data(['time', 'driveenergy'], ids=ids) try: - mask = df['driveenergy'] > 100 - except (KeyError, TypeError): - return False - try: - wps_median = int(df.loc[mask, 'driveenergy'].median()) + wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median()) if mode == 'water': r.median_wps = wps_median else: # pragma: no cover @@ -618,6 +632,10 @@ def update_wps(r, types, mode='water', asynchron=True): r.save() except ValueError: # pragma: no cover pass + except OverflowError: + pass + except ColumnNotFoundError: + pass return True @@ -718,77 +736,28 @@ def fetchcp_new(rower, workouts): data = [] for workout in workouts: - cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) - try: - df, delta, cpvalues = setcp(workout) - df = pd.read_parquet(cpfile) - df['workout'] = str(workout) - df['url'] = workout.url() - data.append(df) - except: - # CP data file doesn't exist yet. has to be created - df, delta, cpvalues = setcp(workout) - df['workout'] = str(workout) - df['url'] = workout.url() + df, delta, cpvalues = setcp(workout) + df = df.drop('id') + df = df.with_columns((pl.lit(str(workout))).alias("workout")) + df = df.with_columns((pl.lit(workout.url())).alias("url")) + if not df.is_empty(): data.append(df) if len(data) == 0: - return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) if len(data) > 1: - df = pd.concat(data, axis=0) - + df = pl.concat(data) try: - df = df[df['cp'] == df.groupby(['delta'])['cp'].transform('max')] - except KeyError: # pragma: no cover - return pd.Series(dtype='float'), pd.Series(dtype='float'), 0, pd.Series(dtype='float'), pd.Series(dtype='float') - - df = df.sort_values(['delta']).reset_index() - df = df[df['cp']>20] + df = df.group_by(pl.col("delta")).agg(pl.max("cp"), pl.max("workout"), pl.max("url")).sort("delta") + except (KeyError, ColumnNotFoundError): # pragma: no cover + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + df = df.filter(pl.col("cp")>20) return df['delta'], df['cp'], 0, df['workout'], df['url'] -def fetchcp(rower, theworkouts, table='cpdata'): # pragma: no cover - # 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) - df.dropna(inplace=True, axis=0) - if df.empty: - avgpower2 = {} - for id in theids: - avgpower2[id] = 0 - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - try: - dfgrouped = df.groupby(['workoutid']) - except KeyError: - avgpower2 = {} - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - try: - avgpower2 = dict(dfgrouped.mean()['power'].astype(int)) - except KeyError: - avgpower2 = {} - for id in theids: - avgpower2[id] = 0 - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - cpdf = getcpdata_sql(rower.id, table=table) - - if not cpdf.empty: - return cpdf['delta'], cpdf['cp'], avgpower2 - else: - _ = myqueue(queuelow, - handle_updatecp, - rower.id, - theids, - table=table) - - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 - - return pd.Series([], dtype='float'), pd.Series([], dtype='float'), avgpower2 def update_rolling_cp(r, types, mode='water', dosend=False): @@ -801,16 +770,18 @@ def update_rolling_cp(r, types, mode='water', dosend=False): delta, cp, avgpower, workoutnames, urls = fetchcp_new(r, workouts) - powerdf = pd.DataFrame({ + powerdf = pl.DataFrame({ 'Delta': delta, 'CP': cp, }) - 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) + powerdf = powerdf.filter(pl.col("CP")>0) + powerdf = powerdf.fill_nan(None).drop_nulls().sort(["Delta", "CP"]) + powerdf = powerdf.unique(subset=["Delta"], keep="first") + if powerdf.is_empty(): + return False + res2 = datautils.cpfit(powerdf) p1 = res2[0] # calculate FTP @@ -1051,7 +1022,7 @@ def checkbreakthrough(w, r): workouttype = w.workouttype if workouttype in rowtypes: cpdf, delta, cpvalues = setcp(w) - if not cpdf.empty: + if not cpdf.is_empty(): if workouttype in otwtypes: try: res, btvalues, res2 = utils.isbreakthrough( @@ -1397,7 +1368,12 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', w.team.add(t) # put stroke data in database - _ = dataprep(row.df, id=w.id, bands=True, + try: + row = rrdata_pl(df=pl.from_pandas(row.df)) + except: + pass + + _ = dataplep(row.df, id=w.id, bands=True, barchart=True, otwpower=True, empower=True, inboard=inboard) isbreakthrough, ishard = checkbreakthrough(w, r) diff --git a/rowers/dataroutines.py b/rowers/dataroutines.py index 0e893166..b08fc1a7 100644 --- a/rowers/dataroutines.py +++ b/rowers/dataroutines.py @@ -1,4 +1,4 @@ -from rowers.metrics import axes, calc_trimp, rowingmetrics, dtypes, metricsgroups +from rowers.metrics import axes, calc_trimp, rowingmetrics, dtypes, metricsgroups, metricsdicts from rowers.utils import lbstoN, wavg, dologging from rowers.mytypes import otwtypes, otetypes, rowtypes import glob @@ -31,6 +31,12 @@ from zipfile import BadZipFile import zipfile import os from rowers.models import strokedatafields +import polars as pl +import polars.selectors as cs +from polars.exceptions import ( + ColumnNotFoundError, SchemaError, ComputeError, + InvalidOperationError, ShapeError +) from rowingdata import ( KinoMapParser, @@ -71,6 +77,7 @@ from pytz.exceptions import UnknownTimeZoneError import collections import pendulum from rowingdata import rowingdata as rrdata +from rowingdata import rowingdata_pl as rrdata_pl from rowingdata import rower as rrower @@ -170,6 +177,29 @@ columndict = { 'cumdist': 'cum_dist', } +def remove_nulls_pl(data): + data = data.lazy().with_columns( + pl.when( + pl.all().is_infinite() + ).then(None).otherwise(pl.all()).keep_name() + ) + data = data.select(pl.all().forward_fill()) + data = data.select(pl.all().backward_fill()) + data = data.fill_nan(None) + + data = data.select(cs.by_dtype(pl.NUMERIC_DTYPES)).collect() + data = data[[s.name for s in data if not s.is_infinite().sum()]] + data = data[[s.name for s in data if not (s.null_count() == data.height)]] + + + if not data.is_empty(): + try: + data = data.drop_nulls() + except: # pragma: no cover + pass + + return data + def get_video_data(w, groups=['basic'], mode='water'): modes = [mode, 'both', 'basic'] @@ -177,8 +207,10 @@ def get_video_data(w, groups=['basic'], mode='water'): columns += [name for name, d in rowingmetrics if d['group'] in groups and d['mode'] in modes] columns = list(set(columns)) - df = getsmallrowdata_db(columns, ids=[w.id], + df = getsmallrowdata_pd(columns, ids=[w.id], workstrokesonly=False, doclean=False, compute=False) + df.dropna(axis=0, how='all', inplace=True) + df.dropna(axis=1, how='all', inplace=True) df['time'] = (df['time']-df['time'].min())/1000. @@ -199,7 +231,6 @@ def get_video_data(w, groups=['basic'], mode='water'): df2 = df2.round(decimals=2) - boatspeed = (100*df2['velo']).astype(int)/100. try: coordinates = get_latlon_time(w.id) @@ -213,10 +244,12 @@ def get_video_data(w, groups=['basic'], mode='water'): coordinates.set_index(pd.to_timedelta( coordinates['time'], unit='s'), inplace=True) - coordinates = coordinates.resample('1s').mean().interpolate() - coordinates['time'] = coordinates['time']-coordinates['time'].min() - latitude = coordinates['latitude'] - longitude = coordinates['longitude'] + coordinates = coordinates.resample('1s').first().interpolate().fillna(method='ffill') + #coordinates['time'] = coordinates['time']-coordinates['time'].min() + df2 = pd.concat([df2, coordinates], axis=1) + latitude = df2['latitude'] + longitude = df2['longitude'] + boatspeed = (100*df2['velo']).astype(int)/100. # bundle data data = { @@ -235,7 +268,10 @@ def get_video_data(w, groups=['basic'], mode='water'): else: sigfigs = dict(rowingmetrics)[c]['sigfigs'] if (c != 'pace'): - da = ((10**sigfigs)*df2[c]).astype(int)/(10**sigfigs) + try: + da = ((10**sigfigs)*df2[c]).astype(int)/(10**sigfigs) + except: + da = df2[c] else: da = df2[c] data[c] = da.values.tolist() @@ -253,6 +289,14 @@ def get_video_data(w, groups=['basic'], mode='water'): maxtime = coordinates['time'].max() + data = pd.DataFrame(data) + data.replace([np.inf, -np.inf], np.nan, inplace=True) + data.dropna(inplace=True) + + data = pl.from_pandas(data) + + data = data.to_dict(as_series=False) + return data, metrics, maxtime @@ -373,7 +417,7 @@ def filter_df(datadf, fieldname, value, largerthan=True): mask = datadf[fieldname] >= value datadf.loc[mask, fieldname] = np.nan - except TypeError: + except TypeError: # pragma: no cover pass return datadf @@ -394,18 +438,22 @@ def df_resample(datadf): def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, - ignoreadvanced=False): + ignoreadvanced=False, for_chart=False): # clean data remove zeros and negative values try: _ = datadf['workoutid'].unique() except KeyError: - datadf['workoutid'] = 0 + try: + datadf['workoutid'] = 0 + except TypeError: # pragma: no cover + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) before = {} - for workoutid in datadf['workoutid'].unique(): + ids = datadf['workoutid'].unique() + for workoutid in ids: before[workoutid] = len(datadf[datadf['workoutid'] == workoutid]) - + data_orig = datadf.copy() # bring metrics which have negative values to positive domain @@ -493,7 +541,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, pass # clean data for useful ranges per column - if not ignorehr: + if not ignorehr: # pragma: no cover try: mask = datadf['hr'] < 30 datadf.mask(mask, inplace=True) @@ -564,6 +612,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, except KeyError: pass + if not ignoreadvanced: try: mask = datadf['rhythm'] < 0 @@ -642,6 +691,9 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, pass after = {} + + if for_chart: # pragma: no cover + return datadf for workoutid in data_orig['workoutid'].unique(): after[workoutid] = len( datadf[datadf['workoutid'] == workoutid].dropna()) @@ -649,6 +701,195 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, if ratio < 0.01 or after[workoutid] < 2: return data_orig + return datadf # pragma: no cover + +def replace_zeros_with_nan(x): # pragma: no cover + return np.nan if x == 0 else x + +def clean_df_stats_pl(datadf, workstrokesonly=True, ignorehr=True, + ignoreadvanced=False, for_chart=False): # pragma: no cover + # clean data remove zeros and negative values + try: + _ = datadf['workoutid'].unique() + except KeyError: # pragma: no cover + try: + datadf['workoutid'] = 0 + except TypeError: + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) + except ColumnNotFoundError: # pragma: no cover + datadf = datadf.with_columns(pl.lit(0).alias("workoutid")) + + before = {} + ids = list(datadf['workoutid'].unique()) + for workoutid in ids: + before[workoutid] = len(datadf.filter(pl.col("workoutid")==workoutid)) + + data_orig = datadf.clone() + + # bring metrics which have negative values to positive domain + if len(datadf) == 0: # pragma: no cover + return data_orig + try: + datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) + except (KeyError, TypeError): # pragma: no cover + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('peakforceangle')+1000).alias('peakforceangle')) + except (KeyError, TypeError): + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) + except (KeyError, TypeError): + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + # protect 0 spm values from being nulled + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('spm')+1.0).alias('spm')) + except (KeyError, TypeError): + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + # protect 0 workoutstate values from being nulled + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('workoutstate')+1).alias('workoutstate')) + except (KeyError, TypeError): + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + try: # pragma: no cover + datadf = datadf.select(pl.all().clip(lower_bound=0)) + # datadf = datadf.clip(lower=0) + except (TypeError): + pass + except(ComputeError, InvalidOperationError, ColumnNotFoundError): + return data_orig + + # protect advanced metrics columns + advancedcols = [ + 'rhythm', + 'power', + 'drivelength', + 'forceratio', + 'drivespeed', + 'driveenergy', + 'catch', + 'finish', + 'averageforce', + 'peakforce', + 'slip', + 'wash', + 'peakforceangle', + 'effectiveangle', + ] # pragma: no cover + + for col in datadf.columns: # pragma: no cover + datadf = datadf.with_columns( + pl.when(datadf[col] == 0).then(pl.lit(np.nan)).otherwise(datadf[col]), + name=col + ) + + # datadf = datadf.map_partitions(lambda df:df.replace(to_replace=0,value=np.nan)) + + # bring spm back to real values + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('spm')-1.0).alias('spm')) + except (TypeError, KeyError): + pass + + # bring workoutstate back to real values + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('workoutstate')-1).alias('workoutstate')) + except (TypeError, KeyError): + pass + + # return from positive domain to negative + try: # pragma: no cover + datadf = datadf.with_columns((-pl.col('catch')).alias('catch')) + except (KeyError, TypeError): + pass + + try: # pragma: no cover + datadf = datadf.with_columns((pl.col('peakforceangle')-1000).alias('peakforceangle')) + except (KeyError, TypeError): + pass + + try: + datadf = datadf.with_columns((pl.col('hr')+10).alias('hr')) + except (KeyError, TypeError): + pass + + # clean data for useful ranges per column + if not ignorehr: + datadf = datadf.filter(pl.col("hr")>=30) + + + datadf = datadf.filter( + pl.col("spm") >=0, + pl.col("spm")>=10, + pl.col("pace")<=300*1000., + pl.col("pace")>=60*1000, + pl.col("power")<=5000, + pl.col("spm")<=120, + ) + + + # try to guess ignoreadvanced + if not ignoreadvanced: + for metric in advancedcols: + try: + sum = datadf[metric].std() + if sum == 0 or np.isnan(sum): + ignoreadvanced = True + except (KeyError, TypeError): + pass + + if not ignoreadvanced: + datadf = datadf.filter(pl.col("rhythm")>=0, + pl.col("rhythm")<=70, + pl.col("power")>=20, + pl.col("efficiency")<=200, + pl.col("drivelength")>=0.5, + pl.col("wash")>=1, + pl.col("efficiency")>=0, + pl.col("forceratio")>=0.2, + pl.col("forceratio")<=1.0, + pl.col("drivespeed")>=0.5, + pl.col("drivespeed")<=4, + pl.col("driveenergy")<=2000, + pl.col("driveenergy")>=100, + pl.col("catch")<=-30) + + + + # workoutstateswork = [1, 4, 5, 8, 9, 6, 7] + workoutstatesrest = [3] + # workoutstatetransition = [0, 2, 10, 11, 12, 13] + + if workstrokesonly == 'True' or workstrokesonly is True: + datadf = datadf.filter(~pl.col("workoutstate").is_in(workoutstatesrest)) + + after = {} + + if for_chart: + return datadf + for workoutid in data_orig['workoutid'].unique(): + after[workoutid] = len(datadf.filter(pl.col("workoutid")==workoutid)) + ratio = float(after[workoutid])/float(before[workoutid]) + if ratio < 0.01 or after[workoutid] < 2: + return data_orig + + + return datadf @@ -860,12 +1101,6 @@ def get_workoutsummaries(userid, startdate): # pragma: no cover return df - - - - - - def checkduplicates(r, workoutdate, workoutstartdatetime, workoutenddatetime): duplicate = False ws = Workout.objects.filter(user=r, date=workoutdate, duplicate=False).exclude( @@ -1192,16 +1427,16 @@ def delete_strokedata(id, debug=False): def update_strokedata(id, df, debug=False): delete_strokedata(id, debug=debug) - _ = dataprep(df, id=id, bands=True, barchart=True, otwpower=True) + _ = dataplep(df, id=id, bands=True, barchart=True, otwpower=True) # Test that all data are of a numerical time def testdata(time, distance, pace, spm): # pragma: no cover - t1 = np.issubdtype(time, np.number) - t2 = np.issubdtype(distance, np.number) - t3 = np.issubdtype(pace, np.number) - t4 = np.issubdtype(spm, np.number) + t1 = time.dtype in pl.NUMERIC_DTYPES + t2 = distance.dtype in pl.NUMERIC_DTYPES + t3 = pace.dtype in pl.NUMERIC_DTYPES + t4 = spm.dtype in pl.NUMERIC_DTYPES return t1 and t2 and t3 and t4 @@ -1210,7 +1445,7 @@ def testdata(time, distance, pace, spm): # pragma: no cover def getrowdata_db(id=0, doclean=False, convertnewtons=True, - checkefficiency=True): + checkefficiency=True, for_chart=False): data = read_df_sql(id) try: data['deltat'] = data['time'].diff() @@ -1220,7 +1455,7 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, if data.empty: rowdata, row = getrowdata(id=id) if not rowdata.empty: # pragma: no cover - data = dataprep(rowdata.df, id=id, bands=True, + data = dataplep(rowdata.df, id=id, bands=True, barchart=True, otwpower=True) else: data = pd.DataFrame() # returning empty dataframe @@ -1235,15 +1470,207 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, data = add_efficiency(id=id) if doclean: # pragma: no cover - data = clean_df_stats(data, ignorehr=True) + data = clean_df_stats(data, ignorehr=True, for_chart=for_chart) return data, row -# Fetch a subset of the data from the DB +def getrowdata_pl(id=0, doclean=False, convertnewtons=True, + checkefficiency=True, for_chart=False): + data = read_df_sql(id,polars=True) + try: + data = data.with_columns((pl.col('time').diff()).alias("deltat")) # data['time'].diff() + except KeyError: # pragma: no cover + data = pl.DataFrame() + + if data.is_empty(): + rowdata, row = getrowdata(id=id) + if not rowdata.empty: # pragma: no cover + data = dataplep(rowdata.df, id=id, bands=True, + barchart=True, otwpower=True, polars=True) + else: + data = pl.DataFrame() # returning empty dataframe + else: + row = Workout.objects.get(id=id) + + if checkefficiency is True and not data.is_empty(): + try: + if data['efficiency'].mean() == 0 and data['power'].mean() != 0: # pragma: no cover + data = add_efficiency_pl(id=id, polars=True) + except KeyError: # pragma: no cover + data = add_efficiency_pl(id=id) + + if doclean: # pragma: no cover + data = clean_df_stats(data, ignorehr=True, for_chart=for_chart) + + return data, row -def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, - debug=False): + +def read_data(columns, ids=[], doclean=True, workstrokesonly=True, debug=False, for_chart=False, compute=True, + startenddict={}): + if ids: + csvfilenames = [ + 'media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] + else: + return pl.DataFrame() + + data = [] + columns = [c for c in columns if c != 'None'] + ['distance', 'spm', 'workoutid','workoutstate', 'driveenergy'] + columns = list(set(columns)) + + for id, f in zip(ids, csvfilenames): + if os.path.isfile(f): + df = pl.scan_parquet(f) + if startenddict: + try: + startsecond, endsecond = startenddict[id] + df = df.filter(pl.col("time") >= 1.0e3*startsecond, + pl.col("time") <= 1.0e3*endsecond) + df = df.with_columns(time = pl.col("time")-1.0e3*startsecond) + if 'cumdist' in columns: + df = df.collect() + df = df.with_columns(cumdist = pl.col("cumdist")-df[0, "cumdist"]).lazy() + except KeyError: + pass + data.append(df) + else: + rowdata, row = getrowdata(id=id) + try: + shutil.rmtree(f) + except: + pass + if rowdata and len(rowdata.df): + _ = dataplep(rowdata.df, id=id, + bands=True, otwpower=True, barchart=True, + polars=True) + df = pl.scan_parquet(f) + if startenddict: + try: + startsecond, endsecond = startenddict[id] + df = df.filter(pl.col("time") >= 1.0e3*startsecond, + pl.col("time") <= 1.0e3*endsecond) + df = df.with_columns(time = pl.col("time")-1.0e3*startsecond) + if 'cumdist' in columns: + df = df.collect() + df = df.with_columns(cumdist = pl.col("cumdist")-df[0, "cumdist"]).lazy() + except KeyError: + pass + data.append(df) + + data = pl.collect_all(data) + if len(data)==0: + return pl.DataFrame() + + try: + datadf = pl.concat(data).select(columns) + except (SchemaError, ShapeError): + data = [ + df.select(columns) + for df in data] + + # float columns + floatcolumns = [] + intcolumns = [] + for c in columns: + try: + if metricsdicts[c]['numtype'] == 'float': + floatcolumns.append(c) + if metricsdicts[c]['numtype'] == 'integer': + intcolumns.append(c) + except KeyError: + pass + data = [ + df.with_columns( + cs.float().cast(pl.Float64) + ).with_columns( + cs.integer().cast(pl.Int64) + ).with_columns( + cs.by_name(intcolumns).cast(pl.Int64) + ).with_columns( + cs.by_name(floatcolumns).cast(pl.Float64) + ) + for df in data + ] + + try: + datadf = pl.concat(data) + except SchemaError: + data = [ + df.with_columns(cs.integer().cast(pl.Float64)) for df in data + ] + datadf = pl.concat(data) + + + + + exprs = [] + + if workstrokesonly: + workoutstatesrest = [3] + exprs.append(~pl.col("workoutstate").is_in(workoutstatesrest)) + + # got data + if not doclean: + if exprs: + datadf2 = datadf.filter(exprs) + if not datadf2.is_empty(): + return datadf2 + + return datadf + + # do clean + if "spm" in datadf.columns: + exprs.append(pl.col("spm") >= 10 ) + exprs.append(pl.col("spm") <= 120) + if "pace" in datadf.columns: + exprs.append(pl.col("pace") <= 300*1000.) + exprs.append(pl.col("pace") >= 60*1000.) + if "power" in datadf.columns: + exprs.append(pl.col("power") <= 5000) + exprs.append(pl.col("power")>=20) + + if "rhythm" in datadf.columns: + exprs.append(pl.col("rhythm")>=0) + exprs.append(pl.col("rhythm")<=70) + if "efficiency" in datadf.columns: + exprs.append(pl.col("efficiency")<=200) + exprs.append(pl.col("efficiency")>=0) + if "wash" in datadf.columns: + exprs.append(pl.col("wash")>=1) + if "drivelength" in datadf.columns: + exprs.append(pl.col("drivelength")>=0.5) + if "forceratio" in datadf.columns: + exprs.append(pl.col("forceratio")>=0.2) + exprs.append(pl.col("forceratio")<=1.0) + if "drivespeed" in datadf.columns: + exprs.append(pl.col("drivespeed")>=0.5) + exprs.append(pl.col("drivespeed")<=4) + if "driveenergy" in datadf.columns: + exprs.append(pl.col("driveenergy")<=2000) + exprs.append(pl.col("driveenergy")>=100) + if "catch" in datadf.columns: + exprs.append(pl.col("catch")<=-30) + + if exprs: + datadf2 = datadf.filter(exprs) + + if not datadf2.is_empty(): + return datadf2 + + exprs = [] + if workstrokesonly: + workoutstatesrest = [3] + exprs.append(~pl.col("workoutstate").is_in(workoutstatesrest)) + + if exprs: + datadf2 = datadf.filter(exprs) + if not datadf2.is_empty(): + return datadf2 + + return datadf + +def getsmallrowdata_pd(columns, ids=[], doclean=True, workstrokesonly=True, compute=True, + debug=False, for_chart=False): # prepmultipledata(ids) if ids: @@ -1266,14 +1693,13 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except (OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(f, columns=columns) data.append(df) except (OSError, ArrowInvalid, IndexError): pass - try: df = pd.concat(data, axis=0) except ValueError: # pragma: no cover @@ -1286,7 +1712,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except (OSError, IndexError, ArrowInvalid): rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(csvfilenames[0], columns=columns) @@ -1297,7 +1723,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp except: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): # pragma: no cover - data = dataprep( + data = dataplep( rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(csvfilenames[0], columns=columns) @@ -1311,9 +1737,10 @@ def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True, comp data = df.copy() if doclean: data = clean_df_stats(data, ignorehr=True, - workstrokesonly=workstrokesonly) + workstrokesonly=workstrokesonly, + for_chart=for_chart) data.dropna(axis=1, how='all', inplace=True) - data.dropna(axis=0, how='any', inplace=True) + data.dropna(axis=0, how='all', inplace=True) return data except TypeError: pass @@ -1363,13 +1790,51 @@ def prepmultipledata(ids, verbose=False): # pragma: no cover if verbose: print(id) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, id=id, bands=True, + _ = dataplep(rowdata.df, id=id, bands=True, barchart=True, otwpower=True) return ids # Read a set of columns for a set of workout ids, returns data as a # pandas dataframe +def read_cols_pl(ids, columns): + extracols = [] + + + columns = list(columns) + ['distance', 'spm', 'workoutid'] + columns = [x for x in columns if x != 'None'] + columns = list(set(columns)) + ids = [int(id) for id in ids] + + df = pl.DataFrame() + + if len(ids) == 0: + return pl.DataFrame() + + df = read_data(columns, ids=ids, doclean=False, compute=False) + + if 'peakforce' in columns: + funits = ((w.id, w.forceunit) + for w in Workout.objects.filter(id__in=ids)) + for id, u in funits: + if u == 'lbs': + df = df.with_columns( + peakforce=pl.when(pl.col('workoutid')==id) + .then(pl.col('peakforce') * lbstoN) + .otherwise(pl.col('peakforce'))) + if 'averageforce' in columns: + funits = ((w.id, w.forceunit) + for w in Workout.objects.filter(id__in=ids)) + for id, u in funits: + if u == 'lbs': + df = df.with_columns( + averageforce=pl.when(pl.col('workoutid')==id) + .then(pl.col('averageforce') * lbstoN) + .otherwise(pl.col('averageforce'))) + + + return df, extracols + def read_cols_df_sql(ids, columns, convertnewtons=True): # drop columns that are not in offical list @@ -1396,7 +1861,7 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): except OSError: rowdata, row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): - _ = dataprep(rowdata.df, + _ = dataplep(rowdata.df, id=ids[0], bands=True, otwpower=True, barchart=True) pq_file = pq.ParquetDataset(filename) columns_in_file = [c for c in columns if c in pq_file.schema.names] @@ -1414,7 +1879,7 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): except (OSError, IndexError, ArrowInvalid): rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): # pragma: no cover - _ = dataprep(rowdata.df, id=id, + _ = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) pq_file = pq.ParquetDataset(f) columns_in_file = [c for c in columns if c in pq_file.schema.names] @@ -1451,14 +1916,35 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): # Read stroke data from the DB for a Workout ID. Returns a pandas dataframe -def read_df_sql(id): +def read_df_sql(id, polars=False): + if polars: + try: + f = 'media/strokedata_{id}.parquet.gz'.format(id=id) + df = pl.read_parquet(f) + except (IsADirectoryError, FileNotFoundError, OSError, ArrowInvalid, IndexError): # pragma: no cover + rowdata, row = getrowdata(id=id) + try: + shutil.rmtree(f) + except: + pass + if rowdata and len(rowdata.df): + _ = dataplep(rowdata.df, id=id, + bands=True, otwpower=True, barchart=True, + polars=True) + try: + df = pl.read_parquet(f, columns=columns) + except (OSError, ArrowInvalid, IndexError): + pass + df = df.fill_nan(None).drop_nulls() + + return df try: f = 'media/strokedata_{id}.parquet.gz'.format(id=id) df = pd.read_parquet(f) except (OSError, ArrowInvalid, IndexError): # pragma: no cover rowdata, row = getrowdata(id=id) if rowdata and len(rowdata.df): - data = dataprep(rowdata.df, id=id, bands=True, + data = dataplep(rowdata.df, id=id, bands=True, otwpower=True, barchart=True) try: df = pd.read_parquet(f) @@ -1496,7 +1982,7 @@ def datafusion(id1, id2, columns, offset): df1[' latitude'] = latitude df1[' longitude'] = longitude - df2 = getsmallrowdata_db(['time'] + columns, ids=[id2], doclean=False) + df2 = getsmallrowdata_pd(['time'] + columns, ids=[id2], doclean=False) forceunit = 'N' @@ -1533,7 +2019,7 @@ def datafusion(id1, id2, columns, offset): def fix_newtons(id=0, limit=3000): # pragma: no cover # rowdata,row = getrowdata_db(id=id,doclean=False,convertnewtons=False) - rowdata = getsmallrowdata_db(['peakforce'], ids=[id], doclean=False) + rowdata = read_data(['peakforce'], ids=[id], doclean=False) try: peakforce = rowdata['peakforce'] if peakforce.mean() > limit: @@ -1546,6 +2032,13 @@ def fix_newtons(id=0, limit=3000): # pragma: no cover pass +def remove_invalid_columns_pl(df): # pragma: no cover + for c in df.get_columns(): + if c not in allowedcolumns: + df = df.drop(c) + + return df + def remove_invalid_columns(df): # pragma: no cover for c in df.columns: if c not in allowedcolumns: @@ -1553,6 +2046,36 @@ def remove_invalid_columns(df): # pragma: no cover return df +def add_efficiency_pl(id=0): # pragma: no cover + rowdata, row = getrowdata_pl(id=id, + doclean=False, + convertnewtons=False, + checkefficiency=False) + power = rowdata['power'] + pace = rowdata['pace'] / 1.0e3 + velo = 500. / pace + ergpw = 2.8 * velo**3 + efficiency = 100. * ergpw / power + + efficiency = efficiency.replace([-np.inf, np.inf], np.nan) + efficiency.fillna(method='ffill') + rowdata = rowdata.with_columns(pl.col(efficiency).alias("efficiency")) # ['efficiency'] = efficiency + + rowdata = remove_invalid_columns_pl(rowdata) + rowdata = rowdata.replace([-np.inf, np.inf], np.nan) + rowdata = rowdata.fillna(method='ffill') + + delete_strokedata(id) + + + if id != 0: + rowdata = rowdata.with_column(pl.lit(id).alias("workoutid")) + filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) + rowdata.write_parquet(filename, compression='gzip') + + + return rowdata + def add_efficiency(id=0): # pragma: no cover rowdata, row = getrowdata_db(id=id, @@ -1588,9 +2111,242 @@ def add_efficiency(id=0): # pragma: no cover # saves it to the stroke_data table in the database # Takes a rowingdata object's DataFrame as input +# polars +def dataplep(rowdatadf, id=0, inboard=0.88, forceunit='lbs', bands=True, barchart=True, otwpower=True, + empower=True, debug=False, polars=True + ): + # rowdatadf is pd.DataFrame + + + if isinstance(rowdatadf, pd.DataFrame): + if rowdatadf.empty: + return 0 + try: + df = pl.from_pandas(rowdatadf) + except ArrowInvalid: + for k, v in dtypes.items(): + try: + rowdatadf[k] = rowdatadf[k].astype(v) + except KeyError: # pragma: no cover + pass + try: + df = pl.from_pandas(rowdatadf) + except ArrowInvalid: + return dataprep(rowdatadf, id=id, inboard=inboard, forceunit=forceunit, bands=bands, barchart=barchart, + otwpower=otwpower, debug=debug,polars=True) + + else: + df = rowdatadf + if df.is_empty(): + return 0 + + df = df.with_columns((pl.col("TimeStamp (sec)")-df[0, "TimeStamp (sec)"]).alias("TimeStamp (sec)")) + df = df.with_columns((pl.col(" Stroke500mPace (sec/500m)").clip(1,3000)).alias(" Stroke500mPace")) + if ' AverageBoatSpeed (m/s)' not in df.columns: + df = df.with_columns((500./pl.col(' Stroke500mPace (sec/500m)')).alias(' AverageBoatSpeed (m/s)')) + if ' WorkoutState' not in df.columns: + df = df.with_columns((pl.lit(0)).alias(" WorkoutState")) + if df[" DriveTime (ms)"].mean() > 0: + df = df.with_columns((100.*pl.col(" DriveTime (ms)")/(pl.col(" DriveTime (ms)")+pl.col(" StrokeRecoveryTime (ms)"))).alias("rhythm")) + else: + df = df.with_columns((pl.lit(0)).alias("rhythm")) + if df[" PeakDriveForce (lbs)"].mean() > 0: + df = df.with_columns((pl.col(" AverageDriveForce (lbs)")/pl.col(" PeakDriveForce (lbs)")).alias("forceratio")) + else: + df = df.with_columns((pl.lit(0)).alias("forceratio")) + f = df['TimeStamp (sec)'].diff().mean() + if f != 0 and not np.isinf(f): + try: + windowsize = 2 * (int(10. / (f))) + 1 + except ValueError: + windowsize = 1 + else: + windowsize = 1 + + if windowsize <= 3: + windowsize = 5 + + try: + df.with_columns( + (pl.col(" Cadence (stokes/min)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" Cadence (stokes/min)")) + except ComputeError: + pass + try: + df.with_columns( + (pl.col(" DriveLength (meters)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" DriveLength (meters)")) + except ComputeError: + pass + try: + df.with_columns( + (pl.col(" HRCur (bpm)").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode() + ).alias(" HRCur (bpm)")) + except ComputeError: + pass + try: + df.with_columns((pl.col("forceratio").map_batches(lambda x: savgol_filter(x.to_numpy(), windowsize, 3)).explode()).alias("forceratio")) + except ComputeError: + pass + + df = df.with_columns((pl.col(" DriveLength (meters)") / pl.col(" DriveTime (ms)") * 1.0e3).alias("drivespeed")) + if df[" DriveTime (ms)"].mean() == 0: + df = df.with_columns((pl.lit(0)).alias("drivespeed")) + + + if 'driveenergy' not in df.columns: + if forceunit == 'lbs': + df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)") * lbstoN).alias("driveenergy")) + else: + df = df.with_columns((pl.col(" DriveLength (meters)") * pl.col(" AverageDriveForce (lbs)")).alias("driveenergy")) + + + if forceunit == 'lbs': + df = df.with_columns((pl.col(" AverageDriveForce (lbs)") * lbstoN).alias(" AverageDriveForce (lbs)")) + df = df.with_columns((pl.col(" PeakDriveForce (lbs)") * lbstoN).alias(" PeakDriveForce (lbs)")) + + if df["driveenergy"].mean() == 0 and df["driveenergy"].std() == 0: + df = df.with_columns((0.0*pl.col("driveenergy")+100).alias("driveenergy")) + + df = df.with_columns((60. * pl.col(" AverageBoatSpeed (m/s)")/pl.col(" Cadence (stokes/min)")).alias("distanceperstroke")) + + t2 = df["TimeStamp (sec)"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) + p2 = df[" Stroke500mPace"].map_elements(lambda x: timedeltaconv(x), return_dtype=pl.Datetime) + + data = pl.DataFrame( + dict( + time=df["TimeStamp (sec)"] * 1e3, + hr=df[" HRCur (bpm)"], + pace=df[" Stroke500mPace"] * 1e3, + spm=df[" Cadence (stokes/min)"], + velo=df[" AverageBoatSpeed (m/s)"], + cumdist=df["cum_dist"], + ftime=niceformat(t2), + fpace=nicepaceformat(p2), + driveenergy=df["driveenergy"], + power=df[' Power (watts)'], + workoutstate=df[" WorkoutState"], + averageforce=df[" AverageDriveForce (lbs)"], + drivelength=df[" DriveLength (meters)"], + peakforce=df[" PeakDriveForce (lbs)"], + forceratio=df["forceratio"], + distance=df["cum_dist"], + drivespeed=df["drivespeed"], + rhythm=df["rhythm"], + distanceperstroke=df["distanceperstroke"], + ) + ) + + data = data.with_columns( + hr_ut2 = df['hr_ut2'], + hr_ut1 = df['hr_ut1'], + hr_at = df['hr_at'], + hr_tr = df['hr_tr'], + hr_an = df['hr_an'], + hr_max = df['hr_max'], + hr_bottom = 0.0*df[' HRCur (bpm)'], + ) + + if 'wash' not in df.columns: + data = data.with_columns( + wash = pl.lit(0.0), + catch = pl.lit(0.0), + peakforceangle = pl.lit(0.0), + finish = pl.lit(0.0), + slip = pl.lit(0.0), + totalangle = pl.lit(0.0), + effectiveangle = pl.lit(0.0), + efficiency = pl.lit(0.0), + ) + else: + wash = df['wash'] + catch = df['catch'] + finish = df['finish'] + peakforceangle = df['peakforceangle'] + arclength = (inboard - 0.05) * (np.radians(finish) - np.radians(catch)) + if arclength.mean() > 0: + drivelength = arclength + else: + drivelength = data['drivelength'] + + slip = df['slip'] + totalangle = finish - catch + effectiveangle = finish - wash - catch - slip + + if windowsize > 3 and windowsize < len(slip): + try: + wash = savgol_filter(wash, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + slip = savgol_filter(slip, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + catch = savgol_filter(catch, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + finish = savgol_filter(finish, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + peakforceangle = savgol_filter(peakforceangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + drivelength = savgol_filter(drivelength, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + totalangle = savgol_filter(totalangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + try: + effectiveangle = savgol_filter(effectiveangle, windowsize, 3) + except TypeError: # pragma: no cover + pass + + data = data.with_columns( + wash = wash, + catch = catch, + slip = slip, + finish = finish, + peakforceangle = peakforceangle, + drivelength = drivelength, + totalangle = totalangle, + effectiveangle = effectiveangle, + ) + + ergpw = 2.8*data['velo']**3 + efficiency = 100. * ergpw / data['power'] + if data['power'].mean() == 0: + efficiency = 100.+0.0*data['power'] + + data = data.with_columns(efficiency=efficiency) + + if id != 0: + data = data.with_columns( + workoutid = pl.lit(id) + ) + # cast data + for k, v in dtypes.items(): + if v == 'int': + data = data.cast({k: pl.Int64}) + filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) + try: + data.write_parquet(filename, compression='gzip') + except IsADirectoryError: + shutil.rmtree(filename) + data.write_parquet(filename, compression='gzip') + + + return data + +# pandas/a little polars def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, - empower=True, inboard=0.88, forceunit='lbs', debug=False): + empower=True, inboard=0.88, forceunit='lbs', debug=False, polars=True): if rowdatadf.empty: return 0 @@ -1870,14 +2626,28 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) df = dd.from_pandas(data, npartitions=1) - try: - df.to_parquet(filename, engine='fastparquet', compression='GZIP') - except FileNotFoundError: - df2 = dd.from_pandas(df, npartitions=1) - df2.to_parquet(filename, engine='fastparquet', compression='GZIP') - except FileExistsError: - os.remove(filename) - df.to_parquet(filename, engine='fastparquet', compression='GZIP') + + if polars: + pldf = pl.from_pandas(data) + try: + pldf.write_parquet(filename, compression='gzip') + except IsADirectoryError: + shutil.rmtree(filename) + pldf.write_parquet(filename, compression='gzip') + else: + try: + df.to_parquet(filename, engine='fastparquet', compression='gzip') + except FileNotFoundError: + df2 = dd.from_pandas(df, npartitions=1) + df2.to_parquet(filename, engine='fastparquet', compression='gzip') + except FileExistsError: + os.remove(filename) + df.to_parquet(filename, engine='fastparquet', compression='GZIP') + + if polars: + pldf = pl.from_pandas(data) + return pldf + return data @@ -2005,18 +2775,20 @@ def add_c2_stroke_data_db(strokedata, workoutid, starttimeunix, csvfilename, ' lapIdx': lapidx, ' WorkoutState': 4, ' ElapsedTime (sec)': seconds, - 'cum_dist': dist2 + 'cum_dist': dist2, }) df.sort_values(by='TimeStamp (sec)', ascending=True) # Create CSV file name and save data to CSV file - - res = df.to_csv(csvfilename, index_label='index', - compression='gzip') + row = rrdata(df=df) + row.write_csv(csvfilename) + row = rrdata_pl(df=pl.from_pandas(row.df)) + #res = df.to_csv(csvfilename, index_label='index', + # compression='gzip') - data = dataprep(df, id=workoutid, bands=False, debug=debug) + data = dataplep(row.df, id=workoutid, bands=False, debug=debug) return data @@ -2059,7 +2831,7 @@ def create_c2_stroke_data_db( else: power = 0 - df = pd.DataFrame({ + df = pl.DataFrame({ 'TimeStamp (sec)': unixtime, ' Horizontal (meters)': d, ' Cadence (stokes/min)': spm, @@ -2080,11 +2852,12 @@ def create_c2_stroke_data_db( 'cum_dist': d }) - df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] + df = df.with_columns((pl.col("TimeStamp (sec)")).alias(" ElapsedTime (sec)")) - _ = df.to_csv(csvfilename, index_label='index', compression='gzip') + row = rrdata_pl(df=df) + row.writecsv(csvfilename, compression=True) - data = dataprep(df, id=workoutid, bands=False, debug=debug) + data = dataplep(df, id=workoutid, bands=False, debug=debug) return data @@ -2123,7 +2896,7 @@ def update_empower(id, inboard, oarlength, boattype, df, f1, debug=False): # pr if debug: # pragma: no cover print("not updated ", id) - _ = dataprep(df, id=id, bands=True, barchart=True, otwpower=True, debug=debug) + _ = dataplep(df, id=id, bands=True, barchart=True, otwpower=True, debug=debug) row = rrdata(df=df) row.write_csv(f1, gzip=True) diff --git a/rowers/datautils.py b/rowers/datautils.py index cb17a357..0b24d252 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -1,8 +1,10 @@ import pandas as pd +import polars as pl import numpy as np from scipy.interpolate import griddata from scipy import optimize + from rowers.mytypes import otwtypes, otetypes, rowtypes from rowers.models import Workout @@ -81,8 +83,9 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): p1 = p0 - thesecs = powerdf['Delta'] - theavpower = powerdf['CP'] + thesecs = powerdf['Delta'].to_numpy() + theavpower = powerdf['CP'].to_numpy() + if len(thesecs) >= 4: try: @@ -95,6 +98,7 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): else: factor = fitfunc(p0, thesecs.mean())/theavpower.mean() p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] + p1 = [abs(p) for p in p1] fitt = pd.Series(10**(4*np.arange(100)/100.)) @@ -102,7 +106,7 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): fitpower = fitfunc(p1, fitt) fitpoints = fitfunc(p1, thesecs) - fitpoints0 = fitpoints.copy() + fitpoints0 = fitpoints dd = fitpoints-theavpower ddmin = dd.min() @@ -356,8 +360,8 @@ def getmaxwattinterval(tt, ww, i): def getfastest(df, thevalue, mode='distance'): - tt = df['time'].copy() - dd = df['cumdist'].copy() + tt = df['time'].clone() + dd = df['cumdist'].clone() tmax = tt.max() if mode == 'distance': # pragma: no cover @@ -368,40 +372,28 @@ def getfastest(df, thevalue, mode='distance'): return 0 -# 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. - newlen = 1000 newt = np.arange(newlen)*tmax/float(newlen) deltat = newt[1]-newt[0] - dd = griddata(tt.values, - dd.values, newt, method='linear', rescale=True) + dd = griddata(tt.to_numpy(), + dd.to_numpy(), newt, method='linear', rescale=True) - tt = pd.Series(newt, dtype='float') - dd = pd.Series(dd, dtype='float') + tt = pl.Series(newt, dtype=pl.Float64) + dd = pl.Series(dd, dtype=pl.Float64) + + G = pl.concat([pl.Series([0.0]), dd]) - G = pd.concat([pd.Series([0]), dd]) - # T = pd.concat([pd.Series([0]), dd]) - # 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) - # Thor = np.outer(ones, T) - # Tver = np.outer(T, ones) + Gver = np.outer(G, ones) Gdif = Ghor-Gver Gdif = np.tril(Gdif.T).T - Gdif = pd.DataFrame(Gdif) + Gdif = pl.DataFrame(Gdif) F = Gdif - F.fillna(inplace=True, method='ffill', axis=1) - F.fillna(inplace=True, value=0) + F = F.fill_nan(0) restime = [] distance = [] @@ -412,7 +404,7 @@ def getfastest(df, thevalue, mode='distance'): restime.append(deltat*i) cp = np.diag(F, i).max() loc = np.argmax(np.diag(F, i)) - thestarttime = tt[loc] + thestarttime = tt.to_numpy()[loc] starttimes.append(thestarttime) distance.append(cp) @@ -422,10 +414,6 @@ def getfastest(df, thevalue, mode='distance'): distance = np.array(distance) starttimes = np.array(starttimes) - # for i in range(len(restime)): - # if restime[i]= rower.ut2).filter(pl.col("hr") < rower.ut1) + frac_ut2 = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{ut1} <= hr < {at}'.format(ut1=rower.ut1, at=rower.at) - frac_ut1 = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.ut1).filter(pl.col("hr") < rower.at) + frac_ut1 = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{at} <= hr < {tr}'.format(at=rower.at, tr=rower.tr) - frac_at = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.at).filter(pl.col("hr") < rower.tr) + frac_at = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = '{tr} <= hr < {an}'.format(tr=rower.tr, an=rower.an) - frac_tr = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.lazy().filter(pl.col("hr") >= rower.tr).filter(pl.col("hr") < rower.an) + frac_tr = totalseconds*qrydata.collect()['deltat'].sum()/sumtimehr - qry = 'hr >= {an}'.format(an=rower.an) - frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr + qrydata = df.filter(pl.col("hr") >= rower.an) + frac_an = totalseconds*qrydata['deltat'].sum()/sumtimehr datadict = { '<{ut2}'.format(ut2=hrzones[1]): frac_lut2, @@ -249,24 +256,14 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']]) - TOOLS = 'save,hover' - - z = figure(title="HR "+title, x_range=(-0.5, 1), height=375, - tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime", - ) - - z.wedge(x=0, y=1, radius=0.4, - start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), - line_color='white', fill_color='color', source=data, legend_group='zone') - - z.axis.axis_label = None - z.axis.visible = False - z.grid.grid_line_color = None - z.outline_line_color = None - z.toolbar_location = 'right' - - return components(z) + data_dict = data.to_dict("records") + chart_data = { + 'data': data_dict, + 'title': "HR "+ title + } + script, div = get_chart("/hrpie", chart_data) + return script, div def pretty_timedelta(secs): hours, remainder = divmod(secs, 3600) @@ -315,26 +312,24 @@ def interactive_workouttype_piechart(workouts): except KeyError: # pragma: no cover pass - p = figure(height=350, title="Types", toolbar_location=None, - tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) + data_dict = data.to_dict("records") - p.wedge(x=0, y=1, radius=0.4, - start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'), - line_color="white", fill_color='color', source=data, legend_group='type', ) + chart_data = { + "data": data_dict, + "title": "Types" + } - p.axis.axis_label = None - p.axis.visible = False - p.grid.grid_line_color = None - p.outline_line_color = None - p.toolbar_location = 'right' + + script, div = get_chart("/workouttypepie", chart_data, debug=False) + + return script, div - return components(p) def interactive_boxchart(datadf, fieldname, extratitle='', spmmin=0, spmmax=0, workmin=0, workmax=0): - if datadf.empty: # pragma: no cover + if datadf.is_empty(): # pragma: no cover return '', 'It looks like there are no data matching your filter' columns = datadf.columns @@ -345,319 +340,53 @@ def interactive_boxchart(datadf, fieldname, extratitle='', if 'date' not in columns: # pragma: no cover return '', 'Not enough data' - tooltips = [ - ('Value', '@'+fieldname), - ] - hover = HoverTool(tooltips=tooltips) - - TOOLS = [hover] - - hv.extension('bokeh') - - try: - boxwhiskers = hv.BoxWhisker(datadf, 'date', fieldname) - boxwhiskers.opts(tools=TOOLS, outlier_color='white') - except DataError: # pragma: no cover - return "", "Invalid Data" - - plot = hv.render(boxwhiskers) - - yrange1 = Range1d(start=yaxminima[fieldname], end=yaxmaxima[fieldname]) - plot.y_range = yrange1 - #plot.sizing_mode = 'stretch_both' - - if extratitle: - plot.title.text = extratitle - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = axlabels[fieldname] - - plot.xaxis.formatter = DatetimeTickFormatter( - days=["%d %B %Y"], - months=["%d %B %Y"], - years=["%d %B %Y"], - ) - - if fieldname == 'pace': # pragma: no cover - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - plot.xaxis.major_label_orientation = pi/4 - - plot.width = 920 - plot.height = 600 - - slidertext = 'SPM: {:.0f}-{:.0f}, WpS: {:.0f}-{:.0f}'.format( - spmmin, spmmax, workmin, workmax - ) - sliderlabel = Label(x=50, y=20, x_units='screen', y_units='screen', - text=slidertext, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(sliderlabel) - - script, div = components(plot) + + datadf = datadf.with_columns((pl.col("date").dt.strftime("%Y-%m-%d")).alias("date")) + datadf = datadf.with_columns((pl.col(fieldname)).alias("value")) + + data_dict = datadf.to_dicts() + boxplot_data = { + "metric": metricsdicts[fieldname]["verbose_name"], + "data": data_dict + } + + script, div = get_chart("/boxplot", boxplot_data, debug=False) return script, div def interactive_planchart(data, startdate, enddate): - hv.extension('bokeh') + # data = data.melt(id_vars=['startdate'], value_vars=['executed', 'planned']) - yaxmaximum = data['executed'].max() - if data['planned'].max() > yaxmaximum: # pragma: no cover - yaxmaximum = data['planned'].max() - - if yaxmaximum == 0: # pragma: no cover - yaxmaximum = 250 - - yrange1 = Range1d(start=0, end=1.1*yaxmaximum) - - tidy_df = data.melt(id_vars=['startdate'], value_vars=[ - 'executed', 'planned']) - bars = hv.Bars(tidy_df, ['startdate', 'variable'], ['value']) - bars.opts( - opts.Bars(show_legend=True, tools=['tap', 'hover'], legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.width = 550 - p.height = 350 - p.y_range = yrange1 - p.toolbar_location = 'above' - #p.sizing_mode = 'stretch_both' - - script, div = components(p) - - return script, div - - -def interactive_activitychart(workouts, startdate, enddate, stack='type', toolbar_location=None, - yaxis='trimp'): - - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] - - startdate = datetime.datetime( - year=startdate.year, month=startdate.month, day=startdate.day) - enddate = datetime.datetime( - year=enddate.year, month=enddate.month, day=enddate.day) - - duration = enddate-startdate - - totaldays = duration.total_seconds()/(24*3600) - - for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') - du = w.duration.hour*60+w.duration.minute - rscore = w.rscore - trimp = w.trimp - - if rscore == 0: # pragma: no cover - rscore = w.hrtss - - if totaldays < 30: - dates.append(dd) - dates_sorting.append(dd2) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - rscores.append(rscore) - trimps.append(trimp) - links.append( - "{siteurl}/rowers/workout/{code}/".format( - siteurl=settings.SITE_URL, - code=encoder.encode_hex(w.id) - ) - ) - - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) - - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate - - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass - - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - if totaldays < 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: # pragma: no cover - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) - durations.append(0) - rscores.append(0) - trimps.append(0) - links.append('') - try: - types.append(types[0]) - except IndexError: - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'link': links, + data = data.with_columns((pl.col("startdate").dt.strftime("%Y-%m-%d")).alias("startdate")) + data_dict = data.to_dicts() + chart_data = { + 'data': data_dict, } - df = pd.DataFrame(thedict) - - df.sort_values('date_sorting', inplace=True) - - hv.extension('bokeh') - - if stack == 'type': - table = hv.Table(df, [('date', 'Date'), ('type', 'Workout Type')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - else: - table = hv.Table(df, [('date', 'Date'), ('rower', 'Rower')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - bars = table.to.bars(['date', stack], [yaxis]) - if stack == 'type': - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - else: - bars.opts( - opts.Bars(cmap='Category10', show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - p.y_range.start = 0 - #p.sizing_mode = 'stretch_both' - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df.link}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) + script, div = get_chart("/plan", chart_data, debug=False) return script, div + - -def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, +def interactive_activitychart2(workouts, startdate, enddate, stack='type', yaxis='duration'): - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - distances = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] startdate = datetime.datetime( year=startdate.year, month=startdate.month, day=startdate.day) enddate = datetime.datetime( year=enddate.year, month=enddate.month, day=enddate.day) - duration = enddate-startdate + totaldays = (enddate-startdate).days - totaldays = duration.total_seconds()/(24*3600) + data_dicts = [] + aantal = 1 for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') + rr = w.user + rowersinitials = rr.user.first_name[0:aantal]+rr.user.last_name[0:aantal] + dd = w.date.strftime('%Y-%m-%d') du = w.duration.hour*60+w.duration.minute trimp = w.trimp @@ -666,845 +395,94 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb if rscore == 0: # pragma: no cover rscore = w.hrtss - if totaldays <= 30: # pragma: no cover - dates.append(dd) - dates_sorting.append(dd2) - else: - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - trimps.append(trimp) - rscores.append(rscore) - distances.append(distance) - links.append( - "{siteurl}/rowers/workout/{code}/".format( + + link = "{siteurl}/rowers/workout/{code}/".format( siteurl=settings.SITE_URL, code=encoder.encode_hex(w.id) ) - ) - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) + data_dicts.append({ + 'date': dd, + 'duration': du, + 'distance': distance, + 'trimp': trimp, + 'rscore': rscore, + 'type': w.workouttype, + 'link': link, + 'rower': rowersinitials + }) - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate + - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass + if totaldays < 30: + datebin = "day" + elif totaldays < 50: + datebin = "week" + else: + datebin = "month" - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - if totaldays <= 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) - durations.append(0) - trimps.append(0) - rscores.append(0) - distances.append(0) - links.append('') - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: # pragma: no cover - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'distance': distances, - 'link': links, + stacknames = { + 'TRIMP': 'trimp', + 'distance': 'distance', + 'time': 'duration', + 'rScore': 'rscore', + 'duration': 'duration', } - df = pd.DataFrame(thedict) + chart_data = { + 'data': data_dicts, + 'title': 'Activity {d1} to {d2}'.format( + d1=startdate.strftime("%Y-%m-%d"), + d2=enddate.strftime("%Y-%m-%d"), + ), + 'datebin': datebin, + 'colorby': stack, + 'stackby': stacknames[yaxis], + 'doreduce': True, + 'dosort': True, + 'colors': mytypes.color_map, + } - if totaldays > 30 and yaxis == 'duration': # pragma: no cover - df['duration'] = df['duration']/60 - elif yaxis == 'TRIMP': - df.drop('duration', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'rScore': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'distance': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') + + script, div = get_chart("/activity_bar", chart_data, debug=False) - df['color'] = df['type'].apply(lambda x: mapcolors(x)) - - df.sort_values('date_sorting', inplace=True) - - hv.extension('bokeh') - - # table = hv.Table(df,[('date','Date'),('type','Workout Type')], - # [('duration','Minutes'),('trimp','TRIMP'),('rscore','rScore'),('link','link')]) - - types_order = mytypes.workouttypes_ordered - # bars=table.to.bars(['date',stack],[yaxis]) - bars = hv.Bars(df, kdims=['date', stack]).aggregate( - function=np.sum).redim.values(types=types_order) - - # print(mytypes.color_map) - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.xaxis.axis_label = 'Period' - if yaxis == 'duration': - p.yaxis.axis_label = 'Duration (min)' - if totaldays > 30: # pragma: no cover - p.yaxis.axis_label = 'Duration (h)' - elif yaxis == 'TRIMP': - p.yaxis.axis_label = 'TRIMP' - elif yaxis == 'distance': # pragma: no cover - p.yaxis.axis_label = 'Distance (m)' - else: # pragma: no cover - p.yaxis.axis_label = 'rScore' - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - #p.sizing_mode = 'stretch_both' - p.y_range.start = 0 - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df['link']}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) return script, div - -def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', - spm_min=15, spm_max=45, - notes='', - dist_min=0,dist_max=0, - work_min=0,work_max=1500): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - +def interactive_forcecurve(theworkouts): ids = [int(w.id) for w in theworkouts] boattype = theworkouts[0].boattype columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy'] + 'workoutstate', 'driveenergy', 'cumdist', 'workoutid'] + columns = columns + [name for name, d in metrics.rowingmetrics] - rowdata = dataprep.getsmallrowdata_db(columns, ids=ids, - workstrokesonly=workstrokesonly) + + rowdata = dataprep.read_data(columns, ids=ids, + workstrokesonly=False) - rowdata.dropna(axis=1, how='all', inplace=True) - rowdata.dropna(axis=0, how='any', inplace=True) + if rowdata.is_empty(): + return "", "No Valid Data Available" - workoutstatesrest = [3] + rowdata = dataprep.remove_nulls_pl(rowdata) - if workstrokesonly: - try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] - except KeyError: # pragma: no cover - pass + data_dict = rowdata.to_dicts() - if rowdata.empty: - return "", "No Valid Data Available", "", "" + thresholdforce = 100. if 'x' in boattype else 200. + + chart_data = { + 'title': theworkouts[0].name, + 'data': data_dict, + 'thresholdforce': thresholdforce, + } - try: - covariancematrix = np.cov( - rowdata['peakforceangle'], y=rowdata['peakforce']) - eig_vals, eig_vecs = np.linalg.eig(covariancematrix) + script, div = get_chart("/forcecurve", chart_data, debug=False) + return script, div - a = rowdata['peakforceangle']-rowdata['peakforceangle'].median() - F = rowdata['peakforce']-rowdata['peakforce'].median() - - Rinv = eig_vecs - R = np.linalg.inv(Rinv) - - x = R[0, 0]*a+R[0, 1]*F - y = R[1, 0]*a+R[1, 1]*F - - x05 = x.quantile(q=0.01) - x25 = x.quantile(q=0.15) - x75 = x.quantile(q=0.85) - x95 = x.quantile(q=0.99) - - y05 = y.quantile(q=0.01) - y25 = y.quantile(q=0.15) - y75 = y.quantile(q=0.85) - y95 = y.quantile(q=0.99) - - a25 = Rinv[0, 0]*x25 + rowdata['peakforceangle'].median() - F25 = Rinv[1, 0]*x25 + rowdata['peakforce'].median() - - a25b = Rinv[0, 1]*y25 + rowdata['peakforceangle'].median() - F25b = Rinv[1, 1]*y25 + rowdata['peakforce'].median() - - a75 = Rinv[0, 0]*x75 + rowdata['peakforceangle'].median() - F75 = Rinv[1, 0]*x75 + rowdata['peakforce'].median() - - a75b = Rinv[0, 1]*y75 + rowdata['peakforceangle'].median() - F75b = Rinv[1, 1]*y75 + rowdata['peakforce'].median() - - a05 = Rinv[0, 0]*x05 + rowdata['peakforceangle'].median() - F05 = Rinv[1, 0]*x05 + rowdata['peakforce'].median() - - a05b = Rinv[0, 1]*y05 + rowdata['peakforceangle'].median() - F05b = Rinv[1, 1]*y05 + rowdata['peakforce'].median() - - a95 = Rinv[0, 0]*x95 + rowdata['peakforceangle'].median() - F95 = Rinv[1, 0]*x95 + rowdata['peakforce'].median() - - a95b = Rinv[0, 1]*y95 + rowdata['peakforceangle'].median() - F95b = Rinv[1, 1]*y95 + rowdata['peakforce'].median() - except KeyError: # pragma: no cover - a25 = 0 - F25 = 0 - - a25b = 0 - F25b = 0 - - a75 = 0 - F75 = 0 - - a75b = 0 - F75b = 0 - - a05 = 0 - F05 = 0 - - a05b = 0 - F05b = 0 - - a95 = 0 - F95 = 0 - - a95b = 0 - F95b = 0 - - try: - catchav = rowdata['catch'].median() - catch25 = rowdata['catch'].quantile(q=0.25) - catch75 = rowdata['catch'].quantile(q=0.75) - catch05 = rowdata['catch'].quantile(q=0.05) - catch95 = rowdata['catch'].quantile(q=0.95) - except KeyError: # pragma: no cover - catchav = 0 - catch25 = 0 - catch75 = 0 - catch05 = 0 - catch95 = 0 - - try: - finishav = rowdata['finish'].median() - finish25 = rowdata['finish'].quantile(q=0.25) - finish75 = rowdata['finish'].quantile(q=0.75) - finish05 = rowdata['finish'].quantile(q=0.05) - finish95 = rowdata['finish'].quantile(q=0.95) - except KeyError: # pragma: no cover - finishav = 0 - finish25 = 0 - finish75 = 0 - finish05 = 0 - finish95 = 0 - - try: - washav = (rowdata['finish']-rowdata['wash']).median() - wash25 = (rowdata['finish']-rowdata['wash']).quantile(q=0.25) - wash75 = (rowdata['finish']-rowdata['wash']).quantile(q=0.75) - wash05 = (rowdata['finish']-rowdata['wash']).quantile(q=0.05) - wash95 = (rowdata['finish']-rowdata['wash']).quantile(q=0.95) - except KeyError: # pragma: no cover - washav = 0 - wash25 = 0 - wash75 = 0 - wash05 = 0 - wash95 = 0 - - try: - slipav = (rowdata['slip']+rowdata['catch']).median() - slip25 = (rowdata['slip']+rowdata['catch']).quantile(q=0.25) - slip75 = (rowdata['slip']+rowdata['catch']).quantile(q=0.75) - slip05 = (rowdata['slip']+rowdata['catch']).quantile(q=0.05) - slip95 = (rowdata['slip']+rowdata['catch']).quantile(q=0.95) - except KeyError: # pragma: no cover - slipav = 0 - slip25 = 0 - slip75 = 0 - slip05 = 0 - slip95 = 0 - - try: - peakforceav = rowdata['peakforce'].median() - except KeyError: # pragma: no cover - peakforceav = 0 - - try: - averageforceav = rowdata['averageforce'].median() - except KeyError: # pragma: no cover - averageforceav = 0 - - try: - peakforceangleav = rowdata['peakforceangle'].median() - except KeyError: # pragma: no cover - peakforceangleav = 0 - - # thresholdforce /= 4.45 # N to lbs - thresholdforce = 100 if 'x' in boattype else 200 - points2575 = [ - (catch25, 0), # 0 - (slip25, thresholdforce), # 1 - (a75, F75), # 4 - (a25b, F25b), # 9 - (a25, F25), # 2 - (wash75, thresholdforce), # 5 - (finish75, 0), # 6 - (finish25, 0), # 7 - (wash25, thresholdforce), # 8 - (a75b, F75b), # 3 - (slip75, thresholdforce), # 10 - (catch75, 0), # 11 - ] - - points0595 = [ - (catch05, 0), # 0 - (slip05, thresholdforce), # 1 - (a95, F95), # 4 - (a05b, F05b), # 9 - (a05, F05), # 2 - (wash95, thresholdforce), # 5 - (finish95, 0), # 6 - (finish05, 0), # 7 - (wash05, thresholdforce), # 8 - (a95b, F95b), # 3 - (slip95, thresholdforce), # 10 - (catch95, 0), # 11 - ] - - angles2575 = [] - forces2575 = [] - - for x, y in points2575: - angles2575.append(x) - forces2575.append(y) - - angles0595 = [] - forces0595 = [] - - for x, y in points0595: - angles0595.append(x) - forces0595.append(y) - - x = [catchav, - slipav, - peakforceangleav, - washav, - finishav] - - y = [0, thresholdforce, - peakforceav, - thresholdforce, 0] - - source = ColumnDataSource( - data=dict( - x=x, - y=y, - )) - - sourceslipwash = ColumnDataSource( - data=dict( - xslip=[slipav, washav], - yslip=[thresholdforce, thresholdforce] - ) - ) - - source2 = ColumnDataSource( - rowdata - ) - - if plottype == 'scatter': # pragma: no cover - try: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=rowdata['peakforceangle'], - peakforce=rowdata['peakforce'] - ) - ) - except KeyError: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=[], - peakforce=[] - ) - ) - else: - sourcepoints = ColumnDataSource( - data=dict( - peakforceangle=[], - peakforce=[] - )) - - sourcerange = ColumnDataSource( - data=dict( - x2575=angles2575, - y2575=forces2575, - x0595=angles0595, - y0595=forces0595, - ) - ) - - plot = figure(tools=TOOLS, - toolbar_sticky=False, toolbar_location="above", width=800, height=600) - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - avf = Span(location=averageforceav, dimension='width', line_color='blue', - line_dash=[6, 6], line_width=2) - - plot.patch('x0595', 'y0595', source=sourcerange, color="red", alpha=0.05) - plot.patch('x2575', 'y2575', source=sourcerange, color="red", alpha=0.2) - plot.line('x', 'y', source=source, color="red") - plot.circle('xslip', 'yslip', source=sourceslipwash, color="red") - - plot.circle('peakforceangle', 'peakforce', - source=sourcepoints, color='black', alpha=0.1) - - if plottype == 'line': - multilinedatax = [] - multilinedatay = [] - for i in range(len(rowdata)): - try: - x = [ - rowdata['catch'].values[i], - rowdata['slip'].values[i]+rowdata['catch'].values[i], - rowdata['peakforceangle'].values[i], - rowdata['finish'].values[i]-rowdata['wash'].values[i], - rowdata['finish'].values[i] - ] - - y = [ - 0, - thresholdforce, - rowdata['peakforce'].values[i], - thresholdforce, - 0] - except KeyError: # pragma: no cover - x = [0, 0] - y = [0, 0] - - multilinedatax.append(x) - multilinedatay.append(y) - - sourcemultiline = ColumnDataSource(dict( - x=multilinedatax, - y=multilinedatay, - )) - - sourcemultiline2 = ColumnDataSource(dict( - x=multilinedatax, - y=multilinedatay, - )) - - glyph = MultiLine(xs='x', ys='y', line_color='black', line_alpha=0.05) - plot.add_glyph(sourcemultiline, glyph) - else: # pragma: no cover - sourcemultiline = ColumnDataSource(dict( - x=[], y=[])) - - sourcemultiline2 = ColumnDataSource(dict( - x=[], y=[])) - - plot.line('x', 'y', source=source, color="red") - - plot.add_layout(avf) - - peakflabel = Label(x=760, y=460, x_units='screen', y_units='screen', - text="Fpeak: {peakforceav:6.2f}".format( - peakforceav=peakforceav), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - avflabel = Label(x=770, y=430, x_units='screen', y_units='screen', - text="Favg: {averageforceav:6.2f}".format( - averageforceav=averageforceav), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - catchlabel = Label(x=765, y=400, x_units='screen', y_units='screen', - text="Catch: {catchav:6.2f}".format(catchav=catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - peakforceanglelabel = Label(x=725, y=370, x_units='screen', y_units='screen', - text="Peak angle: {peakforceangleav:6.2f}".format( - peakforceangleav=peakforceangleav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - finishlabel = Label(x=760, y=340, x_units='screen', y_units='screen', - text="Finish: {finishav:6.2f}".format( - finishav=finishav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - sliplabel = Label(x=775, y=310, x_units='screen', y_units='screen', - text="Slip: {slipav:6.2f}".format(slipav=slipav-catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - washlabel = Label(x=765, y=280, x_units='screen', y_units='screen', - text="Wash: {washav:6.2f}".format( - washav=finishav-washav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='red', - ) - - lengthlabel = Label(x=755, y=250, x_units='screen', y_units='screen', - text="Length: {length:6.2f}".format( - length=finishav-catchav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='green' - ) - - efflengthlabel = Label(x=690, y=220, x_units='screen', y_units='screen', - text="Effective Length: {length:6.2f}".format( - length=washav-slipav), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='green' - ) - - annolabel = Label(x=50, y=450, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - sliderlabel = Label(x=10, y=470, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(peakflabel) - plot.add_layout(peakforceanglelabel) - plot.add_layout(avflabel) - plot.add_layout(catchlabel) - plot.add_layout(sliplabel) - plot.add_layout(washlabel) - plot.add_layout(finishlabel) - plot.add_layout(annolabel) - plot.add_layout(sliderlabel) - plot.add_layout(lengthlabel) - plot.add_layout(efflengthlabel) - - plot.xaxis.axis_label = "Angle" - plot.yaxis.axis_label = "Force (N)" - try: - plot.title.text = theworkouts[0].name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - - yrange1 = Range1d(start=0, end=900) - plot.y_range = yrange1 - - xrange1 = Range1d(start=yaxmaxima['catch'], end=yaxmaxima['finish']) - plot.x_range = xrange1 - - callback = CustomJS(args=dict( - source=source, - source2=source2, - sourceslipwash=sourceslipwash, - sourcepoints=sourcepoints, - avf=avf, - avflabel=avflabel, - catchlabel=catchlabel, - finishlabel=finishlabel, - sliplabel=sliplabel, - washlabel=washlabel, - peakflabel=peakflabel, - peakforceanglelabel=peakforceanglelabel, - annolabel=annolabel, - sliderlabel=sliderlabel, - lengthlabel=lengthlabel, - efflengthlabel=efflengthlabel, - plottype=plottype, - sourcemultiline=sourcemultiline, - sourcemultiline2=sourcemultiline2 - ), code=""" - var data = source.data - var data2 = source2.data - var dataslipwash = sourceslipwash.data - var datapoints = sourcepoints.data - var multilines = sourcemultiline.data - var multilines2 = sourcemultiline2.data - var plottype = plottype - - var multilinesx = multilines2['x'] - var multilinesy = multilines2['y'] - - var x = data['x'] - var y = data['y'] - - var xslip = dataslipwash['xslip'] - - var spm1 = data2['spm'] - var distance1 = data2['distance'] - var driveenergy1 = data2['driveenergy'] - - var thresholdforce = y[1] - - var c = source2.data['catch'] - var finish = data2['finish'] - var slip = data2['slip'] - var wash = data2['wash'] - var peakforceangle = data2['peakforceangle'] - var peakforce = data2['peakforce'] - var averageforce = data2['averageforce'] - - var peakforcepoints = datapoints['peakforce'] - var peakforceanglepoints = datapoints['peakforceangle'] - - var annotation = annotation.value - var minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - - sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0) - sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0) - sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0) - - var catchav = 0 - var finishav = 0 - var slipav = 0 - var washav = 0 - var peakforceangleav = 0 - var averageforceav = 0 - var peakforceav = 0 - var count = 0 - - datapoints['peakforceangle'] = [] - datapoints['peakforce'] = [] - multilines['x'] = [] - multilines['y'] = [] - - for (var i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - if (plottype=='scatter') { - datapoints['peakforceangle'].push(peakforceangle[i]) - datapoints['peakforce'].push(peakforce[i]) - } - if (plottype=='line') { - multilines['x'].push(multilinesx[i]) - multilines['y'].push(multilinesy[i]) - } - catchav += c[i] - finishav += finish[i] - slipav += slip[i] - washav += wash[i] - peakforceangleav += peakforceangle[i] - averageforceav += averageforce[i] - peakforceav += peakforce[i] - count += 1 - } - } - } - } - - catchav /= count - finishav /= count - slipav /= count - washav /= count - peakforceangleav /= count - peakforceav /= count - averageforceav /= count - - data['x'] = [catchav,catchav+slipav,peakforceangleav,finishav-washav,finishav] - data['y'] = [0,thresholdforce,peakforceav,thresholdforce,0] - - dataslipwash['xslip'] = [catchav+slipav,finishav-washav] - dataslipwash['yslip'] = [thresholdforce,thresholdforce] - - var length = finishav-catchav - var efflength = length-slipav-washav - - avf.location = averageforceav - avflabel.text = 'Favg: '+averageforceav.toFixed(2) - catchlabel.text = 'Catch: '+catchav.toFixed(2) - finishlabel.text = 'Finish: '+finishav.toFixed(2) - sliplabel.text = 'Slip: '+slipav.toFixed(2) - washlabel.text = 'Wash: '+washav.toFixed(2) - peakflabel.text = 'Fpeak: '+peakforceav.toFixed(2) - peakforceanglelabel.text = 'Peak angle: '+peakforceangleav.toFixed(2) - annolabel.text = annotation - lengthlabel.text = 'Length: '+length.toFixed(2) - efflengthlabel.text = 'Effective Length: '+efflength.toFixed(2) - - // console.log(count); - // console.log(multilines['x'].length); - // console.log(multilines['y'].length); - - // change DOM elements - document.getElementById("id_spm_min").value = minspm; - document.getElementById("id_spm_max").value = maxspm; - document.getElementById("id_dist_min").value = mindist; - document.getElementById("id_dist_max").value = maxdist; - document.getElementById("id_notes").value = annotation; - document.getElementById("id_work_min").value = minwork; - document.getElementById("id_work_max").value = maxwork; - - // source.trigger('change'); - source.change.emit(); - sourceslipwash.change.emit() - sourcepoints.change.emit(); - sourcemultiline.change.emit(); - """) - - annotation = TextInput( - width=140, title="Type your plot notes here", value="", name="annotation") - annotation.js_on_change('value', callback) - callback.args["annotation"] = annotation - - slider_spm_min = Slider(width=140, start=15.0, end=55, value=15, step=.1, - title="Min SPM", name="min_spm_slider") - slider_spm_min.js_on_change('value', callback) - callback.args["minspm"] = slider_spm_min - - slider_spm_max = Slider(width=140, start=15.0, end=55, value=55, step=.1, - title="Max SPM", name="max_spm_slider") - slider_spm_max.js_on_change('value', callback) - callback.args["maxspm"] = slider_spm_max - - slider_work_min = Slider(width=140, start=0, end=1500, value=0, step=10, - title="Min Work per Stroke", name="min_work_slider") - slider_work_min.js_on_change('value', callback) - callback.args["minwork"] = slider_work_min - - slider_work_max = Slider(width=140, start=0, end=1500, value=1500, step=10, - title="Max Work per Stroke", name="max_work_slider") - slider_work_max.js_on_change('value', callback) - callback.args["maxwork"] = slider_work_max - - distmax = 100+100*int(rowdata['distance'].max()/100.) - - slider_dist_min = Slider(width=140, start=0, end=distmax, value=0, step=50, - title="Min Distance", name="min_dist_slider") - slider_dist_min.js_on_change('value', callback) - callback.args["mindist"] = slider_dist_min - - if dist_max == 0: - dist_max = distmax - - slider_dist_max = Slider(width=140, start=0, end=distmax, value=distmax, - step=50, - title="Max Distance", name="max_dist_slider") - slider_dist_max.js_on_change('value', callback) - callback.args["maxdist"] = slider_dist_max - - thesliders = layoutcolumn([annotation, - slider_spm_min, - slider_spm_max, - slider_dist_min, - slider_dist_max, - slider_work_min, - slider_work_max, - ] - ) - - mylayout = layoutrow([thesliders, plot]) - - #mylayout.sizing_mode = 'stretch_both' - - script, div = components(mylayout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - return [script, div, js_resources, css_resources] def weightfromrecord(row,metricchoice): - vv = row[metricchoice] + vv = row[metricchoice][0] if vv > 0: return vv if metricchoice == 'rscore': # pragma: no cover @@ -1530,18 +508,19 @@ def getfatigues( lambda_c = 2/(kfitness+1) nrdays = (enddate-startdate).days + for i in range(nrdays+1): date = startdate+datetime.timedelta(days=i) datekey = date.strftime('%Y-%m-%d') weight = 0 try: - df2 = df.loc[date.date()] + df2 = df.filter(pl.col("date") == date.date()) - if type(df2) == pd.Series: # pragma: no cover + if type(df2) == pl.Series: # pragma: no cover weight += weightfromrecord(df2,metricchoice) else: - for index, row in df2.iterrows(): + for row in df2.iter_slices(n_rows=1): weight += weightfromrecord(row,metricchoice) except KeyError: pass @@ -1568,7 +547,6 @@ def goldmedalscorechart(user, startdate=None, enddate=None): enddate = arrow.get(enddate).datetime.replace( hour=0, minute=0, second=0, microsecond=0) - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' # marker workouts workouts = Workout.objects.filter(user=user.rower, date__gte=startdate, @@ -1585,20 +563,20 @@ def goldmedalscorechart(user, startdate=None, enddate=None): testduration = [ w.goldmedalseconds if w.rankingpiece else 0 for w in markerworkouts] - df = pd.DataFrame({ + df = pl.DataFrame({ 'id': outids, 'date': dates, 'testpower': testpower, 'testduration': testduration, }) - df.sort_values(['date'], inplace=True) + df = df.sort('date') + df = df.drop_nulls() - mask = df['testpower'].isnull() - dates = df.mask(mask)['date'].dropna().values - testpower = df.mask(mask)['testpower'].dropna().values - ids = df.mask(mask)['id'].dropna().values - - outids = df.mask(mask)['id'].dropna().unique() + dates = df['date'] + testpower = df['testpower'] + ids = df['id'] + + outids = ids.unique() # all workouts alldates, alltestpower, allduration, allids = all_goldmedalstandards( @@ -1640,7 +618,7 @@ def goldmedalscorechart(user, startdate=None, enddate=None): duration.append(np.nan) workoutid.append(0) - df = pd.DataFrame({ + df = pl.DataFrame({ 'markerscore': markerscore, 'markerduration': markerduration, 'score': score, @@ -1649,95 +627,29 @@ def goldmedalscorechart(user, startdate=None, enddate=None): 'id': workoutid, }) - df['url'] = df['id'].apply(lambda x: settings.SITE_URL + - '/rowers/workout/{id}/'.format(id=encoder.encode_hex(x))) - df['workout'] = df['id'].apply(lambda x: workoutname(x)) + df = df.with_columns((pl.col("id").map_elements(lambda x: settings.SITE_URL + + '/rowers/workout/{id}/'.format(id=encoder.encode_hex(x)))).alias("url")) + df = df.with_columns((pl.col("id").map_elements(lambda x: workoutname(x))).alias("workout")) - df.sort_values(['date'], inplace=True) + df = df.sort('date') # find index values where score is max - idx = df.groupby(['date'])['score'].transform(max) == df['score'] - df = df[idx] + dfmax = df.group_by("date", maintain_order=True).max() + dfmax = dfmax.fill_nan(0) + dfmax = dfmax.with_columns((pl.col("date").dt.strftime("%Y-%m-%d")).alias("date")) + dfmax = dfmax.with_columns((pl.col("duration").map_elements(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("duration")) + dfmax = dfmax.with_columns((pl.col("markerduration").map_elements(lambda x: totaltime_sec_to_string(x, shorten=True))).alias("markerduration")) - source = ColumnDataSource( - data=dict( - markerscore=df['markerscore'], - score=df['score'], - markerduration=df['markerduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - duration=df['duration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - url=df['url'], - workout=df['workout'] - ) - ) - - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=600, - toolbar_location='above', - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = 'Gold Medal Score' - - plot.circle('date', 'score', source=source, fill_color='blue', - size=10, - legend_label='Workouts') - - plot.circle('date', 'markerscore', source=source, fill_color='red', - size=10, - legend_label='Marker Workouts') - - plot.legend.location = "bottom_left" - - plot.x_range = Range1d( - startdate, enddate+datetime.timedelta(days=5), - ) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Marker', '@markerscore{int}'), - ('Test', '@markerduration'), - ('Score', '@score{int}'), - ('Duration', '@duration'), - ('Date', '@fdate'), - ('Workout', '@workout') - ]) - - taptool = plot.select(type=TapTool) - taptool.callback = OpenURL(url='@url') - - script, div = components(plot) + data_dicts = dfmax.to_dicts() + chart_data = { + 'data': data_dicts + } + script, div = get_chart("/markerworkouts", chart_data) return script, div, outids + def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue=7, metricchoice='trimp', doform=False, dofatigue=False, showtests=False): @@ -1785,10 +697,9 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= } records.append(dd) - df = pd.DataFrame.from_records(records) - if df.empty: # pragma: no cover + df = pl.from_records(records) + if df.is_empty(): # pragma: no cover return ['', 'No Data', 0, 0, 0, outids] - df.set_index('date', inplace=True) markerworkouts = Workout.objects.filter( user=user.rower, date__gte=startdate-datetime.timedelta(days=90), @@ -1820,7 +731,7 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= kfatigue, kfitness ) - df = pd.DataFrame({ + df = pl.DataFrame({ 'date': dates, 'testpower': testpower, 'testduration': testduration, @@ -1835,186 +746,48 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= endform = endfitness-endfatigue if modelchoice == 'banister': # pragma: no cover - df['fatigue'] = k2*df['fatigue'] - df['fitness'] = p0+k1*df['fitness'] + df = df.with_columns((pl.col("fatigue")*k2)) + df = df.with_columns((p0+pl.col("fitness")*k1)) - df['form'] = df['fitness']-df['fatigue'] + df = df.with_columns((pl.col("fitness")-pl.col("fatigue")).alias("form")) + df = df.sort("date") + + df = df.group_by('date').max() + startdate = startdate.replace(tzinfo=None) + startdate = pytz.utc.localize(startdate) - df.sort_values(['date'], inplace=True) - df = df.groupby(['date']).max() - df['date'] = df.index.values - mask = df['date'] > np.datetime64(startdate.astimezone( - tz=datetime.timezone.utc).replace(tzinfo=None)) - df = df.loc[mask] + df = df.filter(pl.col("date") > startdate) - source = ColumnDataSource( - data=dict( - testpower=df['testpower'], - testduration=df['testduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - fitness=df['fitness'], - fatigue=df['fatigue'], - form=df['form'], - impulse=df['impulse'] - ) - ) + df2 = pl.DataFrame({ + "testpower" :df['testpower'], + "testduration":df['testduration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + "fitness":df['fitness'], + "fatigue":df['fatigue'], + "form":df['form'], + "impulse":df['impulse'], + "date": df['date'].dt.strftime('%Y-%m-%d'), + }) - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=300, - toolbar_location="above", - toolbar_sticky=False) + df2 = df2.fill_nan(0) - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} + data_dict = df2.to_dicts() - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - fitlabel = 'Fitness' - fatiguelabel = 'Fatigue' - formlabel = 'Freshness' - rightaxlabel = 'Freshness' - if dofatigue: # pragma: no cover - yaxlabel = 'Fitness/Fatigue' - else: # pragma: no cover - yaxlabel = 'Fitness' - - if modelchoice == 'banister': # pragma: no cover - fitlabel = 'PTE (fitness)' - fatiguelabel = 'NTE (fatigue)' - formlabel = 'Performance' - rightaxlabel = 'Performance' - if dofatigue: - yaxlabel = 'PTE/NTE' - else: - yaxlabel = 'PTE' + chart_data = { + 'data': data_dict, + 'title': 'Performance Manager '+user.first_name, + 'plotform' : doform, + 'plotfatigue': dofatigue, + } - plot.xaxis.axis_label = None - plot.yaxis.axis_label = yaxlabel - - y2rangemin = df.loc[:, ['form']].min().min() - y2rangemax = df.loc[:, ['form']].max().max() - - if dofatigue: # pragma: no cover - y1rangemax = df.loc[:, ['fitness', 'fatigue']].max().max()*1.02 - else: # pragma: no cover - y1rangemax = df.loc[:, ['fitness']].max().max()*1.02 - - if doform: # pragma: no cover - plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin, end=y2rangemax) - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=rightaxlabel), "right") - - plot.line('date', 'fitness', source=source, color='blue', - legend_label=fitlabel) - band = Band(base='date', upper='fitness', lower=0, source=source, level='underlay', - fill_alpha=0.2, fill_color='blue') - plot.add_layout(band) - - if dofatigue: # pragma: no cover - plot.line('date', 'fatigue', source=source, color='red', - legend_label=fatiguelabel) - if doform: # pragma: no cover - plot.line('date', 'form', source=source, color='green', - legend_label=formlabel, y_range_name="yax2") - - plot.legend.location = "top_left" - - #plot.sizing_mode = 'scale_both' - - startdate = datetime.datetime.combine( - startdate, datetime.datetime.min.time()) - enddate = datetime.datetime.combine(enddate, datetime.datetime.min.time()) - - xrange = Range1d( - startdate, enddate, - ) - plot.x_range = xrange - plot.y_range = Range1d( - start=0, end=y1rangemax, - ) - plot.title.text = 'Performance Manager '+user.first_name - - hover = plot.select(dict(type=HoverTool)) - - linked_crosshair = CrosshairTool(dimensions='height') - - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}') - ]) - - if showtests: - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}'), - ('Gold Medal Score', '@testpower{int}'), - ('Test', '@testduration'), - ]) - - plot2 = figure(tools=TOOLS2, x_axis_type='datetime', - width=900, height=150, - toolbar_location=None, - toolbar_sticky=False) - - plot2.x_range = xrange - plot2.y_range = Range1d(0, df['impulse'].max()) - - plot2.vbar(x=df['date'], top=df['impulse'], color='gray') - plot2.vbar(x=df['date'], top=0*df['testpower']+df['impulse'], color='red') - - #plot2.sizing_mode = 'scale_both' - plot2.yaxis.axis_label = 'Impulse' - plot2.xaxis.axis_label = 'Date' - - plot.add_tools(linked_crosshair) - plot2.add_tools(linked_crosshair) - - mylayout = layoutcolumn([plot, plot2]) - - try: - script, div = components(mylayout) - except Exception as e: # pragma: no cover - df.dropna(inplace=True, axis=0, how='any') - return ( - '', - 'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format( - nrworkouts=workouts.count(), - nrdata=len(df), - e=e, - ), 0, 0, 0, [] - ) + script, div = get_chart("/performance", chart_data) return [script, div, endfitness, endfatigue, endform, outids] + def interactive_histoall(theworkouts, histoparam, includereststrokes, spmmin=0, spmmax=55, extratitle='', @@ -2023,25 +796,19 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, ids = [int(w.id) for w in theworkouts] + columns = [histoparam, 'spm', 'driveenergy', 'distance', 'workoutstate', 'workoutid'] + workstrokesonly = not includereststrokes - rowdata = dataprep.getsmallrowdata_db( - [histoparam], ids=ids, doclean=True, workstrokesonly=workstrokesonly) + rowdata = dataprep.read_data( + columns, ids=ids, doclean=True, workstrokesonly=workstrokesonly) - rowdata.dropna(axis=0, how='any', inplace=True) + rowdata = rowdata.fill_nan(None).drop_nulls() - rowdata = dataprep.filter_df(rowdata, 'spm', spmmin, largerthan=True) - rowdata = dataprep.filter_df(rowdata, 'spm', spmmax, largerthan=False) - - rowdata = dataprep.filter_df( - rowdata, 'driveenergy', workmin, largerthan=True) - rowdata = dataprep.filter_df( - rowdata, 'driveenergy', workmax, largerthan=False) - - if rowdata.empty: + if rowdata.is_empty(): return "", "No Valid Data Available" try: - histopwr = rowdata[histoparam].values + histopwr = rowdata[histoparam].to_numpy() except KeyError: return "", "No data" if len(histopwr) == 0: # pragma: no cover @@ -2056,545 +823,32 @@ def interactive_histoall(theworkouts, histoparam, includereststrokes, histopwr = histopwr[histopwr > yaxminima[histoparam]] histopwr = histopwr[histopwr < yaxmaxima[histoparam]] - plot = figure(tools=TOOLS, width=900, - toolbar_sticky=False, - toolbar_location="above" - ) + data_dict = {"data": histopwr.tolist(), + "metric": metricsdicts[histoparam]["verbose_name"]} - if extratitle: - plot.title.text = extratitle + script, div = get_chart("/histogram", data_dict, debug=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - hist, edges = np.histogram(histopwr, bins=150) - - histsum = np.cumsum(hist) - histsum = 100.*histsum/max(histsum) - - hist_norm = 100.*hist/float(hist.sum()) - - source = ColumnDataSource( - data=dict( - left=edges[:-1], - right=edges[1:], - histsum=histsum, - hist_norm=hist_norm, - ) - ) - - -# plot.quad(top='hist_norm',bottom=0,left=edges[:-1],right=edges[1:]) - plot.quad(top='hist_norm', bottom=0, left='left', - right='right', source=source) - - plot.xaxis.axis_label = axlabels[histoparam] - plot.yaxis.axis_label = "% of strokes" - plot.y_range = Range1d(0, 1.05*max(hist_norm)) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - (axlabels[histoparam], '@left{int}'), - ('% of strokes', '@hist_norm'), - ('Cumulative %', '@histsum{int}'), - ]) - - hover.mode = 'mouse' - - plot.extra_y_ranges["fraction"] = Range1d(start=0, end=105) - plot.line('right', 'histsum', source=source, color="red", - y_range_name="fraction") - plot.add_layout(LinearAxis(y_range_name="fraction", - axis_label="Cumulative % of strokes"), 'right') - - #plot.sizing_mode = 'stretch_both' - - annolabel = Label(x=50, y=450, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - plot.add_layout(annolabel) - - callback = CustomJS(args=dict( - annolabel=annolabel, - ), code=""" - var annotation = annotation.value - annolabel.text = annotation - """) - - annotation = TextInput( - width=140, title="Type your plot notes here", value="") - annotation.js_on_change('value', callback) - callback.args["annotation"] = annotation - - mylayout = layoutcolumn([annotation, plot]) - - try: - script, div = components(mylayout) - except ValueError: # pragma: no cover - script = '' - div = '' - - return [script, div] + return script, div + def course_map(course): - latmean, lonmean, coordinates = course_coord_center(course) - if course.with_cn_nav_waypoints: - latmean, lonmean, coordinates = course_coord_crewnerd_navigation(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - - coordinates = course_spline(coordinates) - - scoordinates = "[" - - for index, row in coordinates.iterrows(): - scoordinates += """[{x},{y}], - """.format( - x=row['latitude'], - y=row['longitude'] - ) - - scoordinates += "]" - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" - - script = """ - - """.format( - id=course.id, - latmean=latmean, - lonmean=lonmean, - scoordinates=scoordinates, - pcoordinates=pcoordinates, - plabels=plabels - ) - - div = """ -
- """.format( - id=course.id, - ) + course_dict = GeoCourseSerializer(course).data + + script, div = get_chart("/map", course_dict) + return script, div -def get_map_script_course( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - course, -): # pragma: no cover - latmean, lonmean, coordinates = course_coord_center(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - - coordinates = course_spline(coordinates) - - scoordinates = "[" - - for index, row in coordinates.iterrows(): - scoordinates += """[{x},{y}], - """.format( - x=row['latitude'], - y=row['longitude'] - ) - - scoordinates += "]" - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - scoordinates=scoordinates, - pcoordinates=pcoordinates, - plabels=plabels - ) - - return script - - -def get_map_script( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, -): - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - return script - - def leaflet_chart(lat, lon, name="", raceresult=0): - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] + try: + if lat.empty or lon.empty: # pragma: no cover + return [0, "invalid coordinate data"] + except AttributeError: + if not len(lat) or not len(lon): # pragma: no cover + return [0, "invalid coordinate data"] + # Throw out 0,0 df = pd.DataFrame({ @@ -2621,48 +875,31 @@ def leaflet_chart(lat, lon, name="", raceresult=0): coordinates = zip(lat, lon) - scoordinates = "[" + data = { + 'coordinates': [{'latitude': c[0], 'longitude': c[1]} for c in list(coordinates)], + 'latmean': latmean, + 'lonmean': lonmean, + 'latbegin': latbegin, + 'latend': latend, + 'longbegin': longbegin, + 'longend': longend, + } - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - if raceresult == 0: - script = get_map_script( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - ) - else: # pragma: no cover + if raceresult != 0: record = VirtualRaceResult.objects.get(id=raceresult) course = record.course - script = get_map_script_course( - latmean, - lonmean, - latbegin, - latend, - longbegin, - longend, - scoordinates, - course, - ) + course_dict = GeoCourseSerializer(course).data + data['course'] = course_dict - div = """ -

 

- """ + + coordinates = zip(lat, lon) + + script, div = get_chart("/workoutmap", data) return script, div + def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): data = [] for id in workoutids: @@ -2671,7 +908,7 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): w = Workout.objects.get(id=id) rowdata = rdata(w.csvfilename) time = rowdata.df['TimeStamp (sec)'] - df = pd.DataFrame({ + df = pl.DataFrame({ 'workoutid': id, 'lat': rowdata.df[' latitude'], 'lon': rowdata.df[' longitude'], @@ -2681,69 +918,28 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except (Workout.DoesNotExist, KeyError): # pragma: no cover pass try: - df = pd.concat(data, axis=0) + df = pl.concat(data, rechunk=True) except ValueError: # pragma: no cover - df = pd.DataFrame() + df = pl.DataFrame() latmean, lonmean, coordinates = course_coord_center(course) - lat_min, lat_max, long_min, long_max = course_coord_maxmin(course) - coordinates = course_spline(coordinates) - - polygons = GeoPolygon.objects.filter( - course=course).order_by("order_in_course") - - plabels = '' - - for p in polygons: - coords = polygon_coord_center(p) - - plabels += """ - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("{name}"); - - """.format( - latbegin=coords[0], - longbegin=coords[1], - name=p.name - ) - - pcoordinates = """[ - """ - - for p in polygons: - pcoordinates += """[ - [""" - - points = GeoPoint.objects.filter(polygon=p).order_by("order_in_poly") - - for pt in points: - pcoordinates += "[{x},{y}],".format( - x=pt.latitude, - y=pt.longitude - ) - - # remove last comma - pcoordinates = pcoordinates[:-1] - pcoordinates += """] - ], - """ - - pcoordinates += """ - ]""" + course_dict = GeoCourseSerializer(course).data # Throw out 0,0 - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) + df = df.with_columns( + (pl.col("lat")+pl.col("lon")).alias("latlon") + ) + df =df.filter(pl.col("latlon")!=0,) + df = df.fill_nan(None) + df = df.select(pl.all()).interpolate() try: lat = df['lat'] lon = df['lon'] except KeyError: # pragma: no cover return [0, "invalid coordinate data"] - if lat.empty or lon.empty: # pragma: no cover + if lat.is_empty() or lon.is_empty(): # pragma: no cover return [0, "invalid coordinate data"] colors = itertools.cycle(palette) @@ -2752,92 +948,10 @@ def leaflet_chart_compare(course, workoutids, labeldict={}, startenddict={}): except AttributeError: items = zip(workoutids, colors) - script = """ - - """ - - div = """ -

 

- """ + mapdata = { + 'course': course_dict, + 'latmean': latmean, + 'lonmean': lonmean, + 'trajectories': trajectories, + } + + script, div = get_chart("/mapcompare", mapdata, debug=False) return script, div - -def leaflet_chart2(lat, lon, name=""): - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - - # Throw out 0,0 - df = pd.DataFrame({ - 'lat': lat, - 'lon': lon - }) - - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - latend = lat[lat.index[-1]] - longend = lon[lon.index[-1]] - - coordinates = zip(lat, lon) - - scoordinates = "[" - - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - latend=latend, - longbegin=longbegin, - longend=longend, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - return script, div - - -def leaflet_chart_video(lat, lon, name=""): - if not len(lat) or not len(lon): # pragma: no cover - return [0, "invalid coordinate data"] - - # Throw out 0,0 - df = pd.DataFrame({ - 'lat': lat, - 'lon': lon - }) - - df = df.replace(0, np.nan) - df = df.loc[(df != 0).any(axis=1)] - df.fillna(method='bfill', axis=0, inplace=True) - df.fillna(method='ffill', axis=0, inplace=True) - lat = df['lat'] - lon = df['lon'] - if lat.empty or lon.empty: # pragma: no cover - return [0, "invalid coordinate data"] - - latmean = lat.mean() - lonmean = lon.mean() - latbegin = lat[lat.index[0]] - longbegin = lon[lon.index[0]] - - coordinates = zip(lat, lon) - - scoordinates = "[" - - for x, y in coordinates: - scoordinates += """[{x},{y}], - """.format( - x=x, - y=y - ) - - scoordinates += "]" - - script = """ - - - var streets = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/streets-v11', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ), - - satellite = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/satellite-v9', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ), - - outdoors = L.tileLayer( - 'https://api.mapbox.com/styles/v1/{{id}}/tiles/{{z}}/{{x}}/{{y}}?access_token={{accessToken}}', {{ - attribution: '© Mapbox © OpenStreetMap Improve this map', - tileSize: 512, - maxZoom: 18, - zoomOffset: -1, - id: 'mapbox/outdoors-v11', - accessToken: 'pk.eyJ1Ijoic2FuZGVycm9vc2VuZGFhbCIsImEiOiJjajY3aTRkeWQwNmx6MzJvMTN3andlcnBlIn0.MFG8Xt0kDeSA9j7puZQ9hA' -}} - ); - - - - var mymap = L.map('map_canvas', {{ - center: [{latmean}, {lonmean}], - zoom: 13, - layers: [streets, satellite] - }}).setView([{latmean},{lonmean}], 13); - - var navionics = new JNC.Leaflet.NavionicsOverlay({{ - navKey: 'Navionics_webapi_03205', - chartType: JNC.NAVIONICS_CHARTS.NAUTICAL, - isTransparent: true, - zIndex: 1 - }}); - - - var osmUrl2='http://tiles.openseamap.org/seamark/{{z}}/{{x}}/{{y}}.png'; - var osmUrl='http://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png'; - - - //create two TileLayer - var nautical=new L.TileLayer(osmUrl,{{ - maxZoom:18}}); - - - L.control.layers({{ - "Streets": streets, - "Satellite": satellite, - "Outdoors": outdoors, - "Nautical": nautical, - }},{{ - "Navionics":navionics, - }}, - {{ - position:'topleft' - }}).addTo(mymap); - - var marker = L.marker([{latbegin}, {longbegin}]).addTo(mymap); - marker.bindPopup("Start"); - - var latlongs = {scoordinates} - var polyline = L.polyline(latlongs, {{color:'red'}}).addTo(mymap) - mymap.fitBounds(polyline.getBounds()) - - """.format( - latmean=latmean, - lonmean=lonmean, - latbegin=latbegin, - longbegin=longbegin, - scoordinates=scoordinates, - ) - - div = """ -

 

- """ - - return script, div - - -def interactive_agegroupcpchart(age, normalized=False): - durations = [1, 4, 30, 60] - distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - - fhduration = [] - fhpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - fhduration.append(duration) - fhpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - fhduration.append(60.*duration) - fhpower.append(worldclasspower) - except ValueError: # pragma: no cover - pass - - flduration = [] - flpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - flduration.append(duration) - flpower.append(worldclasspower) - except ZeroDivisionError: - pass - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='female', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - flduration.append(60.*duration) - flpower.append(worldclasspower) - except ValueError: # pragma: no cover - pass - - mlduration = [] - mlpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='lwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - mlduration.append(duration) - mlpower.append(worldclasspower) - except ZeroDivisionError: - mlduration.append(duration) - mlpower.append(np.nan) - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='lwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mlduration.append(60.*duration) - mlpower.append(worldclasspower) - except ValueError: # pragma: no cover - mlduration.append(60.*duration) - mlpower.append(np.nan) - - mhduration = [] - mhpower = [] - - for distance in distances: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - distance=distance, - weightcategory='hwt' - ) - velo = (worldclasspower/2.8)**(1./3.) - try: # pragma: no cover - duration = distance/velo - mhduration.append(duration) - mhpower.append(worldclasspower) - except ZeroDivisionError: - mhduration.append(duration) - mhpower.append(np.nan) - for duration in durations: - worldclasspower = c2stuff.getagegrouprecord( - age, - sex='male', - duration=duration, - weightcategory='hwt' - ) - try: - velo = (worldclasspower/2.8)**(1./3.) - distance = int(60*duration*velo) - mhduration.append(60.*duration) - mhpower.append(worldclasspower) - except ValueError: # pragma: no cover - mhduration.append(60.*duration) - mhpower.append(np.nan) - - def fitfunc(pars, x): - return pars[0] / (1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - - def errfunc(pars, x, y): - return fitfunc(pars, x)-y - - # p0 = [500,350,10,8000] - - # fitting WC data to three parameter CP model - if len(fhduration) >= 4: - p1fh, success = optimize.leastsq(errfunc, p0[:], - args=(fhduration, fhpower)) - else: # pragma: no cover - p1fh = None - - # fitting WC data to three parameter CP model - if len(flduration) >= 4: - p1fl, success = optimize.leastsq(errfunc, p0[:], - args=(flduration, flpower)) - else: # pragma: no cover - p1fl = None - - # fitting WC data to three parameter CP model - if len(mlduration) >= 4: - p1ml, success = optimize.leastsq(errfunc, p0[:], - args=(mlduration, mlpower)) - else: # pragma: no cover - p1ml = None - - if len(mhduration) >= 4: - p1mh, success = optimize.leastsq(errfunc, p0[:], - args=(mhduration, mhpower)) - else: # pragma: no cover - p1mh = None - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpowerfh = fitfunc(p1fh, fitt) - fitpowerfl = fitfunc(p1fl, fitt) - fitpowerml = fitfunc(p1ml, fitt) - fitpowermh = fitfunc(p1mh, fitt) - - if normalized: - facfh = fitfunc(p1fh, 60) - facfl = fitfunc(p1fl, 60) - facml = fitfunc(p1ml, 60) - facmh = fitfunc(p1mh, 60) - fitpowerfh /= facfh - fitpowerfl /= facfl - fitpowermh /= facmh - fitpowerml /= facml - fhpower /= facfh - flpower /= facfl - mlpower /= facml - mhpower /= facmh - - sourcemh = ColumnDataSource( - data=dict( - mhduration=mhduration, - mhpower=mhpower, - ) - ) - - sourcefl = ColumnDataSource( - data=dict( - flduration=flduration, - flpower=flpower, - ) - ) - - sourcefh = ColumnDataSource( - data=dict( - fhduration=fhduration, - fhpower=fhpower, - ) - ) - - sourceml = ColumnDataSource( - data=dict( - mlduration=mlduration, - mlpower=mlpower, - ) - ) - - sourcefit = ColumnDataSource( - data=dict( - duration=fitt, - fitpowerfh=fitpowerfh, - fitpowerfl=fitpowerfl, - fitpowerml=fitpowerml, - fitpowermh=fitpowermh, - ) - ) - - x_axis_type = 'log' - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - plot = figure(width=900, x_axis_type=x_axis_type, - tools=TOOLS) - #plot.sizing_mode = 'stretch_both' - - plot.line('duration', 'fitpowerfh', source=sourcefit, - legend_label='Female HW', color='blue') - plot.line('duration', 'fitpowerfl', source=sourcefit, - legend_label='Female LW', color='red') - - plot.line('duration', 'fitpowerml', source=sourcefit, - legend_label='Male LW', color='green') - - plot.line('duration', 'fitpowermh', source=sourcefit, - legend_label='Male HW', color='orange') - - plot.circle('flduration', 'flpower', source=sourcefl, - fill_color='red', size=15) - - plot.circle('fhduration', 'fhpower', source=sourcefh, - fill_color='blue', size=15) - - plot.circle('mlduration', 'mlpower', source=sourceml, - fill_color='green', size=15) - - plot.circle('mhduration', 'mhpower', source=sourcemh, - fill_color='orange', size=15) - - plot.title.text = 'age '+str(age) - - plot.xaxis.axis_label = "Duration (seconds)" - if normalized: - plot.yaxis.axis_label = "Power (normalized)" - else: - plot.yaxis.axis_label = "Power (W)" - - script, div = components(plot) - - return script, div - - def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, cpfit='data', title='', type='water', wcpower=[], wcdurations=[], cpoverlay=False): - powerdf2 = powerdf[~(powerdf == 0).any(axis=1)].copy() + powerdf2 = powerdf.filter((pl.col("Delta") > 0) & (pl.col("CP") > 0)) + # plot tools if (promember == 1): # pragma: no cover TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' @@ -3457,11 +1009,9 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, x_axis_type = 'log' deltas = powerdf2['Delta'].apply(lambda x: timedeltaconv(x)) - powerdf2['ftime'] = deltas.apply(lambda x: strfdelta(x)) - powerdf2['Deltaminutes'] = powerdf2['Delta']/60. - - source = ColumnDataSource( - data=powerdf2 + powerdf2 = powerdf2.with_columns( + ftime = deltas.apply(lambda x: strfdelta(x)), + Deltaminutes = pl.col("Delta")/60. ) # there is no Paul's law for OTW @@ -3521,8 +1071,8 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, fitpowerfair = 0*fitpower fitpoweraverage = 0*fitpower - sourcecomplex = ColumnDataSource( - data=dict( + + fit_data = pl.DataFrame(dict( CP=fitpower, CPmax=ratio*fitpower, duration=fitt/60., @@ -3534,94 +1084,18 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, fitpowerfair=fitpowerfair, fitpoweraverage=fitpoweraverage, # url = urls, - ) - ) + )) - # making the plot - plot = figure(tools=TOOLS, x_axis_type=x_axis_type, - width=900, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'scale_both' - - plot.image_url([watermarkurl], 1.8*max(thesecs), watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - y_range_name="watermark", - ) - - plot.circle('Deltaminutes', 'CP', source=source, fill_color='red', size=15, - legend_label='Power Data') - plot.xaxis.axis_label = "Duration (minutes)" - plot.yaxis.axis_label = "Power (W)" - - plot.y_range = Range1d(0, 1.5*max(theavpower)) - plot.x_range = Range1d(0.5*min(thesecs)/60., 2*max(thesecs)/60.) - plot.legend.orientation = "vertical" if not title: title = "Critical Power for "+rowername - plot.title.text = title + + chart_dict = { + 'data': powerdf2.to_dicts(), + 'fitdata': fit_data.to_dicts(), + 'title': title, + } - plot.xaxis[0].formatter = PrintfTickFormatter(format="%5f") - - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Duration ', '@ftime'), - ('Power (W)', '@CP{int}'), - ('Power (W) upper', '@CPmax{int}'), - ('Workout', '@workout'), - ('World Class', '@fitpowerwc{int}') - ]) - - hover.mode = 'mouse' - - taptool = plot.select(type=TapTool) - taptool.callback = OpenURL(url='@url') - - plot.line('duration', 'CP', source=sourcecomplex, legend_label="CP Model", - color='green') - - plot.line('duration', 'CPmax', source=sourcecomplex, legend_label="CP Model", - color='red') - - if p1wc is not None: # pragma: no cover - plot.line('duration', 'fitpowerwc', source=sourcecomplex, - legend_label="Gold Medal Standard", - color='darkgoldenrod', line_dash='dotted') - - plot.line('duration', 'fitpowerexcellent', source=sourcecomplex, - legend_label="90% percentile", - color='goldenrod', line_dash='dotted') - - plot.line('duration', 'fitpowergood', source=sourcecomplex, - legend_label="75% percentile", - color='sandybrown', line_dash='dotted') - - plot.line('duration', 'fitpowerfair', source=sourcecomplex, - legend_label="50% percentile", - color='rosybrown', line_dash='dotted') - - plot.line('duration', 'fitpoweraverage', source=sourcecomplex, - legend_label="25% percentile", - color='tan', line_dash='dotted') - - script, div = components(plot) + script, div = get_chart("/cp", chart_dict) return [script, div, p1, ratio, message] @@ -3705,561 +1179,53 @@ def interactive_agegroup_plot(df, distance=2000, duration=None, return script, div - -def interactive_cpchart(rower, thedistances, thesecs, theavpower, - theworkouts, promember=0, - wcpower=[], wcdurations=[]): - - message = 0 - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - x_axis_type = 'log' - - thesecs = pd.Series(thesecs) - - velo = thedistances/thesecs - p = pd.Series(500./velo) - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source = ColumnDataSource( - data=dict( - dist=thedistances, - duration=thesecs, - spm=0*theavpower, - tim=niceformat( - thesecs.fillna(method='ffill').apply( - lambda x: timedeltaconv(x)) - ), - - power=theavpower, - fpace=nicepaceformat(p2), - ) - ) - - # fitting the data to Paul - if len(thedistances) >= 2: - paulslope, paulintercept, r, p, stderr = linregress( - np.log10(thedistances), p) - else: # pragma: no cover - 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 - - fitvelo = 500./fitp - fitpower = 2.8*(fitvelo**3) - fitt = 10**fitx/fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - sourcepaul = ColumnDataSource( - data=dict( - dist=10**fitx, - duration=fitt, - power=fitpower, - spm=0*fitpower, - tim=niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - fpace=nicepaceformat(fitp2), - ) - ) - - def fitfunc(pars, x): - return pars[0] / (1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) - - def errfunc(pars, x, y): - return fitfunc(pars, x)-y - - # p0 = [500,350,10,8000] - wcpower = pd.Series(wcpower, dtype='float') - wcdurations = pd.Series(wcdurations, dtype='float') - - # fitting WC data to three parameter CP model - if len(wcdurations) >= 4: - p1wc, success = optimize.leastsq(errfunc, p0[:], - args=(wcdurations, wcpower)) - else: # pragma: no cover - p1wc = None - - # fitting the data to three parameter CP model - - success = 0 - p1 = p0 - if len(thesecs) >= 4: - try: - p1, success = optimize.leastsq( - errfunc, p0[:], args=(thesecs, theavpower)) - except (RuntimeError, RuntimeWarning): # pragma: no cover - factor = fitfunc(p0, thesecs.mean())/theavpower.mean() - p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] - success = 0 - else: # pragma: no cover - factor = fitfunc(p0, thesecs.mean())/theavpower.mean() - p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] - success = 0 - - # Get stayer score - if success == 1: # pragma: no cover - power4min = fitfunc(p1, 240.) - power1h = fitfunc(p1, 3600.) - power10sec = fitfunc(p1, 10.) - r10sec4min = 100.*power10sec/power4min - r1h4min = 100.*power1h/power4min - - combined = r1h4min-0.2*(r10sec4min-100) - - dataset = pd.read_csv('static/stats/combined_set.csv') - - stayerscore = int(percentileofscore(dataset['combined'], combined)) - else: - stayerscore = None - - fitt = pd.Series(10**(4*np.arange(100)/100.)) - - fitpower = fitfunc(p1, fitt) - if p1wc is not None: - fitpowerwc = 0.95*fitfunc(p1wc, fitt) - fitpowerexcellent = 0.7*fitfunc(p1wc, fitt) - fitpowergood = 0.6*fitfunc(p1wc, fitt) - fitpowerfair = 0.5*fitfunc(p1wc, fitt) - fitpoweraverage = 0.4*fitfunc(p1wc, fitt) - - else: # pragma: no cover - fitpowerwc = 0*fitpower - fitpowerexcellent = 0*fitpower - fitpowergood = 0*fitpower - fitpowerfair = 0*fitpower - fitpoweraverage = 0*fitpower - - message = "" - if len(fitpower[fitpower < 0]) > 0: # pragma: no cover - message = "CP model fit didn't give correct results" - - fitvelo = (fitpower/2.8)**(1./3.) - fitdist = fitt*fitvelo - fitp = 500./fitvelo - fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - sourcecomplex = ColumnDataSource( - data=dict( - dist=fitdist, - duration=fitt, - tim=niceformat( - fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - ), - spm=0*fitpower, - power=fitpower, - fitpowerwc=fitpowerwc, - fitpowerexcellent=fitpowerexcellent, - fitpowergood=fitpowergood, - fitpowerfair=fitpowerfair, - fitpoweraverage=fitpoweraverage, - fpace=nicepaceformat(fitp2), - ) - ) - - # making the plot - plot = figure(tools=TOOLS, x_axis_type=x_axis_type, - width=900, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'scale_both' - - plot.image_url([watermarkurl], 1.8*max(thesecs), watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - y_range_name="watermark", - ) - - plot.circle('duration', 'power', source=source, fill_color='red', size=15, - legend_label='Power') - plot.xaxis.axis_label = "Duration (seconds)" - plot.yaxis.axis_label = "Power (W)" - - if stayerscore is not None: # pragma: no cover - plot.add_layout( - Label(x=100, y=100, x_units='screen', y_units='screen', - text='Stayer Score '+str(stayerscore)+'%', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black') - ) -# plot.add_layout( -# Label(x=100,y=120,x_units='screen',y_units='screen', -# text='Stayer Score (6min) '+str(stayerscore2)+'%', -# background_fill_alpha=0.7, -# background_fill_color='white', -# text_color='black') -# ) - - cpdata = dataprep.fetchcperg(rower, theworkouts) - - if cpdata.empty: # pragma: no cover - message = 'Calculations are running in the background. Please refresh this page to see updated results' - return ['', '', paulslope, paulintercept, p1, message, p1wc] - - velo = cpdata['distance']/cpdata['delta'] - - p = 500./velo - - p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) - - source2 = ColumnDataSource( - data=dict( - duration=cpdata['delta'], - power=cpdata['cp'], - tim=niceformat( - cpdata['delta'].fillna(method='ffill').apply( - lambda x: timedeltaconv(x)) - ), - dist=cpdata['distance'], - pace=nicepaceformat(p2), - ) - ) - - plot.circle('duration', 'power', source=source2, - fill_color='blue', size=3, - legend_label='Power from segments') - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Duration ', '@tim'), - ('Power (W)', '@power{int}'), - ('Distance (m)', '@dist{int}'), - ('Pace (/500m)', '@fpace'), - ]) - - hover.mode = 'mouse' - - plot.y_range = Range1d(0, 1.5*max(theavpower)) - plot.x_range = Range1d(1, 2*max(thesecs)) - plot.legend.orientation = "vertical" - - plot.line('duration', 'power', source=sourcepaul, - legend_label="Paul's Law") - plot.line('duration', 'power', source=sourcecomplex, legend_label="CP Model", - color='green') - if p1wc is not None: - plot.line('duration', 'fitpowerwc', source=sourcecomplex, - legend_label="World Class", - color='Maroon', line_dash='dotted') - - plot.line('duration', 'fitpowerexcellent', source=sourcecomplex, - legend_label="90% percentile", - color='Purple', line_dash='dotted') - - plot.line('duration', 'fitpowergood', source=sourcecomplex, - legend_label="75% percentile", - color='Olive', line_dash='dotted') - - plot.line('duration', 'fitpowerfair', source=sourcecomplex, - legend_label="50% percentile", - color='Gray', line_dash='dotted') - - plot.line('duration', 'fitpoweraverage', source=sourcecomplex, - legend_label="25% percentile", - color='SkyBlue', line_dash='dotted') - - script, div = components(plot) - - return [script, div, paulslope, paulintercept, p1, message, p1wc] - - -def interactive_windchart(id=0, promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = figure(width=400, height=300) - - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - - rr = rrower(hrmax=r.max, hrut2=r.ut2, - hrut1=r.ut1, hrat=r.at, - hrtr=r.tr, hran=r.an, ftp=r.ftp) - - rowdata = rdata(f1, rower=rr) - if rowdata == 0: # pragma: no cover - return 0 - - try: - dist = rowdata.df.loc[:, 'cum_dist'] - except KeyError: - return ['', 'No Data Found'] - - try: # pragma: no cover - vwind = rowdata.df.loc[:, 'vwind'] - winddirection = rowdata.df.loc[:, 'winddirection'] - bearing = rowdata.df.loc[:, 'bearing'] - except KeyError: - rowdata.add_wind(0, 0) - rowdata.add_bearing() - vwind = rowdata.df.loc[:, 'vwind'] - winddirection = rowdata.df.loc[:, 'winddirection'] - bearing = rowdata.df.loc[:, 'winddirection'] - rowdata.write_csv(f1, gzip=True) - dataprep.update_strokedata(id, rowdata.df) - - winddirection = winddirection % 360 - winddirection = (winddirection + 360) % 360 - - tw = tailwind(bearing, vwind, 1.0*winddirection) - - source = ColumnDataSource( - data=dict( - dist=dist, - vwind=vwind, - tw=tw, - winddirection=winddirection, - ) - ) - - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - # making the plot - plot = figure(tools=TOOLS, width=400, height=500, - # toolbar_location="below", - toolbar_sticky=False, - ) - plot.line('dist', 'vwind', source=source, legend_label="Wind Speed (m/s)") - plot.line('dist', 'tw', source=source, - legend_label="Tail (+)/Head (-) Wind (m/s)", color='black') - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - # plot.title.text_font_size="1.0em" - plot.title.text_font = "1.0em" - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "Wind Speed (m/s)" - plot.y_range = Range1d(-7, 7) - #plot.sizing_mode = 'stretch_both' - - plot.extra_y_ranges = {"winddirection": Range1d(start=0, end=360)} - plot.line('dist', 'winddirection', source=source, - legend_label='Wind Direction', color="red", - y_range_name="winddirection") - plot.add_layout(LinearAxis(y_range_name="winddirection", - axis_label="Wind Direction (degree)"), 'right') - - script, div = components(plot) - - return [script, div] - - -def interactive_streamchart(id=0, promember=0): - # check if valid ID exists (workout exists) - row = Workout.objects.get(id=id) - # g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") - - f1 = row.csvfilename - - # create interactive plot - plot = figure(width=400, - ) - # get user - # u = User.objects.get(id=row.user.id) - r = row.user - - rr = rrower(hrmax=r.max, hrut2=r.ut2, - hrut1=r.ut1, hrat=r.at, - hrtr=r.tr, hran=r.an, ftp=r.ftp) - - rowdata = rdata(f1, rower=rr) - if rowdata == 0: # pragma: no cover - return "", "No Valid Data Available" - - try: - dist = rowdata.df.loc[:, 'cum_dist'] - except KeyError: - return ['', 'No Data found'] - - try: - vstream = rowdata.df.loc[:, 'vstream'] - except KeyError: - rowdata.add_stream(0) - vstream = rowdata.df.loc[:, 'vstream'] - rowdata.write_csv(f1, gzip=True) - dataprep.update_strokedata(id, rowdata.df) - - # plot tools - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - # making the plot - plot = figure(tools=TOOLS, width=400, height=500, - # toolbar_location="below", - toolbar_sticky=False, - ) - plot.line(dist, vstream, legend_label="River Stream Velocity (m/s)") - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - plot.xaxis.axis_label = "Distance (m)" - plot.yaxis.axis_label = "River Current (m/s)" - plot.y_range = Range1d(-2, 2) - #plot.sizing_mode = 'stretch_both' - - script, div = components(plot) - - return [script, div] - def forcecurve_multi_interactive_chart(selected): # pragma: no cover - df_plot = pd.DataFrame() ids = [analysis.id for analysis in selected] + workoutids = [analysis.workout.id for analysis in selected] + + selected_dict = [ForceCurveAnalysisSerializer(analysis).data for analysis in selected] columns = ['catch', 'slip', 'wash', 'finish', 'averageforce', 'peakforceangle', 'peakforce', 'spm', 'distance', - 'workoutstate', 'driveenergy'] + 'workoutstate', 'workoutid', 'driveenergy', 'cumdist'] + columns = columns + [name for name, d in metrics.rowingmetrics] + rowdata = dataprep.read_data(columns, ids=workoutids, + workstrokesonly=False) + + rowdata = rowdata.fill_nan(None).drop_nulls() + + if rowdata.is_empty(): + return "", "No Valid Data Available", "", "" + + data_dict = rowdata.to_dicts() + + thresholdforces = [] for analysis in selected: - workstrokesonly = not analysis.include_rest_strokes - spm_min = analysis.spm_min - spm_max = analysis.spm_max - dist_min = analysis.dist_min - dist_max = analysis.dist_max - work_min = analysis.work_min - work_max = analysis.work_max - rowdata = dataprep.getsmallrowdata_db(columns, ids=[analysis.workout.id], - workstrokesonly=workstrokesonly) + boattype = analysis.workout.boattype + thresholdforce = 100. if 'x' in boattype else 200. + thresholdforces.append({'id': analysis.workout.id, 'thresholdforce': thresholdforce}) - rowdata = rowdata[rowdata['spm']>spm_min] - rowdata = rowdata[rowdata['spm']work_min] - rowdata = rowdata[rowdata['driveenergy']dist_min] + chart_data = { + 'title': '', + 'data': data_dict, + 'thresholdforces': thresholdforces, + 'forcecurve_analyses': selected_dict, + } - catchav = rowdata['catch'].median() - finishav = rowdata['finish'].median() - washav = (rowdata['finish']-rowdata['wash']).median() - slipav = (rowdata['slip']+rowdata['catch']).median() - peakforceav = rowdata['peakforce'].median() - peakforceangleav = rowdata['peakforceangle'].median() - thresholdforce = 100 if 'x' in analysis.workout.boattype else 200 - x = [catchav, - slipav, - peakforceangleav, - washav, - finishav] - - y = [0, thresholdforce, - peakforceav, - thresholdforce, 0] - - xname = 'x_'+str(analysis.id) - yname = 'y_'+str(analysis.id) - - df_plot[xname] = x - df_plot[yname] = y - - source = ColumnDataSource( - df_plot - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - xname = 'x_'+str(id) - yname = 'y_'+str(id) - analysis = ForceCurveAnalysis.objects.get(id=id) - legendlabel = '{name}'.format( - name = analysis.name, - ) - if analysis.notes: - legendlabel = '{name} - {notes}'.format( - name = analysis.name, - notes = analysis.notes - ) - plot.line(xname,yname,source=source,legend_label=legendlabel, - line_width=2, color=color) - - plot.legend.location = "top_left" - plot.xaxis.axis_label = "Angle" - plot.yaxis.axis_label = "Force (N)" - - script, div = components(plot) - - return (script, div) + script, div = get_chart("/forcecurve_compare", chart_data) + return script, div + def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no cover - df_plot = pd.DataFrame() + df2 = [] ids = [analysis.id for analysis in selected] metrics = list(set([analysis.metric for analysis in selected])) maximum_values = {} + workouts = [] for metric in metrics: maximum_values[metric] = 0 + cntr = 1 for analysis in selected: #start_second, end_second, spm_min, spm_max, name activeminutesmin = int(analysis.start_second/60.) @@ -4279,103 +1245,76 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co if mean_vals.max() > maximum_values[analysis.metric]: maximum_values[analysis.metric] = mean_vals.max() xvals = np.arange(len(mean_vals)) - xname = 'x_'+str(analysis.id) - yname = 'y_'+str(analysis.id) - df_plot[xname] = pd.Series(xvals) - df_plot[yname] = pd.Series(mean_vals) - if len(metrics) > 1: - for analysis in selected: - yname = 'y_'+str(analysis.id) - df_plot[yname] = df_plot[yname] / maximum_values[analysis.metric] + data2 = pl.DataFrame({ + 'x': pl.Series(xvals), + 'y': pl.Series(mean_vals), + + }) + data2 = data2.with_columns((pl.lit(cntr)).alias("id")) - source = ColumnDataSource( - df_plot - ) + df2.append(data2) - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - if len(metrics)>1: - plot.yaxis.axis_label = 'Scaled' - else: - plot.yaxis.axis_label = metrics[0] - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - xname = 'x_'+str(id) - yname = 'y_'+str(id) - analysis = InStrokeAnalysis.objects.get(id=id) legendlabel = '{name} - {metric} - {workout}'.format( name = analysis.name, metric = analysis.metric, date = analysis.date, workout = str(analysis.workout) ) - plot.line(xname,yname,source=source,legend_label=legendlabel, - line_width=2, color=color) + workouts.append({'id': cntr, 'label': legendlabel}) + cntr = cntr + 1 - script, div = components(plot) + + ytitle = metrics[0] + if len(metrics) > 1: + cntr = 1 + for analysis in selected: + df2[cntr-1] = df2[cntr-1].with_columns( + (pl.col("y")/ maximum_values[analysis.metric]) + ) + ytitle = 'Scaled' + cntr = cntr+1 - return (script, div) + df2 = pl.concat(df2) + + data_dict = df2.to_dicts() + + chart_data = { + 'title': '', + 'data': data_dict, + 'ytitle': ytitle, + 'workouts': workouts, + } + + script, div = get_chart("/instroke_compare", chart_data) + + return script, div + def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, activeminutesmin, activeminutesmax, individual_curves, name='',notes=''): # pragma: no cover - df_pos = (df+abs(df))/2. - df_min = -(-df+abs(-df))/2. - if df.empty: return "", "No data in selection" - mean_vals = df.median().replace(0, np.nan) - q75 = df_pos.quantile(q=0.75).replace(0,np.nan) - q25 = df_pos.quantile(q=0.25).replace(0,np.nan) - q75min = df_min.quantile(q=0.75).replace(0,np.nan) - q25min = df_min.quantile(q=0.25).replace(0,np.nan) + df_pos = (df+abs(df))/2. + df_min = -(-df+abs(-df))/2. + + + mean_vals = df.median().replace(0, np.nan) + q75 = df_pos.quantile(q = 0.75).replace(0, np.nan) + q25 = df_pos.quantile(q=0.25).replace(0, np.nan) + q75min = df_min.quantile(q=0.75).replace(0, np.nan) + q25min = df_min.quantile(q=0.25).replace(0, np.nan) mean_vals = mean_vals.interpolate() xvals = np.arange(len(mean_vals)) - df_plot = pd.DataFrame({ + df_plot = pl.DataFrame({ 'x':xvals, 'median':mean_vals, 'high':q75, @@ -4384,143 +1323,51 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, 'low 2': q25, }) - df_plot['high'].update(df_plot.pop('high 2')) - df_plot['low'].update(df_plot.pop('low 2')) - try: - df_plot.interpolate(axis=1,inplace=True) - except TypeError: - pass - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - plot.title.text = str(workout) + ' - ' + metric - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - - source = ColumnDataSource( - df_plot + df_plot = df_plot.with_columns( + pl.coalesce(["high", "high 2"]).alias("high") ) - - TIPS = OrderedDict([ - ('x','@x'), - ('median','@median'), - ('high','@high'), - ('low','@low') - ]) - - hover = plot.select(type=HoverTool) - hover.tooltips = TIPS - - s = 'SPM: {spm_min} - {spm_max}'.format( - spm_min = spm_min, - spm_max = spm_max, + df_plot = df_plot.with_columns( + pl.coalesce("low", "low 2").alias("low") ) - label = Label(x=50, y=450, x_units='screen',y_units='screen', - text=s, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - s2 = 'Time: {activeminutesmin} - {activeminutesmax}'.format( - activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), - activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) - ) - - label2 = Label(x=50,y=400, x_units='screen', y_units='screen', - text=s2, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - plot.add_layout(label) - plot.add_layout(label2) - - if name: - namelabel = Label(x=50, y=480, x_units='screen', y_units='screen', - text=name, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(namelabel) - - if notes: - noteslabel = Label(x=50, y=50, x_units='screen', y_units='screen', - text=notes, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(noteslabel) - - if individual_curves: - for index,row in df.iterrows(): - plot.line(xvals,row,color='lightgray',line_width=1) - else: - plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5) - - plot.line('x','median',source=source,legend_label='median',color="black", - line_width=3) - - medrange = mean_vals.max()-mean_vals.min() - yrange = Range1d(start=mean_vals.min()-0.2*medrange, - end=mean_vals.max()+0.2*medrange,) - plot.y_range = yrange - - plot.add_tools(HoverTool(tooltips=TIPS)) + df_plot = df_plot.drop(["high 2", "low 2"]) + df_plot = df_plot.drop_nulls() if metric == 'boat accelerator curve': - plot.yaxis.axis_label = "Boat acceleration (m/s^2)" + ytitle = "Boat acceleration (m/s^2)" elif metric == 'instroke boat speed': - plot.yaxis.axis_label = "Boat Speed (m/s)" + ytitle = "Boat Speed (m/s)" vavg = mean_vals.median() elif metric == 'oar angle velocity curve': - plot.yaxis.axis_label = "Oar Angular Velocity (degree/s)" + ytitle = "Oar Angular Velocity (degree/s)" elif metric == 'seat curve': - plot.yaxis.axis_label = "Seat Speed (m/s)" + ytitle = "Seat Speed (m/s)" + - plot.xaxis.axis_label = 'Time (%)' + lines_dict = df.to_dict("records") + data_dict = df_plot.to_dicts() - try: - script, div = components(plot) - except ValueError: - script = "" - div = "Something went wrong with the chart" + chart_data = { + 'lines': lines_dict, + 'data': data_dict, + 'ytitle': ytitle, + 'title': str(workout) + ' - ' + metric, + 'individual_curves': individual_curves, + 'spmmin': spm_min, + 'spmmax': spm_max, + 'timemin' :'{activeminutesmin}'.format( + activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), + ), + 'timemax': '{activeminutesmax}'.format( + activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) + ), + 'analysis_name': name, + } - return (script, div) + script, div = get_chart("/instroke", chart_data, debug=False) + return script, div def interactive_chart(id=0, promember=0, intervaldata={}): @@ -4532,94 +1379,33 @@ def interactive_chart(id=0, promember=0, intervaldata={}): TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' columns = ['time', 'pace', 'hr', 'fpace', 'ftime', 'spm'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + datadf = dataprep.read_data(columns, ids=[id]) + if datadf.is_empty(): + return "", "No Valid Data Available" - - datadf.dropna(axis=0, how='any', inplace=True) + datadf = datadf.fill_nan(None).drop_nulls() + row = Workout.objects.get(id=id) - if datadf.empty: + if datadf.is_empty(): return "", "No Valid Data Available" try: _ = datadf['spm'] except KeyError: # pragma: no cover - datadf['spm'] = 0 + datadf = datadf.with_columns((pl.lit(0)).alias("spm")) try: _ = datadf['pace'] except KeyError: # pragma: no cover - datadf['pace'] = 0 + datadf = datadf.with_columns((pl.lit(0)).alias("pace")) - source = ColumnDataSource( - datadf - ) + data_dict = datadf.to_dicts() - plot = figure(x_axis_type="datetime", y_axis_type="datetime", - width=400, - height=400, - toolbar_sticky=False, - tools=TOOLS) + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkw = 184 - watermarkh = 35 - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], 0.01, 0.99, - 0.5*watermarkw, 0.5*watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - plot.line('time', 'pace', source=source, legend_label="Pace", name="pace") - - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - #plot.sizing_mode = 'stretch_both' - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - ymax = 90. - ymin = 150. - - if row.workouttype == 'water': - ymax = 90. - ymin = 210. - - plot.y_range = Range1d(1.e3*ymin, 1.e3*ymax) - - plot.extra_y_ranges["spmax"] = Range1d(start=10, end=45) - plot.line('time', 'spm', source=source, color="red", - y_range_name="spmax", legend_label="Stroke Rate", name="spm") - plot.add_layout(LinearAxis(y_range_name="spmax", - axis_label="SPM"), 'right') - - plot.legend.location = "bottom_right" + intervals = [] # add shaded bar chart areas if intervaldata != {}: intervaldf = pd.DataFrame(intervaldata) @@ -4631,30 +1417,24 @@ def interactive_chart(id=0, promember=0, intervaldata={}): intervaldf['value'] = 100 mask = intervaldf['itype'] == 3 intervaldf.loc[mask, 'value'] = 0 - intervaldf['bottom'] = 10 + intervaldf['bottom'] = 0 - intervalsource = ColumnDataSource( - intervaldf - ) + intervals = intervaldf.to_dict("records") - plot.quad(left='time', top='value', bottom='bottom', - right='time_r', source=intervalsource, color='mediumvioletred', - y_range_name='spmax', fill_alpha=0.2, line_alpha=0.2) + chart_data = { + 'title': row.name, + 'x': "time", + 'y1': "pace", + 'y2': "spm", + 'data': data_dict, + 'metrics': metrics_list, + 'intervals': intervals, + } + + script, div = get_chart("/interactive", chart_data) - hover = plot.select(dict(type=HoverTool)) + return script, div - hover.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Pace', '@fpace'), - ('HR', '@hr{int}'), - ('SPM', '@spm{1.1}'), - ]) - - hover.mode = 'mouse' - # hover.name = ["spm", "pace"] - script, div = components(plot) - - return [script, div] def interactive_chart_video(videodata): @@ -4667,12 +1447,10 @@ def interactive_chart_video(videodata): data = zip(time, spm) - data2 = "[" + data2 = [] for t, s in data: - data2 += "{x: %s, y: %s}, " % (t, s) - - data2 = data2[:-2] + "]" + data2.append( {'x': t, 'y': s}) markerpoint = { 'x': time[0], @@ -4680,89 +1458,13 @@ def interactive_chart_video(videodata): 'r': 10, } - div = """ - - - """ - - script = """ - var ctx = document.getElementById("myChart").getContext('2d'); - var data = %s - - var myChart = new Chart(ctx, { - type: 'scatter', - label: 'SPM', - animationSteps: 10, - options: { - legend: { - display: false, - }, - animation: { - duration: 100, - }, - scales: { - yAxes: [{ - scaleLabel: { - display: true, - labelString: 'Stroke Rate' - } - }], - xAxes: [{ - scaleLabel: { - type: 'linear', - display: true, - labelString: 'Time (seconds)' - } - }], - } - }, - data: - { - datasets: [ - { - type: 'bubble', - label: 'now', - data: [ %s ], - backgroundColor: '#36a2eb', - }, - { - label: 'spm', - data: data, - backgroundColor: "#ff0000", - borderColor: "#ff0000", - fill: false, - borderDash: [0, 0], - pointRadius: 1, - pointHoverRadius: 1, - showLine: true, - tension: 0, - }, - - ] - }, - - }); - - var marker = { - datapoint: %s , - setLatLng: function (LatLng) { - var lat = LatLng.lat; - var lng = LatLng.lng; - this.datapoint = { - 'x': lat, - 'y': lng, - 'r': 10, - } - myChart.data.datasets[0].data[0] = this.datapoint; - myChart.update(); + chart_data = { + 'data': data2, + 'markerpoint': markerpoint, } - } - marker.setLatLng({ - 'lat': data[0]['x'], - 'lng': data[0]['y'] - }) - """ % (data2, markerpoint, markerpoint) + + script, div = get_chart("/videochart", chart_data) return [script, div] @@ -4833,156 +1535,24 @@ def interactive_multiflex(datadf, xparam, yparam, groupby, extratitle='', yaxmax = yaxmaxima[yparam] yaxmin = yaxminima[yparam] - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': # pragma: no cover - x_axis_type = 'datetime' - if yparam == 'pace': - y_axis_type = 'datetime' + data_dict = datadf.to_dict("records") + + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': title, + 'x': xparam, + 'y': yparam, + 'data': data_dict, + 'metrics': metrics_list, + 'errorbars':ploterrorbars, + 'groupname': groupname, + } - datadf.index.names = ['index'] + script, div = get_chart("/trendflex", chart_data,debug=False) - source = ColumnDataSource( - datadf, - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap' - - if groupby != 'date': - hover = HoverTool(tooltips=[ - (groupby, '@groupval{1.1}'), - (xparamname, '@x{1.1}'), - (yparamname, '@y') - ]) - else: # pragma: no cover - hover = HoverTool( - tooltips=[ - (groupby, '@groupval'), - (xparamname, '@x{1.1}'), - (yparamname, '@y'), - ]) - - hover.mode = 'mouse' - TOOLS = [SaveTool(), PanTool(), BoxZoomTool(), WheelZoomTool(), - ResetTool(), TapTool(), hover] - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False, width=920) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.title.text = title - plot.title.text_font_size = "1.0em" - #plot.sizing_mode = 'stretch_both' - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - errorbar(plot, xparam, yparam, source=source, - xerr=ploterrorbars, - yerr=ploterrorbars, - point_kwargs={ - 'line_color': "#969696", - 'size': "groupsize", - 'fill_color': "color", - 'fill_alpha': 1.0, - }, - ) - - for nr, gvalue, color in colorlegend: - box = BoxAnnotation(bottom=75+20*nr, left=50, top=95+20*nr, - right=70, - bottom_units='screen', - top_units='screen', - left_units='screen', - right_units='screen', - fill_color=color, - fill_alpha=1.0, - line_color=color) - legendlabel = Label(x=71, y=78+20*nr, x_units='screen', - y_units='screen', - text="{gvalue:3.0f}".format(gvalue=gvalue), - background_fill_alpha=1.0, - text_color='black', - text_font_size="0.7em") - plot.add_layout(box) - plot.add_layout(legendlabel) - - if colorlegend: - legendlabel = Label(x=322, y=250, x_units='screen', - y_units='screen', - text='group legend', - text_color='black', - text_font_size="0.7em", - angle=90, - angle_units='deg') - - if xparam == 'workoutid': # pragma: no cover - plot.xaxis.axis_label = 'Workout' - else: - plot.xaxis.axis_label = axlabels[xparam] - - if yparam == 'workoutid': # pragma: no cover - plot.xaxis.axis_label = 'Workout' - else: - plot.yaxis.axis_label = axlabels[yparam] - - binlabel = Label(x=50, y=50, x_units='screen', - y_units='screen', - text="Bin size {binsize:3.1f}".format(binsize=binsize), - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - slidertext = "SPM: {:.0f}-{:.0f}, WpS: {:.0f}-{:.0f}".format( - spmmin, spmmax, workmin, workmax - ) - sliderlabel = Label(x=50, y=20, x_units='screen', y_units='screen', - text=slidertext, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(binlabel) - plot.add_layout(sliderlabel) - - yrange1 = Range1d(start=yaxmin, end=yaxmax) - plot.y_range = yrange1 - - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - script, div = components(plot) - - return [script, div] + return script, div + def interactive_cum_flex_chart2(theworkouts, promember=0, @@ -4993,394 +1563,119 @@ def interactive_cum_flex_chart2(theworkouts, promember=0, extratitle='', trendline=False): - # datadf = dataprep.smalldataprep(theworkouts,xparam,yparam1,yparam2) ids = [int(w.id) for w in theworkouts] - columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] - datadf = dataprep.getsmallrowdata_db(columns, ids=ids, doclean=True, - workstrokesonly=workstrokesonly) + columns = [name for name, d in metrics.rowingmetrics] + columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] + columns = columns + ['spm', 'driveenergy', 'distance' ,'workoutstate'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance', 'workoutstate'] + + datadf = pd.DataFrame() + if promember: + datadf = dataprep.read_data(columns, ids=ids, doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) + else: + datadf = dataprep.read_data(columns_basic, ids=ids, doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) try: _ = datadf[yparam2] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover yparam2 = 'None' try: _ = datadf[yparam1] - except KeyError: + except (KeyError, ColumnNotFoundError): yparam1 = 'None' - datadf.dropna(axis=1, how='all', inplace=True) - datadf.dropna(axis=0, how='any', inplace=True) + datadf = dataprep.remove_nulls_pl(datadf) # test if we have drive energy try: # pragma: no cover _ = datadf['driveenergy'].mean() - except KeyError: # pragma: no cover - datadf['driveenergy'] = 500. + except (KeyError, ColumnNotFoundError): # pragma: no cover + datadf = datadf.with_columns((pl.lit(500)).alias("driveenergy")) # test if we have power try: # pragma: no cover _ = datadf['power'].mean() - except KeyError: # pragma: no cover - datadf['power'] = 50. + except (KeyError, ColumnNotFoundError): # pragma: no cover + datadf = datadf.with_columns((pl.lit(50)).alias("power")) yparamname1 = axlabels[yparam1] if yparam2 != 'None': yparamname2 = axlabels[yparam2] # check if dataframe not empty - if datadf.empty: # pragma: no cover + if datadf.is_empty(): # pragma: no cover return ['', '

No non-zero data in selection

', '', ''] try: - datadf['x1'] = datadf.loc[:, xparam] - except KeyError: # pragma: no cover + datadf = datadf.with_columns(pl.col(xparam).alias("x1")) + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - datadf['x1'] = datadf['distance'] - except KeyError: + datadf = datadf.with_columns(pl.col("distance").alias("x1")) + except (KeyError, ColumnNotFoundError): try: - datadf['x1'] = datadf['time'] - except KeyError: # pragma: no cover + datadf = datadf.with_columns(pl.col('time').alias("x1")) + except (KeyError, ColumnNotFoundError): # pragma: no cover return ['', '

No non-zero data in selection

', '', ''] try: - datadf['y1'] = datadf.loc[:, yparam1] - except KeyError: + datadf = datadf.with_columns(pl.col(yparam1).alias("y1")) + except (KeyError, ColumnNotFoundError): try: - datadf['y1'] = datadf['pace'] - except KeyError: # pragma: no cover + datadf = datadf.with_columns(pl.col('pace').alias("y1")) + except (KeyError, ColumnNotFoundError): # pragma: no cover return ['', '

No non-zero data in selection

', '', ''] if yparam2 != 'None': try: - datadf['y2'] = datadf.loc[:, yparam2] - except KeyError: # pragma: no cover - datadf['y2'] = datadf['y1'] + datadf = datadf.with_columns(pl.col(yparam2).alias("y2")) + except (KeyError, ColumnNotFoundError): # pragma: no cover + datadf = datadf.with_columns(pl.col("y1").alias("y2")) else: # pragma: no cover - datadf['y2'] = datadf['y1'] + datadf = datadf.with_columns(pl.col("y1").alias("y2")) - # average values - x1mean = datadf['x1'].mean() - y1mean = datadf['y1'].mean() - y2mean = datadf['y2'].mean() + datadf = datadf.with_columns(xname = pl.lit(axlabels[xparam])) + datadf = datadf.with_columns(yname1 = pl.lit(axlabels[yparam1])) - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': # pragma: no cover - y_axis_type = 'datetime' - y1mean = datadf.loc[:, 'pace'].mean() - - datadf['xname'] = axlabels[xparam] - datadf['yname1'] = axlabels[yparam1] if yparam2 != 'None': - datadf['yname2'] = axlabels[yparam2] + datadf = datadf.with_columns(yname2 = pl.lit(axlabels[yparam2])) else: # pragma: no cover - datadf['yname2'] = axlabels[yparam1] + datadf = datadf.with_columns(yname2 = pl.lit(axlabels[yparam1])) def func(x, a, b): return a*x+b x1 = datadf['x1'] y1 = datadf['y1'] - popt, pcov = optimize.curve_fit(func, x1, y1) - ytrend = func(x1, popt[0], popt[1]) - datadf['ytrend'] = ytrend - - source = ColumnDataSource( - datadf - ) - - source2 = ColumnDataSource( - datadf.copy() - ) - - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'stretch_both' - - if extratitle: - plot.title.text = extratitle - - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - x1means = Span(location=x1mean, dimension='height', line_color='green', - line_dash=[6, 6], line_width=2) - - y1means = Span(location=y1mean, dimension='width', line_color='blue', - line_dash=[6, 6], line_width=2) - y2means = y1means - - xlabel = Label(x=50, y=80, x_units='screen', y_units='screen', - text=axlabels[xparam] + - ": {x1mean:6.2f}".format(x1mean=x1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='green', - ) - - sliderlabel = Label(x=10, y=470, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - plot.add_layout(x1means) - plot.add_layout(xlabel) - plot.add_layout(y1means) - plot.add_layout(sliderlabel) - - y1label = Label(x=50, y=50, x_units='screen', y_units='screen', - text=axlabels[yparam1] + - ": {y1mean:6.2f}".format(y1mean=y1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - - if yparam1 != 'time' and yparam1 != 'pace': - plot.add_layout(y1label) - - y2label = y1label - plot.circle('x1', 'y1', source=source2, fill_alpha=0.3, line_color=None, - legend_label=yparamname1, - ) - - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam1] - - yrange1 = Range1d(start=yaxminima[yparam1], end=yaxmaxima[yparam1]) - plot.y_range = yrange1 - - xrange1 = Range1d(start=yaxminima[xparam], end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - if yparam1 == 'pace': # pragma: no cover - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - # trendline - if trendline: - plot.line('x1', 'ytrend', source=source2, legend_label=yparamname1+' (trend)') - - if yparam2 != 'None': - yrange2 = Range1d(start=yaxminima[yparam2], end=yaxmaxima[yparam2]) - plot.extra_y_ranges["yax2"] = yrange2 - - plot.circle('x1', 'y2', color="red", y_range_name="yax2", - legend_label=yparamname2, - source=source2, fill_alpha=0.3, line_color=None) - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlabels[yparam2]), 'right') - - y2means = Span(location=y2mean, dimension='width', line_color='red', - line_dash=[6, 6], line_width=2, y_range_name="yax2") - - plot.add_layout(y2means) - y2label = Label(x=50, y=20, x_units='screen', y_units='screen', - text=axlabels[yparam2] + - ": {y2mean:6.2f}".format(y2mean=y2mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='red', - ) - if yparam2 != 'pace' and yparam2 != 'time': - plot.add_layout(y2label) - - callback = CustomJS(args=dict(source=source, source2=source2, - x1means=x1means, - y1means=y1means, - y1label=y1label, - y2label=y2label, - xlabel=xlabel, - sliderlabel=sliderlabel, - y2means=y2means), code=""" - var data = source.data - var data2 = source2.data - var x1 = data['x1'] - var y1 = data['y1'] - var y2 = data['y2'] - var spm1 = data['spm'] - - var index1 = data['index'] - - var distance1 = data['distance'] - var power1 = data['power'] - var driveenergy1 = data['driveenergy'] - var xname = data['xname'] - var yname1 = data['yname1'] - var yname2 = data['yname2'] - var workoutid1 = data['workoutid'] - var ytrend = data['ytrend'] - - var minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - - sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0) - sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0) - sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0) - - var xm = 0 - var ym1 = 0 - var ym2 = 0 - - data2['x1'] = [] - data2['y1'] = [] - data2['y2'] = [] - data2['distance'] = [] - data2['power'] = [] - data2['x1mean'] = [] - data2['y1mean'] = [] - data2['y2mean'] = [] - data2['driveenergy'] = [] - data2['workoutid'] = [] - data2['xname'] = [] - data2['yname1'] = [] - data2['yname2'] = [] - data2['spm'] = [] - data2['ytrend'] = [] - - for (var i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - data2['x1'].push(x1[i]) - data2['y1'].push(y1[i]) - data2['y2'].push(y2[i]) - data2['spm'].push(spm1[i]) - data2['driveenergy'].push(driveenergy1[i]) - data2['distance'].push(distance1[i]) - data2['power'].push(power1[i]) - data2['workoutid'].push(0) - data2['xname'].push(0) - data2['yname1'].push(0) - data2['yname2'].push(0) - data2['ytrend'].push(ytrend[i]) - - xm += x1[i] - ym1 += y1[i] - ym2 += y2[i] - } - } - } - } - - - xm /= data2['x1'].length - ym1 /= data2['x1'].length - ym2 /= data2['x1'].length - - for (var i=0; i 1: # pragma: no cover for column in columns: @@ -5406,338 +1708,89 @@ def interactive_flexchart_stacked(id, r, xparam='time', if metricsdicts[column]['maysmooth']: nrsteps = int(log2(r.usersmooth)) for i in range(nrsteps): - rowdata[column] = utils.ewmovingaverage( - rowdata[column], 5) + rowdata = rowdata.with_columns( + utils.ewmovingaverage( + rowdata[column], 5).alias(column) + ) except KeyError: pass if len(rowdata) < 2: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], - doclean=False, - workstrokesonly=False) + if ispromember(r.user): + rowdata = dataprep.read_data(columns, ids=[id], + doclean=False, + workstrokesonly=False, + for_chart=True) + else: + rowdata = dataprep.read_data(columns_basic, ids=[id], + doclean=False, + workstrokesonly=False, + for_chart=True) - if rowdata.empty: - return "", "No valid data", '', '', comment + rowdata = dataprep.remove_nulls_pl(rowdata) + + + if rowdata.is_empty(): + return "", "No valid data" try: - tseconds = rowdata.loc[:, 'time'] - except KeyError: # pragma: no cover - return '', 'No time data - cannot make flex plot', '', '', comment + tseconds = rowdata['time'] + except (KeyError, ColumnNotFoundError): # pragma: no cover + return '', 'No time data - cannot make flex plot' try: - rowdata['x1'] = rowdata.loc[:, xparam] - except KeyError: # pragma: no cover - rowdata['x1'] = 0*rowdata.loc[:, 'time'] + rowdata = rowdata.with_columns(x1=pl.col(xparam)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(x1=pl.lit(0)) try: - rowdata['y1'] = rowdata.loc[:, yparam1] - except KeyError: # pragma: no cover - rowdata['y1'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam1] = rowdata['y1'] - - try: # pragma: no cover - rowdata['y2'] = rowdata.loc[:, yparam2] - except KeyError: - rowdata['y2'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam2] = rowdata['y2'] + rowdata = rowdata.with_columns(y1=pl.col(yparam1)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y1=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y1")).alias(yparam1)) try: - rowdata['y3'] = rowdata.loc[:, yparam3] - except KeyError: # pragma: no cover - rowdata['y3'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam3] = rowdata['y3'] + rowdata = rowdata.with_columns(y2=pl.col(yparam2)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y2=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y2")).alias(yparam2)) try: - rowdata['y4'] = rowdata.loc[:, yparam4] - except KeyError: # pragma: no cover - rowdata['y4'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam4] = rowdata['y4'] - - if xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'distance' or xparam == 'cumdist': # pragma: no cover - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - else: # pragma: no cover - try: - xaxmax = get_yaxmaxima(r, xparam, mode) - xaxmin = get_yaxminima(r, xparam, mode) - except KeyError: - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() - - x_axis_type = 'linear' - y1_axis_type = 'linear' - y2_axis_type = 'linear' - y3_axis_type = 'linear' - y4_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': # pragma: no cover - y1_axis_type = 'datetime' - - if yparam2 == 'pace': # pragma: no cover - y2_axis_type = 'datetime' - - if yparam3 == 'pace': # pragma: no cover - y3_axis_type = 'datetime' - - if yparam4 == 'pace': # pragma: no cover - y4_axis_type = 'datetime' + rowdata = rowdata.with_columns(y1=pl.col(yparam3)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y3=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y3")).alias(yparam3)) try: - rowdata['xname'] = axlabels[xparam] - except KeyError: # pragma: no cover - rowdata['xname'] = xparam + rowdata = rowdata.with_columns(y4=pl.col(yparam1)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y4=pl.col("time")) + rowdata = rowdata.with_columns((pl.col("y4")).alias(yparam4)) - try: - rowdata['yname1'] = axlabels[yparam1] - except KeyError: # pragma: no cover - rowdata['yname1'] = yparam1 + + # replace nans + rowdata = rowdata.fill_nan(0) - try: - rowdata['yname2'] = axlabels[yparam2] - except KeyError: # pragma: no cover - rowdata['yname2'] = yparam2 - try: - rowdata['yname3'] = axlabels[yparam3] - except KeyError: # pragma: no cover - rowdata['yname3'] = yparam3 + data_dict = rowdata.to_dicts() - try: - rowdata['yname4'] = axlabels[yparam4] - except KeyError: # pragma: no cover - rowdata['yname4'] = yparam4 + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': row.name, + 'x': xparam, + 'y1': yparam1, + 'y2': yparam2, + 'y3': yparam3, + 'y4': yparam4, + 'data': data_dict, + 'metrics': metrics_list, + } - # prepare data - source = ColumnDataSource( - rowdata - ) + script, div = get_chart("/stacked", chart_data, debug=False) - TOOLS = 'box_zoom,wheel_zoom,reset,tap,hover' - TOOLS2 = 'box_zoom,hover' + return script, div - plot1 = figure(x_axis_type=x_axis_type, y_axis_type=y1_axis_type, width=920, height=150, - tools=TOOLS, toolbar_location='above') - plot2 = figure(x_axis_type=x_axis_type, y_axis_type=y2_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - plot3 = figure(x_axis_type=x_axis_type, y_axis_type=y3_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - plot4 = figure(x_axis_type=x_axis_type, y_axis_type=y4_axis_type, width=920, height=150, - tools=TOOLS2, toolbar_location=None) - - plot1.xaxis.visible = False - plot2.xaxis.visible = False - plot3.xaxis.visible = False - - #plot1.sizing_mode = 'stretch_both' - #plot2.sizing_mode = 'stretch_both' - #plot3.sizing_mode = 'stretch_both' - #plot4.sizing_mode = 'stretch_both' - - linked_crosshair = CrosshairTool(dimensions="height") - plot1.add_tools(linked_crosshair) - plot2.add_tools(linked_crosshair) - plot3.add_tools(linked_crosshair) - plot4.add_tools(linked_crosshair) - - xaxlabel = axlabels.get(xparam, xparam) - yax1label = axlabels.get(yparam1, yparam1) - - plot1.yaxis.axis_label = yax1label - - yax2label = axlabels.get(yparam2, yparam2) - - plot2.yaxis.axis_label = yax2label - - yax3label = axlabels.get(yparam3, yparam3) - - plot3.yaxis.axis_label = yax3label - - yax4label = axlabels.get(yparam4, yparam4) - - plot4.yaxis.axis_label = yax4label - - plot4.xaxis.axis_label = xaxlabel - - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot1.x_range = xrange1 - plot2.x_range = xrange1 - plot3.x_range = xrange1 - plot4.x_range = xrange1 - - if xparam == 'time': - plot4.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - hover1 = plot1.select(dict(type=HoverTool)) - hover2 = plot2.select(dict(type=HoverTool)) - hover3 = plot3.select(dict(type=HoverTool)) - hover4 = plot4.select(dict(type=HoverTool)) - - if yparam1 == 'pace': - y1tooltip = '@fpace' - elif yparam1 != 'None': # pragma: no cover - y1tooltip = '@{yparam1}'.format(yparam1=yparam1) - if metricsdicts[yparam1]['numtype'] == 'integer' or yparam1 == 'power': - y1tooltip += '{int}' - else: # pragma: no cover - y1tooltip += '{0.00}' - else: # pragma: no cover - y1tooltip = '' - comment = 'The metric in the first chart is only accessible with a Pro plan or higher' - - if yparam2 == 'pace': # pragma: no cover - y2tooltip = '@fpace' - elif yparam2 != 'None': - y2tooltip = '@{yparam2}'.format(yparam2=yparam2) - if metricsdicts[yparam2]['numtype'] == 'integer' or yparam2 == 'power': - y2tooltip += '{int}' - else: # pragma: no cover - y2tooltip += '{0.00}' - else: # pragma: no cover - y2tooltip = '' - comment = 'The metric in the second chart is only accessible with a Pro plan or higher' - - if yparam3 == 'pace': # pragma: no cover - y3tooltip = '@fpace' - elif yparam3 != 'None': - y3tooltip = '@{yparam3}'.format(yparam3=yparam3) - if metricsdicts[yparam3]['numtype'] == 'integer' or yparam3 == 'power': - y3tooltip += '{int}' - else: # pragma: no cover - y3tooltip += '{0.00}' - else: # pragma: no cover - y3tooltip = '' - comment = 'The metric in the third chart is only accessible with a Pro plan or higher' - - if yparam4 == 'pace': # pragma: no cover - y4tooltip = '@fpace' - elif yparam4 != 'None': - y4tooltip = '@{yparam4}'.format(yparam4=yparam4) - if metricsdicts[yparam4]['numtype'] == 'integer' or yparam4 == 'power': # pragma: no cover - y4tooltip += '{int}' - else: # pragma: no cover - y4tooltip += '{0.00}' - else: # pragma: no cover - y4tooltip = '' - comment = 'The metric in the fourth chart is only accessible with a Pro plan or higher' - - if yparam1 != 'None': - hover1.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - if yparam2 != 'None': - hover2.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - if yparam3 != 'None': - hover3.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - if yparam4 != 'None': - hover4.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - (axlabels[yparam1], y1tooltip), - (axlabels[yparam2], y2tooltip), - (axlabels[yparam3], y3tooltip), - (axlabels[yparam4], y4tooltip), - ]) - - hover1.mode = 'vline' - hover2.mode = 'vline' - hover3.mode = 'vline' - hover4.mode = 'vline' - - y1min = get_yaxminima(r, yparam1, mode) - y2min = get_yaxminima(r, yparam2, mode) - y3min = get_yaxminima(r, yparam3, mode) - y4min = get_yaxminima(r, yparam4, mode) - - y1max = get_yaxmaxima(r, yparam1, mode) - y2max = get_yaxmaxima(r, yparam2, mode) - y3max = get_yaxmaxima(r, yparam3, mode) - y4max = get_yaxmaxima(r, yparam4, mode) - - plot1.y_range = Range1d(start=y1min, end=y1max) - plot2.y_range = Range1d(start=y2min, end=y2max) - plot3.y_range = Range1d(start=y3min, end=y3max) - plot4.y_range = Range1d(start=y4min, end=y4max) - - if yparam1 == 'pace': - plot1.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot1.y_range = Range1d(y1min, y1max) - - if yparam2 == 'pace': # pragma: no cover - plot2.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot2.y_range = Range1d(y2min, y2max) - - if yparam3 == 'pace': # pragma: no cover - plot3.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot3.y_range = Range1d(y3min, y3max) - - if yparam4 == 'pace': # pragma: no cover - plot4.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - plot4.y_range = Range1d(y4min, y4max) - - plot1.line('x1', 'y1', source=source, color=palette2[1]) - plot2.line('x1', 'y2', source=source, color=palette2[3]) - plot3.line('x1', 'y3', source=source, color=palette2[0]) - plot4.line('x1', 'y4', source=source, color=palette2[2]) - - mylayout = layoutcolumn([ - plot1, - plot2, - plot3, - plot4, - ]) - - #mylayout.sizing_mode = 'stretch_both' - - script, div = components(mylayout) - js_resources = INLINE.render_js() - css_resources = INLINE.render_css() - - return script, div, js_resources, css_resources, comment def interactive_flex_chart2(id, r, promember=0, @@ -5749,117 +1802,100 @@ def interactive_flex_chart2(id, r, promember=0, trendline=False, mode='rower'): - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' + columns = [name for name, d in metrics.rowingmetrics] + columns_basic = [name for name, d in metrics.rowingmetrics if d['group'] == 'basic'] + columns = columns + ['spm', 'driveenergy', 'distance','workoutstate'] + columns_basic = columns_basic + ['spm', 'driveenergy', 'distance','workoutstate'] - columns = [xparam, yparam1, yparam2, - 'ftime', 'distance', 'fpace', - 'power', 'hr', 'spm', 'driveenergy', - 'time', 'pace', 'workoutstate'] + if promember: + rowdata = dataprep.read_data(columns, ids=[id], doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) + else: + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=True, + workstrokesonly=workstrokesonly, for_chart=True) - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], doclean=True, - workstrokesonly=workstrokesonly) + if r.usersmooth > 1: # pragma: no cover for column in columns: try: if metricsdicts[column]['maysmooth']: nrsteps = int(log2(r.usersmooth)) for i in range(nrsteps): - rowdata[column] = utils.ewmovingaverage( - rowdata[column], 5) + rowdata = rowdata.with_columns( + (utils.ewmovingaverage( + rowdata[column], 5)).alias(column) + ) except KeyError: pass - try: - if len(rowdata) < 2: - rowdata = dataprep.getsmallrowdata_db(columns, ids=[id], - doclean=False, - workstrokesonly=False) + if len(rowdata) < 2: + if promember: + rowdata = dataprep.read_data(columns, ids=[id], + doclean=False, + workstrokesonly=False, for_chart=True) + else: + rowdata = dataprep.read_data(columns_basic, ids=[id], doclean=False, + workstrokesonly=False, for_chart=True) workstrokesonly = False - except (KeyError, TypeError): # pragma: no cover - workstrokesonly = False + try: _ = rowdata[yparam2] - except (KeyError, TypeError): # pragma: no cover + except (KeyError, TypeError, ColumnNotFoundError): # pragma: no cover yparam2 = 'None' try: _ = rowdata[yparam1] - except (TypeError, KeyError): # pragma: no cover + except (TypeError, KeyError, ColumnNotFoundError): # pragma: no cover yparam1 = 'None' # test if we have drive energy try: _ = rowdata['driveenergy'].mean() - except (KeyError, TypeError): - rowdata['driveenergy'] = 500. + except (KeyError, TypeError, ColumnNotFoundError): + rowdata = rowdata.with_columns(driveenergy=pl.lit(500)) # test if we have power try: _ = rowdata['power'].mean() - except (KeyError, TypeError): - rowdata['power'] = 50. + except (KeyError, TypeError, ColumnNotFoundError): + rowdata = rowdata.with_columns(power=pl.lit(50)) + # replace nans - rowdata.fillna(value=0, inplace=True) + rowdata = dataprep.remove_nulls_pl(rowdata) row = Workout.objects.get(id=id) - if rowdata.empty: - return "", "No valid data", '', '', workstrokesonly - - workoutstatesrest = [3] - - if workstrokesonly: # pragma: no cover - try: - rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] - except KeyError: - pass + if rowdata.is_empty(): + return "", "No valid data", workstrokesonly try: - tseconds = rowdata.loc[:, 'time'] - except KeyError: # pragma: no cover - return '', 'No time data - cannot make flex plot', '', '', workstrokesonly + tseconds = rowdata['time'] + except (KeyError, ColumnNotFoundError): # pragma: no cover + return '', 'No time data - cannot make flex plot', workstrokesonly try: - rowdata['x1'] = rowdata.loc[:, xparam] - except KeyError: # pragma: no cover - rowdata['x1'] = 0*rowdata.loc[:, 'time'] + rowdata = rowdata.with_columns(x1 = pl.col(xparam)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(x1 = pl.col("time")) try: - rowdata['y1'] = rowdata.loc[:, yparam1] - except KeyError: # pragma: no cover - rowdata['y1'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam1] = rowdata['y1'] + rowdata = rowdata.with_columns(y1 = pl.col(yparam1)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y1 = pl.col("time")) + rowdata = rowdata.with_columns(yparam1 = pl.col("y1")) + if yparam2 != 'None': try: - rowdata['y2'] = rowdata.loc[:, yparam2] - except KeyError: # pragma: no cover - rowdata['y2'] = 0*rowdata.loc[:, 'time'] - rowdata[yparam2] = rowdata['y2'] - else: # pragma: no cover - rowdata['y2'] = rowdata['y1'] + rowdata = rowdata.with_columns(y2 = pl.col(yparam2)) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns(y2 = pl.col("time")) + rowdata = rowdata.with_columns(yparam2 = pl.col("y2")) - if xparam == 'time': - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'distance' or xparam == 'cumdist': - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() else: # pragma: no cover - try: - xaxmax = get_yaxmaxima(r, xparam, mode) - xaxmin = get_yaxminima(r, xparam, mode) - except KeyError: - xaxmax = rowdata['x1'].max() - xaxmin = rowdata['x1'].min() + rowdata = rowdata.with_columns(y2=pl.col("y1")) + # average values if xparam != 'time': @@ -5873,43 +1909,25 @@ def interactive_flex_chart2(id, r, promember=0, y1mean = rowdata['y1'].mean() y2mean = rowdata['y2'].mean() - if xparam != 'time': - xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. - else: - xvals = np.arange(100) - - # constant power plot - if yparam1 == 'driveenergy': - if xparam == 'spm': # pragma: no cover - yconstantpower = rowdata['y1'].mean()*rowdata['x1'].mean()/xvals - - x_axis_type = 'linear' - y_axis_type = 'linear' - if xparam == 'time': - x_axis_type = 'datetime' - - if yparam1 == 'pace': - y_axis_type = 'datetime' - try: - y1mean = rowdata.loc[:, 'pace'].mean() - except KeyError: # pragma: no cover - y1mean = 0 try: - rowdata['xname'] = axlabels[xparam] - except KeyError: # pragma: no cover - rowdata['xname'] = xparam + rowdata = rowdata.with_columns((pl.lit(axlabels[xparam])).alias("xname")) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns((pl.lit(xparam)).alias("xname")) + try: - rowdata['yname1'] = axlabels[yparam1] - except KeyError: # pragma: no cover - rowdata['yname1'] = yparam1 + rowdata = rowdata.with_columns((pl.lit(axlabels[yparam1])).alias("yname1")) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns((pl.lit(yparam1)).alias("yname1")) + if yparam2 != 'None': try: - rowdata['yname2'] = axlabels[yparam2] - except KeyError: # pragma: no cover - rowdata['yname2'] = yparam2 + rowdata = rowdata.with_columns((pl.lit(axlabels[yparam2])).alias("yname2")) + except (KeyError, ColumnNotFoundError): # pragma: no cover + rowdata = rowdata.with_columns((pl.lit(yparam2)).alias("yname2")) + else: # pragma: no cover - rowdata['yname2'] = rowdata['yname1'] + rowdata = rowdata.with_columns((pl.col("yname1")).alias("yname2")) def func(x, a, b): return a*x+b @@ -5919,423 +1937,32 @@ def interactive_flex_chart2(id, r, promember=0, try: popt, pcov = optimize.curve_fit(func, x1, y1) ytrend = func(x1, popt[0], popt[1]) - rowdata['ytrend'] = ytrend + rowdata = rowdata.with_columns(ytrend=ytrend) except TypeError: # pragma: no cover - rowdata['ytrend'] = y1 - - # prepare data - source = ColumnDataSource( - rowdata - ) + rowdata = rowdata.with_columns(ytrend=pl.col("y1")) - # second source for filtering - source2 = ColumnDataSource( - rowdata.copy() - ) - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - else: - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + #rowdata = rowdata.replace([np.inf, -np.inf], np.nan) + rowdata = rowdata.fill_nan(None).drop_nulls() - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, toolbar_location='above', - toolbar_sticky=False, width=800, height=600, - ) - #plot.sizing_mode = 'stretch_both' + data_dict = rowdata.to_dicts() - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'stretch_both' + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + chart_data = { + 'title': row.name, + 'x': xparam, + 'y1': yparam1, + 'y2': yparam2, + 'data': data_dict, + 'metrics': metrics_list, + 'trendline': trendline, + 'plottype': plottype, + } - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) + script, div = get_chart("/flex", chart_data, debug=False) - x1means = Span(location=x1mean, dimension='height', line_color='green', - line_dash=[6, 6], line_width=2) - - y1means = Span(location=y1mean, dimension='width', line_color='blue', - line_dash=[6, 6], line_width=2) - y2means = y1means - - try: - xlabeltext = axlabels[xparam]+": {x1mean:6.2f}".format( - x1mean=x1mean - ) - except KeyError: # pragma: no cover - xlabeltext = xparam+": {x1mean:6.2f}".format(x1mean=x1mean) - - xlabel = Label(x=50, y=80, x_units='screen', y_units='screen', - text=xlabeltext, - background_fill_alpha=.7, - background_fill_color='white', - text_color='green', - ) - - annolabel = Label(x=50, y=450, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - sliderlabel = Label(x=10, y=470, x_units='screen', y_units='screen', - text='', - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', text_font_size='10pt', - ) - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover - plot.add_layout(x1means) - plot.add_layout(xlabel) - - plot.add_layout(y1means) - plot.add_layout(annolabel) - plot.add_layout(sliderlabel) - - try: - yaxlabel = axlabels[yparam1] - except KeyError: # pragma: no cover - yaxlabel = str(yparam1)+' ' - - try: - xaxlabel = axlabels[xparam] - except KeyError: # pragma: no cover - xaxlabel = xparam - - y1label = Label(x=50, y=50, x_units='screen', y_units='screen', - text=yaxlabel+": {y1mean:6.2f}".format(y1mean=y1mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='blue', - ) - if yparam1 != 'time' and yparam1 != 'pace': # pragma: no cover - plot.add_layout(y1label) - y2label = y1label - - # average values - if yparam1 == 'driveenergy': # pragma: no cover - if xparam == 'spm': - plot.line(xvals, yconstantpower, color="green", - legend_label="Constant Power") - - # trendline - if trendline: # pragma: no cover - plot.line('x1', 'ytrend', source=source2, legend_label=yaxlabel+' (trend)') - - if plottype == 'line': - plot.line('x1', 'y1', source=source2, legend_label=yaxlabel) - elif plottype == 'scatter': # pragma: no cover - plot.scatter('x1', 'y1', source=source2, legend_label=yaxlabel, fill_alpha=0.4, - line_color=None) - - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - plot.title.text_font_size = "1.0em" - - #plot.sizing_mode = 'stretch_both' - plot.xaxis.axis_label = xaxlabel - - plot.yaxis.axis_label = yaxlabel - - try: - yrange1 = Range1d(start=get_yaxminima(r, yparam1, mode), - end=get_yaxmaxima(r, yparam1, mode)) - except KeyError: # pragma: no cover - yrange1 = Range1d(start=rowdata[yparam1].min(), - end=rowdata[yparam1].max()) - - plot.y_range = yrange1 - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover - try: - xrange1 = Range1d(start=get_yaxminima(r, xparam, mode), - end=get_yaxmaxima(r, xparam, mode)) - except KeyError: - xrange1 = Range1d(start=rowdata[xparam].min(), - end=rowdata[xparam].max()) - - plot.x_range = xrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - if yparam1 == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - if yparam2 != 'None': - try: - yrange2 = Range1d(start=get_yaxminima(r, yparam2, mode), - end=get_yaxmaxima(r, yparam2, mode)) - except KeyError: # pragma: no cover - yrange2 = Range1d(start=rowdata[yparam2].min(), - end=rowdata[yparam2].max()) - - plot.extra_y_ranges["yax2"] = yrange2 - # = {"yax2": yrange2} - try: - axlegend = axlabels[yparam2] - except KeyError: # pragma: no cover - axlegend = str(yparam2)+' ' - - if plottype == 'line': - plot.line('x1', 'y2', color="red", y_range_name="yax2", - legend_label=axlegend, - source=source2) - - elif plottype == 'scatter': # pragma: no cover - plot.scatter('x1', 'y2', source=source2, legend_label=axlegend, - fill_alpha=0.4, - line_color=None, color="red", y_range_name="yax2") - - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=axlegend), 'right') - - y2means = Span(location=y2mean, dimension='width', line_color='red', - line_dash=[6, 6], line_width=2, y_range_name="yax2") - - plot.add_layout(y2means) - y2label = Label(x=50, y=20, x_units='screen', y_units='screen', - text=axlegend+": {y2mean:6.2f}".format(y2mean=y2mean), - background_fill_alpha=.7, - background_fill_color='white', - text_color='red', - ) - if yparam2 != 'pace' and yparam2 != 'time': - plot.add_layout(y2label) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Distance', '@distance{int}'), - ('Pace', '@fpace'), - ('HR', '@hr{int}'), - ('SPM', '@spm{1.1}'), - ('Power', '@power{int}'), - ]) - - hover.mode = 'mouse' - - callback = CustomJS(args=dict(source=source, source2=source2, - x1means=x1means, - y1means=y1means, - y1label=y1label, - y2label=y2label, - xlabel=xlabel, - annolabel=annolabel, - sliderlabel=sliderlabel, - y2means=y2means, - ), code=""" - var data = source.data - var data2 = source2.data - var x1 = data['x1'] - var y1 = data['y1'] - var y2 = data['y2'] - var spm1 = data['spm'] - var time1 = data['time'] - var ftime1 = data['ftime'] - var pace1 = data['pace'] - var hr1 = data['hr'] - var fpace1 = data['fpace'] - var distance1 = data['distance'] - var power1 = data['power'] - var driveenergy1 = data['driveenergy'] - var xname = data['xname'] - var yname1 = data['yname1'] - var yname2 = data['yname2'] - var workoutid1 = data['workoutid'] - var workoutstate1 = data['workoutstate'] - var ytrend = data['ytrend'] - - var annotation = annotation.value - var minspm = minspm.value - var maxspm = maxspm.value - var mindist = mindist.value - var maxdist = maxdist.value - var minwork = minwork.value - var maxwork = maxwork.value - - sliderlabel.text = 'SPM: '+minspm.toFixed(0)+'-'+maxspm.toFixed(0) - sliderlabel.text += ', Dist: '+mindist.toFixed(0)+'-'+maxdist.toFixed(0) - sliderlabel.text += ', WpS: '+minwork.toFixed(0)+'-'+maxwork.toFixed(0) - - var xm = 0 - var ym1 = 0 - var ym2 = 0 - - data2['x1'] = [] - data2['y1'] = [] - data2['y2'] = [] - data2['spm'] = [] - data2['time'] = [] - data2['ftime'] = [] - data2['pace'] = [] - data2['hr'] = [] - data2['fpace'] = [] - data2['distance'] = [] - data2['power'] = [] - data2['x1mean'] = [] - data2['y1mean'] = [] - data2['y2mean'] = [] - data2['driveenergy'] = [] - data2['workoutid'] = [] - data2['workoutstate'] = [] - data2['xname'] = [] - data2['yname1'] = [] - data2['yname2'] = [] - data2['ytrend'] = [] - - - for (var i=0; i=minspm && spm1[i]<=maxspm) { - if (distance1[i]>=mindist && distance1[i]<=maxdist) { - if (driveenergy1[i]>=minwork && driveenergy1[i]<=maxwork) { - data2['x1'].push(x1[i]) - data2['y1'].push(y1[i]) - data2['y2'].push(y2[i]) - data2['spm'].push(spm1[i]) - data2['time'].push(time1[i]) - data2['ftime'].push(ftime1[i]) - data2['fpace'].push(fpace1[i]) - data2['driveenergy'].push(driveenergy1[i]) - data2['pace'].push(pace1[i]) - data2['hr'].push(hr1[i]) - data2['distance'].push(distance1[i]) - data2['power'].push(power1[i]) - data2['workoutid'].push(0) - data2['workoutstate'].push(0) - data2['xname'].push(0) - data2['yname1'].push(0) - data2['yname2'].push(0) - data2['ytrend'].push(ytrend[i]) - - - xm += x1[i] - ym1 += y1[i] - ym2 += y2[i] - } - } - } - } - - xm /= data2['x1'].length - ym1 /= data2['x1'].length - ym2 /= data2['x1'].length - - for (var i=0; iNo non-zero data in selection

', ''] + + datadf = datadf.with_columns(pl.col("workoutid").cast(pl.UInt32).keep_name()) + + # filter for start end dict + nrworkouts = len(ids) try: - tseconds = datadf.loc[:, 'time'] - except KeyError: # pragma: no cover + tseconds = datadf['time'] + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - tseconds = datadf.loc[:, xparam] + tseconds = datadf[xparam] except: - return ['', '

A chart data error occurred

', '', 'A chart data error occurred'] + return ['

A chart data error occurred

', ''] - # check if dataframe not empty - if datadf.empty: # pragma: no cover - return ['', '

No non-zero data in selection

', '', 'No non-zero data in selection'] + if (xparam == 'time'): + datadf = datadf.with_columns((pl.col(xparam)-datadf[0,xparam]).alias(xparam)) + + data_dict = datadf.to_dicts() - if xparam != 'distance' and xparam != 'time' and xparam != 'cumdist': # pragma: no cover - xaxmax = yaxmaxima[xparam] - xaxmin = yaxminima[xparam] - elif xparam == 'time' and not startenddict: - xaxmax = tseconds.max() - xaxmin = tseconds.min() - elif xparam == 'time' and startenddict: # pragma: no cover - deltas = [pair[1]-pair[0] for key, pair in startenddict.items()] - xaxmin = 0 - xaxmax = pd.Series(deltas).max()*1000. - if xaxmax == 0: - xaxmax = tseconds.max() - else: - xaxmax = datadf['distance'].max() - xaxmin = datadf['distance'].min() + metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] - if yparam == 'distance': # pragma: no cover - yaxmin = datadf['distance'].min() - yaxmax = datadf['distance'].max() - elif yparam == 'cumdist': # pragma: no cover - yaxmin = datadf['cumdist'].min() - yaxmax = datadf['cumdist'].max() - else: - yaxmin = yaxminima[yparam] - yaxmax = yaxmaxima[yparam] + workoutsdict = [{'id': id, 'label': labeldict[id]} for id in ids] - x_axis_type = 'linear' - y_axis_type = 'linear' + chart_data = { + 'title': '', + 'x': xparam, + 'y': yparam, + 'data': data_dict, + 'metrics': metrics_list, + 'plottype': plottype, + 'workouts': workoutsdict, + } - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + script, div = get_chart("/compare", chart_data, debug=False) + return script, div - if yparam == 'pace': - y_axis_type = 'datetime' - yaxmax = 90.*1e3 - yaxmin = 150.*1e3 - - if xparam == 'time': - x_axis_type = 'datetime' - - plot = figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, - tools=TOOLS, - toolbar_location="above", - width=920, height=500, - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkw = 184 - watermarkh = 35 - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'stretch_both' - - plot.image_url([watermarkurl], 0.05, 0.9, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor='top_left', - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - cntr = 0 - l1 = [] - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - group = datadf[datadf['workoutid'] == int(id)].copy() - try: - startsecond, endsecond = startenddict[id] - except KeyError: - startsecond = 0 - endsecond = 0 - - group.sort_values(by='time', ascending=True, inplace=True) - - if endsecond > 0: - group['time'] = group['time'] - 1.e3*startsecond - mask = group['time'] < 0 - group.mask(mask, inplace=True) - mask = group['time'] > 1.e3*(endsecond-startsecond) - group.mask(mask, inplace=True) - - if xparam == 'cumdist': - group['cumdist'] = group['cumdist'] - group['cumdist'].min() - res = make_cumvalues(group[xparam]) - group[xparam] = res[0] - elif xparam == 'distance': - group['distance'] = group['distance'] - group['distance'].min() - - try: - group['x'] = group[xparam] - except KeyError: # pragma: no cover - group['x'] = group['time'] - errormessage = xparam+' has no values. Plot invalid' - try: - group['y'] = group[yparam] - except KeyError: - group['y'] = 0.0*group['x'] - - ymean = group['y'].mean() - f = group['time'].diff().mean() - if f != 0 and not np.isnan(f): - windowsize = 2 * (int(20000./(f))) + 1 - else: - windowsize = 1 - - if windowsize > 3 and windowsize < len(group['y']): - try: - group['y'] = savgol_filter(group['y'], windowsize, 3) - except ValueError: # pragma: no cover - pass - - ylabel = Label(x=100, y=60+nrworkouts*20-20*cntr, - x_units='screen', y_units='screen', - text=axlabels[yparam] + - ": {ymean:6.2f}".format(ymean=ymean), - background_fill_alpha=.7, - background_fill_color='white', - text_color=color, - ) - if yparam != 'time' and yparam != 'pace': - plot.add_layout(ylabel) - - source = ColumnDataSource( - group - ) - - TIPS = OrderedDict([ - ('time', '@ftime'), - ('pace', '@fpace'), - ('hr', '@hr'), - ('spm', '@spm{1.1}'), - ('distance', '@distance{5}'), - ]) - - hover = plot.select(type=HoverTool) - hover.tooltips = TIPS - - if labeldict: - try: - legend_label = labeldict[id] - except KeyError: # pragma: no cover - legend_label = str(id) - else: # pragma: no cover - legend_label = str(id) - - if plottype == 'line': - l1.append(plot.line('x', 'y', source=source, color=color, - legend_label=legend_label, line_width=2)) - else: - l1.append(plot.scatter('x', 'y', source=source, color=color, legend_label=legend_label, - fill_alpha=0.4, line_color=None)) - - plot.add_tools(HoverTool(renderers=[l1[cntr]], tooltips=TIPS)) - cntr += 1 - - plot.legend.location = 'top_right' - plot.xaxis.axis_label = axlabels[xparam] - plot.yaxis.axis_label = axlabels[yparam] - - if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): # pragma: no cover - xrange1 = Range1d(start=yaxminima[xparam], end=yaxmaxima[xparam]) - plot.x_range = xrange1 - - yrange1 = Range1d(start=yaxmin, end=yaxmax) - plot.y_range = yrange1 - - if xparam == 'time': - xrange1 = Range1d(start=xaxmin, end=xaxmax) - plot.x_range = xrange1 - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - - if yparam == 'pace': - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - script, div = components(plot) - - return [script, div, message, errormessage] - - -def interactive_otw_advanced_pace_chart(id=0, promember=0): - # check if valid ID exists (workout exists) - rowdata, row = dataprep.getrowdata_db(id=id) - rowdata.dropna(axis=1, how='all', inplace=True) - rowdata.dropna(axis=0, how='any', inplace=True) - - if rowdata.empty: - return "", "No Valid Data Available" - - # Add hover to this comma-separated string and see what changes - if (promember == 1): - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - else: # pragma: no cover - TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - - source = ColumnDataSource( - rowdata - ) - - plot = figure(x_axis_type="datetime", y_axis_type="datetime", - tools=TOOLS, - width=920, - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - #plot.sizing_mode = 'scale_both' - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - try: - plot.title.text = row.name - except ValueError: # pragma: no cover - plot.title.text = "" - #plot.title.text_font_size = value("1.2em") - plot.xaxis.axis_label = "Time" - plot.yaxis.axis_label = "Pace (/500m)" - plot.xaxis[0].formatter = DatetimeTickFormatter( - hours=["%H"], - minutes=["%M"], - seconds=["%S"], - days=["0"], - months=[""], - years=[""] - ) - plot.yaxis[0].formatter = DatetimeTickFormatter( - seconds=["%S"], - minutes=["%M"] - ) - - ymax = 1.0e3*90 - ymin = 1.0e3*210 - - plot.y_range = Range1d(ymin, ymax) - - hover = plot.select(dict(type=HoverTool)) - - plot.line('time', 'pace', source=source, - legend_label="Pace", color="black") - plot.line('time', 'nowindpace', source=source, - legend_label="Corrected Pace", color="red") - - hover.tooltips = OrderedDict([ - ('Time', '@ftime'), - ('Pace', '@fpace'), - ('Corrected Pace', '@fnowindpace'), - ('HR', '@hr{int}'), - ('SPM', '@spm{1.1}'), - ]) - - hover.mode = 'mouse' - - try: - script, div = components(plot) - except: # pragma: no cover - script = '' - div = '' - - return [script, div] - - -def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='week', +def get_zones_report_pl(rower, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): - dates = [] - dates_sorting = [] - minutes = [] - hours = [] - zones = [] + data = [] enddate = enddate + datetime.timedelta(days=1) @@ -6925,329 +2190,209 @@ def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='we columns = ['workoutid', 'hr', 'power', 'time'] - df = dataprep.getsmallrowdata_db(columns, ids=ids) + df = dataprep.read_data(columns, ids=ids, workstrokesonly=False, doclean=False) + df = dataprep.remove_nulls_pl(df) + try: - df['deltat'] = df['time'].diff().clip(lower=0).clip(upper=20*1e3) - except KeyError: # pragma: no cover + df = df.with_columns((pl.col("time").diff().clip(0, 20*1.e3)).alias("deltat")).lazy() + except ColumnNotFoundError: pass - - df = dataprep.clean_df_stats(df, workstrokesonly=False, - ignoreadvanced=True, ignorehr=False) - + hrzones = rower.hrzones powerzones = rower.powerzones for w in workouts: - dd3 = w.date.strftime('%Y/%m') - dd4 = '{year}/{week:02d}'.format( - week=arrow.get(w.date).isocalendar()[1], - year=w.date.strftime('%y') - ) - dd4 = (w.date - datetime.timedelta(days=w.date.weekday()) - ).strftime('%y/%m/%d') - - # print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar()) iswater = w.workouttype in mytypes.otwtypes - qryw = 'workoutid == {workoutid}'.format(workoutid=w.id) - qry = 'hr < {ut2}'.format(ut2=rower.ut2) - if trainingzones == 'power': - qry = 'power < {ut2}'.format(ut2=rower.pw_ut2) - timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) - if date_agg == 'week': - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - minutes.append(timeinzone) - hours.append(timeinzone/60.) - if trainingzones == 'hr': - zones.append('<{ut2}'.format(ut2=hrzones[1])) - else: - zones.append('<{ut2}'.format(ut2=powerzones[1])) - # print(w,dd,timeinzone,'= rower.ut2, + pl.col("hr") < rower.ut1, + ) + time_pw_ut1 = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_ut2, + pl.col("power") < pw_ut1, + ) - qry = '{at} <= hr < {tr}'.format(at=rower.at, tr=rower.tr) - if trainingzones == 'power': - qry = '{at} <= power < {tr}'.format(at=rower.pw_at, tr=rower.pw_tr) - if iswater: - qry = '{at} <= power < {tr}'.format(at=rower.pw_at*rower.otwslack/100., - tr=rower.pw_tr*rower.otwslack/100.) - timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) - if date_agg == 'week': - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - minutes.append(timeinzone) - hours.append(timeinzone/60.) - if trainingzones == 'hr': - zones.append(hrzones[3]) - else: - zones.append(powerzones[3]) - # print(w,dd,timeinzone,'AT') + #3 + time_at = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.ut1, + pl.col("hr") < rower.at, + ) + time_pw_at = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_ut1, + pl.col("power") < pw_at, + ) - qry = '{tr} <= hr < {an}'.format(tr=rower.tr, an=rower.an) - if trainingzones == 'power': - qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr, an=rower.pw_an) - if iswater: - qry = '{tr} <= power < {an}'.format(tr=rower.pw_tr*rower.otwslack/100., - an=rower.pw_an*rower.otwslack/100.) - timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) - if date_agg == 'week': - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - minutes.append(timeinzone) - hours.append(timeinzone/60.) - if trainingzones == 'hr': - zones.append(hrzones[4]) - else: - zones.append(powerzones[4]) - # print(w,dd,timeinzone,'TR') + #4 + time_tr = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.at, + pl.col("hr") < rower.tr, + ) + time_pw_tr = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_at, + pl.col("power") < pw_tr, + ) - qry = 'hr >= {an}'.format(an=rower.an) - if trainingzones == 'power': - qry = 'power >= {an}'.format(an=rower.pw_an) - if iswater: - qry = 'power >= {an}'.format(an=rower.pw_an*rower.otwslack/100.) - timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) - if date_agg == 'week': - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - minutes.append(timeinzone) - hours.append(timeinzone/60.) - if trainingzones == 'hr': - zones.append(hrzones[5]) - else: - zones.append(powerzones[5]) - # print(w,dd,timeinzone,'AN') + #5 + time_an = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.tr, + pl.col("hr") < rower.an, + ) + time_pw_an = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_tr, + pl.col("power") < pw_an, + ) + + time_max = df.filter( + pl.col("workoutid") == w.id, + pl.col("hr") >= rower.an, + ) + time_pw_max = df.filter( + pl.col("workoutid") == w.id, + pl.col("power") >= pw_an, + ) + + time_in_ut2 = time_ut2.collect()['deltat'].sum()/(60*1.e3) + time_in_ut2_pw = time_pw_ut2.collect()['deltat'].sum()/(60*1.e3) - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate + time_in_ut1 = time_ut1.collect()['deltat'].sum()/(60*1.e3) + time_in_ut1_pw = time_pw_ut1.collect()['deltat'].sum()/(60*1.e3) - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass + time_in_at = time_at.collect()['deltat'].sum()/(60*1.e3) + time_in_at_pw = time_pw_at.collect()['deltat'].sum()/(60*1.e3) - while d <= enddate: - if date_agg == 'week': - dd4 = '{year}/{week:02d}'.format( - week=arrow.get(d).isocalendar()[1], - year=d.strftime('%y') - ) - dd4 = (d - datetime.timedelta(days=d.weekday())).strftime('%y/%m/%d') + time_in_tr = time_tr.collect()['deltat'].sum()/(60*1.e3) + time_in_tr_pw = time_pw_tr.collect()['deltat'].sum()/(60*1.e3) - dates.append(dd4) - dates_sorting.append(dd4) - else: # pragma: no cover - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) + time_in_an = time_an.collect()['deltat'].sum()/(60*1.e3) + time_in_an_pw = time_pw_an.collect()['deltat'].sum()/(60*1.e3) - minutes.append(0) - hours.append(0) - if trainingzones == 'hr': - zones.append(hrzones[1]) - else: - zones.append(powerzones[1]) + time_in_max = time_max.collect()['deltat'].sum()/(60*1.e3) + time_in_max_pw = time_pw_max.collect()['deltat'].sum()/(60*1.e3) - d += datetime.timedelta(days=1) + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': '<{ut2}'.format(ut2=hrzones[1]), + 'time_in_zone': time_in_ut2, + 'pw_zonename': '<{ut2}'.format(ut2=powerzones[1]), + 'pw_time_in_zone': time_in_ut2_pw, + }) - # this should be renamed with rower zones - data = { - 'date': dates, - 'date_sorting': dates_sorting, - 'minutes': minutes, - 'zones': zones, - 'hours': hours, + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[1], + 'time_in_zone': time_in_ut1, + 'pw_zonename': powerzones[1], + 'pw_time_in_zone': time_in_ut1_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[2], + 'time_in_zone': time_in_at, + 'pw_zonename': powerzones[2], + 'pw_time_in_zone': time_in_at_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[3], + 'time_in_zone': time_in_tr, + 'pw_zonename': powerzones[3], + 'pw_time_in_zone': time_in_tr_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[4], + 'time_in_zone': time_in_an, + 'pw_zonename': powerzones[4], + 'pw_time_in_zone': time_in_an_pw, + }) + + data.append({ + 'date': w.date.strftime("%Y-%m-%d"), + 'id': w.id, + 'zonename': hrzones[5], + 'time_in_zone': time_in_max, + 'pw_zonename': powerzones[5], + 'pw_time_in_zone': time_in_max_pw, + }) + + + chart_data = { + 'data': data, + 'hrzones': hrzones, + 'powerzones': powerzones, } - # print(pd.DataFrame(data).head()) - - return data + return chart_data -def interactive_zoneschart(rower, data, startdate, enddate, trainingzones='hr', date_agg='week', +def interactive_zoneschart2(rower, data, startdate, enddate, trainingzones='hr', date_agg='week', yaxis='time'): if startdate >= enddate: # pragma: no cover st = startdate startdate = enddate enddate = st - hrzones = rower.hrzones - powerzones = rower.powerzones + hrzones = data['hrzones'] + powerzones = data['powerzones'] - color_map = { - '<{ut2}'.format(ut2=hrzones[1]): 'green', - hrzones[1]: 'lime', - hrzones[2]: 'yellow', - hrzones[3]: 'blue', - hrzones[4]: 'purple', - hrzones[5]: 'red', - } - if trainingzones == 'power': - color_map = { - '<{ut2}'.format(ut2=powerzones[1]): 'green', - powerzones[1]: 'lime', - powerzones[2]: 'yellow', - powerzones[3]: 'blue', - powerzones[4]: 'purple', - powerzones[5]: 'red', - } - - zones_order = [ - '<{ut2}'.format(ut2=hrzones[1]), - hrzones[1], - hrzones[2], - hrzones[3], - hrzones[4], - hrzones[5] - ] - - if trainingzones == 'power': - zones_order = [ - '<{ut2}'.format(ut2=powerzones[1]), - powerzones[1], - powerzones[2], - powerzones[3], - powerzones[4], - powerzones[5] - ] - - df = pd.DataFrame(data) - df2 = pd.DataFrame(data) - - df.drop('minutes', inplace=True, axis='columns') - - df.sort_values('date_sorting', inplace=True) - df.drop('date_sorting', inplace=True, axis='columns') - df['totaltime'] = 0 - if df.empty: # pragma: no cover - return '', 'No Data Found' - - if yaxis == 'percentage': - dates = list(set(df['date'].values)) - for date in dates: - qry = 'date == "{d}"'.format(d=date) - - totaltime = df.query(qry)['hours'].sum() - - mask = df['date'] == date - df.loc[mask, 'totaltime'] = totaltime - - df['percentage'] = 100.*df['hours']/df['totaltime'] - df.drop('hours', inplace=True, axis='columns') - df.drop('totaltime', inplace=True, axis='columns') - - hv.extension('bokeh') - - xrotation = 0 - nrdates = len(list(set(df['date'].values))) - if nrdates > 10: - xrotation = 45 - - bars = hv.Bars(df, kdims=['date', 'zones']).aggregate( - function=np.sum).redim.values(zones=zones_order) - - bars.opts( - opts.Bars(cmap=color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, padding=(0, (0, .1)), - legend_position='bottom', - xrotation=xrotation, - show_frame=False) - ) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2} for {r}'.format( + data['yaxis'] = yaxis + data['title'] = 'Activity {d1} to {d2} for {r}'.format( d1=startdate.strftime("%Y-%m-%d"), d2=enddate.strftime("%Y-%m-%d"), r=str(rower), ) + + data['stackBy'] = 'time_in_zone' + data['colorBy'] = 'zonename' + if trainingzones == 'power': + data['stackBy'] = 'pw_time_in_zone' + data['colorBy'] = 'pw_zonename' + data['doReduce'] = True + data['datebin'] = date_agg + data['colors'] = ['green', 'lime', 'yellow', 'blue', 'purple', 'red'] - if date_agg == 'week': - p.xaxis.axis_label = 'Week' - else: # pragma: no cover - p.xaxis.axis_label = 'Month' - - if yaxis == 'percentage': - p.yaxis.axis_label = 'Percentage' - - p.width = 550 - p.height = 350 - p.toolbar_location = 'right' - p.y_range.start = 0 - #p.sizing_mode = 'stretch_both' - - if yaxis == 'percentage': - tidy_df = df2.groupby(['date']).sum() - - source2 = ColumnDataSource(tidy_df) - y2rangemax = tidy_df.loc[:, 'hours'].max()*1.1 - p.extra_y_ranges["yax2"] = Range1d(start=0, end=y2rangemax) - p.line('date', 'hours', source=source2, - y_range_name="yax2", color="black", width=5) - p.circle('date', 'hours', source=source2, y_range_name="yax2", color="black", size=10) - -# p.circle('date', 'hours', source=source2, y_range_name="yax2", color="black", size=10, -# legend_label='Hours') - p.add_layout(LinearAxis(y_range_name="yax2", - axis_label='Hours'), 'right') - - script, div = components(p) + script, div = get_chart("/zones", data, debug=False) return script, div + + diff --git a/rowers/metrics.py b/rowers/metrics.py index 285529b3..3766addb 100644 --- a/rowers/metrics.py +++ b/rowers/metrics.py @@ -364,6 +364,7 @@ for key, dict in rowingmetrics: metricsdicts[key] = dict + metricsgroups = list(set([d['group'] for n, d in rowingmetrics])) dtypes = {} diff --git a/rowers/models.py b/rowers/models.py index 6ba7658d..a05de7c5 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3833,9 +3833,12 @@ def auto_delete_file_on_delete(sender, instance, **kwargs): # remove parquet file try: dirname = 'media/strokedata_{id}.parquet.gz'.format(id=instance.id) - shutil.rmtree(dirname) + os.remove(dirname) except FileNotFoundError: - pass + try: + shutil.rmtree(dirname) + except: + pass # remove parquet file try: @@ -5257,6 +5260,8 @@ class ForceCurveAnalysis(models.Model): average_spm = models.FloatField(default=23) average_boatspeed = models.FloatField(default=4.0) include_rest_strokes = models.BooleanField(default=False) + plotcircles = models.BooleanField(default=False) + plotlines = models.BooleanField(default=False) def __str__(self): s = 'Force Curve Analysis {name} ({date})'.format(name = self.name, diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 3ecda13e..c8ca0ab2 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -18,13 +18,12 @@ import rowers.dataprep as dataprep import numpy as np import rowers.metrics as metrics import rowers.mytypes as mytypes -from rowers.courses import get_time_course from rowers.utils import to_pace from rowers.opaque import encoder from rowingdata import rower as rrower from rowingdata import rowingdata as rrdata import arrow -import pandas as pd +import polars as pl import json # Python @@ -203,16 +202,8 @@ def get_execution_report(rower, startdate, enddate, plan=None): startdate__gte=enddate) else: # pragma: no cover plans = TrainingPlan.objects.filter(rowers__in=[rower]) - #plans2 = TrainingPlan.objects.filter( - # enddate__lte=enddate, startdate__lte=enddate, rowers__in=[rower]) - #plans = plans | plans2 - - #plans = plans.exclude(status=False).order_by("-enddate") if not plans: - # make week cycles here - # get monday before startdate - micros = [] else: sorted_plans = sorted(plans, key= lambda t: t.overlap(startdate,enddate)) @@ -290,7 +281,7 @@ def get_execution_report(rower, startdate, enddate, plan=None): else: plannedscore += 60 actualscore += 0 - elif w.hrtss != 0: + elif w.hrtss >= 0: if ratio > 0: plannedscore += w.hrtss/ratio actualscore += w.hrtss @@ -313,7 +304,7 @@ def get_execution_report(rower, startdate, enddate, plan=None): planned += [plannedscore] executed += [actualscore] - data = pd.DataFrame({ + data = pl.DataFrame({ 'startdate': startdates, 'planned': planned, 'executed': executed, @@ -444,8 +435,9 @@ def add_workouts_plannedsession(ws, ps, r): for record in records: record.delete() - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[w.id]) + df = dataprep.remove_nulls_pl(df) fastest_milliseconds, starttime, endtime = datautils.getfastest( df, ps.sessionvalue, mode='distance') @@ -475,11 +467,11 @@ def add_workouts_plannedsession(ws, ps, r): for record in records: record.delete() - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[w.id]) + df = dataprep.remove_nulls_pl(df) fastest_meters, starttime, endtime = datautils.getfastest( df, ps.sessionvalue, mode='time') - if fastest_meters > 0: w.plannedsession = ps w.save() @@ -1694,8 +1686,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False): record.coursecompleted = True record.workoutid = ws[0].id if race.sessiontype == 'fastest_distance': - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[ws[0].id]) + df = dataprep.remove_nulls_pl(df) fastest_milliseconds, startsecond, endsecond = datautils.getfastest( df, race.sessionvalue, mode='distance') velo = race.sessionvalue/fastest_milliseconds @@ -1711,8 +1704,9 @@ def add_workout_fastestrace(ws, race, r, recordid=0, doregister=False): record.endsecond = endsecond record.save() if race.sessiontype == 'fastest_time': # pragma: no cover - df = dataprep.getsmallrowdata_db( + df = dataprep.read_data( ['time', 'cumdist'], ids=[ws[0].id]) + df = dataprep.remove_nulls_pl(df) fastest_meters, startsecond, endsecond = datautils.getfastest( df, race.sessionvalue, mode='time') velo = fastest_meters/(60.*race.sessionvalue) diff --git a/rowers/serializers.py b/rowers/serializers.py index cb9de92b..1dc594ef 100644 --- a/rowers/serializers.py +++ b/rowers/serializers.py @@ -6,7 +6,7 @@ from rest_framework import serializers from rowers.models import ( Workout, Rower, FavoriteChart, VirtualRaceResult, VirtualRace, GeoCourse, StandardCollection, CourseStandard, - GeoPolygon, GeoPoint, PlannedSession, + GeoPolygon, GeoPoint, PlannedSession, ForceCurveAnalysis ) from django.core.exceptions import PermissionDenied @@ -297,7 +297,23 @@ class GeoPointSerializer(serializers.ModelSerializer): ) extra_kwargs = {'id': {'read_only': False, 'required': True}} +class ForceCurveAnalysisSerializer(serializers.ModelSerializer): + class Meta: + model = ForceCurveAnalysis + fields = ( + 'id', + 'name', + 'workout', + 'dist_min', + 'dist_max', + 'spm_min', + 'spm_max', + 'work_min', + 'work_max', + 'include_rest_strokes' + ) + class GeoPolygonSerializer(serializers.ModelSerializer): points = GeoPointSerializer(many=True) diff --git a/rowers/tasks.py b/rowers/tasks.py index f42891c9..8c46ca9f 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -56,7 +56,7 @@ from scipy.signal import savgol_filter from scipy.interpolate import griddata import rowingdata -from rowingdata import make_cumvalues +from rowingdata import make_cumvalues, make_cumvalues_array from uuid import uuid4 from rowingdata import rowingdata as rdata @@ -115,6 +115,9 @@ tpapilocation = TP_API_LOCATION from requests_oauthlib import OAuth1, OAuth1Session import pandas as pd +import polars as pl +from polars.exceptions import ColumnNotFoundError + from django_rq import job from django.utils import timezone @@ -127,7 +130,8 @@ from rowers import mytypes from rowers.dataroutines import ( - getsmallrowdata_db, updatecpdata_sql, update_c2id_sql, + getsmallrowdata_pd, updatecpdata_sql, update_c2id_sql, + read_data, #update_workout_field_sql, update_agegroup_db, update_strokedata, add_c2_stroke_data_db, totaltime_sec_to_string, @@ -559,7 +563,7 @@ def handle_sporttracks_workout_from_data(user, importid, source, strokedata = pd.DataFrame.from_dict({ key: pd.Series(value, dtype='object') for key, value in data.items() }) - + try: workouttype = data['type'] except KeyError: # pragma: no cover @@ -2549,9 +2553,10 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue, totaltime = totaltime + rowdata.df.loc[0, ' ElapsedTime (sec)'] except KeyError: # pragma: no cover pass - df = getsmallrowdata_db( + df = getsmallrowdata_pd( ['power', 'workoutid', 'time'], ids=[workoutid], debug=debug) + thesecs = totaltime maxt = 1.05 * thesecs logarr = datautils.getlogarr(maxt) @@ -2583,53 +2588,6 @@ def handle_otwsetpower(self, f1, boattype, boatclass, coastalbrand, weightvalue, return 1 -@app.task -def handle_updateergcp(rower_id, workoutfilenames, debug=False, **kwargs): - therows = [] - for f1 in workoutfilenames: - try: - rowdata = rdata(csvfile=f1) - except IOError: # pragma: no cover - try: - rowdata = rdata(csvfile=f1 + '.csv') - except IOError: - try: - rowdata = rdata(csvfile=f1 + '.gz') - except IOError: - rowdata = 0 - 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, table='cpdata', **kwargs): - columns = ['power', 'workoutid', 'time'] - df = getsmallrowdata_db(columns, ids=workoutids, debug=debug) - - if df.empty: # pragma: no cover - return 0 - - maxt = 1.05*df['time'].max()/1000. - - logarr = datautils.getlogarr(maxt) - - dfgrouped = df.groupby(['workoutid']) - - delta, cpvalue, avgpower = datautils.getcp(dfgrouped, logarr) - - updatecpdata_sql(rower_id, delta, cpvalue, debug=debug, table=table) - - return 1 - @app.task def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename, @@ -3179,43 +3137,6 @@ def handle_sendemail_invite_reject(email, name, teamname, managername, return 1 -@app.task -def handle_setcp(strokesdf, filename, workoutid, debug=False, **kwargs): - try: - os.remove(filename) - except FileNotFoundError: - pass - if not strokesdf.empty: - - try: - totaltime = strokesdf['time'].max() - except KeyError: # pragma: no cover - return 0 - try: - powermean = strokesdf['power'].mean() - except KeyError: # pragma: no cover - powermean = 0 - - if powermean != 0: - thesecs = totaltime - maxt = 1.05 * thesecs - - if maxt > 0: - logarr = datautils.getlogarr(maxt) - dfgrouped = strokesdf.groupby(['workoutid']) - delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) - - df = pd.DataFrame({ - 'delta': delta, - 'cp': cpvalues, - 'id': workoutid, - }) - df.to_parquet(filename, engine='fastparquet', - compression='GZIP') - return 1 - - return 1 # pragma: no cover - @app.task def handle_sendemail_invite_accept(email, name, teamname, managername, @@ -3251,23 +3172,22 @@ graphql_url = "https://rp3rowing-app.com/graphql" @app.task def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs): - df = getsmallrowdata_db(['time', 'driveenergy'], ids=ids) + df = read_data(['time', 'driveenergy'], ids=ids) try: - mask = df['driveenergy'] > 100 - except (KeyError, TypeError): # pragma: no cover - return 0 - try: - wps_median = int(df.loc[mask, 'driveenergy'].median()) + wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median()) + rower = Rower.objects.get(id=rid) + if mode == 'water': + rower.median_wps = wps_median + else: # pragma: no cover + rower.median_wps_erg = wps_median + + rower.save() except ValueError: # pragma: no cover - return 0 - - rower = Rower.objects.get(id=rid) - if mode == 'water': - rower.median_wps = wps_median - else: - rower.median_wps_erg = wps_median - - rower.save() + wps_median = 0 + except OverflowError: + wps_median = 0 + except ColumnNotFoundError: + wps_median = 0 return wps_median @@ -3647,7 +3567,7 @@ def handle_c2_async_workout(alldata, userid, c2token, c2id, delaysec, loncoord = np.zeros(nr_rows) try: - strokelength = strokedata.loc[:, 'strokelength'] + strokelength = strokedata.loc[:,'strokelength'] except: # pragma: no cover strokelength = np.zeros(nr_rows) @@ -3901,7 +3821,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, pace[np.isinf(pace)] = 0.0 try: - strokedata = pd.DataFrame({'t': 10*t, + strokedata = pl.DataFrame({'t': 10*t, 'd': 10*d, 'p': 10*pace, 'spm': spm, @@ -3947,18 +3867,18 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, starttimeunix = arrow.get(rowdatetime).timestamp() - res = make_cumvalues(0.1*strokedata['t']) - cum_time = res[0] - lapidx = res[1] + res = make_cumvalues_array(0.1*strokedata['t'].to_numpy()) + cum_time = pl.Series(res[0]) + lapidx = pl.Series(res[1]) unixtime = cum_time+starttimeunix - seconds = 0.1*strokedata.loc[:, 't'] + seconds = 0.1*strokedata['t'] nr_rows = len(unixtime) try: - latcoord = strokedata.loc[:, 'lat'] - loncoord = strokedata.loc[:, 'lon'] + latcoord = strokedata['lat'] + loncoord = strokedata['lon'] if latcoord.std() == 0 and loncoord.std() == 0 and workouttype == 'water': # pragma: no cover workouttype = 'rower' except: # pragma: no cover @@ -3968,29 +3888,29 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, workouttype = 'rower' try: - strokelength = strokedata.loc[:, 'strokelength'] + strokelength = strokedata['strokelength'] except: # pragma: no cover strokelength = np.zeros(nr_rows) - dist2 = 0.1*strokedata.loc[:, 'd'] + dist2 = 0.1*strokedata['d'] try: - spm = strokedata.loc[:, 'spm'] - except KeyError: # pragma: no cover + spm = strokedata['spm'] + except (KeyError, ColumnNotFoundError): # pragma: no cover spm = 0*dist2 try: - hr = strokedata.loc[:, 'hr'] - except KeyError: # pragma: no cover + hr = strokedata['hr'] + except (KeyError, ColumnNotFoundError): # pragma: no cover hr = 0*spm - pace = strokedata.loc[:, 'p']/10. + pace = strokedata['p']/10. pace = np.clip(pace, 0, 1e4) - pace = pace.replace(0, 300) + pace = pl.Series(pace).replace(0, 300) velo = 500./pace try: - power = strokedata.loc[:, 'power'] + power = strokedata['power'] except KeyError: # pragma: no cover power = 2.8*velo**3 @@ -3999,7 +3919,7 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, # save csv # Create data frame with all necessary data to write to csv - df = pd.DataFrame({'TimeStamp (sec)': unixtime, + df = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': dist2, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -4019,10 +3939,10 @@ def fetch_strava_workout(stravatoken, oauth_data, stravaid, csvfilename, userid, 'cum_dist': dist2, }) - df.sort_values(by='TimeStamp (sec)', ascending=True) + df.sort('TimeStamp (sec)') - row = rowingdata.rowingdata(df=df) - row.write_csv(csvfilename, gzip=False) + row = rowingdata.rowingdata_pl(df=df) + row.write_csv(csvfilename, compressed=False) # summary = row.allstats() # maxdist = df['cum_dist'].max() diff --git a/rowers/templates/course_edit_view.html b/rowers/templates/course_edit_view.html index 58b2cb64..c281b2b7 100644 --- a/rowers/templates/course_edit_view.html +++ b/rowers/templates/course_edit_view.html @@ -32,8 +32,7 @@
{{ mapdiv|safe }} - - {{ mapscript|safe }} + {{ mapscript|safe }}
diff --git a/rowers/templates/course_replace.html b/rowers/templates/course_replace.html index 53569c1f..067c90ff 100644 --- a/rowers/templates/course_replace.html +++ b/rowers/templates/course_replace.html @@ -37,8 +37,9 @@
{{ mapdiv|safe }} - - {{ mapscript|safe }} +
diff --git a/rowers/templates/course_replace_confirm.html b/rowers/templates/course_replace_confirm.html index 965b9180..c057f2c1 100644 --- a/rowers/templates/course_replace_confirm.html +++ b/rowers/templates/course_replace_confirm.html @@ -45,7 +45,9 @@
  • {{ mapdiv|safe }} - {{ mapscript|safe }} +
  • diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html index e50e35fa..decc0089 100644 --- a/rowers/templates/course_view.html +++ b/rowers/templates/course_view.html @@ -54,8 +54,7 @@
    {{ mapdiv|safe }} - - {{ mapscript|safe }} + {{ mapscript|safe }}
    {% if records %} diff --git a/rowers/templates/coursemap.html b/rowers/templates/coursemap.html index d926f731..c7dd9b7a 100644 --- a/rowers/templates/coursemap.html +++ b/rowers/templates/coursemap.html @@ -19,7 +19,7 @@

    {{ course.name }}

    {{ mapdiv|safe }} - {{ mapscript|safe }} + {{ mapscript|safe }}
    {% endblock %} diff --git a/rowers/templates/disqualification_view.html b/rowers/templates/disqualification_view.html index dfacbe67..5a6406cb 100644 --- a/rowers/templates/disqualification_view.html +++ b/rowers/templates/disqualification_view.html @@ -102,14 +102,11 @@ {% endif %}
  • - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} +
  • diff --git a/rowers/templates/embedded_video.html b/rowers/templates/embedded_video.html index 0ff08fbe..0fe35134 100644 --- a/rowers/templates/embedded_video.html +++ b/rowers/templates/embedded_video.html @@ -116,7 +116,7 @@ function copyText() {
  • {% if user.is_authenticated and user == workout.user.user and not locked %}
  • -

    Paste link to you tube video below

    +

    Paste link to Youtube video below

    Use the slider to locate start point for video on workout map

    Playing the video will advance the data in synchonization. Use the regular YouTube controls @@ -169,9 +169,8 @@ function copyText() {

  • - + + +

    Film Deaths

    + +
      + + +
    • +
      + + {{ the_div|safe }} +
      +
    • +
    + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_analytics.html' %} +{% endblock %} diff --git a/rowers/templates/flexchart3otw.html b/rowers/templates/flexchart3otw.html index 19f7cbeb..01be6c6c 100644 --- a/rowers/templates/flexchart3otw.html +++ b/rowers/templates/flexchart3otw.html @@ -8,16 +8,6 @@ {% localtime on %} {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} - - - - - -{{ the_script |safe }}

    {% if workout|previousworkout:rower.user %} @@ -34,10 +24,9 @@

    Flexible Chart

      +
    • -
      - {{ the_div|safe }} -
      + {{ the_div|safe }}
    + + +{{ the_script |safe }} + {% endblock %} {% endlocaltime %} diff --git a/rowers/templates/flexchartstacked.html b/rowers/templates/flexchartstacked.html index d5788f79..58aa7ffb 100644 --- a/rowers/templates/flexchartstacked.html +++ b/rowers/templates/flexchartstacked.html @@ -8,16 +8,8 @@ {% localtime on %} {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + - - - - -{{ the_script |safe }}

    {% if workout|previousworkout:rower.user %} @@ -34,6 +26,7 @@

    Chart Stack

      +
    • {{ the_div|safe }} @@ -57,6 +50,7 @@
    +{{ the_script |safe }} {% endblock %} {% endlocaltime %} diff --git a/rowers/templates/forcecurve_analysis.html b/rowers/templates/forcecurve_analysis.html index b46f8d37..2216bd01 100644 --- a/rowers/templates/forcecurve_analysis.html +++ b/rowers/templates/forcecurve_analysis.html @@ -6,25 +6,20 @@ {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + + - - - - -{{ the_script |safe }}

    Force Curve Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

      + {% if the_div %}
    • {{ the_div|safe }} + {{ the_script |safe }}
    • {% endif %} diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html index d9ea9729..1d95d9a9 100644 --- a/rowers/templates/forcecurve_single.html +++ b/rowers/templates/forcecurve_single.html @@ -8,32 +8,26 @@ {% localtime on %} {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} + - - - - -{{ the_script |safe }}

      Empower Force Curve

        +
      • {{ the_div|safe }} -
        + {{ the_script |safe }} +
      • {% csrf_token %} - {{ form.as_table }} -
        + {{ form.as_table }} +
        @@ -51,28 +45,14 @@ diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index e56f3624..b0cb042c 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -60,17 +60,6 @@ {% block main %} - - - -
        - {{ chartscript |safe }} -
        - - - {% if rower.user %}

        Gold Medal Scores for {{ rower.user.first_name }}

        {% else %} @@ -79,6 +68,7 @@
          +
        • Hover over the workout to see details, click on the workout to open in a separate page.

          @@ -136,6 +126,11 @@
        + + +
        + {{ chartscript |safe }} +
        {% endblock %} diff --git a/rowers/templates/histo.html b/rowers/templates/histo.html index a46d9d5e..a3bf496f 100644 --- a/rowers/templates/histo.html +++ b/rowers/templates/histo.html @@ -6,6 +6,7 @@ {% block main %} + diff --git a/rowers/templates/histo_single.html b/rowers/templates/histo_single.html index 4d6111d3..868f2f34 100644 --- a/rowers/templates/histo_single.html +++ b/rowers/templates/histo_single.html @@ -6,12 +6,9 @@ {% block main %} - - + + -{{ interactiveplot |safe }} @@ -24,6 +21,7 @@
      +{{ interactiveplot |safe }} {% endif %} {% endblock %} diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 358b91a0..fc89929e 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -5,25 +5,14 @@ {% block title %}Rowsandall {% endblock %} {% block main %} -
      - - -
      - - +
      -
      -
      - {{ tscript|safe}} -
      @@ -52,35 +41,35 @@

      - -
    • +
    • +
    • All workouts

      - +

      - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
      Total Distance{{ totalsdict|lookup:"distance"}} meters
      Total Duration{{ totalsdict|lookup:"duration"}}
      Number of workouts{{ totalsdict|lookup:"nrworkouts"}}
      Average heart rate bpm
      Maximum heart rate bpm
      Average power W
      Maximum power W
      Total Distance{{ totalsdict|lookup:"distance"}} meters
      Total Duration{{ totalsdict|lookup:"duration"}}
      Number of workouts{{ totalsdict|lookup:"nrworkouts"}}
      Average heart rate bpm
      Maximum heart rate bpm
      Average power W
      Maximum power W

    • @@ -90,46 +79,50 @@
    • {{ tdiv|safe }} +
      + {{ tscript|safe}} +
      +
    • {{ totaldiv|safe }}
    • - + {% for ddict in typedicts %}
    • {{ ddict|lookup:"wtype"}}

      - +

      - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + +
      Total Distance{{ ddict|lookup:"distance"}} meters
      Total Duration{{ ddict|lookup:"duration"}}
      Number of workouts{{ ddict|lookup:"nrworkouts"}}
      Average heart rate bpm
      Maximum heart rate bpm
      Average power W
      Maximum power W
      Total Distance{{ ddict|lookup:"distance"}} meters
      Total Duration{{ ddict|lookup:"duration"}}
      Number of workouts{{ ddict|lookup:"nrworkouts"}}
      Average heart rate bpm
      Maximum heart rate bpm
      Average power W
      Maximum power W

    • - + {% endfor %}
    @@ -142,40 +135,41 @@ {% endblock %} diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html index 0ced8ffb..301e3ddb 100644 --- a/rowers/templates/instroke_analysis.html +++ b/rowers/templates/instroke_analysis.html @@ -6,24 +6,20 @@ {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} - - - + -{{ the_script |safe }} -

    In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}

      + {% if the_div %}
    • {{ the_div|safe }} + {{ the_script |safe }}
    • {% endif %} diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index d7d89704..e66bf295 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -11,10 +11,8 @@ jQuery UI Slider - Range slider - - -
      - {{ the_script |safe }} -
      -
      - {{ ds |safe }} -

      In Stroke Metrics

        +
      • {% if user.rower|is_basic %} @@ -144,7 +140,7 @@ $( function() {

        - +

        @@ -168,15 +164,24 @@ $( function() {

      • +
        {{ the_div|safe }}
        -
      • -
      • -
        - {{ dd|safe }} +
        + {{ the_script |safe }}
      • + +
      • +
        + {{ dd|safe }} +
        +
        + {{ ds |safe }} +
        + +
      diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index beeafdad..51e40308 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -10,7 +10,7 @@ {% endblock %} @@ -229,22 +229,11 @@ Total meters: {{ totalmeters }}. Total time {{ totalhours }}:{{ totalminutes }}h. Dig deeper.

      -

      - Activity chart by - TRIMP, - rScore, - Time. -

      - - - - - - {{ interactiveplot |safe }} + {{ the_div |safe }} + {{ interactiveplot |safe }} + {% if announcements %}
    • diff --git a/rowers/templates/map_view.html b/rowers/templates/map_view.html index 17eedd47..f4fe8ce6 100644 --- a/rowers/templates/map_view.html +++ b/rowers/templates/map_view.html @@ -26,8 +26,7 @@
      {{ mapdiv|safe }} - - {{ mapscript|safe }} + {{ mapscript|safe }}
    diff --git a/rowers/templates/mapcompare.html b/rowers/templates/mapcompare.html index a00044a6..52b582b4 100644 --- a/rowers/templates/mapcompare.html +++ b/rowers/templates/mapcompare.html @@ -25,7 +25,9 @@
    {{ mapdiv|safe }} - {{ mapscript|safe }} +
    diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index 6d8ff847..f61797f6 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -101,11 +101,6 @@  Force Curve
  • -
  • - -  Corrected Pace Plot - -
  • {% endif %} @@ -294,26 +289,6 @@ {% endblock %} diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index b379eb83..cd73dd7a 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -38,6 +38,7 @@ from nose.tools import assert_true from mock import Mock, patch #from minimocktest import MockTestCase import pandas as pd +import polars as pl import arrow from django.http import HttpResponseRedirect @@ -271,7 +272,7 @@ def mocked_fetchcperg(*args, **kwargs): return df -import pandas as pd + def mocked_read_df_sql(id): # pragma: no cover df = pd.read_csv('rowers/tests/testdata/fake_strokedata.csv') @@ -300,7 +301,7 @@ def mocked_getrowdata_db(*args, **kwargs): return df,row def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover - df = pd.read_csv('rowers/tests/testdata/uhfull.csv') + df = pl.read_csv('rowers/tests/testdata/uhfull.csv') id = kwargs['id'] @@ -308,13 +309,19 @@ def mocked_getrowdata_uh(*args, **kwargs): # pragma: no cover return df, row + def mocked_getsmallrowdata_uh(*args, **kwargs): # pragma: no cover + df = pl.read_csv('rowers/tests/testdata/uhfull.csv') + + return df + +def mocked_getsmallrowdata_uh_pd(*args, **kwargs): # pragma: no cover df = pd.read_csv('rowers/tests/testdata/uhfull.csv') return df def mocked_getsmallrowdata_forfusion(*args, **kwargs): - df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv') + df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv') return df @@ -335,6 +342,11 @@ def mocked_getsmallrowdata_db(*args, **kwargs): return df +def mocked_read_data(*args, **kwargs): + df = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + + return df + def mocked_getsmallrowdata_db_updatecp(*args, **kwargs): # pragma: no cover df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv') @@ -351,8 +363,8 @@ def mocked_getsmallrowdata_db_water(*args, **kwargs): # pragma: no cover return df -def mocked_getsmallrowdata_db_wps(*args, **kwargs): # pragma: no cover - df = pd.read_csv('rowers/tests/testdata/driveenergies.csv') +def mocked_read_data_wps(*args, **kwargs): # pragma: no cover + df = pl.read_csv('rowers/tests/testdata/driveenergies.csv') return df @@ -384,7 +396,7 @@ def mocked_read_cols_df_sql(*args, **kwargs): def mock_workout_summaries(*args, **kwargs): - df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv') + df = pl.read_csv('rowers/tests/testdata/workout_summaries.csv') return df def mocked_read_df_cols_sql_multi(ids, columns, convertnewtons=True): # pragma: no cover diff --git a/rowers/tests/statements.py b/rowers/tests/statements.py index 3b88c2e2..c809c60e 100644 --- a/rowers/tests/statements.py +++ b/rowers/tests/statements.py @@ -65,6 +65,7 @@ from nose.tools import assert_true from mock import Mock, patch #from minimocktest import MockTestCase import pandas as pd +import polars as pl import rowers.c2stuff as c2stuff import rowers.rojabo_stuff as rojabo_stuff diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index 8c7bc3e0..4bd98e36 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -43,10 +43,10 @@ class ListWorkoutTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_list_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -169,8 +169,8 @@ class ForcecurveTest(TestCase): pass - @patch('rowers.dataprep.getsmallrowdata_db',side_effect = mocked_getempowerdata_db) - def test_forcecurve_plot(self, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect = mocked_read_data) + def test_forcecurve_plot(self, mocked_read_data): login = self.c.login(username=self.u.username, password = self.password) self.assertTrue(login) @@ -296,9 +296,9 @@ class WorkoutCompareTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_compare(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -309,9 +309,9 @@ class WorkoutCompareTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_compare_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -398,9 +398,9 @@ class WorkoutBoxPlotTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_boxplot(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -411,9 +411,9 @@ class WorkoutBoxPlotTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_boxplot_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -499,9 +499,9 @@ class WorkoutHistoTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_histo(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -512,9 +512,9 @@ class WorkoutHistoTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_histo_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -600,9 +600,9 @@ class History(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_workouts_history(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -618,9 +618,9 @@ class History(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_history_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -713,9 +713,9 @@ class GoldMedalScores(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_goldmedalscores(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): ws = Workout.objects.filter(rankingpiece=True) self.assertEqual(ws.count(),2) @@ -729,9 +729,9 @@ class GoldMedalScores(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_goldmedalscores_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -795,9 +795,9 @@ class WorkoutFlexallTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_flexall(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -808,9 +808,9 @@ class WorkoutFlexallTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_flexall_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -903,9 +903,9 @@ class WorkoutStatsTestNew(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_workouts_stats(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -916,11 +916,11 @@ class WorkoutStatsTestNew(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -979,11 +979,11 @@ class WorkoutStatsTestNew(TestCase): script, div = comparisondata(workouts,options) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -1042,11 +1042,11 @@ class WorkoutStatsTestNew(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) @patch('rowers.dataprep.read_cols_df_sql', side_effect=mocked_read_cols_df_sql) def test_analysis_data2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_read_cols_df_sql, ): @@ -1120,9 +1120,9 @@ class WorkoutStatsTestNew(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_workouts_stats_submit(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1241,9 +1241,9 @@ class MarkerPerformanceTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_create_marker_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1257,8 +1257,8 @@ class MarkerPerformanceTest(TestCase): self.assertRedirects(response, expected_url=expected_url, status_code=302,target_status_code=200) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_uh) - def test_trainingzones_view(self,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data', side_effect=mocked_getsmallrowdata_uh) + def test_trainingzones_view(self,mocked_getsmallrowdata_uh): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1326,9 +1326,9 @@ class MarkerPerformanceTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_performancemanager_view(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) @@ -1356,36 +1356,6 @@ class MarkerPerformanceTest(TestCase): response = self.c.post(url,form_data) self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_ranking_view2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date() - enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date() - - url = reverse('rankings_view2') - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'startdate':startdate.strftime("%Y-%m-%d"), - 'enddate': enddate.strftime("%Y-%m-%d"), - 'doform': True, - 'dofatigue': True, - 'metricchoice':'rscore', - 'modelchoice': 'coggan', - 'daterange': '', - } - form = DateRangeForm(form_data) - if not form.is_valid(): - print(form.errors) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) class AlertTest(TestCase): def setUp(self): @@ -1443,9 +1413,9 @@ class AlertTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.alerts.getsmallrowdata_db') + @patch('rowers.alerts.read_data') def test_alerts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username,password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_api.py b/rowers/tests/test_api.py index 69279f53..3aee087e 100644 --- a/rowers/tests/test_api.py +++ b/rowers/tests/test_api.py @@ -58,19 +58,22 @@ class OwnApi(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - url = reverse('strokedatajson',kwargs={'id':w.id}) + with patch('rowers.dataprep.read_data') as mock_read_data: + mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + url = reverse('strokedatajson',kwargs={'id':w.id}) - request = self.factory.get(url) - request.user = self.u - force_authenticate(request, user=self.u) - response = strokedatajson(request,id=w.id) - self.assertEqual(response.status_code,200) + request = self.factory.get(url) + request.user = self.u + force_authenticate(request, user=self.u) + response = strokedatajson(request,id=w.id) + self.assertEqual(response.status_code,200) - # response must be json - strokedata = json.loads(response.content) - df = pd.DataFrame(strokedata) + # response must be json + + strokedata = json.loads(response.content) + df = pl.from_dict(strokedata) - self.assertFalse(df.empty) + self.assertFalse(df.is_empty()) form_data = { "distance": [23, 46, 48], @@ -119,13 +122,16 @@ class OwnApi(TestCase): request = self.factory.get(url) request.user = self.u force_authenticate(request, user=self.u) - response = strokedatajson_v2(request,id=w.id) - self.assertEqual(response.status_code,200) + with patch('rowers.dataprep.read_data') as mock_read_data: + mock_read_data.return_value = pl.read_csv('rowers/tests/testdata/colsfromdb.csv') + response = strokedatajson_v2(request,id=w.id) + self.assertEqual(response.status_code,200) - # response must be json - strokedata = json.loads(response.content) - df = pd.DataFrame(strokedata) - self.assertFalse(df.empty) + # response must be json + strokedata = json.loads(response.content) + df = pl.from_dicts(strokedata['data']) + + self.assertFalse(df.is_empty()) form_data = { diff --git a/rowers/tests/test_async_tasks.py b/rowers/tests/test_async_tasks.py index f644e53d..f7af8923 100644 --- a/rowers/tests/test_async_tasks.py +++ b/rowers/tests/test_async_tasks.py @@ -484,54 +484,10 @@ class AsyncTaskTests(TestCase): res = tasks.handle_c2_import_stroke_data(c2token,c2id,workoutid,starttimeunix,csvfilename) self.assertEqual(res,1) - @patch('rowers.tasks.grpc',side_effect=mocked_grpc) - @patch('rowers.tasks.send_template_email',side_effect=mocked_send_template_email) - def test_handle_otwsetpower(self,mocked_send_template_email,mocked_grpc): - f1 = get_random_file(filename='rowers/tests/testdata/sprintervals.csv')['filename'] - boattype = '1x' - boatclass = 'water' - coastalbrand = 'other' - weightvalue = 80. - first_name = self.u.first_name - last_name = self.u.last_name - email = self.u.email - workoutid = self.wwater.id - job = fakerequest() - - res = tasks.handle_otwsetpower(f1,boattype,boatclass,coastalbrand, - weightvalue,first_name,last_name,email,workoutid, - jobkey='23') - - self.assertEqual(res,1) - - @patch('rowers.dataprep.create_engine') - def test_handle_updateergcp(self,mocked_sqlalchemy): - f1 = get_random_file()['filename'] - res = tasks.handle_updateergcp(1,[f1]) - self.assertEqual(res,1) - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_updatecp(self,mocked_getsmallrowdata_db_updatecp): - rower_id = 1 - workoutids = [1] - res = tasks.handle_updatecp(rower_id,workoutids) - self.assertEqual(res,1) - - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_setcp(self,mocked_getsmallrowdata_db_setcp): - strokesdf = pd.read_csv('rowers/tests/testdata/uhfull.csv') - filename = 'rowers/tests/testdata/temp/pq.gz' - workoutids = 1 - res = tasks.handle_setcp(strokesdf,filename,1) - self.assertEqual(res,1) - try: - os.remove(filename) - except FileNotFoundError: - pass - - @patch('rowers.dataprep.getsmallrowdata_db') - def test_handle_update_wps(self,mocked_getsmallrowdata_db_wps): + @patch('rowers.dataprep.read_data') + def test_handle_update_wps(self,mocked_read_data_wps): ids = [1,2,3] result = tasks.handle_update_wps(self.r.id,['water'],ids,mode='water') diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index fdbb7e75..79c139b1 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -62,10 +62,10 @@ class ListWorkoutTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_list_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -87,10 +87,10 @@ class ListWorkoutTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.myqueue') def test_bulk_workouts(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db, + mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) @@ -234,9 +234,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.middleware.myqueue') - def test_forcecurve(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_forcecurve(self, mocked_sqlalchemy, mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -255,9 +255,9 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.middleware.myqueue') - def test_resample(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_resample(self, mocked_sqlalchemy, mocked_read_data, mocked_myqueue): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -283,9 +283,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @pytest.mark.django_db(transaction=True) - def test_joins(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + def test_joins(self, mocked_sqlalchemy, mocked_read_data): with transaction.atomic(): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -350,10 +350,10 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.weather.requests.get', side_effect=mocked_requests) def test_waterworkout_view(self, - mocked_sqlalchemy, mocked_getsmallrowdata_db, mocked_requests): + mocked_sqlalchemy, mocked_read_data, mocked_requests): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -362,22 +362,6 @@ class WorkoutViewTest(TestCase): response = self.c.get(url) self.assertEqual(response.status_code,200) - url = reverse('workout_downloadmetar_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - 'airportcode': 'LKHO' - } - ) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) - - url = reverse('workout_downloadwind_view',kwargs={ - 'id': encoder.encode_hex(self.wwater.id), - } - ) - - response = self.c.get(url,follow=True) - self.assertEqual(response.status_code,200) # Stacked Flex Chart url = reverse('workout_flexchart_stacked_view',kwargs={ @@ -422,8 +406,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_smoothen(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_smoothen(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -459,125 +443,12 @@ class WorkoutViewTest(TestCase): status_code=302,target_status_code=200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_windform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_wind_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'vwind1':2.0, - 'vwind2':1.4, - 'windunit':'m', - 'winddirection1': 0, - 'winddirection2': 90, - } - - form = UpdateWindForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_streamform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_stream_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'dist1':1000, - 'dist2':2000, - 'stream1':2.0, - 'stream2':1.4, - 'streamunit':'m', - } - - form = UpdateStreamForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - @patch('rowers.middleware.myqueue') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, - mocked_myqueue): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('instroke_view',kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - rowdata = rowingdata.rowingdata(csvfile=self.winstroke.csvfilename) - instrokemetrics = rowdata.get_instroke_columns() - - self.assertTrue(len(instrokemetrics)>0) - - url = reverse('instroke_chart', - kwargs={ - 'id':encoder.encode_hex(self.winstroke.id), - 'metric':instrokemetrics[0], - }) - url2 = reverse(self.r.defaultlandingpage,kwargs={'id':encoder.encode_hex(self.winstroke.id)}) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_setpowerform(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - - url = reverse('workout_otwsetpower_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'quick_calc':True, - 'boattype': '1x', - 'weightvalue': 75.0, - 'boatbrand':'maas', - } - - form = AdvancedWorkoutForm(form_data) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data,follow=True) - self.assertEqual(response.status_code,200) - - expected_url = reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - - self.assertRedirects(response, - expected_url=expected_url, - status_code=302,target_status_code=200) - - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_commentview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_commentview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -602,8 +473,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_mapview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_mapview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -614,9 +485,9 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def notworking_test_workout_image(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -642,10 +513,10 @@ class WorkoutViewTest(TestCase): status_code=302,target_status_code=200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) @pytest.mark.django_db(transaction=True) - def test_workout_split(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_split(self, mocked_sqlalchemy, mocked_read_data, mocked_getrowdata_db): with transaction.atomic(): login = self.c.login(username=self.u.username, password=self.password) @@ -670,9 +541,9 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_forfusion) + @patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_forfusion) def test_workout_fusion(self, mocked_sqlalchemy, mocked_getrowdata_db, - mocked_getsmallrowdata_db): + mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -699,27 +570,11 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) - def test_remove_power_view(self): - login = self.c.login(username=self.u.username, password=self.password) - self.assertTrue(login) - - url = reverse('remove_power_view',kwargs={'id':encoder.encode_hex(self.wwater.id)}) - url2 = reverse(self.r.defaultlandingpage, - kwargs={ - 'id':encoder.encode_hex(self.wwater.id) - } - ) - response = self.c.get(url) - self.assertRedirects(response, - expected_url=url2, - status_code=302, - target_status_code=200 - ) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_video_view(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -779,9 +634,9 @@ class WorkoutViewTest(TestCase): self.assertRedirects(response,expected_url=expected_url,status_code=302,target_status_code=200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_workout_video_view_erg(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -843,7 +698,7 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getrowdata_db',side_effect=mocked_getrowdata_db) @patch('rowers.dataprep.get_video_data',side_effect=mocked_videodata) - def test_video_selectworkout(self, mocked_sqlalchemy, mocked_getsmallrowdata_db, + def test_video_selectworkout(self, mocked_sqlalchemy, mocked_read_data, mocked_videodata): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -874,8 +729,8 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_editsummaryview(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_editsummaryview(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -940,8 +795,8 @@ class WorkoutViewTest(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_workout_delete(self, mocked_sqlalchemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_workout_delete(self, mocked_sqlalchemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_cpchart.py b/rowers/tests/test_cpchart.py index 6752450c..6f46c0cf 100644 --- a/rowers/tests/test_cpchart.py +++ b/rowers/tests/test_cpchart.py @@ -166,65 +166,3 @@ class CPChartTest(TestCase): self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.fetchcperg', side_effect = mocked_fetchcperg) - @patch('rowers.dataprep.create_engine') - def test_rankingpieces(self, mocked_fetchcperg, mocked_sqlalchemy): - url = '/rowers/ote-bests2/' - - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - # update_records(url='rowers/tests/c2worldrecords.html',verbose=False) - - form_data = { - 'name': faker.word(), - 'date': nu.date(), - 'timezone': 'UTC', - 'duration': '00:02:00.0', - 'starttime': '10:01:43', - 'distance': 500, - 'workouttype': 'rower', - 'boattype': '1x', - 'weightcategory': 'hwt', - 'adaptiveclass': 'None', - 'notes': faker.text(), - 'rankingpiece': True, - 'duplicate': False, - 'avghr': '160', - 'avgpwr': 0, - 'avgspm': 40, - } - - url2 = '/rowers/workout/addmanual/' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 100 - form_data['duration'] = '00:00:18.5' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 2000 - form_data['duration'] = '00:06:56.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 5000 - form_data['duration'] = '00:20:48.0' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 6000 - form_data['duration'] = '00:22:17.6' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 10000 - form_data['duration'] = '00:38:16.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - response = self.c.get(url) - self.assertEqual(response.status_code, 200) diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py index 6523754b..ae26ef5e 100644 --- a/rowers/tests/test_emails.py +++ b/rowers/tests/test_emails.py @@ -55,8 +55,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi(self,mocked_sqlalchemy,mocked_read_data): form_data = { 'title': 'test', 'workouttype':'rower', @@ -108,8 +108,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi2(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi2(self,mocked_sqlalchemy,mocked_read_data): form_data = {"secret": settings.UPLOAD_SERVICE_SECRET, "user": 1, "file": 'media/mailbox_attachments/colin3.csv', @@ -140,8 +140,8 @@ class EmailUpload(TestCase): @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi3(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi3(self,mocked_sqlalchemy,mocked_read_data): with transaction.atomic(): form_data = { 'title': 'test', @@ -167,8 +167,8 @@ class EmailUpload(TestCase): self.assertEqual(w.notes,'aap noot mies') @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_uploadapi_credentials(self,mocked_sqlalchemy,mocked_read_data): form_data = { 'title': 'test', 'workouttype':'rower', diff --git a/rowers/tests/test_flexchart.py b/rowers/tests/test_flexchart.py index d66d20ea..455eb1d8 100644 --- a/rowers/tests/test_flexchart.py +++ b/rowers/tests/test_flexchart.py @@ -103,8 +103,8 @@ class WorkoutViewTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_flexchart_water(self, mocked_sqlalechemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_flexchart_water(self, mocked_sqlalechemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) @@ -151,8 +151,8 @@ class WorkoutViewTest(TestCase): self.assertEqual(response.status_code,200) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_flexchart_erg(self, mocked_sqlalechemy, mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data') + def test_flexchart_erg(self, mocked_sqlalechemy, mocked_read_data): login = self.c.login(username=self.u.username, password=self.password) self.assertTrue(login) diff --git a/rowers/tests/test_imports.py b/rowers/tests/test_imports.py index 16346575..78818eb5 100644 --- a/rowers/tests/test_imports.py +++ b/rowers/tests/test_imports.py @@ -720,8 +720,8 @@ class NKObjects(DjangoTestCase): ) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_nk_intervals(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_nk_intervals(self,mocked_sqlalchemy,mocked_read_data): with open('rowers/tests/testdata/carlos_workout.json','r') as f: workoutdata = json.load(f) with open('rowers/tests/testdata/carlos_strokes.json','r') as f: @@ -817,10 +817,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): integration = NKIntegration(self.u) result = integration.token_refresh() @@ -854,10 +854,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import_task(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): alldata = { 469: {'elapsedTime': 3901900, 'totalDistanceImp': 0, @@ -904,10 +904,10 @@ class NKObjects(DjangoTestCase): @patch('rowers.integrations.nk.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.nk.requests.post', side_effect=mocked_requests) @patch('rowers.nkimportutils.requests.session', side_effect=mocked_session) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_nk_import_impeller(self, mock_get, mock_post, mocked_session, - mocked_getsmallrowdata_db): + mocked_read_data): integration = NKIntegration(self.u) result = integration.token_refresh() @@ -1083,9 +1083,9 @@ class RP3Objects(DjangoTestCase): @patch('rowers.integrations.rp3.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.rp3.requests.post', side_effect=mocked_requests) - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_rp3_import(self, mock_get, mockpost, - mocked_getsmallrowdata_db): + mocked_read_data): response = self.c.get('/rowers/workout/rp3import/591621',follow=True) @@ -1268,9 +1268,9 @@ class StravaObjects(DjangoTestCase): @patch('rowers.utils.requests.get', side_effect=mocked_requests) @patch('rowers.integrations.strava.requests.post', side_effect=mocked_requests) - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') def test_strava_import(self, mock_get, mock_post, - mocked_getsmallrowdata_db): + mocked_read_data): response = self.c.get('/rowers/workout/stravaimport/12',follow=True) expected_url = reverse('workout_import_view',kwargs={'source':'strava'}) @@ -1307,7 +1307,7 @@ class STObjects(DjangoTestCase): self.r.sporttrackstoken = '12' - self.r.sporttracksrefreshtoken = '12' + self.r.sporttroacksrefreshtoken = '12' self.r.sporttrackstokenexpirydate = arrow.get(datetime.datetime.now()+datetime.timedelta(days=1)).datetime self.r.save() diff --git a/rowers/tests/test_interactivecharts.py b/rowers/tests/test_interactivecharts.py index 2d217f0b..c46855f8 100644 --- a/rowers/tests/test_interactivecharts.py +++ b/rowers/tests/test_interactivecharts.py @@ -51,16 +51,16 @@ class InteractiveChartTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart1(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_chart(self.wote.id) @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart2(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_chart(self.wote.id,promember=1) @@ -80,52 +80,52 @@ class InteractiveChartTest(TestCase): @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart7(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r,promember=0, xparam='time', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart8(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=0,xparam='distance', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart9(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='time', yparam1='pace',yparam2='hr',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart10(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='distance', yparam1='pace',yparam2='hr',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart11(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1,xparam='time', yparam1='pace',yparam2='spm',mode='water') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) def test_interactive_chart12(self, mocked_sqlalchemy,mocked_read_df_sql, - mocked_getsmallrowdata_db): + mocked_read_data): res = iplots.interactive_flex_chart2(self.wote.id,self.r, promember=1, xparam='distance', diff --git a/rowers/tests/test_permissions.py b/rowers/tests/test_permissions.py index 2f527fda..4ee00a3b 100644 --- a/rowers/tests/test_permissions.py +++ b/rowers/tests/test_permissions.py @@ -911,8 +911,8 @@ class PermissionsViewTests(TestCase): ## Coach can upload on behalf of athlete - if team allows @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_coach_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rbasic.team.add(self.teamcoach) self.rbasic.coachinggroups.add(self.coachinggroup) @@ -957,8 +957,8 @@ class PermissionsViewTests(TestCase): ## Coach can upload on behalf of athlete - if team allows @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_coach_edit_athlete_uploadnot(self,mocked_sqlalchemy,mocked_read_data): self.rbasic.team.add(self.teamcoach) login = self.c.login(username=self.ucoach.username, password=self.ucoachpassword) @@ -1168,8 +1168,8 @@ class PermissionsViewTests(TestCase): ## Self Coach cannot upload on behalf of athlete @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rpro.team.add(self.teamplan) login = self.c.login(username=self.uplan2.username, password=self.uplan2password) @@ -1291,8 +1291,8 @@ class PermissionsViewTests(TestCase): ## Self Coach cannot upload on behalf of athlete @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_plan_edit_athlete_upload(self,mocked_sqlalchemy,mocked_read_data): self.rpro.team.add(self.teamplan) login = self.c.login(username=self.uplan2.username, password=self.uplan2password) diff --git a/rowers/tests/test_permissions2.py b/rowers/tests/test_permissions2.py index 52d495aa..074f4e7f 100644 --- a/rowers/tests/test_permissions2.py +++ b/rowers/tests/test_permissions2.py @@ -218,7 +218,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -228,7 +228,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -253,7 +253,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -263,7 +263,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -335,7 +335,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -345,7 +345,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -418,7 +418,7 @@ class PermissionsViewTests(TestCase): @patch('rowers.integrations.c2.C2Integration.open') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -428,7 +428,7 @@ class PermissionsViewTests(TestCase): mock_c2open, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index a4984a1a..4903774b 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -13,6 +13,7 @@ from django.db import transaction nu = datetime.datetime.now() import datetime import pytz +import polars as pl # interactive plots from rowers import interactiveplots @@ -581,7 +582,7 @@ class DataPrepTests(TestCase): age = dataprep.calculate_age(born,today=today) self.assertEqual(age,49) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh) + @patch('rowers.dataprep.read_data',side_effect=mocked_getsmallrowdata_uh) def test_goldmedalstandard(self,mocked_getsmallrowdata_uh): maxvalue, delta = dataprep.calculate_goldmedalstandard(self.r,self.wuh_otw) records = CalcAgePerformance.objects.filter( @@ -596,8 +597,8 @@ class DataPrepTests(TestCase): result = getagegrouprecord(25) self.assertEqual(int(result),590) - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_uh) - def test_get_videodata(self,mocked_getsmallrowdata_uh): + @patch('rowers.dataprep.getsmallrowdata_pd',side_effect=mocked_getsmallrowdata_uh_pd) + def test_get_videodata(self,mocked_getsmallrowdata_uh_pd): data, metrics, maxtime = dataprep.get_video_data(self.wuh_otw) self.assertEqual(len(data),9) @@ -659,7 +660,7 @@ class InteractivePlotTests(TestCase): pass def test_interactive_hr_piechart(self): - df = pd.read_csv('rowers/tests/testdata/getrowdata_mock.csv') + df = pl.read_csv('rowers/tests/testdata/getrowdata_mock.csv') script, div = interactiveplots.interactive_hr_piechart(df, self.r,'') self.assertFalse(len(script)==0) @@ -672,7 +673,8 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) def test_interactive_boxchart(self): - df = pd.read_csv('rowers/tests/testdata/boxplotdata.csv') + df = pl.read_csv('rowers/tests/testdata/boxplotdata.csv') + df = df.with_columns(pl.col("date").str.to_datetime("%Y-%m-%d")) script, div = interactiveplots.interactive_boxchart(df, 'spm') @@ -685,12 +687,12 @@ class InteractivePlotTests(TestCase): startdate = workouts[0].date-datetime.timedelta(days=5) enddate = workouts2[0].date+datetime.timedelta(days=5) - script, div = interactiveplots.interactive_activitychart(workouts,startdate,enddate) + script, div = interactiveplots.interactive_activitychart2(workouts,startdate,enddate) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) def test_interactive_otwcpchart(self): - df = pd.read_csv('rowers/tests/testdata/otwcp.csv') + df = pl.read_csv('rowers/tests/testdata/otwcp.csv') script, div, p1, ratio, message = interactiveplots.interactive_otwcpchart(df,r=self.r,cpfit='data') self.assertFalse(len(script)==0) @@ -701,9 +703,9 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_interactive_chart(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id @@ -733,12 +735,12 @@ class InteractivePlotTests(TestCase): self.assertFalse(len(div)==0) @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db', side_effect=mocked_getsmallrowdata_db) + @patch('rowers.dataprep.read_data', side_effect=mocked_read_data) def test_interactive_flexchart_stacked(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): + mocked_read_data): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id - script, div, js_res, css_res, comment = interactiveplots.interactive_flexchart_stacked(id,self.r) + script, div = interactiveplots.interactive_flexchart_stacked(id,self.r) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) diff --git a/rowers/tests/test_units.py b/rowers/tests/test_units.py index 68754ddc..9fb66dac 100644 --- a/rowers/tests/test_units.py +++ b/rowers/tests/test_units.py @@ -71,9 +71,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=1) self.assertEqual(w.forceunit,'lbs') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,398) + self.assertEqual(average_N,400) data = dataprep.read_df_sql(13) average_N = int(data['averageforce'].mean()) @@ -119,9 +120,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=13) self.assertEqual(w.forceunit,'N') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,263) + self.assertEqual(average_N,271) def test_upload_speedcoach_colin(self): login = self.c.login(username=self.u.username, password=self.password) @@ -155,9 +157,10 @@ class ForceUnits(TestCase): w = Workout.objects.get(id=13) self.assertEqual(w.forceunit,'N') - df = dataprep.getsmallrowdata_db(['averageforce'],ids=[13]) + df = dataprep.read_data(['averageforce'],ids=[13]) + df = dataprep.remove_nulls_pl(df) average_N = int(df['averageforce'].mean()) - self.assertEqual(average_N,105) + self.assertEqual(average_N,122) @override_settings(TESTING=True) class TestForceUnit(TestCase): @@ -206,7 +209,7 @@ class TestForceUnit(TestCase): rowdata = dataprep.rdata('rowers/tests/testdata/PainsledForce.csv') df = dataprep.dataprep(rowdata.df) - #df = dataprep.getsmallrowdata_db(['averageforce'],ids=[w[0].id],doclean=False, + #df = dataprep.read_data(['averageforce'],ids=[w[0].id],doclean=False, # compute=False) try: average_N = int(df['averageforce'].mean()) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 68a61119..b27a198a 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -29,8 +29,8 @@ class ViewTest(TestCase): self.nu = datetime.datetime.now() @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_upload_view_sled(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_upload_view_sled(self, mocked_sqlalchemy,mocked_read_data): login = self.c.login(username='john',password='koeinsloot') self.assertTrue(login) @@ -74,10 +74,6 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) - response = self.c.get('/rowers/workout/'+encoded1+'/histo/', form_data, follow=True) - self.assertEqual(response.status_code, 200) - - f.close() @@ -124,8 +120,8 @@ class ViewTest(TestCase): pass @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) - def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_getsmallrowdata_db): + @patch('rowers.dataprep.read_data',side_effect=mocked_read_data) + def test_upload_view_sled_bg(self, mocked_sqlalchemy,mocked_read_data): login = self.c.login(username='john',password='koeinsloot') self.assertTrue(login) diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index aa736aec..4f2d6d9c 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -79,11 +79,6 @@ class URLTests(TestCase): '/rowers/502/', '/rowers/about/', '/rowers/workout/addmanual/', - '/rowers/agegroupcp/30/', - '/rowers/agegroupcp/30/1/', - '/rowers/agegrouprecords/male/hwt/', - '/rowers/agegrouprecords/male/hwt/2000m/', - '/rowers/agegrouprecords/male/hwt/30min/', '/rowers/ajax_agegroup/45/hwt/male/1/', '/rowers/analysis/', '/rowers/analysis/user/1/', @@ -122,8 +117,6 @@ class URLTests(TestCase): '/rowers/me/workflowconfig2/', '/rowers/me/workflowconfig2/user/1/', '/rowers/me/workflowdefault/', - '/rowers/ote-bests2/', - '/rowers/ote-bests2/user/1/', '/rowers/partners/', '/rowers/physics/', '/rowers/planrequired/', @@ -148,29 +141,23 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/addtimeplot/', '/rowers/workout/'+encoded1+'/addtimeplot2/', '/rowers/workout/'+encoded1+'/comment/', - '/rowers/workout/'+encoded1+'/darkskywind/', '/rowers/workout/'+encoded1+'/data/', '/rowers/workout/'+encoded1+'/edit/', '/rowers/workout/'+encoded1+'/editintervals/', '/rowers/workout/'+encoded1+'/flexchart/', '/rowers/workout/'+encoded1+'/forcecurve/', '/rowers/workout/'+encoded1+'/get-thumbnails/', - '/rowers/workout/'+encoded1+'/histo/', '/rowers/workout/'+encoded1+'/image/', '/rowers/workout/'+encoded1+'/instroke/', - '/rowers/workout/'+encoded1+'/interactiveotwplot/', '/rowers/workout/'+encoded1+'/map/', - '/rowers/workout/'+encoded1+'/otwsetpower/', '/rowers/workout/'+encoded1+'/recalcsummary/', '/rowers/workout/'+encoded1+'/restore/', '/rowers/workout/'+encoded1+'/smoothenpace/', '/rowers/workout/'+encoded1+'/split/', '/rowers/workout/'+encoded1+'/stats/', - '/rowers/workout/'+encoded1+'/stream/', '/rowers/workout/'+encoded1+'/undosmoothenpace/', '/rowers/workout/'+encoded1+'/unsubscribe/', '/rowers/workout/'+encoded1+'/view/', - '/rowers/workout/'+encoded1+'/wind/', '/rowers/workout/'+encoded1+'/workflow/', '/rowers/workout/fusion/'+encoded1+'/', '/rowers/workout/upload/', @@ -206,7 +193,7 @@ class URLTests(TestCase): @parameterized.expand(lijst) @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.read_df_sql') - @patch('rowers.dataprep.getsmallrowdata_db') + @patch('rowers.dataprep.read_data') @patch('requests.get',side_effect=mocked_requests) @patch('requests.post',side_effect=mocked_requests) @patch('rowers.dataprep.get_video_data',side_effect=mocked_get_video_data) @@ -214,7 +201,7 @@ class URLTests(TestCase): def test_url_generator(self,url,expected, mocked_sqlalchemy, mocked_read_df_sql, - mocked_getsmallrowdata_db, + mocked_read_data, mock_get, mock_post, mocked_get_video_data, @@ -225,7 +212,7 @@ class URLTests(TestCase): self.assertTrue(login) response = self.c.get(url,follow=True) if response.status_code != expected: - print(url ) + print(url) print(response.status_code) self.assertEqual(response.status_code, diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 1148cc31..d6f26560 100644 Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index 9fa22e20..245dbb17 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -1,9 +1,7 @@ ,id,view,function,anonymous,anonymous_response,own,own_response,own_nonperm,member,member_response,member_nonperm,coachee,coachee_response,coachee_nonperm,is_staff,userid,workoutid,dotest,realtest,kwargs 0,0,workouts_summaries_email_view,sends summary excel with workouts list and links to data to user,TRUE,302,basic,200,302,FALSE,403,403,FALSE,403,403,FALSE,FALSE,FALSE,TRUE,TRUE, 1,1,rower_update_empower_view,updates old Empower Oarlock files (corrects Power bug),TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, -2,2,agegroupcpview,needs age,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 3,4,ajax_agegrouprecords,gets age group records from C2 ,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, -5,6,agegrouprecordview,shows ergo age group records,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 6,7,workouts_view,workouts list,TRUE,302,basic,200,302,basic,200,403,coach,200,403,FALSE,TRUE,FALSE,TRUE,TRUE, 7,8,virtualevents_view,virtual races list,TRUE,200,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 8,9,virtualevent_create_view,create virtual event,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, @@ -31,7 +29,6 @@ 33,35,kill_async_job,kill job,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 34,36,post_progress,post progress,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 35,37,graphs_view,view charts,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, -38,40,rankings_view2,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, 41,43,cum_flex,flex all chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 42,44,analysis_view_data,redirects to analysis direct,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 43,47,cum_flex_data,flex all chart data (json),TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, @@ -43,7 +40,6 @@ 52,58,workout_toggle_ranking,toggle ranking,TRUE,302,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE, 53,59,team_workout_upload_view,upload workout for team member,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 54,60,workout_upload_view,upload a workout,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, -55,61,workout_histo_view,histogram ,TRUE,302,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 56,62,workout_forcecurve_view,force curve,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 57,63,workout_unsubscribe_view,unsubscribe from comments,TRUE,302,basic,200,302,basic,200,302,basic,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 58,64,workout_comment_view,comment on workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, @@ -57,17 +53,11 @@ 67,73,instroke_view,Create in stroke chart,TRUE,403,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 68,74,workout_stats_view,View Workout Stats,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 69,75,workout_data_view,shows the detailed data for a workout,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, -70,76,workout_otwsetpower_view,set weight and start offline OTW power calculations,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, -71,77,workout_otwpowerplot_view,generates OTW power chart,TRUE,200,basic,200,200,basic,200,200,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, -72,78,workout_wind_view,set wind,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 73,79,workout_uploadimage_view,upload image,TRUE,403,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 74,80,virtualevent_compare_view,compare workouts from a virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 75,81,virtualevent_uploadimage_view,upload image to virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 76,82,virtualevent_setlogo_view,set logo of virtual event,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 77,83,logo_delete_view,delete logo,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, -78,84,workout_downloadwind_view,download Wind,TRUE,403,pro,302,302,pro,403,403,coach,302,403,FALSE,FALSE,TRUE,FALSE,FALSE, -79,85,workout_downloadmetar_view,download METAR,TRUE,403,pro,200,302,pro,403,403,coach,200,403,FALSE,FALSE,TRUE,FALSE,FALSE, -80,86,workout_stream_view,edit stream (redirects as no data in test suite),TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 81,87,workout_summary_edit_view,edit workout summary,TRUE,302,basic,200,403,basic,403,403,coach,200,403,FALSE,FALSE,TRUE,TRUE,TRUE, 82,88,workout_summary_restore_view,restore workout summary,TRUE,403,basic,302,403,basic,403,403,coach,302,403,FALSE,FALSE,TRUE,TRUE,TRUE, 83,89,workout_split_view,split workout,TRUE,403,pro,200,302,pro,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, diff --git a/rowers/urls.py b/rowers/urls.py index c5d829b8..0b0f761e 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -273,19 +273,13 @@ urlpatterns = [ re_path(r'^exportallworkouts/?/$', views.workouts_summaries_email_view, name='workouts_summaries_email_view'), path('failedjobs/', views.failed_queue_view, name='failed_queue_view'), + path('sleep/', views.sleep_view, name='sleep_view'), + path('filmdeaths/', views.filmdeaths_view, name='filmdeaths_view'), path('failedjobs/empty/', views.failed_queue_empty, name='failed_queue_empty'), re_path('^failedjobs/(?P\w+.*)/$', views.failed_job_view, name='failed_job_view'), re_path(r'^update_empower/$', views.rower_update_empower_view, name='rower_update_empower_view'), - re_path(r'^agegroupcp/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/user/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), - re_path(r'^agegroupcp/(?P\d+)/(?P\d+)/user/(?P\d+)/$', - views.agegroupcpview, name='agegroupcpview'), re_path(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)/$', views.ajax_agegrouprecords, name='ajax_agegrouprecords'), re_path(r'^agegrouprecords/(?P\w+.*)/(?P\w+.*)/(?P\d+)m/$', @@ -448,9 +442,6 @@ urlpatterns = [ views.trainingzones_view_data, name="trainingzones_view_data"), re_path(r'^trainingzones/data/$', views.trainingzones_view_data, name="trainingzones_view_data"), - re_path(r'^ote-bests2/user/(?P\d+)/$', - views.rankings_view2, name='rankings_view2'), - re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'), re_path(r'^analysisdata/user/(?P\d+)/$', views.analysis_view_data, name='analysis_view_data'), re_path(r'^analysisdata/$', views.analysis_view_data, @@ -475,8 +466,6 @@ urlpatterns = [ views.workout_upload_view, name='workout_upload_view'), re_path(r'^workout/upload/$', views.workout_upload_view, name='workout_upload_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view, - name='workout_histo_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view, name='workout_forcecurve_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/forcecurve/(?P\d+)/$', views.workout_forcecurve_view, @@ -513,14 +502,6 @@ urlpatterns = [ name='workout_erase_column_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower-confirm/$', views.remove_power_confirm_view, name='remove_power_confirm_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower/$', views.remove_power_view, - name='remove_power_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/otwsetpower/$', - views.workout_otwsetpower_view, name='workout_otwsetpower_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/interactiveotwplot/$', - views.workout_otwpowerplot_view, name='workout_otwpowerplot_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/wind/$', - views.workout_wind_view, name='workout_wind_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/image/$', views.workout_uploadimage_view, name='workout_uploadimage_view'), re_path(r'^virtualevent/(?P\d+)/compare/$', @@ -543,8 +524,6 @@ urlpatterns = [ views.workout_downloadwind_view, name='workout_downloadwind_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/metar/(?P\w+)/$', views.workout_downloadmetar_view, name='workout_downloadmetar_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/stream/$', - views.workout_stream_view, name='workout_stream_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/editintervals/$', views.workout_summary_edit_view, name='workout_summary_edit_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/restore/$', diff --git a/rowers/utils.py b/rowers/utils.py index 34f808ea..7f96a94a 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -6,6 +6,8 @@ from django.utils import timezone import math import numpy as np import pandas as pd +import polars as pl +from polars.exceptions import ColumnNotFoundError import colorsys from django.conf import settings import collections @@ -155,12 +157,7 @@ def get_call(): call1 = random.choice(coxes_calls) call2 = random.choice(info_calls) - call = """
    -

    - %s (%s) -

    -
    -""" % (call1, call2) + call = """

    %s (%s)

    """ % (call1, call2) return call @@ -347,22 +344,22 @@ def isbreakthrough(delta, cpvalues, p0, p1, p2, p3, ratio): pwr *= ratio - delta = delta.astype(int, errors='ignore').values - cpvalues = cpvalues.astype(int, errors='ignore').values - pwr = pwr.astype(int, errors='ignore').values + delta = delta.cast(pl.Int32) + cpvalues = cpvalues.cast(pl.Int32) + pwr = pwr.cast(pl.Int32) - res = np.sum(cpvalues > pwr+1) - res2 = np.sum(cpvalues > pwr2+1) + btdf = pl.DataFrame({ + 'delta': delta, + 'cpvalues': cpvalues, + 'pwr': pwr, + 'pwr2': pwr2 + }) - btdf = pd.DataFrame( - { - 'delta': delta[cpvalues > pwr], - 'cpvalues': cpvalues[cpvalues > pwr], - 'pwr': pwr[cpvalues > pwr], - } - ) + res = btdf.select(pl.col("cpvalues")>pl.col("pwr")+1)['cpvalues'].sum() + res2 = btdf.select(pl.col("cpvalues")> pl.col("pwr2")+1)['cpvalues'].sum() - btdf.sort_values('delta', axis=0, inplace=True) + btdf = btdf.filter(pl.col("cpvalues")>pl.col("pwr")) + btdf = btdf.sort('delta') return res >= 1, btdf, res2 >= 1 @@ -444,17 +441,19 @@ def wavg(group, avg_name, weight_name): """ try: d = group[avg_name] - except KeyError: + except (KeyError, ColumnNotFoundError): return 0 try: w = group[weight_name] - except KeyError: + except (KeyError, ColumnNotFoundError): return d.mean() try: return (d * w).sum() / w.sum() except ZeroDivisionError: # pragma: no cover return d.mean() + return 0 + from string import Formatter def totaltime_sec_to_string(totaltime, shorten=False): diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 9bc2fe0a..587db08d 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -296,7 +296,10 @@ def analysis_new(request, df = cpdata(tw, options) options['savedata'] = False request.session['options'] = options - response = HttpResponse(df.to_csv()) + try: + response = HttpResponse(df.to_csv()) + except AttributeError: + response = HttpResponse(df.write_csv()) code = str(uuid4()) filename = code+'.csv' chartform.fields['savedata'].initial = False @@ -384,6 +387,9 @@ def trendflexdata(workouts, options, userid=0): datadf = dataprep.filter_df(datadf, 'driveneergy', workmax, largerthan=False) + if yparam == 'power': + datadf = dataprep.filter_df(datadf, 'power', 1, largerthan=True) + datadf.dropna(axis=0, how='any', inplace=True) datemapping = { @@ -472,8 +478,8 @@ def trendflexdata(workouts, options, userid=0): 'groupsize': groupsize, }) - if yparam == 'pace': - df['y'] = dataprep.paceformatsecs(df['y']/1.0e3) + #if yparam == 'pace': + # df['y'] = dataprep.paceformatsecs(df['y']/1.0e3) aantal = len(df) @@ -568,7 +574,7 @@ def flexalldata(workouts, options): workstrokesonly = not includereststrokes columns = [xparam, yparam1, yparam2, 'spm', 'driveenergy', 'distance'] ids = [int(w.id) for w in workouts] - df = dataprep.getsmallrowdata_db(columns, ids=ids, + df = dataprep.read_data(columns, ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) @@ -609,7 +615,7 @@ def histodata(workouts, options): if savedata: # pragma: no cover workstrokesonly = not includereststrokes ids = [int(w.id) for w in workouts] - df = dataprep.getsmallrowdata_db([plotfield], ids=ids, + df = dataprep.read_data([plotfield], ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) @@ -625,6 +631,7 @@ def histodata(workouts, options): def cpdata(workouts, options): + start = timezone.now() userid = options['userid'] cpfit = options['cpfit'] cpoverlay = options['cpoverlay'] @@ -635,7 +642,7 @@ def cpdata(workouts, options): delta, cpvalue, avgpower, workoutnames, urls = dataprep.fetchcp_new( r, workouts) - powerdf = pd.DataFrame({ + powerdf = pl.DataFrame({ 'Delta': delta, 'CP': cpvalue, 'workout': workoutnames, @@ -644,16 +651,17 @@ def cpdata(workouts, options): savedata = options.get('savedata',False) if savedata: # pragma: no cover - return powerdf + return powerdf.to_pandas() - if powerdf.empty: # pragma: no cover + if powerdf.is_empty(): # pragma: no cover return('', '

    No valid data found

    ') - 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) + powerdf = powerdf.lazy().filter(pl.col("CP")>0) + powerdf = powerdf.sort(["Delta", "CP"], descending=[False, True]) + powerdf = powerdf.unique(subset="Delta", keep="first") + powerdf = powerdf.fill_nan(None).drop_nulls() + powerdf = powerdf.collect() rowername = r.user.first_name+" "+r.user.last_name @@ -880,7 +888,7 @@ def comparisondata(workouts, options): 'time', 'pace', 'workoutstate', 'workoutid'] - df = dataprep.getsmallrowdata_db(columns, ids=ids, + df = dataprep.read_data(columns, ids=ids, workstrokesonly=workstrokesonly, doclean=True, ) @@ -923,28 +931,22 @@ def boxplotdata(workouts, options): ids = [w.id for w in workouts] # prepare data frame - datadf, extracols = dataprep.read_cols_df_sql(ids, fieldlist) + datadf = dataprep.read_data(fieldlist, ids) - datadf = dataprep.clean_df_stats(datadf, workstrokesonly=workstrokesonly) + datadf = dataprep.remove_nulls_pl(datadf) - datadf = dataprep.filter_df(datadf, 'spm', spmmin, - largerthan=True) - datadf = dataprep.filter_df(datadf, 'spm', spmmax, - largerthan=False) - datadf = dataprep.filter_df(datadf, 'driveenergy', workmin, - largerthan=True) - datadf = dataprep.filter_df(datadf, 'driveneergy', workmax, - largerthan=False) + try: + datadf = datadf.filter( + pl.col("spm")>spmmin, + pl.col("spm")workmin, + pl.col("driveenergy") 200: + date_agg = "month" - script, div = interactive_zoneschart( + data = get_zones_report_pl(r, startdate, enddate, + trainingzones=zones, date_agg=date_agg, yaxis=yaxis) + + script, div = interactive_zoneschart2( r, data, startdate, enddate, trainingzones=zones, date_agg=date_agg, yaxis=yaxis) + return JSONResponse({ 'script': script, 'div': div, @@ -1384,547 +1384,6 @@ def ajax_agegrouprecords(request, ) -# Show ranking distances including predicted paces -@login_required() -@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True) -def rankings_view2(request, userid=0, - startdate=timezone.now()-datetime.timedelta(days=365), - enddate=timezone.now(), - deltadays=-1, - startdatestring="", - enddatestring=""): - - if deltadays > 0: # pragma: no cover - startdate = enddate-datetime.timedelta(days=int(deltadays)) - - if startdatestring != "": # pragma: no cover - startdate = iso8601.parse_date(startdatestring) - - if enddatestring != "": # pragma: no cover - enddate = iso8601.parse_date(enddatestring) - - if enddate < startdate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - - if userid == 0: - userid = request.user.id - else: - lastupdated = "1900-01-01" - - promember = 0 - r = getrequestrower(request, userid=userid) - theuser = r.user - - wcdurations = [] - wcpower = [] - - lastupdated = "1900-01-01" - userid = 0 - if 'options' in request.session: - options = request.session['options'] - try: - wcdurations = options['wcdurations'] - wcpower = options['wcpower'] - lastupdated = options['lastupdated'] - except KeyError: # pragma: no cover - pass - try: - userid = options['userid'] - except KeyError: # pragma: no cover - userid = 0 - else: - options = {} - - lastupdatedtime = arrow.get(lastupdated).timestamp() - current_time = arrow.utcnow().timestamp() - - deltatime_seconds = current_time - lastupdatedtime - recalc = False - if str(userid) != str(theuser) or deltatime_seconds > 3600: - recalc = True - options['lastupdated'] = arrow.utcnow().isoformat() - else: # pragma: no cover - recalc = False - - options['userid'] = theuser.id - - if r.birthdate: - age = calculate_age(r.birthdate) - else: - age = 0 - - agerecords = CalcAgePerformance.objects.filter( - age=age, - sex=r.sex, - weightcategory=r.weightcategory) - - if len(agerecords) == 0: - recalc = True - wcpower = [] - wcdurations = [] - else: - wcdurations = [] - wcpower = [] - for record in agerecords: - wcdurations.append(record.duration) - wcpower.append(record.power) - - options['wcpower'] = wcpower - options['wcdurations'] = wcdurations - if theuser: - options['userid'] = theuser.id - - request.session['options'] = options - - result = request.user.is_authenticated and ispromember(request.user) - if result: - promember = 1 - - # get all indoor rows in date range - - # process form - if request.method == 'POST' and "daterange" in request.POST: - dateform = DateRangeForm(request.POST) - deltaform = DeltaDaysForm(request.POST) - if dateform.is_valid(): - startdate = dateform.cleaned_data['startdate'] - enddate = dateform.cleaned_data['enddate'] - if startdate > enddate: # pragma: no cover - s = enddate - enddate = startdate - startdate = s - elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover - deltaform = DeltaDaysForm(request.POST) - if deltaform.is_valid(): - deltadays = deltaform.cleaned_data['deltadays'] - if deltadays: - enddate = timezone.now() - startdate = enddate-datetime.timedelta(days=deltadays) - if startdate > enddate: - s = enddate - enddate = startdate - startdate = s - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - else: - dateform = DateRangeForm() - deltaform = DeltaDaysForm() - - else: - dateform = DateRangeForm(initial={ - 'startdate': startdate, - 'enddate': enddate, - }) - deltaform = DeltaDaysForm() - - # get all 2k (if any) - this rower, in date range - try: - r = getrower(theuser) - except Rower.DoesNotExist: # pragma: no cover - r = 0 - - uu = theuser - - # test to fix bug - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - startdate = arrow.get(startdate).datetime - enddate = arrow.get(enddate).datetime - - thedistances = [] - theworkouts = [] - thesecs = [] - - rankingdistances.sort() - rankingdurations.sort() - - for rankingdistance in rankingdistances: - - workouts = Workout.objects.filter( - user=r, distance=rankingdistance, - workouttype__in=['rower', 'dynamic', 'slides'], - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('duration') - if workouts: - thedistances.append(rankingdistance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-6*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - for rankingduration in rankingdurations: - - workouts = Workout.objects.filter( - user=r, duration=rankingduration, - workouttype='rower', - rankingpiece=True, - startdatetime__gte=startdate, - startdatetime__lte=enddate).order_by('-distance') - if workouts: - thedistances.append(workouts[0].distance) - theworkouts.append(workouts[0]) - - timesecs = 3600*workouts[0].duration.hour - timesecs += 60*workouts[0].duration.minute - timesecs += workouts[0].duration.second - timesecs += 1.e-5*workouts[0].duration.microsecond - - thesecs.append(timesecs) - - thedistances = np.array(thedistances) - thesecs = np.array(thesecs) - - thevelos = thedistances/thesecs - theavpower = 2.8*(thevelos**3) - - # create interactive plot - if len(thedistances) != 0: - res = interactive_cpchart( - r, thedistances, thesecs, theavpower, - theworkouts, promember=promember, - wcdurations=wcdurations, wcpower=wcpower - ) - script = res[0] - div = res[1] - paulslope = res[2] - paulintercept = res[3] - p1 = res[4] - message = res[5] - else: - script = '' - div = '

    No ranking pieces found.

    ' - paulslope = 1 - paulintercept = 1 - p1 = [1, 1, 1, 1] - message = "" - - if request.method == 'POST' and "piece" in request.POST: # pragma: no cover - form = PredictedPieceForm(request.POST) - if form.is_valid(): - value = form.cleaned_data['value'] - hourvalue, value = divmod(value, 60) - if hourvalue >= 24: - hourvalue = 23 - pieceunit = form.cleaned_data['pieceunit'] - if pieceunit == 'd': - rankingdistances.append(value) - else: - rankingdurations.append(datetime.time( - minute=int(value), hour=int(hourvalue))) - else: - form = PredictedPieceForm() - - rankingdistances.sort() - rankingdurations.sort() - - predictions = [] - cpredictions = [] - - for rankingdistance in rankingdistances: - # Paul's model - p = paulslope*np.log10(rankingdistance)+paulintercept - velo = 500./p - t = rankingdistance/velo - pwr = 2.8*(velo**3) - try: - pwr = int(pwr) - except (ValueError, AttributeError): # pragma: no cover - pwr = 0 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - - # CP model - - pwr2 = p1[0]/(1+t/p1[2]) - pwr2 += p1[1]/(1+t/p1[3]) - - if pwr2 <= 0: # pragma: no cover - pwr2 = 50. - - velo2 = (pwr2/2.8)**(1./3.) - - if np.isnan(velo2) or velo2 <= 0: # pragma: no cover - velo2 = 1.0 - - t2 = rankingdistance/velo2 - - pwr3 = p1[0]/(1+t2/p1[2]) - pwr3 += p1[1]/(1+t2/p1[3]) - - if pwr3 <= 0: # pragma: no cover - pwr3 = 50. - - velo3 = (pwr3/2.8)**(1./3.) - if np.isnan(velo3) or velo3 <= 0: # pragma: no cover - velo3 = 1.0 - - t3 = rankingdistance/velo3 - p3 = 500./velo3 - - a = {'distance': rankingdistance, - 'duration': timedeltaconv(t3), - 'pace': timedeltaconv(p3), - 'power': int(pwr3)} - cpredictions.append(a) - - for rankingduration in rankingdurations: - t = 3600.*rankingduration.hour - t += 60.*rankingduration.minute - t += rankingduration.second - t += rankingduration.microsecond/1.e6 - - # Paul's model - ratio = paulintercept/paulslope - - u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope - - d = 500*t*np.log(10.) - d = d/(paulslope*lambertw(u)) - d = d.real - - velo = d/t - p = 500./velo - pwr = 2.8*(velo**3) - try: - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - predictions.append(a) - except: # pragma: no cover - pass - - # CP model - pwr = p1[0] / (1 + t / p1[2]) - pwr += p1[1] / (1 + t / p1[3]) - - if pwr <= 0: # pragma: no cover - pwr = 50. - - velo = (pwr / 2.8)**(1. / 3.) - - if np.isnan(velo) or velo <= 0: # pragma: no cover - velo = 1.0 - - d = t * velo - p = 500. / velo - a = {'distance': int(d), - 'duration': timedeltaconv(t), - 'pace': timedeltaconv(p), - 'power': int(pwr)} - cpredictions.append(a) - - if recalc: - wcdurations = [] - wcpower = [] - durations = [1, 4, 30, 60] - distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] - - df = pd.DataFrame( - list( - C2WorldClassAgePerformance.objects.filter( - sex=r.sex, - weightcategory=r.weightcategory - ).values() - ) - ) - - jsondf = df.to_json() - - messages.error(request, message) - return render(request, 'rankings.html', - {'rankingworkouts': theworkouts, - 'interactiveplot': script, - 'the_div': div, - 'predictions': predictions, - 'cpredictions': cpredictions, - 'nrdata': len(thedistances), - 'form': form, - 'dateform': dateform, - 'deltaform': deltaform, - 'id': theuser, - 'theuser': uu, - 'rower': r, - 'active': 'nav-analysis', - 'age': age, - 'sex': r.sex, - 'recalc': recalc, - 'weightcategory': r.weightcategory, - 'startdate': startdate, - 'enddate': enddate, - 'teams': get_my_teams(request.user), - }) - - -@login_required() -def otecp_toadmin_view(request, theuser=0, - startdate=timezone.now() - datetime.timedelta(days=365), - enddate=timezone.now(), - startdatestring="", - enddatestring="", - ): # pragma: no cover - - if startdatestring != "": # pragma: no cover - try: - startdate = iso8601.parse_date(startdatestring) - except ParseError: - pass - - if enddatestring != "": # pragma: no cover - try: - enddate = iso8601.parse_date(enddatestring) - except ParseError: - pass - - if theuser == 0: # pragma: no cover - theuser = request.user.id - - u = User.objects.get(id=theuser) - r = Rower.objects.get(user=u) - - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - - theworkouts = Workout.objects.filter( - user=r, rankingpiece=True, - workouttype__in=[ - 'rower', - 'dynamic', - 'slides' - ], - startdatetime__gte=startdate, - startdatetime__lte=enddate - ).order_by("-startdatetime") - - delta, cpvalue, avgpower = dataprep.fetchcp( - r, theworkouts, table='cpergdata' - ) - - powerdf = pd.DataFrame({ - 'Delta': delta, - 'CP': cpvalue, - }) - - csvfilename = 'CP_data_user_{id}.csv'.format( - id=theuser - ) - - 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) - powerdf.to_csv(csvfilename) - - _ = myqueue(queuehigh, - handle_sendemailfile, - 'Sander', - 'Roosendaal', - 'roosendaalsander@gmail.com', - csvfilename, - delete=True) - - successmessage = "The CSV file was sent to the site admin per email" - messages.info(request, successmessage) - response = HttpResponseRedirect('/rowers/list-workouts/') - - return response - - -@login_required() -def otwcp_toadmin_view(request, theuser=0, - startdate=timezone.now() - datetime.timedelta(days=365), - enddate=timezone.now(), - startdatestring="", - enddatestring="", - ): # pragma: no cover - - if startdatestring != "": - try: - startdate = iso8601.parse_date(startdatestring) - except ParseError: - pass - - if enddatestring != "": - try: - enddate = iso8601.parse_date(enddatestring) - except ParseError: - pass - - if theuser == 0: - theuser = request.user.id - - u = User.objects.get(id=theuser) - r = Rower.objects.get(user=u) - - startdate = datetime.datetime.combine(startdate, datetime.time()) - enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59)) - - theworkouts = Workout.objects.filter( - user=r, rankingpiece=True, - workouttype='water', - startdatetime__gte=startdate, - startdatetime__lte=enddate - ).order_by("-startdatetime") - - delta, cpvalue, avgpower = dataprep.fetchcp( - r, theworkouts, table='cpdata' - ) - - powerdf = pd.DataFrame({ - 'Delta': delta, - 'CP': cpvalue, - }) - - csvfilename = 'CP_data_user_{id}.csv'.format( - id=theuser - ) - - 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) - powerdf.to_csv(csvfilename) - - _ = myqueue(queuehigh, - handle_sendemailfile, - 'Sander', - 'Roosendaal', - 'roosendaalsander@gmail.com', - csvfilename, - delete=True) - - successmessage = "The CSV file was sent to the site admin per email" - messages.info(request, successmessage) - response = HttpResponseRedirect('/rowers/list-workouts/') - - return response - - -def agegroupcpview(request, age, normalize=0, userid=0): - script, div = interactive_agegroupcpchart(age, normalized=normalize) - - response = render(request, 'agegroupcp.html', - { - 'active': 'nav-analysis', - 'interactiveplot': script, - 'the_div': div, - } - ) - - return response - def agegrouprecordview(request, sex='male', weightcategory='hwt', distance=2000, duration=None): @@ -2761,15 +2220,16 @@ def history_view_data(request, userid=0): ids = [w.id for w in g_workouts] - columns = ['hr', 'power', 'time'] - - df = getsmallrowdata_db(columns, ids=ids) + columns = ['hr', 'power', 'time', 'workoutstate', 'workoutid'] + + df = dataprep.read_data(columns, ids=ids) + df = dataprep.remove_nulls_pl(df) + try: - df['deltat'] = df['time'].diff().clip(lower=0) + df = df.with_columns(pl.col('time').diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover pass - df = dataprep.clean_df_stats(df, workstrokesonly=True, - ignoreadvanced=True, ignorehr=False) + totalmeters, totalhours, totalminutes, totalseconds = get_totals( g_workouts) @@ -2798,13 +2258,14 @@ def history_view_data(request, userid=0): whours=whours, wminutes=wminutes, wseconds=wseconds, ) - ddf = getsmallrowdata_db(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.remove_nulls_pl(ddf) try: - ddf['deltat'] = ddf['time'].diff().clip(lower=0) + ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat")) except KeyError: # pragma: no cover pass - ddf = dataprep.clean_df_stats(ddf, workstrokesonly=False, + ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=False, ignoreadvanced=True) try: @@ -2812,14 +2273,14 @@ def history_view_data(request, userid=0): except (KeyError, ValueError, AttributeError): # pragma: no cover ddict['hrmean'] = 0 try: - ddict['hrmax'] = ddf['hr'].max().astype(int) + ddict['hrmax'] = int(ddf['hr'].max()) except (KeyError, ValueError, AttributeError): # pragma: no cover ddict['hrmax'] = 0 ddict['powermean'] = int(wavg(ddf, 'power', 'deltat')) try: - ddict['powermax'] = ddf['power'].max().astype(int) - except KeyError: # pragma: no cover + ddict['powermax'] = int(ddf['power'].max()) + except (KeyError, ColumnNotFoundError): # pragma: no cover ddict['powermax'] = 0 ddict['nrworkouts'] = a_workouts.count() listofdicts.append(ddict) @@ -2833,14 +2294,14 @@ def history_view_data(request, userid=0): totalsdict['distance'] = totalmeters try: totalsdict['powermean'] = int(wavg(df, 'power', 'deltat')) - totalsdict['powermax'] = df['power'].max().astype(int) - except KeyError: # pragma: no cover + totalsdict['powermax'] = int(df['power'].max()) + except (KeyError, ColumnNotFoundError): # pragma: no cover totalsdict['powermean'] = 0 totalsdict['powermax'] = 0 try: totalsdict['hrmean'] = int(wavg(df, 'hr', 'deltat')) - totalsdict['hrmax'] = df['hr'].max().astype(int) - except KeyError: # pragma: no cover + totalsdict['hrmax'] = int(df['hr'].max()) + except (KeyError, ColumnNotFoundError): # pragma: no cover totalsdict['hrmean'] = 0 totalsdict['hrmax'] = 0 @@ -2859,14 +2320,22 @@ def history_view_data(request, userid=0): a_workouts = g_workouts.filter(workouttype=typeselect) meters, hours, minutes, seconds = get_totals(a_workouts) totalseconds = 3600 * hours + 60 * minutes + seconds - ddf = getsmallrowdata_db(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.read_data(columns, ids=[w.id for w in a_workouts]) + ddf = dataprep.remove_nulls_pl(ddf) + if ddf.is_empty(): + totalscript = "" + totaldiv = "No data" try: - ddf['deltat'] = ddf['time'].diff().clip(lower=0) + ddf = ddf.with_columns(pl.col("time").diff().clip(lower_bound=0).alias("deltat")) except KeyError: pass + except ColumnNotFoundError: + totalscript = "" + totaldiv = "No data" + - ddf = dataprep.clean_df_stats(ddf, workstrokesonly=True, + ddf = dataprep.clean_df_stats_pl(ddf, workstrokesonly=True, ignoreadvanced=True) totalscript, totaldiv = interactive_hr_piechart( diff --git a/rowers/views/apiviews.py b/rowers/views/apiviews.py index 9ee2464b..4795800f 100644 --- a/rowers/views/apiviews.py +++ b/rowers/views/apiviews.py @@ -69,102 +69,102 @@ def strokedataform(request, id=0): def api_get_dataframe(startdatetime, df): try: time = df['time']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: time = df['t']/10. - except KeyError: - return 400, "Missing time", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing time", pl.DataFrame() try: spm = df['spm'] - except KeyError: # pragma: no cover - return 400, "Missing spm", pd.DataFrame() + except (KeyError, ColumnNotFoundError): # pragma: no cover + return 400, "Missing spm", pl.DataFrame() try: distance = df['distance'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: distance = df['d']/10. - except KeyError: - return 400, "Missing distance", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing distance", pl.DataFrame() try: pace = df['pace']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: pace = df['p']/10. - except KeyError: - return 400, "Missing pace", pd.DataFrame() + except (KeyError, ColumnNotFoundError): + return 400, "Missing pace", pl.DataFrame() try: power = df['power'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover power = 0*time try: drivelength = df['drivelength'] - except KeyError: + except (KeyError, ColumnNotFoundError): drivelength = 0*time try: dragfactor = df['dragfactor'] - except KeyError: + except (KeyError, ColumnNotFoundError): dragfactor = 0*time try: drivetime = df['drivetime'] - except KeyError: + except (KeyError, ColumnNotFoundError): drivetime = 0*time try: strokerecoverytime = df['strokerecoverytime'] - except KeyError: + except (KeyError, ColumnNotFoundError): strokerecoverytime = 0*time try: averagedriveforce = df['averagedriveforce'] - except KeyError: + except (KeyError, ColumnNotFoundError): averagedriveforce = 0*time try: peakdriveforce = df['peakdriveforce'] - except KeyError: + except (KeyError, ColumnNotFoundError): peakdriveforce = 0*time try: wash = df['wash'] - except KeyError: + except (KeyError, ColumnNotFoundError): wash = 0*time try: catch = df['catch'] - except KeyError: + except (KeyError, ColumnNotFoundError): catch = 0*time try: finish = df['finish'] - except KeyError: + except (KeyError, ColumnNotFoundError): finish = 0*time try: peakforceangle = df['peakforceangle'] - except KeyError: + except (KeyError, ColumnNotFoundError): peakforceangle = 0*time try: driveenergy = df['driveenergy'] - except KeyError: + except (KeyError, ColumnNotFoundError): driveenergy = 60.*power/spm try: slip = df['slip'] - except KeyError: + except (KeyError, ColumnNotFoundError): slip = 0*time try: lapidx = df['lapidx'] - except KeyError: + except (KeyError, ColumnNotFoundError): lapidx = 0*time try: hr = df['hr'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover hr = 0*df['time'] try: latitude = df['latitude'] - except KeyError: + except (KeyError, ColumnNotFoundError): latitude = 0*df['time'] try: longitude = df['longitude'] - except KeyError: + except (KeyError, ColumnNotFoundError): longitude = 0*df['time'] starttime = totimestamp(startdatetime)+time[0] @@ -172,7 +172,7 @@ def api_get_dataframe(startdatetime, df): dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)") - data = pd.DataFrame({'TimeStamp (sec)': unixtime, + data = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': distance, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -526,7 +526,7 @@ def strokedatajson_v3(request): title = request.data.get('name','') try: elapsedTime = request.data['elapsedTime'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: duration = request.data['duration'] try: @@ -538,7 +538,7 @@ def strokedatajson_v3(request): return HttpResponse("Missing Elapsed Time", status=400) try: totalDistance = request.data['distance'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("Missing Total Distance", status=400) timeZone = request.data.get('timezone','UTC') workouttype = request.data.get('workouttype','rower') @@ -556,23 +556,21 @@ def strokedatajson_v3(request): dologging('apilog.log',totalDistance) dologging('apilog.log',elapsedTime) - df = pd.DataFrame() + df = pl.DataFrame() try: strokes = request.data['strokes'] - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("No Stroke Data in JSON", status=400) try: - df = pd.DataFrame(strokes['data']) - except KeyError: # pragma: no cover + df = pl.DataFrame(strokes['data']) + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - df = pd.DataFrame(request.data['strokedata']) + df = pl.DataFrame(request.data['strokedata']) except: return HttpResponse("No JSON Object could be decoded", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) - + df = df.sort("time") status, comment, data = api_get_dataframe(startdatetime, df) if status != 200: # pragma: no cover @@ -580,7 +578,8 @@ def strokedatajson_v3(request): csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16]) - _ = data.to_csv(csvfilename, index_label='index', compression='gzip') + with gzip.open(csvfilename, 'w') as f: + _ = data.write_csv(f) duration = datetime.time(0,0,1) w = Workout( @@ -672,10 +671,11 @@ def strokedatajson_v2(request, id): if request.method == 'GET': columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + datadf = dataprep.read_data(columns, ids=[id]) + datadf = dataprep.remove_nulls_pl(datadf) dologging('apilog.log',request.user.username+"(strokedatajson_v2 GET)") - data = datadf.to_json(orient='records') + data = datadf.write_json(row_oriented=True) data2 = json.loads(data) data2 = {"data": data2} @@ -686,28 +686,27 @@ def strokedatajson_v2(request, id): try: for d in request.data['data']: dologging('apilog.log',json.dumps(d)) - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover try: for d in request.data['strokedata']: dologging('apilog.log',json.dumps(d)) - except KeyError: + except (KeyError, ColumnNotFoundError): dologging('apilog.log','No data in request.data') checkdata, r = dataprep.getrowdata_db(id=row.id) if not checkdata.empty: # pragma: no cover return HttpResponse("Duplicate Error", status=409) - df = pd.DataFrame() + df = pl.DataFrame() try: - df = pd.DataFrame(request.data['data']) - except KeyError: # pragma: no cover + df = pl.DataFrame(request.data['data']) + except (KeyError, ColumnNotFoundError): # pragma: no cover try: - df = pd.DataFrame(request.data['strokedata']) + df = pl.DataFrame(request.data['strokedata']) except: return HttpResponse("No JSON object could be decoded", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) + df = df.sort("time") status, comment, data = api_get_dataframe(row.startdatetime, df) if status != 200: # pragma: no cover @@ -729,7 +728,8 @@ def strokedatajson_v2(request, id): row.duplicate = True row.save() - _ = data.to_csv(csvfilename+'.gz', index_label='index', compression='gzip') + with gzip.open(csvfilename+'.gz', 'w') as f: + _ = data.write_csv(f) row.csvfilename = csvfilename row.save() @@ -748,7 +748,7 @@ def strokedatajson_v2(request, id): powerperc=powerperc, powerzones=r.powerzones) rowdata = rdata(csvfile=row.csvfilename, rower=rr).df - datadf = dataprep.dataprep( + datadf = dataprep.dataplep( rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True) _ = myqueue(queuehigh, handle_calctrimp, row.id, @@ -802,7 +802,11 @@ def strokedatajson(request, id=0): if request.method == 'GET': # currently only returns a subset. columns = ['spm', 'time', 'hr', 'pace', 'power', 'distance'] - datadf = dataprep.getsmallrowdata_db(columns, ids=[id]) + + datadf = dataprep.read_data(columns, ids=[id]) + datadf = dataprep.remove_nulls_pl(datadf) + datadf = datadf.to_pandas() + dologging("apilog.log",request.user.username+"(strokedatajson GET) ") return JSONResponse(datadf) @@ -823,14 +827,13 @@ def strokedatajson(request, id=0): return HttpResponse("No JSON object could be decoded", status=400) try: - df = pd.DataFrame(strokedata) + df = pl.DataFrame(strokedata) except ValueError: # pragma: no cover return HttpResponse("Arrays must all be same length", status=400) - df.index = df.index.astype(int) - df.sort_index(inplace=True) + df = df.sort("time") try: time = df['time']/1.e3 - except KeyError: # pragma: no cover + except (KeyError, ColumnNotFoundError): # pragma: no cover return HttpResponse("There must be time values", status=400) aantal = len(time) pace = df['pace']/1.e3 @@ -869,7 +872,7 @@ def strokedatajson(request, id=0): dologging("apilog.log",request.user.username+"(POST)") - data = pd.DataFrame({'TimeStamp (sec)': unixtime, + data = pl.DataFrame({'TimeStamp (sec)': unixtime, ' Horizontal (meters)': distance, ' Cadence (stokes/min)': spm, ' HRCur (bpm)': hr, @@ -896,8 +899,8 @@ def strokedatajson(request, id=0): timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S") csvfilename = 'media/Import_'+timestr+'.csv' - res = data.to_csv(csvfilename+'.gz', index_label='index', - compression='gzip') + with gzip.open(csvfilename+'.gz','w') as f: + res = data.write_csv(f) row.csvfilename = csvfilename row.save() @@ -916,7 +919,7 @@ def strokedatajson(request, id=0): powerperc=powerperc, powerzones=r.powerzones) rowdata = rdata(csvfile=row.csvfilename, rower=rr).df - datadf = dataprep.dataprep( + datadf = dataprep.dataplep( rowdata, id=row.id, bands=True, barchart=True, otwpower=True, empower=True) # mangling diff --git a/rowers/views/exportviews.py b/rowers/views/exportviews.py index 15bf47f6..593fa23d 100644 --- a/rowers/views/exportviews.py +++ b/rowers/views/exportviews.py @@ -208,7 +208,7 @@ def workouts_summaries_email_view(request): ) df = dataprep.workout_summary_to_df( r, startdate=startdate, enddate=enddate) - df.to_csv(filename, encoding='utf-8') + df.write_csv(filename) _ = myqueue(queuehigh, handle_sendemailsummary, r.user.first_name, r.user.last_name, diff --git a/rowers/views/otherviews.py b/rowers/views/otherviews.py index a88d4070..0e9e286e 100644 --- a/rowers/views/otherviews.py +++ b/rowers/views/otherviews.py @@ -1,10 +1,33 @@ from rowers.views.statements import * +from rowers.interactiveplots import sleep from rq import Queue from redis import Redis from rq.job import Job +@login_required() +def sleep_view(request): + + script, div = sleep() + + return render(request, + "sleep.html", + { + "the_div": div, + "the_script": script, + }) + +def filmdeaths_view(request): + script, div = filmdeaths() + + return render(request, + "filmdeaths.html", + { + "the_div": div, + "the_script": script, + } + ) @login_required() def download_fit(request, filename=''): diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index e44609ac..eb0db639 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -3569,7 +3569,7 @@ def rower_trainingplan_execution_view(request, else: # pragma: no cover data, message = get_execution_report(r, startdate, enddate) - if not data.empty: + if not data.is_empty(): script, div = interactive_planchart(data, startdate, enddate) else: # pragma: no cover script = '' diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 80006ded..ac88517b 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -16,7 +16,7 @@ from rowers.utils import ( from rowers.celery import result as celery_result from rowers.interactiveplots import * from scipy.interpolate import griddata -from rowers.dataprep import getsmallrowdata_db +from rowers.dataprep import getsmallrowdata_pd, read_data from rowers.dataprep import timedeltaconv from scipy.special import lambertw from io import BytesIO @@ -35,6 +35,7 @@ import threading import redis import colorsys import re +import gzip import zipfile import bleach import arrow @@ -136,9 +137,9 @@ from rowers.forms import ( FitnessMetricForm, PredictedPieceFormNoDistance, EmailForm, RegistrationForm, RegistrationFormTermsOfService, RegistrationFormUniqueEmail, RegistrationFormSex, - CNsummaryForm, UpdateWindForm, + CNsummaryForm, StandardsForm, - UpdateStreamForm, WorkoutMultipleCompareForm, ChartParamChoiceForm, + WorkoutMultipleCompareForm, ChartParamChoiceForm, FusionMetricChoiceForm, BoxPlotChoiceForm, MultiFlexChoiceForm, TrendFlexModalForm, WorkoutSplitForm, WorkoutJoinParamForm, AnalysisOptionsForm, AnalysisChoiceForm, @@ -205,6 +206,7 @@ from rowers.rojabo_stuff import rojabo_open from rowers.integrations import * +from polars.exceptions import ColumnNotFoundError import rowers.ownapistuff as ownapistuff from rowers.ownapistuff import TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI @@ -251,7 +253,7 @@ from rowers.tasks import ( handle_sendemailfile, handle_sendemailkml, handle_sendemailnewresponse, handle_updatedps, - handle_updatecp, long_test_task, long_test_task2, + long_test_task, long_test_task2, handle_zip_file, handle_getagegrouprecords, handle_update_empower, handle_sendemailics, @@ -1344,9 +1346,9 @@ def trydf(df, aantal, column): # pragma: no cover s = df[column] if len(s) != aantal: return np.zeros(aantal) - if not np.issubdtype(s, np.number): + if not s.dtype in pl.NUMERIC_DTYPES: return np.zeros(aantal) - except KeyError: + except (KeyError, ColumnNotFoundError): s = np.zeros(aantal) return s diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index b22a7da4..e4b49aef 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -126,8 +126,7 @@ def workout_video_view_mini(request, id=''): hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -237,8 +236,7 @@ def workout_video_view(request, id=''): hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -349,12 +347,12 @@ def workout_video_create_view(request, id=0): # get data data, metrics, maxtime = dataprep.get_video_data( w, groups=metricsgroups, mode=mode) + hascoordinates = pd.Series(data['latitude']).std() > 0 # create map if hascoordinates and mode == 'water': - mapscript, mapdiv = leaflet_chart_video(data['latitude'], data['longitude'], - w.name) + mapscript, mapdiv = leaflet_chart(data['latitude'], data['longitude']) else: mapscript, mapdiv = interactive_chart_video(data) data['longitude'] = data['spm'] @@ -380,6 +378,7 @@ def workout_video_create_view(request, id=0): template = 'embedded_video.html' + return render(request, template, { @@ -429,12 +428,14 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly work_max = forceanalysis.work_max notes = forceanalysis.notes name = forceanalysis.name + plotcircles = forceanalysis.plotcircles + plotlines = forceanalysis.plotlines includereststrokes = forceanalysis.include_rest_strokes except (ForceCurveAnalysis.DoesNotExist, ValueError): pass else: dist_min = 0 - dist_max = 0 + dist_max = row.distance spm_min = 15 spm_max = 55 work_min = 0 @@ -442,6 +443,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly notes = '' includereststrokes = False name = '' + plotcircles = True + plotlines = False form = ForceCurveOptionsForm(initial={ 'spm_min': spm_min, @@ -451,10 +454,10 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly 'work_min': work_min, 'work_max': work_max, 'notes': notes, - 'plottype': 'line', 'name': name, + 'plotcircles': plotcircles, + 'plotlines': plotlines, }) - plottype = 'line' if request.method == 'POST': @@ -468,10 +471,11 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly work_max = form.cleaned_data['work_max'] notes = form.cleaned_data['notes'] name = form.cleaned_data['name'] + plotlines = form.cleaned_data['plotlines'] + plotcircles = form.cleaned_data['plotcircles'] if not name: name = row.name includereststrokes = form.cleaned_data['includereststrokes'] - plottype = form.cleaned_data['plottype'] workstrokesonly = not includereststrokes if "_save" in request.POST and "new" not in request.POST: # pragma: no cover @@ -488,6 +492,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly spm_min = spm_min, spm_max = spm_max, rower=row.user, + plotlines = plotlines, + plotcircles = plotcircles, include_rest_strokes = includereststrokes, ) else: @@ -501,6 +507,8 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly forceanalysis.work_max = work_max forceanalysis.spm_min = spm_min forceanalysis.spm_max = spm_max + forceanalysis.plotcircles = plotcircles + forceanalysis.plotlines = plotlines forceanalysis.include_rest_strokes = includereststrokes forceanalysis.save() dosave = True @@ -526,20 +534,10 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly else: # pragma: no cover workstrokesonly = True - plottype = 'line' - script, div, js_resources, css_resources = interactive_forcecurve( - [row], - workstrokesonly=workstrokesonly, - plottype=plottype, - dist_min = dist_min, - dist_max = dist_max, - spm_min = spm_min, - spm_max = spm_max, - work_min = work_min, - work_max = work_max, - notes=notes, + script, div = interactive_forcecurve( + [row] ) breadcrumbs = [ @@ -580,8 +578,6 @@ def workout_forcecurve_view(request, id=0, analysis=0, userid=0, workstrokesonly 'work_max': work_max, 'annotation': notes, 'the_div': div, - 'js_res': js_resources, - 'css_res': css_resources, 'id': id, 'mayedit': mayedit, 'teams': get_my_teams(request.user), @@ -633,50 +629,6 @@ def otw_use_gps(request, id=0): return HttpResponseRedirect(url) -# Show Stroke power histogram for a workout -@login_required() -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -def workout_histo_view(request, id=0): - w = get_workoutuser(id, request) - r = getrequestrower(request) - - mayedit = 0 - if w.user == r: - mayedit = 1 - - res = interactive_histoall([w], 'power', False) - script = res[0] - div = res[1] - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_histo_view', kwargs={'id': id}), - 'name': 'Histogram' - } - - ] - - return render(request, - 'histo_single.html', - {'interactiveplot': script, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'workout': w, - 'rower': r, - 'the_div': div, - 'id': id, - 'mayedit': mayedit, - 'teams': get_my_teams(request.user), - }) - # add a workout manually @login_required() @@ -1343,7 +1295,7 @@ def remove_power_view(request, id=0): row = rdata(csvfile=f, rower=rr) row.df[' Power (watts)'] = 0 row.write_csv(f) - _ = dataprep.dataprep(row.df, id=workout.id) + _ = dataprep.dataplep(row.df, id=workout.id) cpdf, delta, cpvalues = dataprep.setcp(workout) workout.normp = 0 @@ -1667,15 +1619,10 @@ def course_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { @@ -1849,15 +1796,10 @@ def virtualevent_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { @@ -2237,10 +2179,6 @@ def workouts_view(request, message='', successmessage='', for w in workoutsnohr: # pragma: no cover _ = dataprep.workout_trimp(w) - # ids = [w.id for w in workouts] - # df = dataprep.getsmallrowdata_db(['time','power'],ids=ids) - # polarization = dataprep.polarization_index(df,r) - if query: # pragma: no cover query_list = query.split() workouts = workouts.filter( @@ -2278,7 +2216,7 @@ def workouts_view(request, message='', successmessage='', if yaxis not in ['duration', 'trimp', 'rscore']: # pragma: no cover yaxis = 'duration' - script, div = interactive_activitychart(g_workouts, + script, div = interactive_activitychart2(g_workouts, g_startdate, g_enddate, stack=stack, @@ -2823,348 +2761,6 @@ def workout_downloadmetar_view(request, id=0, return response -# Show form to update wind data -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, - login_url="/rowers/paidplans", - message="This functionality requires a Pro plan or higher." - " If you are already a Pro user, please log in to access this functionality", - redirect_field_name=None) -def workout_wind_view(request, id=0, message="", successmessage=""): - row = get_workoutuser(id, request) - r = getrower(request.user) - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': row.name - }, - { - 'url': reverse('workout_wind_view', kwargs={'id': id}), - 'name': 'Wind' - } - - ] - - # get data - f1 = row.csvfilename - u = row.user.user - r = getrower(u) - - # create bearing - rowdata = rdata(csvfile=f1) - if row == 0: # pragma: no cover - return HttpResponse("Error: CSV Data File Not Found") - - hascoordinates = 1 - try: - latitude = rowdata.df.loc[:, ' latitude'] - except KeyError: - hascoordinates = 0 - - if hascoordinates and not latitude.std(): # pragma: no cover - hascoordinates = 0 - - try: - _ = rowdata.df.loc[:, 'bearing'].values - except KeyError: - rowdata.add_bearing() - rowdata.write_csv(f1, gzip=True) - - if hascoordinates: - avglat = rowdata.df[' latitude'].mean() - avglon = rowdata.df[' longitude'].mean() - airportcode, newlat, newlon, airportdistance = get_airport_code( - avglat, avglon) - airportcode = airportcode.upper() - airportdistance = airportdistance[0] - else: - airportcode = 'UNKNOWN' - airportdistance = 0 - - if request.method == 'POST': - # process form - form = UpdateWindForm(request.POST) - - if form.is_valid(): - - vwind1 = form.cleaned_data['vwind1'] - vwind2 = form.cleaned_data['vwind2'] - dist1 = form.cleaned_data['dist1'] - dist2 = form.cleaned_data['dist2'] - winddirection1 = form.cleaned_data['winddirection1'] - winddirection2 = form.cleaned_data['winddirection2'] - windunit = form.cleaned_data['windunit'] - - rowdata.update_wind(vwind1, vwind2, - winddirection1, - winddirection2, - dist1, dist2, - units=windunit) - - rowdata.write_csv(f1, gzip=True) - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id - } - url = reverse('workout_wind_view', kwargs=kwargs) - _ = HttpResponseRedirect(url) - - else: - form = UpdateWindForm() - - # create interactive plot - res = interactive_windchart(encoder.decode_hex(id), promember=1) - script = res[0] - div = res[1] - - if hascoordinates: - gmscript, gmdiv = leaflet_chart( - rowdata.df[' latitude'], - rowdata.df[' longitude'], - row.name) - else: - gmscript = "" - gmdiv = "No GPS data available" - - messages.info(request, successmessage) - messages.error(request, message) - - return render(request, - 'windedit.html', - {'workout': row, - 'rower': r, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'form': form, - 'airport': airportcode, - 'airportdistance': airportdistance, - 'the_div': div, - 'gmap': gmscript, - 'gmapdiv': gmdiv}) - - -# Show form to update River stream data (for river dwellers) -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, - login_url="/rowers/paidplans", - message="This functionality requires a Pro plan or higher." - " If you are already a Pro user, please log in to access this functionality", - redirect_field_name=None) -def workout_stream_view(request, id=0, message="", successmessage=""): - row = get_workoutuser(id, request) - r = getrower(request.user) - - # create interactive plot - f1 = row.csvfilename - u = row.user.user - r = getrower(u) - - rowdata = rdata(csvfile=f1) - if rowdata == 0: # pragma: no cover - messages.info(request, "Error: CSV data file not found") - url = reverse('workout_edit_view', kwargs={ - 'id': encoder.encode_hex(row.id)}) - return HttpResponseRedirect(url) - - if request.method == 'POST': - # process form - form = UpdateStreamForm(request.POST) - - if form.is_valid(): - - dist1 = form.cleaned_data['dist1'] - dist2 = form.cleaned_data['dist2'] - stream1 = form.cleaned_data['stream1'] - stream2 = form.cleaned_data['stream2'] - streamunit = form.cleaned_data['streamunit'] - - rowdata.update_stream(stream1, stream2, dist1, dist2, - units=streamunit) - - rowdata.write_csv(f1, gzip=True) - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id} - url = reverse('workout_wind_view', kwargs=kwargs) - _ = HttpResponseRedirect(url) - - else: - form = UpdateStreamForm() - - # create interactive plot - res = interactive_streamchart(encoder.decode_hex(id), promember=1) - script = res[0] - div = res[1] - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': row.name - }, - { - 'url': reverse('workout_stream_view', kwargs={'id': id}), - 'name': 'Stream' - } - - ] - - messages.info(request, successmessage) - messages.error(request, message) - return render(request, - 'streamedit.html', - {'workout': row, - 'rower': r, - 'breadcrumbs': breadcrumbs, - 'active': 'nav-workouts', - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'form': form, - 'the_div': div}) - -# Form to set average crew weight and boat type, then run power calcs - - -@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) -@user_passes_test(ispromember, login_url="/rowers/paidplans", redirect_field_name=None) -def workout_otwsetpower_view(request, id=0, message="", successmessage=""): - w = get_workoutuser(id, request) - r = getrower(request.user) - - mayedit = 1 - - if request.method == 'POST': - # process form - form = AdvancedWorkoutForm(request.POST) - - if form.is_valid(): - boattype = form.cleaned_data['boattype'] - weightvalue = form.cleaned_data['weightvalue'] - coastalbrand = form.cleaned_data['boatbrand'] - boatclass = w.workouttype - w.boattype = boattype - w.weightvalue = weightvalue - w.boatbrand = coastalbrand - w.save() - - # load row data & create power/wind/bearing columns if not set - f1 = w.csvfilename - rowdata = rdata(csvfile=f1) - if rowdata == 0: # pragma: no cover - return HttpResponse("Error: CSV Data File Not Found") - try: - _ = rowdata.df['vstream'] - except KeyError: - rowdata.add_stream(0) - rowdata.write_csv(f1, gzip=True) - - try: - _ = rowdata.df['bearing'] - except KeyError: - rowdata.add_bearing() - rowdata.write_csv(f1, gzip=True) - - try: - _ = rowdata.df['vwind'] - except KeyError: - rowdata.add_wind(0, 0) - rowdata.write_csv(f1, gzip=True) - - # do power calculation (asynchronous) - r = w.user - u = r.user - - first_name = u.first_name - last_name = u.last_name - emailaddress = u.email - - job = myqueue(queue, - handle_otwsetpower, f1, boattype, boatclass, coastalbrand, - weightvalue, - first_name, last_name, emailaddress, encoder.decode_hex( - id), - ps=[r.p0, r.p1, r.p2, r.p3], - ratio=r.cpratio, - # quick_calc = quick_calc, - # go_service=go_service, - emailbounced=r.emailbounced - ) - - try: - request.session['async_tasks'] += [(job.id, 'otwsetpower')] - except KeyError: - request.session['async_tasks'] = [(job.id, 'otwsetpower')] - - successmessage = 'Your calculations have been submitted." \ - " You will receive an email when they are done." \ - " You can check the status of your calculations" \ - " here' - messages.info(request, successmessage) - kwargs = { - 'id': id} - - try: - url = request.session['referer'] - except KeyError: - url = reverse('workout_edit_view', kwargs=kwargs) - - response = HttpResponseRedirect(url) - return response - - else: # pragma: no cover - message = "Invalid Form" - messages.error(request, message) - kwargs = { - 'id': id} - url = reverse('workout_otwsetpower_view', kwargs=kwargs) - response = HttpResponseRedirect(url) - - else: - form = AdvancedWorkoutForm(instance=w) - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_otwsetpower_view', kwargs={'id': id}), - 'name': 'OTW Power' - } - - ] - - messages.error(request, message) - messages.info(request, successmessage) - return render(request, - 'otwsetpower.html', - {'workout': w, - 'rower': w, - 'mayedit': mayedit, - 'active': 'nav-workouts', - 'breadcrumbs': breadcrumbs, - 'teams': get_my_teams(request.user), - 'form': form, - }) @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) @@ -3598,7 +3194,7 @@ def workout_erase_column_view(request, id=0, column=''): return HttpResponseRedirect(url) try: - data = dataprep.getsmallrowdata_db([column], ids=[w.id]) + data = dataprep.read_data([column], ids=[w.id]) except: # pragma: no cover messages.error(request, 'Invalid column') url = reverse('workout_data_view', kwargs={ @@ -3654,12 +3250,18 @@ def workout_erase_column_view(request, id=0, column=''): row, workout = dataprep.getrowdata(id=w.id) row.df[columnl] = defaultvalue - os.remove(w.csvfilename+'.gz') + try: + os.remove(w.csvfilename+'.gz') + except FileNotFoundError: + try: + os.remove(w.csvfilename) + except FileNotFoundError: + pass row.write_csv(w.csvfilename, gzip=True) row, workout = dataprep.getrowdata(id=w.id) - _ = dataprep.dataprep(row.df, id=w.id) + _ = dataprep.dataplep(row.df, id=w.id) if column == 'hr': w.hrtss = 0 @@ -4141,7 +3743,7 @@ def workout_workflow_view(request, id): if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row): rowdata = rdata(csvfile=row.csvfilename) - mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'], + mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'], rowdata.df[' longitude'], row.name) else: @@ -4370,7 +3972,7 @@ def workout_flexchart3_view(request, *args, **kwargs): # create interactive plot ( - script, div, js_resources, css_resources, workstrokesonly + script, div, workstrokesonly ) = interactive_flex_chart2( encoder.decode_hex(id), request.user.rower, xparam=xparam, yparam1=yparam1, @@ -4456,8 +4058,6 @@ def workout_flexchart3_view(request, *args, **kwargs): 'workout': row, 'chartform': flexaxesform, 'optionsform': flexoptionsform, - 'js_res': js_resources, - 'css_res': css_resources, 'teams': get_my_teams(request.user), 'id': id, 'xparam': xparam, @@ -4505,7 +4105,7 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): yparam4 = cd['yaxis4'] ( - script, div, js_resources, css_resources, comment + script, div ) = interactive_flexchart_stacked( encoder.decode_hex(id), r, xparam=xparam, yparam1=yparam1, @@ -4515,9 +4115,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): mode=workout.workouttype, ) - if comment is not None: # pragma: no cover - messages.error(request, comment) - initial = { 'xaxis': xparam, 'yaxis1': yparam1, @@ -4554,8 +4151,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): 'active': 'nav-workouts', 'workout': workout, 'chartform': flexaxesform, - 'js_res': js_resources, - 'css_res': css_resources, 'id': id, 'xparam': xparam, 'yparam1': yparam1, @@ -4568,57 +4163,6 @@ def workout_flexchart_stacked_view(request, *args, **kwargs): # The interactive plot with wind corrected pace for OTW outings -def workout_otwpowerplot_view(request, id=0, message="", successmessage=""): - w = get_workout(id) - r = getrower(request.user) - - breadcrumbs = [ - { - 'url': '/rowers/list-workouts/', - 'name': 'Workouts' - }, - { - 'url': get_workout_default_page(request, id), - 'name': w.name - }, - { - 'url': reverse('workout_otwpowerplot_view', kwargs={'id': id}), - 'name': 'Interactive OTW Power Plot' - } - - ] - - # check if user is owner of this workout - - # create interactive plot - - promember = 0 - mayedit = 0 - result = request.user.is_authenticated and ispromember(request.user) - if result: - promember = 1 - if request.user == w.user.user: - mayedit = 1 - - # create interactive plot - res = interactive_otw_advanced_pace_chart( - encoder.decode_hex(id), promember=promember) - script = res[0] - div = res[1] - - messages.error(request, message) - messages.info(request, successmessage) - - return render(request, - 'otwinteractive.html', - {'workout': w, - 'rower': r, - 'active': 'nav-workouts', - 'breadcrumbs': breadcrumbs, - 'teams': get_my_teams(request.user), - 'interactiveplot': script, - 'the_div': div, - 'mayedit': mayedit}) # @@ -4780,7 +4324,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""): row = get_workoutuser(id, request) if request.user.rower.rowerplan == 'basic' and 'speedcoach2' in row.workoutsource: # pragma: no cover - data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) + data = read_data(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') @@ -4793,7 +4337,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""): pass if request.user.rower.rowerplan == 'basic' and 'nklinklogbook' in row.workoutsource: # pragma: no cover - data = getsmallrowdata_db(['wash'], ids=[encoder.decode_hex(id)]) + data = read_data(['wash'], ids=[encoder.decode_hex(id)]) try: if data['wash'].std() != 0: url = reverse('paidplans_view') @@ -5063,7 +4607,7 @@ def workout_map_view(request, id=0): hascoordinates = 0 if hascoordinates: - mapscript, mapdiv = leaflet_chart2(rowdata.df[' latitude'], + mapscript, mapdiv = leaflet_chart(rowdata.df[' latitude'], rowdata.df[' longitude'], w.name) else: @@ -5188,7 +4732,7 @@ def workout_uploadimage_view(request, id): # pragma: no cover # Generic chart creation @permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) def workout_add_chart_view(request, id, plotnr=1): - + w = get_workoutuser(id, request) r = getrower(request.user) diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index 92f7b5b4..1f1ae122 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -599,6 +599,13 @@ except KeyError: # pragma: no cover WORKOUTS_FIT_TOKEN = 'aapnootmies' WORKOUTS_FIT_URL = 'http://localhost:50053/tojson' +try: + ROWSANDALL_CHARTS_TOKEN = CFG['rowsandall_charts_token'] + ROWSANDALL_CHARTS_URL = CFG['rowsandall_charts_url'] +except KeyError: + ROWSANDALL_CHARTS_TOKEN = 'aapnootmies' + ROWSANDALL_CHARTS_URL = 'http://localhost:3000' + # Recaptcha try: diff --git a/static/css/rowsandall2.css b/static/css/rowsandall2.css index 11026c9f..c52e318e 100644 --- a/static/css/rowsandall2.css +++ b/static/css/rowsandall2.css @@ -32,6 +32,16 @@ color: red; } +.chart { + background-image: url(/static/img/logo7.png); + background-size: 200px; + background-repeat: no-repeat; + background-position: top 50px right 100px; +} + +.chartlabel { + font-size: 1.2em; +} .watermark { position: absolute;