Private
Public Access
1
0

Merge branch 'release/v18.9.7'

This commit is contained in:
Sander Roosendaal
2023-01-18 20:30:32 +01:00
11 changed files with 501 additions and 177 deletions
+9 -4
View File
@@ -1214,7 +1214,12 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
delta = datetime.timedelta( delta = datetime.timedelta(
seconds=totaltime) seconds=totaltime)
workoutenddatetime = workoutstartdatetime+delta try:
workoutenddatetime = workoutstartdatetime+delta
except AttributeError as e:
workoutstartdatetime = pendulum.parse(str(workoutstartdatetime))
workoutenddatetime = workoutstartdatetime+delta
# check for duplicate start times and duration # check for duplicate start times and duration
duplicate = checkduplicates( duplicate = checkduplicates(
@@ -1570,7 +1575,7 @@ def get_existing_job(w):
def workout_trimp(w, reset=False): def workout_trimp(w, reset=False):
r = w.user r = w.user
if w.trimp > 0 and not reset: if w.trimp > -1 and not reset:
return w.trimp, w.hrtss return w.trimp, w.hrtss
if get_existing_job(w): if get_existing_job(w):
@@ -1615,7 +1620,7 @@ def workout_trimp(w, reset=False):
def workout_rscore(w, reset=False): def workout_rscore(w, reset=False):
if w.rscore > 0 and not reset: if w.rscore > -1 and not reset:
return w.rscore, w.normp return w.rscore, w.normp
if get_existing_job(w): if get_existing_job(w):
@@ -1646,7 +1651,7 @@ def workout_rscore(w, reset=False):
def workout_normv(w, pp=4.0): def workout_normv(w, pp=4.0):
if w.normv > 0: # pragma: no cover if w.normv > -1: # pragma: no cover
return w.normv, w.normw return w.normv, w.normw
if get_existing_job(w): if get_existing_job(w):
+12 -2
View File
@@ -67,6 +67,7 @@ from rowers.models import (
) )
import pytz import pytz
from pytz.exceptions import UnknownTimeZoneError
import collections import collections
import pendulum import pendulum
from rowingdata import rowingdata as rrdata from rowingdata import rowingdata as rrdata
@@ -915,6 +916,12 @@ parsers = {
def get_startdate_time_zone(r, row, startdatetime=None): def get_startdate_time_zone(r, row, startdatetime=None):
if isinstance(startdatetime, str):
try:
startdatetime = pendulum.parse(startdatetime)
except:
dologging('debuglog.log','Could not parse start date time '+startdatetime)
if startdatetime is not None and startdatetime != '': if startdatetime is not None and startdatetime != '':
try: try:
timezone_str = pendulum.instance(startdatetime).timezone.name timezone_str = pendulum.instance(startdatetime).timezone.name
@@ -969,8 +976,11 @@ def get_startdate_time_zone(r, row, startdatetime=None):
else: else:
timezone_str = str(startdatetime.tzinfo) timezone_str = str(startdatetime.tzinfo)
startdatetime = startdatetime.astimezone(pytz.timezone(timezone_str)) try:
startdatetime = startdatetime.astimezone(pytz.timezone(timezone_str))
except UnknownTimeZoneError:
startdatetime = startdatetime.astimezone(pytz.utc)
startdate = startdatetime.strftime('%Y-%m-%d') startdate = startdatetime.strftime('%Y-%m-%d')
starttime = startdatetime.strftime('%H:%M:%S') starttime = startdatetime.strftime('%H:%M:%S')
+8 -8
View File
@@ -1524,16 +1524,16 @@ def getfatigues(
for w in ws: for w in ws:
if getattr(w, metricchoice) > 0: if getattr(w, metricchoice) > 0:
weight += getattr(w, metricchoice) weight += getattr(w, metricchoice)
if getattr(w, metricchoice) <= 0: if getattr(w, metricchoice) == 0:
if metricchoice == 'rscore' and w.hrtss > 0: # pragma: no cover if metricchoice == 'rscore' and w.hrtss > 0: # pragma: no cover
weight += w.hrtss weight += w.hrtss
else: if getattr(w, metricchoice) < 0:
trimp, hrtss = dataprep.workout_trimp(w) trimp, hrtss = dataprep.workout_trimp(w)
rscore, normp = dataprep.workout_rscore(w) rscore, normp = dataprep.workout_rscore(w)
if w.rpe and w.rpe > 0: # pragma: no cover if w.rpe and w.rpe > 0: # pragma: no cover
dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second
dd = dd/3600 dd = dd/3600
weight += rpetotss[w.rpe]*dd weight += rpetotss[w.rpe]*dd
impulses.append(weight) impulses.append(weight)
+13 -2
View File
@@ -9,13 +9,15 @@ from rowsandall_app.settings import (
RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET, RP3_CLIENT_ID, RP3_CLIENT_KEY, RP3_REDIRECT_URI, RP3_CLIENT_SECRET,
UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET UPLOAD_SERVICE_URL, UPLOAD_SERVICE_SECRET
) )
from rowers.utils import myqueue from rowers.utils import myqueue, NoTokenError
# All the functionality needed to connect to Runkeeper # All the functionality needed to connect to Runkeeper
from rowers.imports import * from rowers.imports import *
# Python # Python
import gzip import gzip
from datetime import timedelta
import base64 import base64
from io import BytesIO from io import BytesIO
@@ -48,6 +50,11 @@ graphql_url = "https://rp3rowing-app.com/graphql"
# Checks if user has UnderArmour token, renews them if they are expired # Checks if user has UnderArmour token, renews them if they are expired
def rp3_open(user): def rp3_open(user):
tokenexpirydate = user.rower.rp3tokenexpirydate
if timezone.now()-timedelta(days=120)>tokenexpirydate:
user.rower.rp3tokenexpirydate = timezone.now()-timedelta(days=1)
user.rower.save()
raise NoTokenError
return imports_open(user, oauth_data) return imports_open(user, oauth_data)
# Refresh ST token using refresh token # Refresh ST token using refresh token
@@ -137,8 +144,12 @@ def get_rp3_workouts(rower, do_async=True): # pragma: no cover
w.uploadedtorp3 for w in Workout.objects.filter(user=rower) w.uploadedtorp3 for w in Workout.objects.filter(user=rower)
]) ])
dologging('rp3_import.log',rp3ids)
newids = [rp3id for rp3id in rp3ids if rp3id not in knownrp3ids] newids = [rp3id for rp3id in rp3ids if rp3id not in knownrp3ids]
dologging('rp3_import.log',newids)
for id in newids: for id in newids:
startdatetime = workouts_list.loc[id, 'executed_at'] startdatetime = workouts_list.loc[id, 'executed_at']
dologging('rp3_import.log', startdatetime) dologging('rp3_import.log', startdatetime)
@@ -150,7 +161,7 @@ def get_rp3_workouts(rower, do_async=True): # pragma: no cover
auth_token, auth_token,
id, id,
startdatetime, startdatetime,
10, 20,
) )
return 1 return 1
+8
View File
@@ -2967,6 +2967,7 @@ def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempt
headers=headers, headers=headers,
json={'query': get_download_link} json={'query': get_download_link}
) )
dologging('rp3_import.log',response.status_code)
if response.status_code != 200: # pragma: no cover if response.status_code != 200: # pragma: no cover
have_link = True have_link = True
@@ -2974,6 +2975,7 @@ def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempt
try: try:
workout_download_details = pd.json_normalize( workout_download_details = pd.json_normalize(
response.json()['data']['download']) response.json()['data']['download'])
dologging('rp3_import.log', response.json())
except: # pragma: no cover except: # pragma: no cover
return 0 return 0
@@ -2981,8 +2983,12 @@ def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempt
download_url = workout_download_details.iat[0, 2] download_url = workout_download_details.iat[0, 2]
have_link = True have_link = True
dologging('rp3_import.log', download_url)
counter += 1 counter += 1
dologging('rp3_import.log', counter)
if counter > max_attempts: # pragma: no cover if counter > max_attempts: # pragma: no cover
have_link = True have_link = True
@@ -2994,6 +3000,8 @@ def handle_rp3_async_workout(userid, rp3token, rp3id, startdatetime, max_attempt
filename = 'media/RP3Import_'+str(rp3id)+'.csv' filename = 'media/RP3Import_'+str(rp3id)+'.csv'
res = requests.get(download_url, headers=headers) res = requests.get(download_url, headers=headers)
dologging('rp3_import.log','tasks.py '+str(rp3id))
dologging('rp3_import.log',startdatetime)
if not startdatetime: # pragma: no cover if not startdatetime: # pragma: no cover
startdatetime = str(timezone.now()) startdatetime = str(timezone.now())
+84
View File
@@ -658,6 +658,90 @@
fails a test, its values are silently replaced by zeros.</p> fails a test, its values are silently replaced by zeros.</p>
</td> </td>
</tr> </tr>
<tr>
<td>Stroke Data (v3)</td>
<td><a href="/rowers/api/v3/workouts/">/rowers/api/v3/workouts/</a>
</td>
<td>POST</td>
<td>
<pre>{
"totalDistance": 100,
"elapsedTime": 29000,
"title": "Test Workout (GO)",
"startdatetime": "2023-01-16 17:54:35.588838+00:00",
"workouttype": "water",
"boattype": "1x",
"notes": "some\nnotes",
"strokes": {
"data": [
{
"time": 3200.0000476837,
"pace": 155068.4885951763,
"hr": 85.7857142857,
"power": 84.6531131591,
"distance": 23,
"spm": 16.380952381
},
{
"time": 6700.0000476837,
"pace" : 144402.6407586741,
"hr": 91.2142857143,
"power": 117.458827834,
"distance": 36,
"spm": 21.1666666667
},
{
"time": 10099.9999046326,
"pace": 138830.8712654931,
"hr": 95.7142857143,
"power": 141.31057207,
"distance": 48,
"spm": 19.8095238095
}
]
}
}
</pre>
With v3, you can post stroke data and workout metadata in one JSON object.
<br>
<p>For v3, mandatory data fields are:</p>
<p>
<ul class="contentli">
<li><b>time</b>: Time (milliseconds since workout start)</li>
<li><b>distance</b>: Distance (meters)</li>
<li><b>pace</b>: Pace (milliseconds per 500m)</li>
<li><b>spm</b> Stroke rate (strokes per minute)</li>
</ul>
</p>
<p>Optional data fiels are:</p>
<p>
<ul class="contentli">
<li><b>power</b>: Power (Watt)</li>
<li><b>latitude</b>: GPS position (latitude)</li>
<li><b>longitude</b>: GPS position (longitude)</li>
<li><b>drivelength</b>: Drive length (meters)</li>
<li><b>dragfactor</b>: Drag factor</li>
<li><b>drivetime</b>: Drive time (ms)</li>
<li><b>strokerecoverytime</b>: Recovery time (ms)</li>
<li><b>averagedriveforce</b>: Average handle force (lbs)</li>
<li><b>peakdriveforce</b>: Peak handle force (lbs)</li>
<li><b>lapidx</b>: Lap identifier</li>
<li><b>hr</b>: Heart rate (beats per minute)</li>
<li><b>wash</b>: Wash as defined per Empower oarlock (degrees)</li>
<li><b>catch</b>: Catch angle per Empower oarlock (degrees)</li>
<li><b>finish</b>: Finish angle per Empower oarlock (degrees)</li>
<li><b>peakforceangle</b>: Peak Force Angle per Empower oarlock (degrees)</li>
<li><b>slip</b>: Slip as defined per Empower oarlock (degrees)</li>
</ul>
</p>
<p>Also for this API version, consistency checks will be done and the stroke data will be
refused if the mandatory data fields don't pass the checks.
All mandatory data fields
must have the same number of records. If an optional data field
fails a test, its values are silently replaced by zeros.</p>
</td>
</tr>
</tbody> </tbody>
</table> </table>
</p> </p>
+72
View File
@@ -180,3 +180,75 @@ class OwnApi(TestCase):
response = strokedatajson_v2(request,id=w.id) response = strokedatajson_v2(request,id=w.id)
self.assertEqual(response.status_code,200) self.assertEqual(response.status_code,200)
def test_strokedataform_v3(self):
login = self.c.login(username=self.u.username, password=self.password)
self.assertTrue(login)
w = self.user_workouts[1]
url = reverse('strokedatajson_v3')
request = self.factory.get(url)
request.user = self.u
force_authenticate(request, user=self.u)
response = strokedatajson_v3(request)
self.assertEqual(response.status_code,405)
strokedata = {
"data": [
{
"time": 3200.0000476837,
"pace": 155068.4885951763,
"hr": 85.7857142857,
"power": 84.6531131591,
"distance": 23,
"spm": 16.380952381
},
{
"time": 6700.0000476837,
"pace" : 144402.6407586741,
"hr": 91.2142857143,
"power": 117.458827834,
"distance": 36,
"spm": 21.1666666667
},
{
"time": 10099.9999046326,
"pace": 138830.8712654931,
"hr": 95.7142857143,
"power": 141.31057207,
"distance": 48,
"spm": 19.8095238095
}
]
}
form_data = {
"totalDistance": 100,
"elapsedTime": 29000,
"title": "Test Workout (GO)",
"startdatetime": "2023-01-16 17:54:35.588838+00:00",
"workouttype": "water",
"boattype": "1x",
"notes": "some\nnotes",
"strokes": strokedata,
}
url = reverse('strokedatajson_v3')
request = self.factory.post(url,form_data,format='json')
request.user = self.u
request.data = json.dumps(form_data)
force_authenticate(request, user=self.u)
with patch('rowers.dataprep.getrowdata_db') as mock_getrowdata:
mock_getrowdata.return_value = (pd.DataFrame(),None)
response = strokedatajson_v3(request)
self.assertEqual(response.status_code,200)
response = json.loads(response.content)
x = response['workout id']
+2
View File
@@ -247,6 +247,8 @@ urlpatterns = [
name='strokedatajson'), name='strokedatajson'),
re_path(r'^api/v2/workouts/(?P<id>\b[0-9A-Fa-f]+\b)/strokedata/$', views.strokedatajson_v2, re_path(r'^api/v2/workouts/(?P<id>\b[0-9A-Fa-f]+\b)/strokedata/$', views.strokedatajson_v2,
name='strokedatajson_v2'), name='strokedatajson_v2'),
re_path(r'^api/v3/workouts/$', views.strokedatajson_v3,
name='strokedatajson_v3'),
re_path(r'^500v/$', views.error500_view, name='error500_view'), re_path(r'^500v/$', views.error500_view, name='error500_view'),
re_path(r'^500q/$', views.servererror_view, name='servererror_view'), re_path(r'^500q/$', views.servererror_view, name='servererror_view'),
path('502/', TemplateView.as_view(template_name='502.html'), name='502'), path('502/', TemplateView.as_view(template_name='502.html'), name='502'),
+257 -131
View File
@@ -3,6 +3,8 @@ from rowers.tasks import handle_calctrimp
from rowers.opaque import encoder from rowers.opaque import encoder
import arrow import arrow
import pendulum
from rowsandall_app.settings import UPLOAD_SERVICE_SECRET, UPLOAD_SERVICE_URL
# Stroke data form to test API upload # Stroke data form to test API upload
@@ -37,6 +39,138 @@ def strokedataform(request, id=0):
'workout': w, 'workout': w,
}) # pragma: no cover }) # pragma: no cover
def api_get_dataframe(startdatetime, df):
try:
time = df['time']/1.e3
except KeyError: # pragma: no cover
try:
time = df['t']/10.
except KeyError:
return 400, "Missing time", pd.DataFrame()
try:
spm = df['spm']
except KeyError: # pragma: no cover
return 400, "Missing spm", pd.DataFrame()
try:
distance = df['distance']
except KeyError: # pragma: no cover
try:
distance = df['d']/10.
except KeyError:
return 400, "Missing distance", pd.DataFrame()
try:
pace = df['pace']/1.e3
except KeyError: # pragma: no cover
try:
pace = df['p']/10.
except KeyError:
return 400, "Missing pace", pd.DataFrame()
try:
power = df['power']
except KeyError: # pragma: no cover
power = 0*time
try:
drivelength = df['drivelength']
except KeyError:
drivelength = 0*time
try:
dragfactor = df['dragfactor']
except KeyError:
dragfactor = 0*time
try:
drivetime = df['drivetime']
except KeyError:
drivetime = 0*time
try:
strokerecoverytime = df['strokerecoverytime']
except KeyError:
strokerecoverytime = 0*time
try:
averagedriveforce = df['averagedriveforce']
except KeyError:
averagedriveforce = 0*time
try:
peakdriveforce = df['peakdriveforce']
except KeyError:
peakdriveforce = 0*time
try:
wash = df['wash']
except KeyError:
wash = 0*time
try:
catch = df['catch']
except KeyError:
catch = 0*time
try:
finish = df['finish']
except KeyError:
finish = 0*time
try:
peakforceangle = df['peakforceangle']
except KeyError:
peakforceangle = 0*time
try:
driveenergy = df['driveenergy']
except KeyError:
driveenergy = 60.*power/spm
try:
slip = df['slip']
except KeyError:
slip = 0*time
try:
lapidx = df['lapidx']
except KeyError:
lapidx = 0*time
try:
hr = df['hr']
except KeyError: # pragma: no cover
hr = 0*df['time']
try:
latitude = df['latitude']
except KeyError:
latitude = 0*df['time']
try:
longitude = df['longitude']
except KeyError:
longitude = 0*df['time']
starttime = totimestamp(startdatetime)+time[0]
unixtime = starttime+time
dologging('apilog.log',"(strokedatajson_v2/3 POST - data parsed)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
' DragFactor': dragfactor,
' Stroke500mPace (sec/500m)': pace,
' Power (watts)': power,
' DriveLength (meters)': drivelength,
' DriveTime (ms)': drivetime,
' StrokeRecoveryTime (ms)': strokerecoverytime,
' AverageDriveForce (lbs)': averagedriveforce,
' PeakDriveForce (lbs)': peakdriveforce,
' lapIdx': lapidx,
' ElapsedTime (sec)': time,
'catch': catch,
'slip': slip,
'finish': finish,
'wash': wash,
'driveenergy': driveenergy,
'peakforceangle': peakforceangle,
' latitude': latitude,
' longitude': longitude,
})
return 200, "", data
@login_required() @login_required()
def strokedataform_v2(request, id=0): def strokedataform_v2(request, id=0):
@@ -69,10 +203,127 @@ def strokedataform_v2(request, id=0):
}) # pragma: no cover }) # pragma: no cover
@csrf_exempt
@login_required()
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def strokedatajson_v3(request):
"""
POST: Add Stroke data to workout
GET: Get stroke data of workout
This v2 API works on stroke based data dict:
{
"totalDistance": 100,
"elapsedTime": 592,
"title": "Test Workout (GO)",
"startdatetime": "2023-01-16 17:54:35.588838+00:00",
"workouttype": "water",
"boattype": "1x",
"notes": "some\nnotes",
"strokes": {"data": [
{"distance":5, "power": 112, "hr": 132, "pace": 145800, "spm": 11, "time": 0, "latitude":52.2264097,"longitude":6.8493638},
{"distance":12, "power": 221, "hr": 131, "pace": 116400, "spm": 41, "time": 2200, "latitude":52.2263474,"longitude":6.8495814},
{"distance":19, "power": 511, "hr": 131, "pace": 88100, "spm": 56, "time": 4599, "latitude":52.2262715,"longitude":6.8496975},
{"distance":27, "power": 673, "hr": 132, "pace": 80400, "spm": 59, "time": 7000, "latitude":52.2262003,"longitude":6.8498095},
{"distance":35, "power": 744, "hr": 133, "pace": 77700, "spm": 55, "time": 9599, "latitude":52.2261312, "longitude":6.8499267},
{"distance":43, "power": 754, "hr": 136, "pace": 77400, "spm": 48, "time": 12000, "latitude":52.2260576,"longitude":6.8500366},
{"distance":51, "power": 754, "hr": 139, "pace": 77400, "spm": 48, "time": 14400, "latitude":52.2259872,"longitude":6.8501561},
{"distance":59, "power": 749, "hr": 142, "pace": 77600, "spm": 48, "time": 16799, "latitude":52.2259154,"longitude":6.8502769},
{"distance":67, "power": 729, "hr": 145, "pace": 78300, "spm": 48, "time": 19400, "latitude":52.2257749,"longitude":6.8503918},
{"distance":75, "power": 729, "hr": 147, "pace": 78300, "spm": 48, "time": 21799, "latitude":52.2259154,"longitude":6.8502769},
{"distance":82, "power": 726, "hr": 150, "pace": 78400, "spm": 48, "time": 24200, "latitude":52.2259872,"longitude":6.8501561},
{"distance":90, "power": 709, "hr": 152, "pace": 79000, "spm": 48, "time": 26599, "latitude":52.2260576,"longitude":6.8500366},
{"distance":100, "power": 707, "hr": 153, "pace": 79100, "spm": 49, "time": 29000, "latitude":52.2261312,"longitude":6.8499267}]}
}
"""
if request.method != 'POST':
return HttpResponseNotAllowed("Method not supported") # pragma: no cover
dologging('apilog.log', request.user.username+" (strokedatajson_v3 POST)")
title = request.data.get('title','')
try:
elapsedTime = request.data['elapsedTime']
except KeyError:
return HttpResponse("Missing Elapsed Time", status=400)
try:
totalDistance = request.data['totalDistance']
except KeyError:
return HttpResponse("Missing Total Distance", status=400)
timeZone = request.data.get('timezone','UTC')
workouttype = request.data.get('workouttype','rower')
boattype = request.data.get('boattype','1x')
notes = request.data.get('notes','')
rpe = request.data.get('rpe',0)
startdatetime = request.data.get('startdatetime',"%s" % timezone.now())
startdatetime = pendulum.parse(startdatetime)
df = pd.DataFrame()
try:
strokes = request.data['strokes']
except KeyError:
return HttpResponse("No Stroke Data in JSON", status=400)
try:
df = pd.DataFrame(strokes['data'])
except KeyError:
try:
df = pd.DataFrame(request.data['strokedata'])
except:
return HttpResponse("No JSON Object could be decoded", status=400)
df.index = df.index.astype(int)
df.sort_index(inplace=True)
status, comment, data = api_get_dataframe(startdatetime, df)
if status != 200:
return HttpResponse(comment, status=status)
csvfilename = 'media/{code}.csv.gz'.format(code=uuid4().hex[:16])
_ = data.to_csv(csvfilename, index_label='index', compression='gzip')
uploadoptions = {
'secret': UPLOAD_SERVICE_SECRET,
'user': request.user.id,
'file': csvfilename,
'title': title,
'workouttype': workouttype,
'boattype': boattype,
'elapsedTime': elapsedTime/1000., # in seconds
'totalDistance': totalDistance,
'rpe': rpe,
'notes': notes,
'timezone': timeZone,
}
session = requests.session()
newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
session.headers.update(newHeaders)
response = session.post(UPLOAD_SERVICE_URL, json=uploadoptions)
if response.status_code != 200:
return HttpResponse(response.text, response.status_code)
try:
workoutid = response.json()['id']
except KeyError:
workoutid = 1
return JsonResponse(
{"workout public id": encoder.encode_hex(workoutid),
"workout id": workoutid,
"status": "success",
})
# Process the POSTed stroke data according to the API definition # Process the POSTed stroke data according to the API definition
# Return the GET stroke data according to the API definition # Return the GET stroke data according to the API definition
@csrf_exempt @csrf_exempt
@login_required() @login_required()
@api_view(["GET", "POST"]) @api_view(["GET", "POST"])
@@ -161,135 +412,10 @@ def strokedatajson_v2(request, id):
df.index = df.index.astype(int) df.index = df.index.astype(int)
df.sort_index(inplace=True) df.sort_index(inplace=True)
try: status, comment, data = api_get_dataframe(row.startdatetime, df)
time = df['time']/1.e3 if status != 200:
except KeyError: # pragma: no cover return HttpResponse(comment, status=status)
try:
time = df['t']/10.
except KeyError:
return HttpResponse("Missing time", status=400)
try:
spm = df['spm']
except KeyError: # pragma: no cover
return HttpResponse("Missing spm", status=400)
try:
distance = df['distance']
except KeyError: # pragma: no cover
try:
distance = df['d']/10.
except KeyError:
return HttpResponse("Missing distance", status=400)
try:
pace = df['pace']/1.e3
except KeyError: # pragma: no cover
try:
pace = df['p']/10.
except KeyError:
return HttpResponse("Missing pace", status=400)
try:
power = df['power']
except KeyError: # pragma: no cover
power = 0*time
try:
drivelength = df['drivelength']
except KeyError:
drivelength = 0*time
try:
dragfactor = df['dragfactor']
except KeyError:
dragfactor = 0*time
try:
drivetime = df['drivetime']
except KeyError:
drivetime = 0*time
try:
strokerecoverytime = df['strokerecoverytime']
except KeyError:
strokerecoverytime = 0*time
try:
averagedriveforce = df['averagedriveforce']
except KeyError:
averagedriveforce = 0*time
try:
peakdriveforce = df['peakdriveforce']
except KeyError:
peakdriveforce = 0*time
try:
wash = df['wash']
except KeyError:
wash = 0*time
try:
catch = df['catch']
except KeyError:
catch = 0*time
try:
finish = df['finish']
except KeyError:
finish = 0*time
try:
peakforceangle = df['peakforceangle']
except KeyError:
peakforceangle = 0*time
try:
driveenergy = df['driveenergy']
except KeyError:
driveenergy = 60.*power/spm
try:
slip = df['slip']
except KeyError:
slip = 0*time
try:
lapidx = df['lapidx']
except KeyError:
lapidx = 0*time
try:
hr = df['hr']
except KeyError: # pragma: no cover
hr = 0*df['time']
try:
latitude = df['latitude']
except KeyError:
latitude = 0*df['time']
try:
longitude = df['longitude']
except KeyError:
longitude = 0*df['time']
starttime = totimestamp(row.startdatetime)+time[0]
unixtime = starttime+time
dologging('apilog.log',"(strokedatajson_v2 POST - data parsed)")
data = pd.DataFrame({'TimeStamp (sec)': unixtime,
' Horizontal (meters)': distance,
' Cadence (stokes/min)': spm,
' HRCur (bpm)': hr,
' DragFactor': dragfactor,
' Stroke500mPace (sec/500m)': pace,
' Power (watts)': power,
' DriveLength (meters)': drivelength,
' DriveTime (ms)': drivetime,
' StrokeRecoveryTime (ms)': strokerecoverytime,
' AverageDriveForce (lbs)': averagedriveforce,
' PeakDriveForce (lbs)': peakdriveforce,
' lapIdx': lapidx,
' ElapsedTime (sec)': time,
'catch': catch,
'slip': slip,
'finish': finish,
'wash': wash,
'driveenergy': driveenergy,
'peakforceangle': peakforceangle,
' latitude': latitude,
' longitude': longitude,
})
r = getrower(request.user) r = getrower(request.user)
timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S") timestr = row.startdatetime.strftime("%Y%m%d-%H%M%S")
+1 -1
View File
@@ -33,7 +33,7 @@ from rq.exceptions import NoSuchJobError
import threading import threading
import redis import redis
import colorsys import colorsys
import re
import zipfile import zipfile
import bleach import bleach
import arrow import arrow
+35 -29
View File
@@ -5234,6 +5234,8 @@ def workout_upload_api(request):
r = None r = None
if form.is_valid(): if form.is_valid():
t = form.cleaned_data['title'] t = form.cleaned_data['title']
t = re.sub('\r',' ',t)
t = re.sub('\n',' ',t)
boattype = form.cleaned_data['boattype'] boattype = form.cleaned_data['boattype']
workouttype = form.cleaned_data['workouttype'] workouttype = form.cleaned_data['workouttype']
try: try:
@@ -6701,40 +6703,44 @@ def workout_summary_edit_view(request, id, message="", successmessage=""
vals = None vals = None
# feeling lucky / ruptures # feeling lucky / ruptures
if request.method == 'POST' and "ruptures" in request.POST: if request.method == 'POST' and "ruptures" in request.POST:
df = pd.DataFrame({
'spm': rowdata.df[' Cadence (stokes/min)'],
'power': rowdata.df[' Power (watts)'],
'v': rowdata.df[' AverageBoatSpeed (m/s)']
})
algo = rpt.Pelt(model="rbf").fit(df.values)
result = algo.predict(pen=10)
df['time'] = rowdata.df['TimeStamp (sec)'].values
try: try:
timeprev = int(df['time'].values[0]) df = pd.DataFrame({
timenext = int(df['time'].values[result[0]]) 'spm': rowdata.df[' Cadence (stokes/min)'],
s = '{delta}sec'.format(delta=timenext-timeprev) 'power': rowdata.df[' Power (watts)'],
except IndexError: # pragma: no cover 'v': rowdata.df[' AverageBoatSpeed (m/s)']
s = '0sec' })
algo = rpt.Pelt(model="rbf").fit(df.values)
result = algo.predict(pen=10)
df['time'] = rowdata.df['TimeStamp (sec)'].values
try:
timeprev = int(df['time'].values[0])
timenext = int(df['time'].values[result[0]])
s = '{delta}sec'.format(delta=timenext-timeprev)
except IndexError: # pragma: no cover
s = '0sec'
for i in range(len(result)-1): for i in range(len(result)-1):
timeprev = int(df['time'].values[result[i]-1]) timeprev = int(df['time'].values[result[i]-1])
timenext = int(df['time'].values[result[i+1]-1]) timenext = int(df['time'].values[result[i+1]-1])
interval = '+{delta}sec'.format(delta=timenext-timeprev) interval = '+{delta}sec'.format(delta=timenext-timeprev)
s += interval s += interval
try: try:
rowdata.updateinterval_string(s) rowdata.updateinterval_string(s)
except: # pragma: no cover except: # pragma: no cover
messages.error(request, "Nope, you were not lucky")
intervalstats = rowdata.allstats()
itime, idist, itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savestringform'
intervalString = s
form = SummaryStringForm(initial={'intervalstring': intervalString})
except KeyError:
messages.error(request, "Nope, you were not lucky") messages.error(request, "Nope, you were not lucky")
intervalstats = rowdata.allstats()
itime, idist, itype = rowdata.intervalstats_values()
nrintervals = len(idist)
savebutton = 'savestringform'
intervalString = s
form = SummaryStringForm(initial={'intervalstring': intervalString})
# We have submitted the mini language interpreter # We have submitted the mini language interpreter
if request.method == 'POST' and "intervalstring" in request.POST: if request.method == 'POST' and "intervalstring" in request.POST:
form = SummaryStringForm(request.POST) form = SummaryStringForm(request.POST)