import os os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" from YamJam import yamjam CFG = yamjam()['rowsandallapp'] try: os.environ.setdefault("DJANGO_SETTINGS_MODULE",CFG['settings_name']) except KeyError: # pragma: no cover os.environ.setdefault("DJANGO_SETTINGS_MODULE","rowsandall_app.settings") from django.core.wsgi import get_wsgi_application application = get_wsgi_application() from rowers.models import ( Workout, GeoPolygon, GeoPoint, GeoCourse, VirtualRaceResult, CourseTestResult, Rower, GraphImage, Team, PlannedSession ) from rowers.session_utils import is_session_complete import math from rowers.courseutils import ( coursetime_paths, coursetime_first, time_in_path, InvalidTrajectoryError ) from rowers.emails import send_template_email from rowers.mytypes import intervalsmappinginv from rowers.nkimportutils import ( get_nk_summary, get_nk_allstats, get_nk_intervalstats, getdict, strokeDataToDf, add_workout_from_data ) from stravalib.exc import ActivityUploadFailed import stravalib import arrow import rowers.longtask as longtask import requests import rowers.datautils as datautils from rowers.models import create_or_update_syncrecord """ Background tasks done by QR (production) """ import time import gc import gzip import shutil import numpy as np import re import sys import json import traceback from time import strftime import base64 from io import BytesIO from scipy import optimize from scipy.signal import savgol_filter from scipy.interpolate import griddata import rowingdata from rowingdata import make_cumvalues, make_cumvalues_array from uuid import uuid4 from rowingdata import rowingdata as rdata from rowingdata import FITParser as FP from rowingdata.otherparsers import FitSummaryData from datetime import timedelta import datetime import pytz import iso8601 from iso8601 import ParseError from json.decoder import JSONDecodeError from pytz.exceptions import UnknownTimeZoneError from matplotlib.backends.backend_agg import FigureCanvas import matplotlib.pyplot as plt from matplotlib import path import grpc import rowers.otw_power_calculator_pb2 as calculator_pb2 import rowers.otw_power_calculator_pb2_grpc as calculator_pb2_grpc import rowers.rowing_workout_metrics_pb2 as metrics_pb2 import rowers.rowing_workout_metrics_pb2_grpc as metrics_pb2_grpc from django.conf import settings from django.db.utils import IntegrityError # extra read of config SITE_URL = CFG['site_url'] SITE_URL_DEV = CFG['site_url'] PROGRESS_CACHE_SECRET = CFG['progress_cache_secret'] try: SETTINGS_NAME = CFG['settings_name'] except KeyError: # pragma: no cover SETTINGS_NAME = 'rowsandall_ap.settings' try: UPLOAD_SERVICE_URL = CFG['upload_service_url'] except KeyError: # pragma: no cover UPLOAD_SERVICE_URL = "http://localhost:8000/rowers/workout/api/upload/" try: UPLOAD_SERVICE_SECRET = CFG['upload_service_secret'] except KeyError: # pragma: no cover UPLOAD_SERVICE_SECRET = "FoYezZWLSyfAVimumpHEeYsJjsNCerxV" NK_API_LOCATION = CFG["nk_api_location"] TP_CLIENT_ID = CFG["tp_client_id"] TP_CLIENT_SECRET = CFG["tp_client_secret"] TP_API_LOCATION = CFG["tp_api_location"] tpapilocation = TP_API_LOCATION from requests_oauthlib import OAuth1, OAuth1Session import pandas as pd import polars as pl from polars.exceptions import ( ColumnNotFoundError, ComputeError, ShapeError ) from django_rq import job from django.utils import timezone from django.utils.html import strip_tags from rq.decorators import job from rowers.utils import deserialize_list, ewmovingaverage, wavg, dologging import rowers.utils as utils from rowers.emails import htmlstrip from rowers import mytypes from rowers.dataroutines import ( 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, create_c2_stroke_data_db, update_empower, # database_url_debug, database_url, dataprep, # create_strava_stroke_data_db ) database_url_debug = database_url from rowers.opaque import encoder from django.core.mail import ( send_mail, EmailMessage, EmailMultiAlternatives, ) from django.template import Context from django.db.utils import OperationalError from jinja2 import Template, Environment, FileSystemLoader env = Environment(loader=FileSystemLoader(["rowers/templates"])) def safetimedelta(x): try: return timedelta(seconds=x) except ValueError: return timedelta(seconds=0) siteurl = SITE_URL # testing task from rowers.utils import intensitymap def strcapitalize(s): if s is None: return None if isinstance(s, str): return s[0].upper() + s[1:] return s def correct_intensity(workout): # reads the steps and if the intensity is an integer, converts it to a string steps = workout['steps'] for step in steps: if 'intensity' in step: if isinstance(step['intensity'], int): step['intensity'] = intensitymap[step['intensity']] step['durationType'] = strcapitalize(step['durationType']) step['targetType'] = strcapitalize(step['targetType']) step['intensity'] = strcapitalize(step['intensity']) return workout @job('default') def handle_loadnextweek(rower, debug=False, **kwargs): plan = rower.training_plan_code secret = rower.training_plan_secret post_data = { 'fitness': rower.actualfit, 'fatigue': rower.actualfatigue, 'plan': plan, 'secret': secret, } url = "http://localhost:8898/next-week-plan/" response = requests.post(url, data=post_data) if response.status_code in [200, 201]: data = response.json() today = timezone.now() startdate = today - timedelta(days=today.weekday())+timezone.timedelta(days=7) enddate = startdate + timedelta(days=6) sps = PlannedSession.objects.filter( rower__in=[rower], startdate__gte=startdate, enddate__lte=enddate, is_template=False, ) for ps in sps: ps.delete() trainingdays = data['cycles'] # start date is the first day of the following week ndays = 0 for day in trainingdays: try: workouts = day[0][1:] except IndexError: workouts =[] for workout in workouts: sessionsport = 'water' try: sessionsport = mytypes.fitmappinginv[workout['sport'].lower()] except KeyError: pass preferreddate = startdate+timedelta(days=ndays) sessionmode = 'time' ps = PlannedSession( startdate=preferreddate - timedelta(days=preferreddate.weekday()), enddate=preferreddate + timedelta(days=-preferreddate.weekday()-1, weeks=1), preferreddate=preferreddate, sessionsport=sessionsport, # change this name=workout['workoutName'], steps=correct_intensity(workout), manager=rower.user, sessionmode=sessionmode, comment=workout['description'], from_plan=None, ) ps.save() ps.rower.add(rower) ps.save() if ps.fitfile: ps.steps = {} ps.save() ndays += 1 return 1 return 0 @job('default') def create_sessions_from_json_async(plansteps, rower, startdate, manager, planbyrscore, plan, plan_past_days, debug=False, **kwargs): trainingdays = plansteps['trainingDays'] planstartdate = startdate for day in trainingdays: for workout in day['workouts']: sessionsport = 'water' try: sessionsport = mytypes.fitmappinginv[workout['sport'].lower()] except KeyError: pass preferreddate = planstartdate+timedelta(days=day['order']) sessionmode = 'time' if planbyrscore: sessionmode = 'rScore' create_session = False if plan_past_days: create_session = True elif preferreddate >= timezone.now().date(): create_session = True if create_session: ps = PlannedSession( startdate=preferreddate - timedelta(days=preferreddate.weekday()), enddate=preferreddate + timedelta(days=-preferreddate.weekday()-1, weeks=1), preferreddate=preferreddate, sessionsport=sessionsport, # change this name=workout['workoutName'], steps=workout, manager=manager, sessionmode=sessionmode, comment=workout['description'], from_plan=plan, ) ps.save() teams = Team.objects.filter(manager=ps.manager) members = Rower.objects.filter(team__in=teams).distinct() if rower in members: ps.rower.add(rower) ps.save() elif ps.manager.rower == rower: ps.rower.add(rower) ps.save() return 1 @job('default') def handle_remove_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover for w in ws: w.team.remove(t) return 1 @job('default') def handle_add_workouts_team(ws, t, debug=False, **kwargs): # pragma: no cover for w in ws: w.team.add(t) return 1 def uploadactivity(access_token, filename, description='', name='Rowsandall.com workout', workouttype='rowing'): # pragma: no cover data_gz = BytesIO() try: with open(filename, 'rb') as inF: s = inF.read() with gzip.GzipFile(fileobj=data_gz, mode="w") as gzf: gzf.write(s) except FileNotFoundError: return 0, 0, 0, 0 headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer %s' % access_token } # Data field is base64 encoded file read from filename data = { "UploadClient": "rowsandall", "Filename": filename, "SetWorkoutPublic": True, "Title": name, "Type": workouttype, "Comment": description, "Data": base64.b64encode(data_gz.getvalue()).decode("ascii") } resp = requests.post(tpapilocation+"/v3/file", data=json.dumps(data), headers=headers, verify=False) if resp.status_code not in (200, 202): # pragma: no cover dologging('tp_export.log',resp.status_code) dologging('tp_export.log',resp.reason) return 0, resp.reason, resp.status_code, headers else: return 1, "ok", 200, resp.headers return 0, 0, 0, 0 # pragma: no cover @job('default') def send_session_stats(user, debug=False, **kwargs): ws = Workout.objects.filter(plannedsession__isnull=False) results = [] for w in ws: ps = w.plannedsession r = w.user ratio, status, cdate = is_session_complete(r, ps) d = { 'date':w.date, 'session_id':ps.id, 'session_name':ps.name, 'complete': ratio, 'status': status, 'rscore': w.rscore, 'duration': w.duration, } results.append(d) df = pd.DataFrame(results) code = str(uuid4()) filename = code+'.csv' df.to_csv(filename) subject = "Session Stats" from_email = 'Rowsandall ' useremail = user.email _ = send_template_email( from_email, [useremail], subject, 'sessionstats.html', d, attach_file=filename, ) os.remove(filename) return 1 @job('default') def check_tp_workout_id(workout, location, attempts=5, debug=False, **kwargs): # pragma: no cover authorizationstring = str('Bearer ' + workout.user.tptoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} response = requests.get(location, headers=headers, params={}) if response.status_code == 200: status = response.json()['Status'] if status == 'Success': tpid = response.json()['WorkoutIds'][0] workout.uploadedtotp = tpid record = create_or_update_syncrecord(workout.user, workout, tpid=tpid) workout.save() else: dologging('tp_export.log','failed to get workout id from trainingpeaks') dologging('tp_export.log',response.text) dologging('tp_export.log',status) return 0 else: dologging('tp_export.log','failed to get workout id from trainingpeaks') dologging('tp_export.log',response.text) return 0 return 1 @job('default') def handle_workout_tp_upload(w, thetoken, tcxfilename, workouttype, debug=False, **kwargs): # pragma: no cover tpid = 0 r = w.user dologging('tp_export.log','uploading workout {workoutid} to trainingpeaks for user {id}'.format(id=r.id,workoutid=w.id)) if not tcxfilename: return 0 res, reason, status_code, headers = uploadactivity( thetoken, tcxfilename, name=w.name, workouttype=workouttype, ) if res == 0: w.uploadedtotcx = -1 try: os.remove(tcxfilename) except: pass w.save() return 0 dologging('tp_export.log','uploading workout {workoutid} to trainingpeaks for user {id} succeeded'.format(id=r.id,workoutid=w.id)) w.uploadedtotp = res record = create_or_update_syncrecord(w.user, w, tpid=tpid) tpid = res w.save() try: os.remove(tcxfilename) except FileNotFoundError: pass check_tp_workout_id(w,headers['Location']) return tpid @job('default') def instroke_static(w, metric, debug=False, **kwargs): # pragma: no cover f1 = w.csvfilename[6:-4] rowdata = rdata(csvfile=w.csvfilename) timestr = strftime("%Y%m%d-%H%M%S") imagename = f1+timestr+'.png' fullpathimagename = 'static/plots/'+imagename r = w.user fig1 = rowdata.get_plot_instroke(metric) canvas = FigureCanvas(fig1) canvas.print_figure('static/plots/'+imagename) plt.close(fig1) fig1.clf() try: width, height = Image.open(fullpathimagename).size except: width = 1200 height = 600 imgs = GraphImage.objects.filter(workout=w) if imgs.count() < 7: i = GraphImage(workout=w, creationdatetime=timezone.now(), filename=fullpathimagename, width=width, height=height) i.save() else: return 0 return 1 @job('default') def handle_request_post(url, data, debug=False, **kwargs): # pragma: no cover if 'localhost' in url: url = 'http'+url[4:] response = requests.post(url, data, verify=False) dologging('upload_api.log', data) dologging('upload_api.log', response.status_code) return response.status_code @job('default') def add(x, y): # pragma: no cover return x + y @job('default') def handle_c2_sync(workoutid, url, headers, data, debug=False, **kwargs): response = requests.post(url, headers=headers, data=data) if response.status_code not in [200, 201]: # pragma: no cover s = 'C2 upload failed for user workoutid {workoutid} with response code {code} and error {text}'.format( code=response.status_code, workoutid = workoutid, text = response.text ) dologging('c2_log.log',s) return 0 s = response.json() c2id = s['data']['id'] try: workout = Workout.objects.get(id=workoutid) except Workout.DoesNotExist: # pragma: no cover dologging('c2_log.log','failed for c2id {c2id}'.format(c2id=c2id)) return 0 s = 'C2 upload succeeded with {c2id} user id {userid}'.format( c2id=c2id, userid=workout.user.user.id ) dologging('c2_log.log',s) workout.uploadedtoc2 = c2id workout.save() record = create_or_update_syncrecord(workout.user, workout, c2id=c2id) return 1 @job('default') def handle_sporttracks_sync(workoutid, url, headers, data, debug=False, **kwargs): response = requests.post(url, headers=headers, data=data) if response.status_code not in [200, 201]: # pragma: no cover return 0 t = response.json() uri = t['uris'][0] regex = '.*?sporttracks\.mobi\/api\/v2\/fitnessActivities/(\d+)\.json$' m = re.compile(regex).match(uri).group(1) id = int(m) workout = Workout.objects.get(id=workoutid) workout.uploadedtosporttracks = id workout.save() record = create_or_update_syncrecord(workout.user, workout, sporttracksid=id) return 1 @job('default') def handle_strava_sync(stravatoken, workoutid, filename, name, activity_type, description, debug=False, **kwargs): client = stravalib.Client(access_token=stravatoken) failed = False try: with open(filename, 'rb') as f: try: act = client.upload_activity(f, 'tcx.gz', name=name) try: res = act.wait(poll_interval=1.0, timeout=30) except stravalib.exc.ActivityUploadFailed: # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id} ActivityUploadFailed'.format( id=workoutid) ) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True except stravalib.exc.TimeoutExceeded: # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id} TimeOutExceeded'.format( id=workoutid) ) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True except JSONDecodeError: # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id} JSONDecodeError'.format( id=workoutid) ) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True except stravalib.exc.ObjectNotFound: # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id} ObjectNotFound'.format( id=workoutid) ) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True except IndexError: # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id} IndexError'.format( id=workoutid) ) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True # temporary hack until stravalib is fixed if 'LatLon' in tb: dologging('strava_fail.log', 'Trying temporary fix') failed = False except ( ActivityUploadFailed, stravalib.exc.RateLimitExceeded, JSONDecodeError ): # pragma: no cover dologging( 'strava_fail.log', 'Strava upload failed for Workout {id}'.format(id=workoutid)) tb = traceback.format_exc() dologging('strava_fail.log', tb) failed = True except FileNotFoundError: # pragma: no cover dologging('strava_fail.log', 'Strava upload failed for Workout {id}'.format(id=workoutid)) failed = True if not failed: try: workout = Workout.objects.get(id=workoutid) except Workout.DoesNotExist: # pragma: no cover return 0 workout.uploadedtostrava = res.id workout.save() record = create_or_update_syncrecord(workout.user, workout, stravaid=res.id) trainer = False if workout.workouttype in mytypes.otetypes: trainer = True try: act = client.update_activity(res.id, activity_type=activity_type, description=description, device_name='Rowsandall.com', trainer=trainer) dologging('strava_export_log.log', 'Updating activity {id} to {type}'.format( id=workoutid, type=activity_type )) except TypeError: # pragma: no cover act = client.update_activity(res.id, activity_type=activity_type, description=description, trainer=trainer) dologging('strava_export_log.log', 'Updating activity {id} to {type}'.format( id=workoutid, type=activity_type )) except: # pragma: no cover e = sys.exc_info()[0] dologging('strava_export_log.log', 'Update activity failed with error {e} for {id} to {type}'.format( id=workoutid, type=activity_type, e=e )) try: os.remove(filename) except: # pragma: no cover pass return 1 @job('default') def handle_c2_import_stroke_data(c2token, c2id, workoutid, starttimeunix, csvfilename, debug=True, **kwargs): if 'workouttype' in kwargs: # pragma: no cover workouttype = kwargs['workouttype'] else: workouttype = 'rower' authorizationstring = str('Bearer ' + c2token) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json'} url = "https://log.concept2.com/api/users/me/results/"+str(c2id)+"/strokes" s = requests.get(url, headers=headers) if s.status_code == 200: strokedata = pd.DataFrame.from_dict(s.json()['data']) _ = add_c2_stroke_data_db( strokedata, workoutid, starttimeunix, csvfilename, debug=debug, workouttype=workouttype ) return 1 else: # pragma: no cover url = "https://log.concept2.com/api/users/me/results/{id}".format( id=c2id) s = requests.get(url, headers=headers) if s.status_code == 200: workoutdata = s.json()['data'] distance = workoutdata['distance'] c2id = workoutdata['id'] workouttype = workoutdata['type'] totaltime = workoutdata['time']/10. duration = totaltime_sec_to_string(totaltime) duration = datetime.datetime.strptime( duration, '%H:%M:%S.%f').time() _ = create_c2_stroke_data_db( distance, duration, workouttype, workoutid, starttimeunix, csvfilename, debug=debug, ) return 1 return 0 return 0 # pragma: no cover def getagegrouprecord(age, sex='male', weightcategory='hwt', distance=2000, duration=None, indf=pd.DataFrame()): power = 0 if not duration: try: df = indf[indf['distance'] == distance] except KeyError: # pragma: no cover df = pd.DataFrame() else: duration = 60*int(duration) try: df = indf[indf['duration'] == duration] except KeyError: # pragma: no cover df = pd.DataFrame() if not df.empty: ages = df['age'] powers = df['power'] def fitfunc(pars, x): return np.abs(pars[0])*(1-x/max(120, pars[1]))-np.abs( pars[2])*np.exp(-x/np.abs(pars[3]))+np.abs(pars[4])*(np.sin(np.pi*x/max(50, pars[5]))) def errfunc(pars, x, y): return fitfunc(pars, x)-y p0 = [700, 120, 700, 10, 100, 100] p1, success = optimize.leastsq(errfunc, p0[:], args=(ages, powers)) if success and age is not None: power = fitfunc(p1, float(age)) power = 0.5*(np.abs(power)+power) elif age is not None: # pragma: no cover power = 0.5*(np.abs(power)+power) else: # pragma: no cover power = 0 return power from rowers.models import polygon_to_path @job('default') def handle_check_race_course(f1, workoutid, courseid, recordid, useremail, userfirstname, **kwargs): logfile = 'courselog_{workoutid}_{courseid}.log'.format( workoutid=workoutid, courseid=courseid) logfile2 = 'courses.log' dologging(logfile2,logfile) if 'debug' in kwargs: # pragma: no cover debug = kwargs['debug'] else: debug = False if 'splitsecond' in kwargs: # pragma: no cover splitsecond = kwargs['splitsecond'] else: splitsecond = 0 if 'referencespeed' in kwargs: # pragma: no cover referencespeed = kwargs['referencespeed'] else: referencespeed = 5.0 if 'coursedistance' in kwargs: # pragma: no cover coursedistance = kwargs['coursedistance'] else: coursedistance = 0 mode = 'race' if 'mode' in kwargs: # pragma: no cover mode = kwargs['mode'] summary = False if 'summary' in kwargs: # pragma: no cover summary = kwargs['summary'] successemail = False if 'successemail' in kwargs: # pragma: no cover successemail = kwargs['successemail'] try: row = rdata(csvfile=f1) except IOError: # pragma: no cover try: row = rdata(csvfile=f1 + '.csv') except IOError: # pragma: no cover try: row = rdata(csvfile=f1 + '.gz') except IOError: # pragma: no cover dologging(logfile,"Did not find file "+f1) dologging(logfile2,"Did not find file "+f1) return 0 try: row.extend_data() except KeyError: # pragma: no cover dologging(logfile,"Could not extend data") dologging(logfile2,"Could not extend data") return 0 # row.df.interpolate(inplace=True) row.calc_dist_from_gps() rowdata = row.df rowdata['cum_dist'] = rowdata['gps_dist_calculated'] try: _ = rowdata[' latitude'] except KeyError: # pragma: no cover dologging(logfile,"No GPS Data") dologging(logfile2,"No GPS Data") return 0 rowdata.rename(columns={ ' latitude': 'latitude', ' longitude': 'longitude', 'TimeStamp (sec)': 'time', }, inplace=True) rowdata.fillna(method='backfill', inplace=True) rowdata.loc[:, 'time'] = rowdata.loc[:, 'time'].copy()-rowdata.loc[0, 'time'] rowdata = rowdata.copy()[rowdata['time'] > splitsecond] # we may want to expand the time (interpolate) rowdata.loc[:,'dt'] = rowdata['time'].apply( lambda x: safetimedelta(x) ).values rowdata = rowdata.select_dtypes(['number']) try: rowdata = rowdata.resample('100ms', on='dt').mean() rowdata = rowdata.interpolate() except TypeError: # pragma: no cover pass course = GeoCourse.objects.get(id=courseid) polygons = course.polygons.all() paths = [] for polygon in polygons: path = polygon_to_path(polygon, debug=debug) paths.append(path) startsecond = 0 endsecond = rowdata['time'].max() # check how many times went through start polygon try: try: entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True, name=polygons[0].name, logfile=logfile) except AttributeError: # pragma: no cover entrytimes, entrydistances = time_in_path(rowdata, paths[0], maxmin='max', getall=True, name='Start', logfile=logfile) logmessage = 'Course id {n}, Record id {m}'.format(n=courseid, m=recordid) dologging(logfile,logmessage) dologging(logfile2,logmessage) logmessage = 'Found {n} entrytimes'.format(n=len(entrytimes)) dologging(logfile,logmessage) dologging(logfile2,logmessage) except InvalidTrajectoryError: # pragma: no cover entrytimes = [] coursecompleted = False coursemeters = 0 coursetimeseconds = 0 cseconds = [] cmeters = [] ccomplete = [] startseconds = [] endseconds = [] for startt in entrytimes: logmessage = 'Path starting at {t}'.format(t=startt) dologging(logfile, logmessage) dologging(logfile2, logmessage) rowdata2 = rowdata[rowdata['time'] > (startt-10.)] #rowdata2.to_csv('debug_course.csv') ( coursetimeseconds, coursemeters, coursecompleted, ) = coursetime_paths(rowdata2, paths, polygons=polygons, logfile=logfile) ( coursetimefirst, coursemetersfirst, firstcompleted ) = coursetime_first( rowdata2, paths, polygons=polygons, logfile=logfile) dologging(logfile, "First time through all gates {t} seconds, {m} meters, completed {c}".format( t=coursetimeseconds, m=coursemeters, c=coursecompleted )) dologging(logfile, "Start time through all gates {t} seconds, {m} meters, completed {c}".format( t=coursetimefirst, m=coursemetersfirst, c=firstcompleted )) coursetimesecondsnet = coursetimeseconds-coursetimefirst coursemeters = coursemeters-coursemetersfirst cseconds.append(coursetimesecondsnet) cmeters.append(coursemeters) ccomplete.append(coursecompleted) endseconds.append(coursetimeseconds) startseconds.append(coursetimefirst) records = pd.DataFrame({ 'coursetimeseconds': cseconds, 'coursecompleted': ccomplete, 'coursemeters': cmeters, 'startsecond': startseconds, 'endsecond': endseconds, }) records = records.loc[records['coursecompleted'], : ] if len(records): coursecompleted = True mintime = records['coursetimeseconds'].min() coursetimeseconds = records[records['coursetimeseconds'] == mintime]['coursetimeseconds'].min() coursemeters = records[records['coursetimeseconds'] == mintime]['coursemeters'].min() startsecond = records[records['coursetimeseconds'] == mintime]['startsecond'].min() endsecond = records[records['coursetimeseconds'] == mintime]['endsecond'].min() else: # pragma: no cover coursecompleted = False points = 0 if coursecompleted: if coursedistance == 0: coursedistance = coursemeters velo = coursedistance/coursetimeseconds points = 100*(2.-referencespeed/velo) if mode != 'coursetest': record = VirtualRaceResult.objects.get(id=recordid) record.duration = totaltime_sec_to_string(coursetimeseconds) record.distance=int(coursemeters) record.points = points record.startsecond = startsecond record.endsecond = endsecond record.workoutid = workoutid record.coursecompleted = 1 record.save() else: # pragma: no cover record = CourseTestResult.objects.get(id=recordid) record.duration = totaltime_sec_to_string(coursetimeseconds) record.distance = int(coursemeters) record.workoutid = workoutid record.courseid = courseid record.startsecond = startsecond record.endsecond = endsecond record.points = points record.coursecompleted = 1 record.save() if summary: # pragma: no cover try: row = rdata(csvfile=f1) except IOError: # pragma: no cover try: row = rdata(csvfile=f1 + '.csv') except IOError: # pragma: no cover try: row = rdata(csvfile=f1 + '.gz') except IOError: # pragma: no cover pass vals, units, typ = row.updateinterval_metric( ' AverageBoatSpeed (m/s)', 0.1, mode='larger', debug=False, smoothwindow=15., activewindow=[startsecond, endsecond] ) summary = row.allstats() row.write_csv(f1, gzip=True) workout = Workout.objects.get(id=workoutid) workout.summary = summary workout.save() if successemail: # pragma: no cover handle_sendemail_coursesucceed( useremail, userfirstname, logfile, workoutid ) os.remove(logfile) return 1 else: # pragma: no cover record = VirtualRaceResult.objects.get(id=recordid) record.duration = totaltime_sec_to_string(0) record.distance = 0 record.workoutid = workoutid record.startsecond = startsecond record.endsecond = endsecond record.points = 0 record.save() if mode == 'coursetest': record = CourseTestResult.objects.get(id=recordid) record.duration = totaltime_sec_to_string(0) record.distance = 0 record.workoutid = workoutid record.startsecond = startsecond record.endsecond = endsecond record.points = 0 record.save() # add times for all gates to log file dologging(logfile,'--- LOG of all gate times---') dologging(logfile2,'--- LOG of all gate times---') for path, polygon in zip(paths, polygons): (secs, meters, completed) = coursetime_paths(rowdata, [path], polygons=[polygon], logfile=logfile) logmessage = " time: {t} seconds, distance: {m} meters".format(t=secs, m=meters) dologging(logfile,logmessage) dologging(logfile2,logmessage) # send email handle_sendemail_coursefail( useremail, userfirstname, logfile ) os.remove(logfile) return 2 return 0 # pragma: no cover @job('default') def handle_getagegrouprecords(df, distances, durations, age, sex, weightcategory, **kwargs): wcdurations = [] wcpower = [] if 'debug' in kwargs: # pragma: no cover debug = kwargs['debug'] else: debug = False df = pd.read_json(df) if sex == 'not specified': # pragma: no cover return 0 for distance in distances: worldclasspower = getagegrouprecord( age, sex=sex, distance=distance, weightcategory=weightcategory, indf=df, ) velo = (worldclasspower/2.8)**(1./3.) if not np.isinf(worldclasspower) and not np.isnan(worldclasspower): try: duration = distance/velo wcdurations.append(duration) wcpower.append(worldclasspower) except ZeroDivisionError: # pragma: no cover pass for duration in durations: worldclasspower = getagegrouprecord( age, sex=sex, duration=duration, weightcategory=weightcategory, indf=df ) if not np.isinf(worldclasspower) and not np.isnan(worldclasspower): try: velo = (worldclasspower/2.8)**(1./3.) distance = int(60*duration*velo) wcdurations.append(60.*duration) wcpower.append(worldclasspower) except ValueError: # pragma: no cover pass update_agegroup_db(age, sex, weightcategory, wcdurations, wcpower, debug=debug) return 1 @job('default') def handle_get_garmin_file(client_id, client_secret, garmintoken, garminrefreshtoken, userid, url, filetype, *args, **kwargs): dologging('garminlog.log','Fetching URL {url}'.format(url=url)) regex = '.*\?id=(\d+)' try: # pragma: no cover m = re.compile(regex).match(url).group(1) garminid = int(m) except AttributeError: garminid = '' garmin = OAuth1Session(client_id, client_secret=client_secret, resource_owner_key=garmintoken, resource_owner_secret=garminrefreshtoken, ) filename = 'media/{code}_{id}.'.format( code=uuid4().hex[:16], id=userid )+filetype response = garmin.get(url, stream=True) if response.status_code == 200: with open(filename, 'wb') as out_file: shutil.copyfileobj(response.raw, out_file) del response uploadoptions = { 'secret': UPLOAD_SERVICE_SECRET, 'user': userid, 'file': filename, 'title': '', 'workouttype': 'water', 'boattype': '1x', 'garminid': garminid, } session = requests.session() newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'} session.headers.update(newHeaders) _ = session.post(UPLOAD_SERVICE_URL, json=uploadoptions) return 1 @job('default') def long_test_task( aantal, debug=False, myjob=None, session_key=None): # pragma: no cover myjob = self.request return longtask.longtask(aantal, jobid=myjob.id, debug=debug, session_key=session_key) @job('default') def long_test_task2( aantal, **kwargs): # pragma: no cover # debug=False,job=None,jobid='aap'): myjob = self.request job_id = myjob.id if 'jobkey' in kwargs: job_id = kwargs.pop('jobkey') kwargs['jobid'] = job_id return longtask.longtask2(aantal, **kwargs) # process and update workouts @job('default') def handle_update_empower( useremail, workoutdicts, debug=False, **kwargs): # pragma: no cover myjob = self.request job_id = myjob.id if 'jobkey' in kwargs: job_id = kwargs.pop('jobkey') aantal = len(workoutdicts) counter = 0 for workoutdict in workoutdicts: wid = workoutdict['id'] inboard = workoutdict['inboard'] oarlength = workoutdict['oarlength'] boattype = workoutdict['boattype'] f1 = workoutdict['filename'] # oarlength consistency checks will be done in view havedata = 1 try: rowdata = rdata(csvfile=f1) except IOError: try: rowdata = rdata(csvfile=f1 + '.csv') except IOError: try: rowdata = rdata(csvfile=f1 + '.gz') except IOError: havedata = 0 progressurl = SITE_URL if debug: progressurl = SITE_URL_DEV # siteurl = SITE_URL_DEV secret = PROGRESS_CACHE_SECRET kwargs['job_id'] = job_id progressurl += "/rowers/record-progress/" progressurl += job_id if havedata: _ = update_empower(wid, inboard, oarlength, boattype, rowdata.df, f1, debug=debug) counter += 1 progress = 100.*float(counter)/float(aantal) post_data = { "secret": secret, "value": progress, } _ = requests.post(progressurl, data=post_data) subject = "Rowsandall.com Your Old Empower Oarlock data have been corrected" message = """ We have updated Power and Work per Stroke data according to the instructions by Nielsen-Kellerman. """ email = EmailMessage(subject, message, 'Rowsandall ', [useremail]) if 'emailbounced' in kwargs: emailbounced = kwargs['emailbounced'] else: emailbounced = False if not emailbounced: _ = email.send() return 1 @job('default') def handle_calctrimp(id, csvfilename, ftp, sex, hrftp, hrmax, hrmin, wps_avg, debug=False, **kwargs): tss = 0 normp = 0 trimp = 0 hrtss = 0 normv = 0 normw = 0 spmtss = 0 # 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 0 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 handle_calctrimp') return 0 stub = metrics_pb2_grpc.MetricsStub(channel) req = metrics_pb2.WorkoutMetricsRequest( filename=csvfile, ftp=ftp, sex=sex, hrftp=hrftp, hrmax=hrmax, hrmin=hrmin, wpsavg=wps_avg, ) try: response = stub.CalcMetrics(req, timeout=60) except Exception as e: # pragma: no cover dologging('metrics.log',traceback.format_exc()) return 0 tss = response.tss normp = response.normp trimp = response.trimp normv = response.normv normw = response.normw hrtss = response.hrtss spmtss = response.spmtss dologging('metrics.log','File {csvfile}. Got tss {tss}, normp {normp} trimp {trimp} normv {normv} normw {normw} hrtss {hrtss} spmtss {spmtss}'.format( tss = tss, normp = normp, trimp = trimp, normv = normv, normw = normw, hrtss = hrtss, spmtss = spmtss, csvfile=csvfile, )) if np.isnan(tss): # pragma: no cover tss = 0 if np.isnan(normp): # pragma: no cover normp = 0 if np.isnan(trimp): # pragma: no cover trimp = 0 if np.isnan(normv): # pragma: no cover normv = 0 if np.isnan(normw): # pragma: no cover normw = 0 if np.isnan(hrtss): # pragma: no cover hrtss = 0 if tss > 1000: # pragma: no cover tss = 0 if trimp > 1000: # pragma: no cover trimp = 0 if normp > 2000: # pragma: no cover normp = 0 if normv > 2000: # pragma: no cover normv = 0 if normw > 10000: # pragma: no cover normw = 0 if hrtss > 1000: # pragma: no cover hrtss = 0 if spmtss > 1000: # pragma: no cover spmtss = 0 try: workout = Workout.objects.get(id=id) except Workout.DoesNotExist: # pragma: no cover dologging('metrics.log','Could not find workout {id}'.format(id=id)) return 0 workout.rscore = int(tss) workout.normp = int(normp) workout.trimp = int(trimp) workout.hrtss = int(hrtss) workout.normv = normv workout.normw = normw workout.spmtss = int(spmtss) workout.save() dologging('metrics.log','Saving to workout {id} {obscure}'.format( id = id, obscure = encoder.encode_hex(id) )) return 1 @job('default') def handle_updatedps(useremail, workoutids, debug=False, **kwargs): for wid, f1 in workoutids: havedata = 1 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: havedata = 0 if havedata: update_strokedata(wid, rowdata.df, debug=debug) subject = "Rowsandall.com Your Distance per Stroke metric has been updated" message = "All your workouts now have Distance per Stroke" email = EmailMessage(subject, message, 'Rowsandall ', [useremail]) if 'emailbounced' in kwargs: # pragma: no cover emailbounced = kwargs['emailbounced'] else: emailbounced = False if not emailbounced: _ = email.send() return 1 def sigdig(value, digits=3): try: order = int(math.floor(math.log10(math.fabs(value)))) except (ValueError, TypeError): # pragma: no cover return value # return integers as is if value % 1 == 0: # pragma: no cover return value places = digits - order - 1 if places > 0: fmtstr = "%%.%df" % (places) else: fmtstr = "%.0f" return fmtstr % (round(value, places)) @job('default') def handle_send_email_noinvoice( useremail, userfirstname, userlastname, reason, **kwargs): subject = "Idoklad: No invoice created" from_email = 'Rowsandall ' d = { 'name': userfirstname+' '+userlastname, 'siteurl': siteurl, 'reason': reason, } _ = send_template_email(from_email, ["support@rowsandall.com"], subject, 'paymentconfirmationemail_noinvoice.html', d, **kwargs) @job('default') def handle_send_email_alert( useremail, userfirstname, userlastname, rowerfirstname, rowerlastname, alertname, stats, **kwargs): if 'othertexts' in kwargs: # pragma: no cover othertexts = kwargs['othertexts'] else: othertexts = None report = {} try: report['Percentage'] = int(stats['percentage']) except KeyError: # pragma: no cover pass try: report['Number of workouts'] = int(stats['workouts']) except KeyError: # pragma: no cover pass try: report['Data set'] = "{a} strokes out of {b}".format( a=stats['nr_strokes_qualifying'], b=stats['nr_strokes'] ) except KeyError: # pragma: no cover pass try: report['Median'] = sigdig(stats['median']) except KeyError: # pragma: no cover pass try: report['Median of qualifying strokes'] = sigdig(stats['median_q']) except KeyError: # pragma: no cover pass subject = "Rowsandall.com: {alertname} ({startdate} to {enddate})".format( startdate=stats['startdate'], enddate=stats['enddate'], alertname=alertname, ) from_email = 'Rowsandall ' d = { 'report': report, 'first_name': userfirstname, 'last_name': userlastname, 'startdate': stats['startdate'], 'enddate': stats['enddate'], 'siteurl': siteurl, 'rowerfirstname': rowerfirstname, 'rowerlastname': rowerlastname, 'alertname': alertname, 'othertexts': othertexts, } _ = send_template_email(from_email, [useremail], subject, 'alertemail.html', d, **kwargs) return 1 @job('default') def handle_send_email_transaction( username, useremail, amount, **kwargs): subject = "Rowsandall Payment Confirmation" from_email = 'Rowsandall ' d = { 'name': username, 'siteurl': siteurl, 'amount': amount, } _ = send_template_email(from_email, [useremail], subject, 'paymentconfirmationemail.html', d, **kwargs) return 1 @job('default') def handle_send_email_instantplan_notification( username, useremail, amount, planname, startdate, enddate, **kwargs ): # pragma: no cover subject = "Rowsandall Instant Plan Notification" from_email = 'Rowsandall ' d = { 'name': username, 'siteurl': siteurl, 'amount': amount, 'planname': planname, 'startdate': startdate, 'enddate': enddate, } _ = send_template_email(from_email, ['roosendaalsander@gmail.com'], subject, 'instantplansold.html', d, **kwargs) return 1 @job('default') def handle_send_email_failed_cancel( name, email, username, id, **kwargs): subject = "Rowsandall Subscription Cancellation Error" from_email = 'Rowsandall ' d = { 'name': name, 'siteurl': siteurl, 'email': email, 'username': username, 'id': id, } _ = send_template_email(from_email, ["support@rowsandall.com"], subject, 'cancel_subscription_fail_email.html', d, **kwargs) return 1 @job('default') def handle_send_email_subscription_update( username, useremail, planname, recurring, price, amount, end_of_billing_period, method, **kwargs): from_email = 'Rowsandall ' d = { 'name': username, 'siteurl': siteurl, 'amount': amount, 'price': price, 'planname': planname, 'recurring': recurring, 'end_of_billing_period': end_of_billing_period, } if method == 'down': template_name = 'subscription_downgrade_email.html' notification_template_name = 'subscription_downgrade_notification.html' subject = "Rowsandall Change Confirmation" else: template_name = 'subscription_update_email.html' notification_template_name = 'subscription_update_notification.html' subject = "Rowsandall Payment Confirmation" _ = send_template_email(from_email, [useremail], subject, template_name, d, **kwargs) _ = send_template_email(from_email, ['info@rowsandall.com'], 'Subscription Update Notification', notification_template_name, d, **kwargs) return 1 @job('default') def handle_send_email_subscription_create( username, useremail, planname, recurring, price, amount, end_of_billing_period, **kwargs): subject = "Rowsandall Payment Confirmation" from_email = 'Rowsandall ' d = { 'name': username, 'siteurl': siteurl, 'amount': amount, 'price': price, 'planname': planname, 'end_of_billing_period': end_of_billing_period, 'recurring': recurring, } _ = send_template_email(from_email, [useremail], subject, 'subscription_create_email.html', d, **kwargs) _ = send_template_email(from_email, ['info@rowsandall.com'], 'Subscription Update Notification', 'subscription_create_notification.html', d, **kwargs) return 1 @job('default') def handle_sendemail_raceregistration( useremail, username, registeredname, racename, raceid, **kwargs): subject = "A new competitor has registered for virtual challenge {n}".format( n=racename ) from_email = 'Rowsandall ' d = { 'username': username, 'registeredname': registeredname, 'siteurl': siteurl, 'racename': racename, 'raceid': raceid, } _ = send_template_email(from_email, [useremail], subject, 'raceregisteredemail.html', d, **kwargs) return 1 def handle_sendemail_coursesucceed(useremail, username, logfile, workoutid, **kwargs): # pragma: no cover subject = "The validation of your course has succeeded" from_email = 'Rowsandall ' d = { 'username': username, 'workoutid': encoder.encode_hex(workoutid), } _ = send_template_email(from_email, [useremail], subject, 'trajectorysuccessemail.html', d, attach_file=logfile, **kwargs) return 1 def handle_sendemail_coursefail( useremail, username, logfile, **kwargs): subject = "The validation of your course has failed" from_email = 'Rowsandall ' d = { 'username': username, } _ = send_template_email(from_email, [useremail], subject, 'trajectoryfailemail.html', d, cc=['info@rowsandall.com'], attach_file=logfile, **kwargs) return 1 @job('default') def handle_sendemail_optout( useremail, username, registeredname, racename, raceid, **kwargs): subject = "{name} has opted out from social media posts around challenge {n}".format( n=racename, name=registeredname ) from_email = 'Rowsandall ' d = { 'username': username, 'registeredname': registeredname, 'siteurl': siteurl, 'racename': racename, 'raceid': raceid, } _ = send_template_email(from_email, [useremail], subject, 'raceoptoutsocialmedia.html', d, **kwargs) return 1 @job('default') def handle_sendemail_racesubmission( useremail, username, registeredname, racename, raceid, **kwargs): subject = "A new result has been submitted for virtual challenge {n}".format( n=racename ) from_email = 'Rowsandall ' d = { 'username': username, 'siteurl': siteurl, 'registeredname': registeredname, 'racename': racename, 'raceid': raceid, } _ = send_template_email(from_email, [useremail], subject, 'racesubmissionemail.html', d, **kwargs) return 1 @job('default') def handle_send_disqualification_email( useremail, username, reason, message, racename, **kwargs): subject = "Your result for {n} has been disqualified on rowsandall.com".format( n=racename ) from_email = 'Rowsandall ' d = { 'username': username, 'reason': reason, 'siteurl': siteurl, 'message': htmlstrip(message), 'racename': racename, } _ = send_template_email(from_email, [useremail], subject, 'disqualificationemail.html', d, **kwargs) return 1 @job('default') def handle_send_withdraw_email( useremail, username, reason, message, racename, **kwargs): subject = "Your result for {n} has been removed on rowsandall.com".format( n=racename ) from_email = 'Rowsandall ' d = { 'username': username, 'reason': reason, 'siteurl': siteurl, 'message': htmlstrip(message), 'racename': racename, } _ = send_template_email(from_email, [useremail], subject, 'withdraw_email.html', d, **kwargs) return 1 @job('default') def handle_sendemail_expired(useremail, userfirstname, userlastname, expireddate, **kwargs): subject = "Your rowsandall.com paid account has expired" from_email = 'Rowsandall ' d = { 'first_name': userfirstname, 'last_name': userlastname, 'siteurl': siteurl, 'expireddate': expireddate, } _ = send_template_email(from_email, [useremail], subject, 'accountexpiredemail.html', d, cc=['support@rowsandall.com'], **kwargs) return 1 @job('default') def handle_sendemail_newftp(rower,power,mode, **kwargs): # pragma: no cover subject = "You may want to update your FTP on rowsandall.com" from_email = 'Rowsandall ' power = int(power) d = { 'first_name': rower.user.first_name, 'last_name': rower.user.last_name, 'siteurl': siteurl, 'ftp': rower.ftp, 'newftp': power, } _ = send_template_email(from_email, [rower.user.email], subject, 'newftpemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_breakthrough(workoutid, useremail, userfirstname, userlastname, btvalues=pd.DataFrame().to_json(), surname=False, **kwargs): dologging('tasks.log',btvalues) btvalues = pd.read_json(btvalues) try: btvalues.sort_values('delta', axis=0, inplace=True) except KeyError: dologging('tasks.log','KeyError') return 0 lastname = '' if surname: # pragma: no cover lastname = userlastname tablevalues = [ {'delta': t.delta, 'time': str(timedelta(seconds=t.delta)), 'cpvalue': t.cpvalues, 'pwr': t.pwr } for t in btvalues.itertuples() ] # send email with attachment subject = "A breakthrough workout on rowsandall.com" from_email = 'Rowsandall ' d = { 'first_name': userfirstname, 'last_name': lastname, 'siteurl': siteurl, 'workoutid': encoder.encode_hex(workoutid), 'btvalues': tablevalues, } dologging('tasks.log',siteurl) dologging('tasks.log',json.dumps(tablevalues)) _ = send_template_email(from_email, [useremail], subject, 'breakthroughemail.html', d, **kwargs) return 1 # send email when a breakthrough workout is uploaded @job('default') def handle_sendemail_hard(workoutid, useremail, userfirstname, userlastname, btvalues=pd.DataFrame().to_json(), surname=False, debug=False, **kwargs): btvalues = pd.read_json(btvalues) try: btvalues.sort_values('delta', axis=0, inplace=True) except KeyError: return 0 tablevalues = [ {'delta': t.delta, 'cpvalue': t.cpvalues, 'pwr': t.pwr } for t in btvalues.itertuples() ] lastname = '' if surname: # pragma: no cover lastname = userlastname # send email with attachment subject = "That was a pretty hard workout on rowsandall.com" from_email = 'Rowsandall ' d = { 'first_name': userfirstname, 'last_name': lastname, 'siteurl': siteurl, 'workoutid': encoder.encode_hex(workoutid), 'btvalues': tablevalues, } _ = send_template_email(from_email, [useremail], subject, 'hardemail.html', d, **kwargs) return 1 # send email when user deletes account @job('default') def handle_sendemail_userdeleted(name, email, debug=False, **kwargs): fullemail = 'roosendaalsander@gmail.com' subject = 'User account deleted' message = 'Sander,\n\n' message += 'The user {name} ({email}) has just deleted his account'.format( name=name, email=email ) email = EmailMessage(subject, message, 'Rowsandall ', [fullemail]) if 'emailbounced' in kwargs: # pragma: no cover emailbounced = kwargs['emailbounced'] else: emailbounced = False if not emailbounced: _ = email.send() return 1 # send email to me when an unrecognized file is uploaded @job('default') def handle_sendemail_unrecognized(unrecognizedfile, useremail, debug=False, **kwargs): # send email with attachment fullemail = 'roosendaalsander@gmail.com' subject = "Unrecognized file from Rowsandall.com" message = "Dear Sander,\n\n" message += "Please find attached a file that someone tried to upload to rowsandall.com." message += " The file was not recognized as a valid file type.\n\n" message += "User Email " + useremail + "\n\n" message += "Best Regards, the Rowsandall Team" email = EmailMessage(subject, message, 'Rowsandall ', [fullemail]) try: email.attach_file(unrecognizedfile) except IOError: # pragma: no cover pass if 'emailbounced' in kwargs: # pragma: no cover emailbounced = kwargs['emailbounced'] else: emailbounced = False if not emailbounced: _ = email.send() # remove tcx file try: os.remove(unrecognizedfile) except: # pragma: no cover pass return 1 # send email to owner when an unrecognized file is uploaded @job('default') def handle_sendemail_unrecognizedowner(useremail, userfirstname, debug=False, **kwargs): # send email with attachment fullemail = useremail subject = "Unrecognized file from Rowsandall.com" d = { 'first_name': userfirstname, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'unrecognizedemail.html', d, **kwargs) return 1 @job('default') def handle_sendemailics(first_name, last_name, email, icsfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "Calendar File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'icsemail.html', d, attach_file=icsfile, **kwargs) os.remove(icsfile) return 1 @job('default') def handle_sendemailkml(first_name, last_name, email, kmlfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'kmlemail.html', d, attach_file=kmlfile, **kwargs) os.remove(kmlfile) return 1 # Send email with TCX attachment @job('default') def handle_sendemailtcx(first_name, last_name, email, tcxfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'tcxemail.html', d, attach_file=tcxfile, **kwargs) os.remove(tcxfile) return 1 @job('default') def handle_zip_file(emailfrom, subject, file, **kwargs): # pragma: no cover message = "... zip processing ... " try: debug = kwargs['debug'] except KeyError: debug = False if debug: print(message) email = EmailMessage(subject, message, emailfrom, ['workouts@rowsandall.com']) email.attach_file(file) if debug: print("attaching") _ = email.send() if debug: print("sent") time.sleep(60) return 1 # Send email with CSV attachment @job('default') def handle_sendemailsummary(first_name, last_name, email, csvfile, **kwargs): fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'summarymail.html', d, attach_file=csvfile, **kwargs) try: os.remove(csvfile) except: # pragma: no cover pass return 1 @job('default') def handle_sendemailcsv(first_name, last_name, email, csvfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'csvemail.html', d, attach_file=csvfile, **kwargs) return 1 @job('default') def handle_sendemail_ical(first_name, last_name, email, url, icsfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "Calendar File for your sessions from Rowsandall.com" if 'debug' in kwargs: # pragma: no cover debug = kwargs['debug'] else: debug = False siteurl = SITE_URL if debug: # pragma: no cover # progressurl = SITE_URL_DEV siteurl = SITE_URL_DEV d = {'first_name': first_name, 'siteurl': siteurl, 'url': url, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'icsemail.html', d, attach_file=icsfile, **kwargs) try: os.remove(icsfile) except: # pragma: no cover pass return 1 @job('default') def handle_sendemailfile(first_name, last_name, email, csvfile, **kwargs): # send email with attachment fullemail = first_name + " " + last_name + " " + "<" + email + ">" subject = "File from Rowsandall.com" d = {'first_name': first_name, 'siteurl': siteurl, } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'fileemail.html', d, attach_file=csvfile, **kwargs) if 'delete' in kwargs: # pragma: no cover dodelete = kwargs['delete'] else: dodelete = False if dodelete: # pragma: no cover try: os.remove(csvfile) except: pass return 1 # Calculate wind and stream corrections for OTW rowing @job('default') def handle_otwsetpower( f1, boattype, boatclass, coastalbrand, weightvalue, first_name, last_name, email, workoutid, **kwargs): myjob = self.request job_id = myjob.id if 'jobkey' in kwargs: job_id = kwargs.pop('jobkey') if 'ps' in kwargs: # pragma: no cover ps = kwargs['ps'] else: ps = [1, 1, 1, 1] if 'ratio' in kwargs: # pragma: no cover ratio = kwargs['ratio'] else: ratio = 1.0 if 'debug' in kwargs: # pragma: no cover debug = kwargs['debug'] else: debug = False kwargs['jobid'] = job_id weightvalue = float(weightvalue) # check what the real file name is if os.path.exists(f1): csvfile = f1 elif os.path.exists(f1+'.csv'): # pragma: no cover csvfile = f1+'.csv' elif os.path.exists(f1+'.gz'): # pragma: no cover csvfile = f1+'.gz' else: # pragma: no cover return 0 csvfile = os.path.abspath(csvfile) # do something with boat type try: rowdata = rdata(csvfile=csvfile) except IOError: # pragma: no cover try: rowdata = rdata(csvfile=csvfile) except IOError: rowdata = rdata(csvfile=csvfile) # do calculation, but do not overwrite NK Empower Power data powermeasured = False try: # pragma: no cover w = rowdata.df['wash'] if w.mean() != 0: powermeasured = True except KeyError: pass progressurl = SITE_URL siteurl = SITE_URL if debug: # pragma: no cover progressurl = SITE_URL_DEV siteurl = SITE_URL_DEV secret = PROGRESS_CACHE_SECRET progressurl += "/rowers/record-progress/" progressurl += job_id+'/' # do something (this should return from go service) with grpc.insecure_channel( target='localhost:50051', 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 return 0 stub = calculator_pb2_grpc.PowerStub(channel) response = stub.CalcPower(calculator_pb2.WorkoutPowerRequest( filename=csvfile, boattype=boattype, coastalbrand=coastalbrand, crewmass=weightvalue, powermeasured=powermeasured, progressurl=progressurl, secret=secret, silent=False, boatclass=boatclass, ), timeout=1200) result = response.result if result == 0: # pragma: no cover # send failure email return 0 # do something with boat type try: rowdata = rdata(csvfile=csvfile) except IOError: # pragma: no cover try: rowdata = rdata(csvfile=csvfile) except IOError: rowdata = rdata(csvfile=csvfile) update_strokedata(workoutid, rowdata.df, debug=debug) totaltime = rowdata.df['TimeStamp (sec)'].max( ) - rowdata.df['TimeStamp (sec)'].min() try: totaltime = totaltime + rowdata.df.loc[0, ' ElapsedTime (sec)'] except KeyError: # pragma: no cover pass df = getsmallrowdata_pd( ['power', 'workoutid', 'time'], ids=[workoutid], debug=debug) thesecs = totaltime maxt = 1.05 * thesecs logarr = datautils.getlogarr(maxt) dfgrouped = df.groupby(['workoutid']) delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) res, btvalues, res2 = utils.isbreakthrough( delta, cpvalues, ps[0], ps[1], ps[2], ps[3], ratio) if res: # pragma: no cover handle_sendemail_breakthrough( workoutid, email, first_name, last_name, btvalues=btvalues.to_json()) subject = "Your OTW Physics Calculations are ready" from_email = 'Rowsandall ' fullemail = first_name + " " + last_name + " " + "<" + email + ">" d = { 'first_name': first_name, 'siteurl': siteurl, 'workoutid': encoder.encode_hex(workoutid), } _ = send_template_email(from_email, [fullemail], subject, 'otwpoweremail.html', d, **kwargs) return 1 @job('default') def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename, debug=False, **kwargs): hrmax = hrdata['hrmax'] hrut2 = hrdata['hrut2'] hrut1 = hrdata['hrut1'] hrat = hrdata['hrat'] hrtr = hrdata['hrtr'] hran = hrdata['hran'] ftp = hrdata['ftp'] powerzones = deserialize_list(hrdata['powerzones']) hrzones = deserialize_list(hrdata['hrzones']) powerperc = np.array(deserialize_list(hrdata['powerperc'])).astype(float) rr = rowingdata.rower(hrmax=hrmax, hrut2=hrut2, hrut1=hrut1, hrat=hrat, hrtr=hrtr, hran=hran, ftp=ftp, powerperc=powerperc, powerzones=powerzones, hrzones=hrzones) try: row = rdata(csvfile=f2, rower=rr) except: # pragma: no cover row = rdata(csvfile=f2 + '.gz', rower=rr) try: haspower = row.df[' Power (watts)'].mean() > 50 except (TypeError, KeyError): haspower = False oterange = kwargs.pop('oterange', [85, 240]) otwrange = kwargs.pop('otwrange', [85, 185]) nr_rows = len(row.df) if (plotnr in [1, 2, 4, 5, 8, 11, 9, 12]) and (nr_rows > 1200): # pragma: no cover bin = int(nr_rows / 1200.) try: df = row.df.select_dtypes(['number']) df = df.groupby(lambda x: x / bin).mean() row.df = df except: pass nr_rows = len(row.df) if (plotnr == 1): fig1 = row.get_timeplot_erg(t, pacerange=oterange, **kwargs) elif (plotnr == 2): fig1 = row.get_metersplot_erg(t, pacerange=oterange, **kwargs) elif (plotnr == 3): try: t += ' - Heart Rate Distribution' except TypeError: # pragma: no cover t = 'Heart Rate Distribution' fig1 = row.get_piechart(t, **kwargs) elif (plotnr == 4): if haspower: # pragma: no cover fig1 = row.get_timeplot_otwempower(t, pacerange=otwrange, **kwargs) else: fig1 = row.get_timeplot_otw(t, pacerange=otwrange, **kwargs) elif (plotnr == 5): if haspower: # pragma: no cover fig1 = row.get_metersplot_otwempower( t, pacerange=otwrange, **kwargs) else: fig1 = row.get_metersplot_otw(t, pacerange=otwrange, **kwargs) elif (plotnr == 6): t += ' - Heart Rate Distribution' fig1 = row.get_piechart(t, **kwargs) elif (plotnr == 7) or (plotnr == 10): fig1 = row.get_metersplot_erg2(t, **kwargs) elif (plotnr == 8) or (plotnr == 11): fig1 = row.get_timeplot_erg2(t, **kwargs) elif (plotnr == 9) or (plotnr == 12): fig1 = row.get_time_otwpower(t, pacerange=otwrange, **kwargs) elif (plotnr == 13) or (plotnr == 16): t += ' - Power Distribution' fig1 = row.get_power_piechart(t, **kwargs) if fig1 is None: # pragma: no cover return 0 canvas = FigureCanvas(fig1) canvas.print_figure('static/plots/' + imagename) plt.close(fig1) fig1.clf() gc.collect() return imagename # Team related remote tasks @job('default') def handle_sendemail_coachrequest(email, name, code, coachname, debug=False, **kwargs): fullemail = email subject = 'Invitation to add {n} to your athletes'.format(n=name) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'code': code, 'siteurl': siteurl } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'coachrequestemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_coachoffer_rejected(coachemail, coachname, name, debug=False, **kwargs): fullemail = coachemail subject = '{n} has rejected your offer to be his coach on rowsandall.com'.format( n=name) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'coachofferrejectedemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_coachrequest_rejected(email, coachname, name, debug=False, **kwargs): fullemail = email subject = '{n} has rejected your coaching request on rowsandall.com'.format( n=coachname) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'coachrequestrejectedemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_coachrequest_accepted(email, coachname, name, debug=False, **kwargs): fullemail = email subject = '{n} has accepted your coaching request on rowsandall.com'.format( n=coachname) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'coachrequestacceptedemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_coachoffer_accepted(coachemail, coachname, name, debug=False, **kwargs): fullemail = coachemail subject = '{n} has accepted your coaching offer on rowsandall.com'.format( n=name) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'coachofferacceptedemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_coacheerequest(email, name, code, coachname, debug=False, **kwargs): fullemail = email subject = '{n} requests coach access to your data on rowsandall.com'.format( n=coachname) from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'coach': coachname, 'code': code, 'siteurl': siteurl } _ = send_template_email(from_email, [fullemail], subject, 'coacheerequestemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_invite(email, name, code, teamname, manager, debug=False, **kwargs): fullemail = email subject = 'Invitation to join team ' + teamname siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'name': name, 'manager': manager, 'code': code, 'teamname': teamname, 'siteurl': siteurl } from_email = 'Rowsandall ' _ = send_template_email(from_email, [fullemail], subject, 'teaminviteemail.html', d, **kwargs) return 1 @job('default') def handle_sendemailnewresponse(first_name, last_name, email, commenter_first_name, commenter_last_name, comment, workoutname, workoutid, commentid, debug=False, **kwargs): fullemail = email from_email = 'Rowsandall ' subject = 'New comment on session ' + workoutname comment = u''+comment siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV sessiontype = 'workout' if 'sessiontype' in kwargs: # pragma: no cover sessiontype = kwargs.pop('sessiontype') commentlink = '/rowers/workout/{workoutid}/comment/'.format( workoutid=encoder.encode_hex(workoutid)) if 'commentlink' in kwargs: # pragma: no cover commentlink = kwargs.pop('commentlink') d = { 'first_name': first_name, 'commenter_first_name': commenter_first_name, 'commenter_last_name': commenter_last_name, 'comment': comment, 'workoutname': workoutname, 'siteurl': siteurl, 'workoutid': workoutid, 'commentid': commentid, 'sessiontype': sessiontype, 'commentlink': commentlink, } _ = send_template_email(from_email, [fullemail], subject, 'teamresponseemail.html', d, **kwargs) return 1 @job('default') def handle_sendemailnewcomment(first_name, last_name, email, commenter_first_name, commenter_last_name, comment, workoutname, workoutid, debug=False, **kwargs): fullemail = email from_email = 'Rowsandall ' subject = 'New comment on session ' + workoutname comment = u''+comment siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV sessiontype = 'workout' if 'sessiontype' in kwargs: # pragma: no cover sessiontype = kwargs.pop('sessiontype') commentlink = '/rowers/workout/{workoutid}/comment/'.format( workoutid=encoder.encode_hex(workoutid)) if 'commentlink' in kwargs: # pragma: no cover commentlink = kwargs.pop('commentlink') d = { 'first_name': first_name, 'commenter_first_name': commenter_first_name, 'commenter_last_name': commenter_last_name, 'comment': comment, 'workoutname': workoutname, 'siteurl': siteurl, 'workoutid': encoder.encode_hex(workoutid), 'sessiontype': sessiontype, 'commentlink': commentlink, } _ = send_template_email(from_email, [fullemail], subject, 'teamresponseemail.html', d, **kwargs) return 1 @job('default') def handle_send_template_email(template, email, fromemail, rowername, subject, message, debug=False, **kwargs): fullemail = [email] d = { 'message': message, 'rowername': rowername, } _ = send_template_email('Rowsandall ', ['info@rowsandall.com'], subject, template, d, cc=[fromemail], bcc=fullemail, **kwargs) return 1 @job('default') def handle_sendemail_message(email, fromemail, rowername, message, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'New message from team ' + teamname from_email = 'Rowsandall ' d = { 'rowername': rowername, 'teamname': teamname, 'managername': managername, 'message': message, } _ = send_template_email(from_email, [fullemail], subject, 'teammessage.html', d, **kwargs) return 1 @job('default') def handle_sendemail_request(email, name, code, teamname, requestor, id, debug=False, **kwargs): fullemail = email subject = 'Request to join team ' + teamname from_email = 'Rowsandall ' siteurl = SITE_URL if debug: # pragma: no cover siteurl = SITE_URL_DEV d = { 'requestor': requestor, 'teamname': teamname, 'code': code, 'siteurl': siteurl, 'id': id, 'first_name': name, } _ = send_template_email(from_email, [fullemail], subject, 'teamrequestemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_request_accept(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'Welcome to ' + teamname from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teamwelcomeemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_request_reject(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'Your application to ' + teamname + ' was rejected' from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teamrejectemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_member_dropped(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'You were removed from ' + teamname from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teamdropemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_team_removed(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'You were removed from ' + teamname from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teamremoveemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_invite_reject(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'Your invitation to ' + name + ' was rejected' from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teaminviterejectemail.html', d, **kwargs) return 1 @job('default') def handle_sendemail_invite_accept(email, name, teamname, managername, debug=False, **kwargs): fullemail = email subject = 'Your invitation to ' + name + ' was accepted' from_email = 'Rowsandall ' siteurl = SITE_URL if debug: siteurl = SITE_URL_DEV d = { 'first_name': name, 'managername': managername, 'teamname': teamname, 'siteurl': siteurl, } _ = send_template_email(from_email, [fullemail], subject, 'teaminviteacceptemail.html', d, **kwargs) return 1 # Another simple task for debugging purposes def add2(x, y, debug=False, **kwargs): # pragma: no cover return x + y @job('default') def handle_update_wps(rid, types, ids, mode, debug=False, **kwargs): df = read_data(['time', 'driveenergy'], ids=ids) try: 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 wps_median = 0 except OverflowError: wps_median = 0 except ColumnNotFoundError: wps_median = 0 except TypeError: wps_median = 0 except ComputeError: wps_median = 0 return wps_median @job('default') def handle_nk_async_workout(alldata, userid, nktoken, nkid, delaysec, defaulttimezone, debug=False, **kwargs): time.sleep(delaysec) s = 'Importing from NK Logbook ID {nkid}'.format(nkid=nkid) dologging('nklog.log', s) try: data = alldata[nkid] except KeyError: # pragma: no cover try: data = alldata[int(nkid)] except KeyError: return 0 params = { 'sessionIds': nkid, } authorizationstring = str('Bearer ' + nktoken) headers = {'Authorization': authorizationstring, 'user-agent': 'sanderroosendaal', 'Content-Type': 'application/json', } # get strokes url = NK_API_LOCATION+"api/v1/sessions/strokes" response = requests.get(url, headers=headers, params=params) if response.status_code != 200: # pragma: no cover # error handling and logging dologging('nklog.log', 'Response status code {code}'.format( code=response.status_code)) return 0 jsonData = response.json() try: strokeData = jsonData[str(nkid)] except KeyError: # pragma: no cover dologging('nklog.log','Could not find strokeData') return 0 seatNumber = 1 try: oarlockSessions = data['oarlockSessions'] if oarlockSessions: oarlocksession = oarlockSessions[0] seatNumber = oarlocksession['seatNumber'] except KeyError: # pragma: no cover pass df = strokeDataToDf(strokeData, seatIndex=seatNumber) csvfilename = 'media/{code}_{nkid}.csv.gz'.format( nkid=nkid, code=uuid4().hex[:16] ) df.to_csv(csvfilename, index_label='index', compression='gzip') workoutid, error = add_workout_from_data(userid, nkid, data, df) # dologging('nklog.log','NK Workout ID {id}'.format(id=workoutid)) if workoutid == 0: # pragma: no cover return 0 try: workout = Workout.objects.get(id=workoutid) newnkid = workout.uploadedtonk sr = create_or_update_syncrecord(workout.user, workout, nkid=newnkid) except Workout.DoesNotExist: # pragma: no cover pass return workoutid @job('default') def handle_intervals_updateworkout(workout, debug=False, **kwargs): rower = workout.user intervalstoken = rower.intervals_token authorizationstring = str('Bearer ' + intervalstoken) headers = { 'authorization': authorizationstring } thetype = mytypes.intervalsmapping[workout.workouttype] jsondict = {'type': thetype} subtype = workout.sub_type if subtype == "Warming Up": jsondict['sub_type'] = "WARMUP" elif subtype == "Cooling Down": jsondict['sub_type'] = "COOLDOWN" elif subtype == "Commute": jsondict['commute'] = True jsondict['sub_type'] = "COMMUTE" else: jsondict['sub_type'] = "NONE" jsondict['name'] = workout.name if workout.rpe is not None and workout.rpe > 0: jsondict['icu_rpe'] = workout.rpe jsondict['commute'] = workout.is_commute if workout.plannedsession: jsondict['paired_event_id'] = workout.plannedsession.intervals_icu_id url = "https://intervals.icu/api/v1/activity/{activityid}".format(activityid=workout.uploadedtointervals) response = requests.put(url, headers=headers, json=jsondict) response = requests.put(url, headers=headers, json=jsondict) if response.status_code not in [200, 201]: dologging('intervals.icu.log', response.reason) return 0 @job('default') def fetch_rojabo_session(id,alldata,userid,rowerid,debug=False, **kwargs): # pragma: no cover try: item = alldata[id] except KeyError: return 0 return 1