from rowers.metrics import axes, calc_trimp, rowingmetrics, dtypes, metricsgroups from rowers.utils import lbstoN, myqueue, wavg, dologging from rowers.mytypes import otwtypes, otetypes, rowtypes import glob import rowingdata.tcxtools as tcxtools from rowers.utils import totaltime_sec_to_string from rowers.datautils import p0 from scipy import optimize from scipy.signal import find_peaks 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 * from rowers import mytypes from rowsandall_app.settings import SITE_URL import django_rq from timezonefinder import TimezoneFinder import rowers.datautils as datautils import rowers.utils as utils import sys import sqlalchemy as sa from sqlalchemy import create_engine from django.conf import settings import math from fitparse.base import FitHeaderError 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 from rowers.models import strokedatafields import grpc import grpc.experimental import rowers.rowing_workout_metrics_pb2 as metrics_pb2 import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc import traceback from rowingdata import ( KinoMapParser, ExcelTemplate, TCXParser, MysteryParser, RowProParser, RitmoTimeParser, QuiskeParser, RowPerfectParser, CoxMateParser, BoatCoachParser, BoatCoachOTWParser, BoatCoachAdvancedParser, painsledDesktopParser, speedcoachParser, SpeedCoach2Parser, ErgStickParser, FITParser, ErgDataParser, HumonParser, ETHParser, NKLiNKLogbookParser, HeroParser, SmartRowParser,) # All the data preparation, data cleaning and data mangling should # be defined here from rowers.models import ( Workout, Team, CalcAgePerformance, C2WorldClassAgePerformance, User ) 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 import yaml import shutil from shutil import copyfile from rowingdata import ( get_file_type, get_empower_rigging, get_empower_firmware ) from rowers.dataroutines import * from rowers.tasks import ( handle_sendemail_newftp, handle_sendemail_unrecognized, handle_getagegrouprecords, handle_update_wps, handle_request_post, handle_calctrimp, handle_sendemail_breakthrough, handle_sendemail_hard, ) from rowers.tasks import handle_zip_file from pandas import DataFrame, Series import dask.dataframe as dd from dask.delayed import delayed import pyarrow.parquet as pq import pyarrow as pa from pyarrow.lib import ArrowInvalid from django.utils import timezone as tz from django.utils.timezone import get_current_timezone from django.urls import reverse import requests from django.core.exceptions import ValidationError from time import strftime import arrow from rq.job import Job from rq.registry import StartedJobRegistry from redis import Redis from rq import Queue from rowers.datautils import rpetotss def rscore_approx(row): if isinstance(row, pl.DataFrame): row = { 'rscore': row['rscore'][0], 'hrtss': row['hrtss'][0], 'id': row['id'][0], 'rpe': row['rpe'][0], 'duration': row['duration'][0], } if isinstance(row, pl.Series): row = { 'rscore': row['rscore'][0], 'hrtss': row['hrtss'][0], 'id': row['id'][0], 'rpe': row['rpe'][0], 'duration': row['duration'][0], } if row['rscore'] > 0: return row['rscore'] if row['rscore'] == 0: # pragma: no cover if row['hrtss'] > 0: return row['hrtss'] else: # pragma: no cover dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = dd/3600 try: return int(rpetotss[row['rpe']]*dd) except KeyError: minutes = row['duration'].hour*60+row['duration'].minute return minutes if row['rscore'] < 0: w = Workout.objects.get(id=row['id']) trimp, hrtss = workout_trimp(w) rscore, normp = workout_rscore(w) if row['rpe'] and row['rpe'] > 0: # pragma: no cover dd = 3600*row['duration'].hour+60*row['duration'].minute+row['duration'].second dd = dd/3600 return int(rpetotss[row['rpe']]*dd) else: # pragma: no cover minutes = row['duration'].hour*60+row['duration'].minute return minutes return 0 # pragma: no cover thetimezone = get_current_timezone() allowedcolumns = [key for key, value in strokedatafields.items()] queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('default') def fetchcperg(rower, theworkouts): thefilenames = [w.csvfilename for w in theworkouts] cpdf = getcpdata_sql(rower.id, table='ergcpdata') _ = myqueue( queuelow, handle_updateergcp, rower.id, thefilenames) return cpdf def workout_goldmedalstandard(workout, reset=False): if workout.goldmedalstandard > 0 and not reset: return workout.goldmedalstandard, workout.goldmedalseconds if workout.workouttype in rowtypes: goldmedalstandard, goldmedalseconds = calculate_goldmedalstandard( workout.user, workout) if workout.workouttype in otwtypes: try: factor = 100./(100.-workout.user.otwslack) except ZeroDivisionError: # pragma: no cover factor = 1. workout.user.otwslack = 0 workout.user.save() goldmedalstandard = goldmedalstandard*factor workout.goldmedalstandard = goldmedalstandard workout.goldmedalseconds = goldmedalseconds workout.save() return goldmedalstandard, goldmedalseconds return 0, 0 # pragma: no cover def check_marker(workout): r = workout.user if workout.workoutsource == 'strava': return None gmstandard, gmseconds = workout_goldmedalstandard(workout) if gmseconds < 60: return None dd = arrow.get(workout.date).datetime-datetime.timedelta(days=r.kfit) ws = Workout.objects.filter(date__gte=dd, date__lte=workout.date, user=r, duplicate=False, workouttype__in=mytypes.rowtypes, ).order_by("date") ids = [] gms = [] for w in ws: gmstandard, gmseconds = workout_goldmedalstandard(w) if gmseconds > 60: ids.append(w.id) gms.append(gmstandard) df = pl.DataFrame({ 'id': ids, 'gms': gms, }) if df.is_empty(): # pragma: no cover workout.ranking = True workout.save() return workout theid = df.filter(pl.col("gms") == pl.col("gms").max())['id'][0] wmax = Workout.objects.get(id=theid) # gms_max = wmax.goldmedalstandard # check if equal, bigger, or smaller than previous if not wmax.rankingpiece: rankingworkouts = ws.filter(rankingpiece=True) if len(rankingworkouts) == 0: wmax.rankingpiece = True wmax.save() return wmax lastranking = rankingworkouts[len(rankingworkouts)-1] if lastranking.goldmedalstandard+0.2 < wmax.goldmedalstandard: # pragma: no cover wmax.rankingpiece = True wmax.save() return wmax else: # pragma: no cover return wmax return None def workout_summary_to_df( rower, startdate=datetime.datetime(1970, 1, 1), enddate=tz.now()+tz.timedelta(days=1)): ws = Workout.objects.filter( user=rower, date__gte=startdate, date__lte=enddate, duplicate=False ).order_by("startdatetime") types = [] names = [] ids = [] startdatetimes = [] timezones = [] distances = [] durations = [] weightcategories = [] adaptivetypes = [] weightvalues = [] notes = [] tcx_links = [] csv_links = [] workout_links = [] goldstandards = [] goldstandarddurations = [] rscores = [] hrtss = [] trimps = [] rankingpieces = [] boattypes = [] counter1 = 0 counter2 = len(ws) for w in ws: counter1 += 1 if counter1 % 10 == 0: # pragma: no cover print(counter1, '/', counter2) types.append(w.workouttype) names.append(w.name) ids.append(encoder.encode_hex(w.id)) startdatetimes.append(w.startdatetime) timezones.append(w.timezone) distances.append(w.distance) durations.append(w.duration) weightcategories.append(w.weightcategory) adaptivetypes.append(w.adaptiveclass) weightvalues.append(w.weightvalue) boattypes.append(w.boattype) notes.append(w.notes) tcx_link = SITE_URL+'/rowers/workout/{id}/emailtcx'.format( id=encoder.encode_hex(w.id) ) tcx_links.append(tcx_link) csv_link = SITE_URL+'/rowers/workout/{id}/emailcsv'.format( id=encoder.encode_hex(w.id) ) csv_links.append(csv_link) workout_link = SITE_URL+'/rowers/workout/{id}/'.format( id=encoder.encode_hex(w.id) ) workout_links.append(workout_link) trimps.append(workout_trimp(w)[0]) rscore = workout_rscore(w) rscores.append(int(rscore[0])) hrtss.append(int(w.hrtss)) goldstandard, goldstandardduration = workout_goldmedalstandard(w) goldstandards.append(int(goldstandard)) goldstandarddurations.append(int(goldstandardduration)) rankingpieces.append(w.rankingpiece) df = pl.DataFrame({ 'ID': ids, 'date': startdatetimes, 'name': names, 'link': workout_links, 'timezone': timezones, 'type': types, 'boat type': boattypes, 'distance (m)': distances, 'duration ': durations, 'ranking piece': rankingpieces, 'weight category': weightcategories, 'adaptive classification': adaptivetypes, 'weight (kg)': weightvalues, 'Stroke Data TCX': tcx_links, 'Stroke Data CSV': csv_links, 'TRIMP Training Load': trimps, 'TSS Training Load': rscores, 'hrTSS Training Load': hrtss, 'GS': goldstandards, 'GS_secs': goldstandarddurations, 'notes': notes, }) return df def resample(id, r, parent, overwrite=False): data, row = getrowdata_db(id=id) rowdata = rrdata(csvfile=parent.csvfilename).df # drop all columns except ' latitude' and ' longitude' and 'TimeStamp (sec)' from rowdata allowedcolumns = [' latitude', ' longitude', 'TimeStamp (sec)'] rowdata = rowdata.filter(allowedcolumns) rowdata.rename(columns={'TimeStamp (sec)': 'time'}, inplace=True) rowdata['time'] = (rowdata['time']-rowdata.loc[0,'time'])*1000. rowdata.set_index('time', inplace=True) data.set_index('time', inplace=True) rowdata_interpolated = rowdata.reindex(data.index.union(rowdata.index)).interpolate('index') data = data.merge(rowdata_interpolated, left_index=True, right_index=True, how='left') data = data.reset_index() messages = [] # resample startdatetime = row.startdatetime data['datetime'] = data['time'].apply( lambda x: startdatetime+datetime.timedelta(seconds=x/1000.)) try: data = data.resample('S', on='datetime').mean() except TypeError: data1 = data._get_numeric_data() data1['datetime'] = data['datetime'] data = data1.resample('S', on='datetime').mean() data.interpolate(method='linear', inplace=True) data.reset_index(drop=True, inplace=True) # data.drop('datetime',inplace=True) data['pace'] = data['pace'] / 1000. data['time'] = data['time'] / 1000. if overwrite == True: # remove CP data try: cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=parent.id) os.remove(cpfile) except FileNotFoundError: pass # save data.rename(columns=columndict, inplace=True) starttimeunix = arrow.get(startdatetime).timestamp() data[' ElapsedTime (sec)'] = data['TimeStamp (sec)'] data['TimeStamp (sec)'] = data['TimeStamp (sec)'] + starttimeunix row = rrdata(df=data) row.write_csv(parent.csvfilename, gzip=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) _ = update_wps(r, mytypes.otwtypes) _ = update_wps(r, mytypes.otetypes) tss, normp = workout_rscore(parent) goldmedalstandard, goldmedalseconds = workout_goldmedalstandard(parent) else: id, message = new_workout_from_df(r, data, title=parent.name + '(Resampled)', parent=parent, forceunit='N') messages.append(message) return data, id, messages def calculate_goldmedalstandard(rower, workout, recurrance=True): cpfile = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) try: 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 if recurrance: df, delta, cpvalues = setcp(workout, background=background) if df.is_empty(): return 0, 0 else: return 0,0 if df.is_empty() and recurrance: # pragma: no cover df, delta, cpvalues = setcp(workout, recurrance=False, background=True) if df.is_empty(): return 0, 0 age = calculate_age(rower.birthdate, today=workout.date) agerecords = CalcAgePerformance.objects.filter( age=age, sex=rower.sex, weightcategory=rower.weightcategory ) wcdurations = [] wcpower = [] getrecords = False if not settings.TESTING: # pragma: no cover if len(agerecords) == 0: # pragma: no cover getrecords = True for record in agerecords: # pragma: no cover if record.power > 0: wcdurations.append(record.duration) wcpower.append(record.power) else: getrecords = True if getrecords: # pragma: no cover durations = [1, 4, 30, 60] distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195] df2 = pl.DataFrame( list( C2WorldClassAgePerformance.objects.filter( sex=rower.sex, weightcategory=rower.weightcategory ).values() ) ) jsondf = df2.write_json() _ = myqueue(queuelow, handle_getagegrouprecords, jsondf, distances, durations, age, rower.sex, rower.weightcategory) wcpower = pd.Series(wcpower, dtype='float') wcdurations = pd.Series(wcdurations, dtype='float') 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 if len(wcdurations) >= 4: # pragma: no cover p1wc, success = optimize.leastsq( errfunc, p0[:], args=(wcdurations, wcpower)) else: factor = fitfunc(p0, wcdurations.mean()/wcpower.mean()) p1wc = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]] return 0, 0 times = df['delta'] powers = df['cp'] wcpowers = fitfunc(p1wc, times) scores = 100.*powers/wcpowers try: 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, IndexError): # pragma: no cover indexmax = 0 delta = 0 maxvalue = 0 return maxvalue, delta def setcp(workout, background=False, recurrance=True): try: filename = 'media/cpdata_{id}.parquet.gz'.format(id=workout.id) df = pl.read_parquet(filename) if not df.is_empty(): # check dts tarr = datautils.getlogarr(4000) if df['delta'][0] in tarr: return(df, df['delta'], df['cp']) except Exception as e: try: os.remove(filename) except: pass strokesdf = read_data( ['power', 'workoutid', 'time'], ids=[workout.id]) strokesdf = remove_nulls_pl(strokesdf) 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. logarr = datautils.getlogarr(maxt) csvfilename = workout.csvfilename # check what the real file name is if os.path.exists(csvfilename): csvfile = csvfilename elif os.path.exists(csvfilename+'.csv'): # pragma: no cover csvfile = csvfilename+'.csv' elif os.path.exists(csvfilename+'.gz'): # pragma: no cover csvfile = csvfilename+'.gz' else: # pragma: no cover return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) csvfile = os.path.abspath(csvfile) with grpc.insecure_channel( target='localhost:50052', options=[('grpc.lb_policy_name', 'pick_first'), ('grpc.enable_retries', 0), ('grpc.keepalive_timeout_ms', 10000)] ) as channel: try: grpc.channel_ready_future(channel).result(timeout=10) except grpc.FutureTimeoutError: # pragma: no cover dologging('metrics.log','grpc channel time out in setcp') 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) try: response = stub.GetCP(req, timeout=60) except Exception as e: dologging('metrics.log', traceback.format_exc()) return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) delta = pl.Series(np.array(response.delta)) cpvalues = pl.Series(np.array(response.power)) powermean = response.avgpower try: df = pl.DataFrame({ 'delta': delta, 'cp': cpvalues, 'id': workout.id, }) df = df.drop_nulls() df.write_parquet(filename,compression='gzip') except Exception as e: dologging("metrics.log", "setcp: "+ str(e)) return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) #df.to_parquet(filename, engine='fastparquet', compression='GZIP') if recurrance: goldmedalstandard, goldmedalduration = calculate_goldmedalstandard( workout.user, workout, recurrance=False) workout.goldmedalstandard = goldmedalstandard workout.goldmedalduration = goldmedalduration workout.save() return df, delta, cpvalues def update_wps(r, types, mode='water', asynchron=True): firstdate = tz.now()-datetime.timedelta(days=r.cprange) workouts = Workout.objects.filter( date__gte=firstdate, workouttype__in=types, user=r ) ids = [w.id for w in workouts] if asynchron: _ = myqueue( queue, handle_update_wps, r.id, types, ids, mode ) df = read_data(['time', 'driveenergy'], ids=ids) try: wps_median = int(df.filter(pl.col("driveenergy")>100)["driveenergy"].median()) if mode == 'water': r.median_wps = wps_median else: # pragma: no cover r.median_wps_erg = wps_median r.save() except ValueError: # pragma: no cover pass except OverflowError: pass except TypeError: pass except ColumnNotFoundError: pass return True def join_workouts(r, ids, title='Joined Workout', parent=None, setprivate=False, forceunit='lbs', killparents=False): message = None summary = '' if parent: # pragma: no cover oarlength = parent.oarlength inboard = parent.inboard workouttype = parent.workouttype notes = parent.notes summary = parent.summary if parent.privacy == 'hidden': makeprivate = True else: makeprivate = False startdatetime = parent.startdatetime else: oarlength = 2.89 inboard = 0.88 workouttype = 'rower' notes = '' summary = '' makeprivate = False startdatetime = tz.now() if setprivate is True and makeprivate is False: # pragma: no cover makeprivate = True elif setprivate is False and makeprivate is True: # pragma: no cover makeprivate = False # reorder in chronological order ws = Workout.objects.filter(id__in=ids).order_by("startdatetime") if not parent: parent = ws[0] oarlength = parent.oarlength inboard = parent.inboard workouttype = parent.workouttype notes = parent.notes summary = parent.summary if parent.privacy == 'hidden': makeprivate = True else: makeprivate = False startdatetime = parent.startdatetime files = [w.csvfilename for w in ws] row = rdata(files[0]) files = files[1:] while len(files): row2 = rdata(files[0]) if row2 != 0 and row2 is not None: row = row+row2 files = files[1:] timestr = strftime("%Y%m%d-%H%M%S") csvfilename = 'media/df_' + timestr + '.csv' row.write_csv(csvfilename, gzip=True) id, message = save_workout_database(csvfilename, r, workouttype=workouttype, title=title, notes=notes, oarlength=oarlength, inboard=inboard, startdatetime=startdatetime, makeprivate=makeprivate, summary=summary, dosmooth=False, consistencychecks=False) if killparents: # pragma: no cover for w in ws: w.delete() w = Workout.objects.get(id=id) w.duplicate = False w.save() if message is not None and "duplicate" in message: message = "" return (id, message) def fetchcp_new(rower, workouts): data = [] for workout in workouts: 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 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 = pl.concat(data) try: #df = df.group_by(pl.col("delta")).agg(pl.max("cp"), pl.max("workout"), pl.max("url")).sort("delta") df = df.groupby('delta').agg([ pl.all().sort_by('cp').last(), ]) 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 update_rolling_cp(r, types, mode='water', dosend=False): firstdate = tz.now()-datetime.timedelta(days=r.cprange) workouts = Workout.objects.filter( date__gte=firstdate, workouttype__in=types, user=r ) delta, cp, avgpower, workoutnames, urls = fetchcp_new(r, workouts) powerdf = pl.DataFrame({ 'Delta': delta, 'CP': cp, }) 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 hourseconds = 3600. pwr = p1[0]/(1+hourseconds/p1[2]) pwr += p1[1]/(1+hourseconds/p1[3]) if len(powerdf) != 0: if mode == 'water': r.p0 = p1[0] r.p1 = p1[1] r.p2 = p1[2] r.p3 = p1[3] r.cpratio = res2[3] r.save() if dosend and pwr-5 > r.ftp*(100.-r.otwslack)/100. and r.getemailnotifications and not r.emailbounced: _ = myqueue(queuehigh, handle_sendemail_newftp,r,pwr,'water') else: r.ep0 = p1[0] r.ep1 = p1[1] r.ep2 = p1[2] r.ep3 = p1[3] r.ecpratio = res2[3] r.save() if dosend and pwr-5 > r.ftp and r.getemailnotifications and not r.emailbounced: _ = myqueue(queuehigh, handle_sendemail_newftp,r,pwr,'water') return True return False def initiate_cp(r): _ = update_rolling_cp(r, otwtypes, 'water') _ = update_rolling_cp(r, otetypes, 'erg') def split_workout(r, parent, splitsecond, splitmode): data, row = getrowdata_db(id=parent.id) latitude, longitude = get_latlon(parent.id) if not latitude.empty and not longitude.empty: data[' latitude'] = latitude data[' longitude'] = longitude data['time'] = data['time'] / 1000. data1 = data[data['time'] <= splitsecond].copy() data2 = data[data['time'] > splitsecond].copy() data1 = data1.sort_values(['time']) data1 = data1.interpolate(method='linear', axis=0, limit_direction='both', limit=10) data1.fillna(method='bfill', inplace=True) # Some new stuff to try out #data1 = data1.groupby('time', axis=0).mean() #data1['time'] = data1.index #data1.reset_index(drop=True, inplace=True) data2 = data2.sort_values(['time']) data2 = data2.interpolate(method='linear', axis=0, limit_direction='both', limit=10) data2.fillna(method='bfill', inplace=True) # Some new stuff to try out #data2 = data2.groupby('time', axis=0).mean() #data2['time'] = data2.index #data2.reset_index(drop=True, inplace=True) data1['pace'] = data1['pace'] / 1000. data2['pace'] = data2['pace'] / 1000. data1.drop_duplicates(subset='time', inplace=True) data2.drop_duplicates(subset='time', inplace=True) messages = [] ids = [] if 'keep first' in splitmode: if 'firstprivate' in splitmode: # pragma: no cover setprivate = True else: setprivate = False id, message = new_workout_from_df(r, data1, title=parent.name + ' (1)', parent=parent, setprivate=setprivate, forceunit='N') messages.append(message) ids.append(encoder.encode_hex(id)) if 'keep second' in splitmode: data2['cumdist'] = data2['cumdist'] - data2.iloc[ 0, data2.columns.get_loc('cumdist') ] data2['distance'] = data2['distance'] - data2.iloc[ 0, data2.columns.get_loc('distance') ] data2['time'] = data2['time'] - data2.iloc[ 0, data2.columns.get_loc('time') ] if 'secondprivate' in splitmode: # pragma: no cover setprivate = True else: setprivate = False dt = datetime.timedelta(seconds=splitsecond) id, message = new_workout_from_df(r, data2, title=parent.name + ' (2)', parent=parent, setprivate=setprivate, dt=dt, forceunit='N') messages.append(message) ids.append(encoder.encode_hex(id)) if 'keep original' not in splitmode: # pragma: no cover if 'keep second' in splitmode or 'keep first' in splitmode: parent.delete() messages.append('Deleted Workout: ' + parent.name) else: messages.append('That would delete your workout') ids.append(encoder.encode_hex(parent.id)) elif 'originalprivate' in splitmode: # pragma: no cover parent.privacy = 'hidden' parent.save() return ids, messages # create a new workout from manually entered data def create_row_df(r, distance, duration, startdatetime, workouttype='rower', avghr=None, avgpwr=None, avgspm=None, rankingpiece=False, duplicate=False, rpe=-1, title='Manual entry', notes='', weightcategory='hwt', adaptiveclass='None'): if duration is not None: totalseconds = duration.hour*3600. totalseconds += duration.minute*60. totalseconds += duration.second totalseconds += duration.microsecond/1.e6 else: # pragma: no cover totalseconds = 60. if distance is None: # pragma: no cover distance = 0 try: nr_strokes = int(distance/10.) except TypeError: # pragma: no cover nr_strokes = int(20.*totalseconds) if nr_strokes == 0: # pragma: no cover nr_strokes = 100 unixstarttime = arrow.get(startdatetime).timestamp() if not avgspm: # pragma: no cover try: spm = 60.*nr_strokes/totalseconds except ZeroDivisionError: spm = 20. else: spm = avgspm # step = totalseconds/float(nr_strokes) elapsed = np.arange(nr_strokes)*totalseconds/(float(nr_strokes-1)) # dstep = distance/float(nr_strokes) d = np.arange(nr_strokes)*distance/(float(nr_strokes-1)) unixtime = unixstarttime + elapsed try: pace = 500.*totalseconds/distance except ZeroDivisionError: # pragma: no cover pace = 240. if workouttype in ['rower', 'slides', 'dynamic']: try: velo = distance/totalseconds except ZeroDivisionError: # pragma: no cover velo = 2.4 power = 2.8*velo**3 elif avgpwr is not None: # pragma: no cover power = avgpwr else: # pragma: no cover power = 0 if avghr is not None: hr = avghr else: # pragma: no cover hr = 0 df = pd.DataFrame({ 'TimeStamp (sec)': unixtime, ' Horizontal (meters)': d, ' Cadence (stokes/min)': spm, ' Stroke500mPace (sec/500m)': pace, ' ElapsedTime (sec)': elapsed, ' Power (watts)': power, ' HRCur (bpm)': hr, }) timestr = strftime("%Y%m%d-%H%M%S") csvfilename = 'media/df_' + timestr + '.csv' df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] row = rrdata(df=df) row.write_csv(csvfilename, gzip=True) id, message = save_workout_database(csvfilename, r, title=title, notes=notes, rankingpiece=rankingpiece, duplicate=duplicate, dosmooth=False, workouttype=workouttype, consistencychecks=False, weightcategory=weightcategory, adaptiveclass=adaptiveclass, totaltime=totalseconds) return (id, message) def checkbreakthrough(w, r): isbreakthrough = False ishard = False workouttype = w.workouttype if workouttype in rowtypes: cpdf, delta, cpvalues = setcp(w) if not cpdf.is_empty(): if workouttype in otwtypes: try: res, btvalues, res2 = utils.isbreakthrough( delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio) _ = update_rolling_cp(r, otwtypes, 'water') except ValueError: res = 0 res2 = 0 elif workouttype in otetypes: try: res, btvalues, res2 = utils.isbreakthrough( delta, cpvalues, r.ep0, r.ep1, r.ep2, r.ep3, r.ecpratio) _ = update_rolling_cp(r, otetypes, 'erg') except ValueError: res = 0 res2 = 0 else: # pragma: no cover res = 0 res2 = 0 if res: isbreakthrough = True if res2 and not isbreakthrough: # pragma: no cover ishard = True # submit email task to send email about breakthrough workout if isbreakthrough: if not w.duplicate: w.rankingpiece = True w.save() if r.getemailnotifications and not r.emailbounced: # pragma: no cover _ = myqueue(queuehigh, handle_sendemail_breakthrough, w.id, r.user.email, r.user.first_name, r.user.last_name, btvalues=btvalues.write_json(row_oriented=True)) for coach in r.get_coaches(): if coach.getemailnotifications and not coach.emailbounced: _ = myqueue(queuehigh, handle_sendemail_breakthrough, w.id, coach.user.email, r.user.first_name, r.user.last_name, btvalues=btvalues.write_json(row_oriented=True), surname=True) # submit email task to send email about breakthrough workout if ishard: # pragma: no cover if not w.duplicate: w.rankingpiece = True w.save() if r.getemailnotifications and not r.emailbounced: _ = myqueue(queuehigh, handle_sendemail_hard, w.id, r.user.email, r.user.first_name, r.user.last_name, btvalues=btvalues.write_json()) for coach in r.get_coaches(): if coach.getemailnotifications and not coach.emailbounced: _ = myqueue(queuehigh, handle_sendemail_hard, w.id, coach.user.email, r.user.first_name, r.user.last_name, btvalues=btvalues.write_json(), surname=True) return isbreakthrough, ishard # Processes painsled CSV file to database def save_workout_database(f2, r, dosmooth=True, workouttype='rower', boattype='static', adaptiveclass='None', weightcategory='hwt', dosummary=True, title='Workout', workoutsource='unknown', notes='', totaldist=0, totaltime=0, rankingpiece=False, rpe=-1, duplicate=False, summary='', makeprivate=False, oarlength=2.89, inboard=0.88, boatname='', empowerside='port', seatnumber=1, forceunit='lbs', consistencychecks=False, startdatetime='', workoutid='', impeller=False, timezone=''): message = None powerperc = 100 * np.array([r.pw_ut2, r.pw_ut1, r.pw_at, r.pw_tr, r.pw_an]) / r.ftp # make workout and put in database rr = rrower(hrmax=r.max, hrut2=r.ut2, hrut1=r.ut1, hrat=r.at, hrtr=r.tr, hran=r.an, ftp=r.ftp, powerperc=powerperc, powerzones=r.powerzones) row = rdata(f2, rower=rr) startdatetime, startdate, starttime, timezone_str, partofday = get_startdate_time_zone( r, row, startdatetime=startdatetime, timezone=timezone) if title is None or title == '': title = 'Workout' if partofday is not None: title = '{partofday} {workouttype}'.format( partofday=partofday, workouttype=workouttype, ) if row.df.empty: # pragma: no cover return (0, 'Error: CSV data file was empty') dtavg = row.df['TimeStamp (sec)'].diff().mean() if dtavg < 1: newdf = df_resample(row.df) try: os.remove(f2) except: pass return new_workout_from_df(r, newdf, title=title, boattype=boattype, workouttype=workouttype, workoutsource=workoutsource, startdatetime=startdatetime, workoutid=workoutid) try: checks = row.check_consistency() allchecks = 1 for key, value in checks.items(): if not value: allchecks = 0 except ZeroDivisionError: # pragma: no cover pass if not allchecks and consistencychecks: # row.repair() pass if row == 0: # pragma: no cover return (0, 'Error: CSV data file not found') try: lat = row.df[' latitude'] if lat.mean() != 0 and lat.std() != 0 and workouttype == 'rower': workouttype = 'water' except KeyError: pass # remove negative power peaks x = row.df[' Power (watts)'].values x = x * - 1 neg_peaks, _ = find_peaks(x, height=0) # hieght is the threshold value row.df[' Power (watts)'][neg_peaks] = row.df[' Power (watts)'][neg_peaks-1] x = row.df[' Power (watts)'].values x = x * - 1 neg_peaks, _ = find_peaks(x, height=0) # hieght is the threshold value row.df[' Power (watts)'][neg_peaks] = row.df[' Power (watts)'][neg_peaks-1] #row.df[' Power (watts)'].replace(to_replace=0,method='ffill', inplace=True) if dosmooth: # auto smoothing pace = row.df[' Stroke500mPace (sec/500m)'].values velo = 500. / pace f = row.df['TimeStamp (sec)'].diff().mean() if f != 0 and not np.isnan(f): windowsize = 2 * (int(10. / (f))) + 1 else: # pragma: no cover windowsize = 1 if 'originalvelo' not in row.df: row.df['originalvelo'] = velo if windowsize > 3 and windowsize < len(velo): velo2 = savgol_filter(velo, windowsize, 3) else: # pragma: no cover velo2 = velo velo3 = pd.Series(velo2, dtype='float') velo3 = velo3.replace([-np.inf, np.inf], np.nan) velo3 = velo3.fillna(method='ffill') pace2 = 500. / abs(velo3) row.df[' Stroke500mPace (sec/500m)'] = pace2 row.df = row.df.fillna(0) row.write_csv(f2, gzip=True) try: os.remove(f2) except: pass # recalculate power data if workouttype in otetypes: try: if r.erg_recalculatepower: row.erg_recalculatepower() row.write_csv(f2, gzip=True) except: pass averagehr = row.df[' HRCur (bpm)'].mean() maxhr = row.df[' HRCur (bpm)'].max() if totaldist == 0: totaldist = row.df['cum_dist'].max() if totaltime == 0: totaltime = row.df['TimeStamp (sec)'].max( ) - row.df['TimeStamp (sec)'].min() try: totaltime = totaltime + row.df.loc[:, ' ElapsedTime (sec)'].iloc[0] except KeyError: # pragma: no cover pass if np.isnan(totaltime): # pragma: no cover totaltime = 0 if dosummary: summary = row.allstats() workoutstartdatetime = startdatetime dologging('debuglog.log', 'Dataprep line 1721, Workout Startdatetime {workoutstartdatetime}'.format( workoutstartdatetime=workoutstartdatetime, )) duration = totaltime_sec_to_string(totaltime) workoutdate = startdate workoutstarttime = starttime s = 'Dataprep line 1730 workoutdate and time set to {workoutdate} and {workoutstarttime}'.format( workoutdate=workoutdate, workoutstarttime=workoutstarttime, ) dologging('debuglog.log', s) if makeprivate: # pragma: no cover privacy = 'hidden' elif workoutsource != 'strava': privacy = 'visible' else: privacy = 'hidden' # checking for inf values totaldist = np.nan_to_num(totaldist) maxhr = np.nan_to_num(maxhr) averagehr = np.nan_to_num(averagehr) dragfactor = 0 if workouttype in otetypes: dragfactor = row.dragfactor #t = datetime.datetime.strptime(duration, "%H:%M:%S.%f") delta = datetime.timedelta( seconds=totaltime) try: workoutenddatetime = workoutstartdatetime+delta except AttributeError as e: workoutstartdatetime = pendulum.parse(str(workoutstartdatetime)) workoutenddatetime = workoutstartdatetime+delta # check for duplicate start times and duration duplicate = checkduplicates( r, workoutdate, workoutstartdatetime, workoutenddatetime) if duplicate: rankingpiece = False # test title length if title is not None and len(title) > 140: # pragma: no cover title = title[0:140] timezone_str = str(workoutstartdatetime.tzinfo) if workoutid: try: w = Workout.objects.get(id=workoutid) w.name = title w.date = workoutdate w.workouttype = workouttype w.boattype = boattype w.dragfactor = dragfactor w.duration = duration w.distance = totaldist w.weightcategory = weightcategory w.adaptiveclass = adaptiveclass w.starttime = workoutstarttime w.duplicate = duplicate w.workoutsource = workoutsource w.rankingpiece = rankingpiece w.forceunit = forceunit w.rpe = rpe w.csvfilename = f2 w.notes = notes w.summary = summary w.maxhr = maxhr w.averagehr = averagehr w.startdatetime = workoutstartdatetime w.inboard = inboard w.oarlength = oarlength w.seatnumber = seatnumber w.boatname = boatname w.empowerside = empowerside w.timezone = timezone_str w.privacy = privacy w.impeller = impeller except Workout.DoesNotExist: workoutid = '' if not workoutid: w = Workout(user=r, name=title, date=workoutdate, workouttype=workouttype, boattype=boattype, dragfactor=dragfactor, duration=duration, distance=totaldist, weightcategory=weightcategory, adaptiveclass=adaptiveclass, starttime=workoutstarttime, duplicate=duplicate, workoutsource=workoutsource, rankingpiece=rankingpiece, forceunit=forceunit, rpe=rpe, csvfilename=f2, notes=notes, summary=summary, maxhr=maxhr, averagehr=averagehr, startdatetime=workoutstartdatetime, inboard=inboard, oarlength=oarlength, boatname=boatname, empowerside=empowerside, seatnumber=seatnumber, timezone=timezone_str, privacy=privacy, impeller=impeller) try: w.save() except ValidationError: # pragma: no cover try: w.startdatetime = tz.now() w.save() except ValidationError: return (0, 'Unable to create your workout') except AttributeError: return (0, 'Unable to create your workout') if privacy == 'visible': ts = Team.objects.filter(rower=r) for t in ts: w.team.add(t) # put stroke data in database 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) _ = check_marker(w) _ = update_wps(r, mytypes.otwtypes) _ = update_wps(r, mytypes.otetypes) # set user running wps if not duplicate and workouttype in otetypes: cntr = Workout.objects.filter(user=r, workouttype__in=otetypes, startdatetime__gt=tz.now()-tz.timedelta(days=42), duplicate=False).count() new_value = (cntr*r.running_wps_erg + row.df['driveenergy'].mean())/(cntr+1.0) # if new_value is not zero or infinite or -inf, r.running_wps can be set to value if not (math.isnan(new_value) or math.isinf(new_value) or new_value == 0): r.running_wps_erg = new_value elif not (math.isnan(r.running_wps_erg) or math.isinf(r.running_wps_erg) or r.running_wps_erg == 0): pass else: r.running_wps_erg = 600. r.save() if not duplicate and workouttype in otwtypes: cntr = Workout.objects.filter(user=r, workouttype__in=otwtypes, startdatetime__gt=tz.now()-tz.timedelta(days=42), duplicate=False).count() new_value = (cntr*r.running_wps_erg + row.df['driveenergy'].mean())/(cntr+1.0) if not (math.isnan(new_value) or math.isinf(new_value) or new_value == 0): r.running_wps = new_value elif not (math.isnan(r.running_wps) or math.isinf(r.running_wps) or r.running_wps == 0): pass else: r.running_wps = 400. r.save() if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue(queuehigh, handle_calctrimp, w.id, f2, r.ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg) return (w.id, message) def new_workout_from_file(r, f2, workouttype='rower', workoutsource=None, title='Workout', boattype='static', rpe=-1, makeprivate=False, startdatetime='', timezone='', notes='', workoutid='', oarlockfirmware='', inboard=None, oarlength=None, boatname='', seatnumber=1, empowerside='port', impeller=False, uploadoptions={'boattype': '1x', 'workouttype': 'rower'}): message = "" try: fileformat = get_file_type(f2) except (IOError, UnicodeDecodeError): # pragma: no cover os.remove(f2) message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect." return (0, message, f2) summary = '' oarlength = 2.89 inboard = 0.88 # Save zip files to email box for further processing if len(fileformat) == 3 and fileformat[0] == 'zip': # pragma: no cover uploadoptions['secret'] = settings.UPLOAD_SERVICE_SECRET uploadoptions['user'] = r.user.id uploadoptions['title'] = title try: zip_file = zipfile.ZipFile(f2) for id, filename in enumerate(zip_file.namelist()): datafile = zip_file.extract(filename, path='media/') if id > 0: uploadoptions['title'] = title+' ('+str(id+1)+')' else: uploadoptions['title'] = title uploadoptions['file'] = datafile url = settings.UPLOAD_SERVICE_URL _ = myqueue(queuehigh, handle_request_post, url, uploadoptions) except BadZipFile: # pragma: no cover pass return -1, message, f2 # Some people try to upload Concept2 logbook summaries if fileformat == 'imageformat': # pragma: no cover os.remove(f2) message = "You cannot upload image files here" return (0, message, f2) if fileformat == 'json': # pragma: no cover os.remove(f2) message = "JSON format not supported in direct upload" return (0, message, f2) if fileformat == 'c2log': os.remove(f2) message = "This summary does not contain stroke data. Use the files containing stroke by stroke data." return (0, message, f2) if fileformat == 'nostrokes': # pragma: no cover os.remove(f2) message = "It looks like this file doesn't contain stroke data." return (0, message, f2) if fileformat == 'kml': # pragma: no cover os.remove(f2) message = "KML files are not supported" return (0, message, f2) # Some people upload corrupted zip files if fileformat == 'notgzip': # pragma: no cover os.remove(f2) message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect." return (0, message, f2) # Some people try to upload RowPro summary logs if fileformat == 'rowprolog': # pragma: no cover os.remove(f2) message = "This RowPro logbook summary does not contain stroke data. Please use the Stroke Data CSV file for the individual workout in your log." return (0, message, f2) # Sometimes people try an unsupported file type. # Send an email to info@rowsandall.com with the file attached # for me to check if it is a bug, or a new file type # worth supporting if fileformat == 'gpx': # pragma: no cover os.remove(f2) message = "GPX files support is on our roadmap. Check back soon." return (0, message, f2) if fileformat == 'unknown': # pragma: no cover message = "We couldn't recognize the file type" extension = os.path.splitext(f2)[1] filename = os.path.splitext(f2)[0] if extension == '.gz': filename = os.path.splitext(filename)[0] extension2 = os.path.splitext(filename)[1]+extension extension = extension2 f4 = filename+'a'+extension copyfile(f2, f4) _ = myqueue(queuehigh, handle_sendemail_unrecognized, f4, r.user.email) return (0, message, f2) if fileformat == 'att': # pragma: no cover # email attachment which can safely be ignored os.remove(f2) return (0, '', f2) if fileformat == 'quiskesummary': os.remove(f2) return (0,'',f2) # Get workout type from fit & tcx if (fileformat == 'fit'): # pragma: no cover workouttype = get_workouttype_from_fit(f2, workouttype=workouttype) new_title = get_title_from_fit(f2) if new_title: title = new_title new_notes = get_notes_from_fit(f2) if new_notes: notes = new_notes # if (fileformat == 'tcx'): # workouttype_from_tcx = get_workouttype_from_tcx(f2,workouttype=workouttype) # if workouttype != 'rower' and workouttype_from_tcx not in mytypes.otwtypes: # workouttype = workouttype_from_tcx # handle non-Painsled by converting it to painsled compatible CSV if (fileformat != 'csv'): f2, summary, oarlength, inboard, fileformat, impeller = handle_nonpainsled( f2, fileformat, startdatetime=startdatetime, summary=summary, empowerfirmware=oarlockfirmware, impeller=impeller, ) if not f2: # pragma: no cover message = 'Something went wrong' return (0, message, '') dosummary = (fileformat != 'fit' and 'speedcoach2' not in fileformat) dosummary = dosummary or summary == '' if 'speedcoach2' in fileformat and workouttype == 'rower': workouttype = 'water' if workoutsource is None: workoutsource = fileformat dologging('debuglog.log', 'Saving to database with start date time {startdatetime}'.format( startdatetime=startdatetime, )) try: id, message = save_workout_database( f2, r, notes=notes, workouttype=workouttype, weightcategory=r.weightcategory, adaptiveclass=r.adaptiveclass, boattype=boattype, makeprivate=makeprivate, dosummary=dosummary, workoutsource=workoutsource, summary=summary, startdatetime=startdatetime, timezone=timezone, rpe=rpe, inboard=inboard, oarlength=oarlength, seatnumber=seatnumber, boatname=boatname, empowerside=empowerside, title=title, forceunit='N', impeller=impeller, workoutid=workoutid, ) except ValueError: message = "error saving" id = 0 return (id, message, f2) def new_workout_from_df(r, df, title='New Workout', workoutsource='unknown', boattype='static', workouttype='rower', parent=None, workoutid='', startdatetime='', setprivate=False, forceunit='lbs', dt=datetime.timedelta()): message = None summary = '' if parent: oarlength = parent.oarlength inboard = parent.inboard workoutsource = parent.workoutsource workouttype = parent.workouttype boattype = parent.boattype notes = parent.notes summary = parent.summary rpe = parent.rpe if parent.privacy == 'hidden': # pragma: no cover makeprivate = True else: makeprivate = False startdatetime = parent.startdatetime + dt else: oarlength = 2.89 inboard = 0.88 notes = '' summary = '' makeprivate = False rpe = 0 if startdatetime == '': # pragma: no cover startdatetime = tz.now() if setprivate: # pragma: no cover makeprivate = True timestr = strftime("%Y%m%d-%H%M%S") csvfilename = 'media/df_' + timestr + '.csv' if forceunit == 'N': # change to lbs for now df['peakforce'] /= lbstoN df['averageforce'] /= lbstoN df.rename(columns=columndict, inplace=True) starttimeunix = arrow.get(startdatetime).timestamp() df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] df['TimeStamp (sec)'] = df['TimeStamp (sec)'] + starttimeunix row = rrdata(df=df) row.write_csv(csvfilename, gzip=True) id, message = save_workout_database(csvfilename, r, workouttype=workouttype, boattype=boattype, title=title, workoutsource=workoutsource, notes=notes, summary=summary, oarlength=oarlength, inboard=inboard, makeprivate=makeprivate, dosmooth=False, workoutid=workoutid, rpe=rpe, consistencychecks=False) if workouttype in otwtypes: wps_avg = r.median_wps elif workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue(queuehigh, handle_calctrimp, id, csvfilename, r.ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg) return (id, message) # A wrapper around the rowingdata class, with some error catching from redis import StrictRedis, Redis def get_existing_job(w): # check if this filename is in the queue r = StrictRedis() for key in r.keys(): res = key.decode('utf8').split(':') if len(res) == 3: jobid = res[2] try: job = Job.fetch(jobid, connection=r) if isinstance(job.args[1], str): if job.args[1] == w.csvfilename and len(job.args)==7: return 1 except: pass return 0 def workout_trimp(w, reset=False): r = w.user if w.trimp is None: if get_existing_job(w): return 0, 0 elif w.averagehr is None: if get_existing_job(w): return w.trimp, 0 elif w.trimp <= 0 and w.averagehr > 0: if get_existing_job(w): try: return 0, 100.*(w.averagehr/r.hrftp)*(w.duration.hour*60 + w.duration.minute)/60. except ZeroDivisionError: return 0, 0 if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 ftp = float(r.ftp) _ = myqueue( queuehigh, handle_calctrimp, w.id, w.csvfilename, ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg, ) return w.trimp, w.hrtss elif w.trimp > -1 and not reset: return w.trimp, w.hrtss if get_existing_job(w): return 0, 0 r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: ftp = ftp*(100.-r.otwslack)/100. if r.hrftp == 0: hrftp = (r.an+r.tr)/2. r.hrftp = int(hrftp) r.save() if w.averagehr is None: rowdata = rdata(w.csvfilename) try: avghr = rowdata.df[' HRCur (bpm)'].mean() maxhr = rowdata.df[' HRCur (bpm)'].max() except KeyError: # pragma: no cover avghr = None maxhr = None w.averagehr = avghr w.maxhr = maxhr w.save() if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue( queuehigh, handle_calctrimp, w.id, w.csvfilename, ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg,) trimp = 0 averagehr = 0 if w.trimp is not None and w.trimp > 0: trimp = w.trimp if w.averagehr is not None and w.averagehr > 0: averagehr = w.averagehr return trimp, averagehr def workout_spmtss(w, reset=False): if w.spmtss > -1 and not reset: return w.spmtss if get_existing_job(w): return 0, 0 r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: ftp = ftp*(100.-r.otwslack)/100. if r.hrftp == 0: hrftp = (r.an+r.tr)/2. r.hrftp = int(hrftp) r.save() if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue( queuehigh, handle_calctrimp, w.id, w.csvfilename, ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg,) return w.spmtss def workout_rscore(w, reset=False): dologging('metrics.log','Workout_rscore for {w} {id}, {reset}'.format( w = w, id = w.id, reset = reset )) if w.rscore > -1 and not reset: dologging('metrics.log','Returning {rscore}, {normp}'.format( rscore = w.rscore, normp = w.normp, )) return w.rscore, w.normp if get_existing_job(w): dologging('metrics.log','Got existing job, returning whatever we have') return w.rscore, w.normp r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: ftp = ftp*(100.-r.otwslack)/100. if r.hrftp == 0: hrftp = (r.an+r.tr)/2. r.hrftp = int(hrftp) r.save() dologging('metrics.log','Queueing an asynchronous task') if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue( queuehigh, handle_calctrimp, w.id, w.csvfilename, ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg,) return w.rscore, w.normp def workout_normv(w, pp=4.0): if w.normv > -1: # pragma: no cover return w.normv, w.normw if get_existing_job(w): return 0, 0 r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: ftp = ftp*(100.-r.otwslack)/100. if r.hrftp == 0: # pragma: no cover hrftp = (r.an+r.tr)/2. r.hrftp = int(hrftp) r.save() if w.workouttype in otwtypes: wps_avg = r.median_wps elif w.workouttype in otetypes: wps_avg = r.median_wps_erg else: wps_avg = 0 _ = myqueue( queuehigh, handle_calctrimp, w.id, w.csvfilename, ftp, r.sex, r.hrftp, r.max, r.rest, wps_avg) return 0, 0