Private
Public Access
1
0

submit race result now starts async process

This commit is contained in:
Sander Roosendaal
2018-05-17 16:59:19 +02:00
parent d2b6c09106
commit b6c67f7e2b
7 changed files with 261 additions and 119 deletions

View File

@@ -33,14 +33,8 @@ from rowers.models import (
) )
from utils import geo_distance from utils import geo_distance
from rowers.courseutils import coursetime_paths, coursetime_first
# low level methods
class InvalidTrajectoryError(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
def get_course_timezone(course): def get_course_timezone(course):
@@ -65,31 +59,6 @@ def get_course_timezone(course):
def time_in_path(df,p,maxmin='max'):
if df.empty:
return 0
latitude = df.latitude
longitude = df.longitude
f = lambda x: coordinate_in_path(x['latitude'],x['longitude'],p)
df['inpolygon'] = df.apply(f,axis=1)
if maxmin=='max':
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
else:
b = (~df['inpolygon']).shift(1)+df['inpolygon']
if len(df[b==2]):
return df[b==2]['time'].min(),df[b==2]['cum_dist'].min()
raise InvalidTrajectoryError("Trajectory doesn't go through path")
return 0
def crewnerdcourse(doc): def crewnerdcourse(doc):
courses = [] courses = []
@@ -198,63 +167,6 @@ def createcourse(
return c return c
def coursetime_first(data,paths):
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
try:
entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max')
coursecompleted = True
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime, entrydistance, coursecompleted
def coursetime_paths(data,paths,finalmaxmin='min'):
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
# corner case - empty list of paths
if len(paths) == 0:
return 0,True
# end - just the Finish polygon
if len(paths) == 1:
try:
(
entrytime,
entrydistance
) = time_in_path(data,paths[0],maxmin=finalmaxmin)
coursecompleted = True
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime,entrydistance,coursecompleted
if len(paths) > 1:
try:
time,dist = time_in_path(data, paths[0])
data = data[data['time']>time]
data['time'] = data['time']-time
data['cum_dist'] = data['cum_dist']-dist
(
timenext,
distnext,
coursecompleted
) = coursetime_paths(data,paths[1:])
return time+timenext, dist+distnext,coursecompleted
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime, entrydistance, coursecompleted
def get_time_course(ws,course): def get_time_course(ws,course):
coursetimeseconds = 0.0 coursetimeseconds = 0.0

96
rowers/courseutils.py Normal file
View File

@@ -0,0 +1,96 @@
# low level methods
def coordinate_in_path(latitude,longitude, p):
return p.contains_points([(latitude,longitude)])[0]
class InvalidTrajectoryError(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
def time_in_path(df,p,maxmin='max'):
if df.empty:
return 0
latitude = df.latitude
longitude = df.longitude
f = lambda x: coordinate_in_path(x['latitude'],x['longitude'],p)
df['inpolygon'] = df.apply(f,axis=1)
if maxmin=='max':
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
else:
b = (~df['inpolygon']).shift(1)+df['inpolygon']
if len(df[b==2]):
return df[b==2]['time'].min(),df[b==2]['cum_dist'].min()
raise InvalidTrajectoryError("Trajectory doesn't go through path")
return 0
def coursetime_first(data,paths):
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
try:
entrytime,entrydistance = time_in_path(data,paths[0],maxmin='max')
coursecompleted = True
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime, entrydistance, coursecompleted
def coursetime_paths(data,paths,finalmaxmin='min'):
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
# corner case - empty list of paths
if len(paths) == 0:
return 0,True
# end - just the Finish polygon
if len(paths) == 1:
try:
(
entrytime,
entrydistance
) = time_in_path(data,paths[0],maxmin=finalmaxmin)
coursecompleted = True
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime,entrydistance,coursecompleted
if len(paths) > 1:
try:
time,dist = time_in_path(data, paths[0])
data = data[data['time']>time]
data['time'] = data['time']-time
data['cum_dist'] = data['cum_dist']-dist
(
timenext,
distnext,
coursecompleted
) = coursetime_paths(data,paths[1:])
return time+timenext, dist+distnext,coursecompleted
except InvalidTrajectoryError:
entrytime = data['time'].max()
entrydistance = data['cum_dist'].max()
coursecompleted = False
return entrytime, entrydistance, coursecompleted

View File

@@ -376,9 +376,7 @@ def polygon_to_path(polygon):
return p return p
def coordinate_in_path(latitude,longitude, p): from rowers.courseutils import coordinate_in_path
return p.contains_points([(latitude,longitude)])[0]
def course_spline(coordinates): def course_spline(coordinates):
latitudes = coordinates['latitude'].values latitudes = coordinates['latitude'].values

View File

@@ -26,6 +26,8 @@ import numpy as np
import dataprep import dataprep
import courses import courses
from rowers.tasks import handle_check_race_course
# Low Level functions - to be called by higher level methods # Low Level functions - to be called by higher level methods
def add_workouts_plannedsession(ws,ps,r): def add_workouts_plannedsession(ws,ps,r):
result = 0 result = 0
@@ -660,11 +662,11 @@ def add_workout_race(ws,race,r):
dates = [w.date for w in ws] dates = [w.date for w in ws]
if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']: if (not all(d == dates[0] for d in dates)) and race.sessiontype not in ['challenge','cycletarget']:
errors.append('For tests and training sessions, selected workouts must all be done on the same date') errors.append('For tests and training sessions, selected workouts must all be done on the same date')
return result,comments,errors return result,comments,errors,0
if len(ws)>1 and race.sessiontype == 'test': if len(ws)>1 and race.sessiontype == 'test':
errors.append('For tests, you can only attach one workout') errors.append('For tests, you can only attach one workout')
return result,comments,errors return result,comments,errors,0
@@ -673,7 +675,7 @@ def add_workout_race(ws,race,r):
if len(ids)>1 and race.sessiontype in ['test','coursetest','race']: if len(ids)>1 and race.sessiontype in ['test','coursetest','race']:
errors.append('For tests, you can only attach one workout') errors.append('For tests, you can only attach one workout')
return result,comments,errors return result,comments,errors,0
# start adding sessions # start adding sessions
for w in ws: for w in ws:
@@ -682,10 +684,9 @@ def add_workout_race(ws,race,r):
w.save() w.save()
result += 1 result += 1
comments.append('Your result has been submitted')
else: else:
errors.append('Workout %i did not match the race window' % w.id) errors.append('Workout %i did not match the race window' % w.id)
return result,comments,errors return result,comments,errors,0
if result>0: if result>0:
username = r.user.first_name+' '+r.user.last_name username = r.user.first_name+' '+r.user.last_name
@@ -693,16 +694,6 @@ def add_workout_race(ws,race,r):
age = calculate_age(r.birthdate) age = calculate_age(r.birthdate)
else: else:
age = None age = None
(
coursetime,
coursemeters,
coursecompleted
) = courses.get_time_course(ws,race.course)
if not coursecompleted:
errors.append('Your trajectory did not match the race course')
return result,comments,errors
duration = totaltime_sec_to_string(coursetime)
records = VirtualRaceResult.objects.filter( records = VirtualRaceResult.objects.filter(
userid=r.id, userid=r.id,
@@ -713,22 +704,20 @@ def add_workout_race(ws,race,r):
if ws[0].boattype != record.boattype: if ws[0].boattype != record.boattype:
errors.append('Your workout boat type did not match the boat type you registered') errors.append('Your workout boat type did not match the boat type you registered')
return result,comments,errors return result,comments,errors,0
if ws[0].weightcategory != record.weightcategory: if ws[0].weightcategory != record.weightcategory:
errors.append('Your workout weight category did not match the weight category you registered') errors.append('Your workout weight category did not match the weight category you registered')
return result,comments, errors return result,comments, errors,0
record.coursecompleted=coursecompleted
record.distance = int(coursemeters) job = myqueue(queue,handle_check_race_course,ws[0].csvfilename,
record.workoutid=ws[0].id ws[0].id,race.course.id,record.id)
record.duration = duration
record.save()
add_workouts_plannedsession(ws,race,r) add_workouts_plannedsession(ws,race,r)
return result,comments,errors return result,comments,errors,job.id
def delete_race_result(workout,race): def delete_race_result(workout,race):
results = VirtualRaceResult.objects.filter(workoutid=workout.id,race=race) results = VirtualRaceResult.objects.filter(workoutid=workout.id,race=race)

View File

@@ -12,6 +12,8 @@ from scipy import optimize
import rowingdata import rowingdata
from rowingdata import rowingdata as rdata from rowingdata import rowingdata as rdata
from datetime import timedelta
from sqlalchemy import create_engine
from celery import app from celery import app
import datetime import datetime
@@ -21,6 +23,7 @@ import iso8601
from matplotlib.backends.backend_agg import FigureCanvas from matplotlib.backends.backend_agg import FigureCanvas
#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas #from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib import path
from rowsandall_app.settings import SITE_URL from rowsandall_app.settings import SITE_URL
from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV from rowsandall_app.settings_dev import SITE_URL as SITE_URL_DEV
@@ -41,7 +44,8 @@ from rowers.dataprepnodjango import (
getsmallrowdata_db, updatecpdata_sql, getsmallrowdata_db, updatecpdata_sql,
update_agegroup_db,fitnessmetric_to_sql, update_agegroup_db,fitnessmetric_to_sql,
add_c2_stroke_data_db,totaltime_sec_to_string, add_c2_stroke_data_db,totaltime_sec_to_string,
create_c2_stroke_data_db,update_empower create_c2_stroke_data_db,update_empower,
database_url_debug,database_url,
) )
@@ -66,6 +70,7 @@ siteurl = SITE_URL
# testing task # testing task
from rowers.emails import send_template_email from rowers.emails import send_template_email
from rowers.courseutils import coursetime_paths, coursetime_first
@app.task @app.task
def add(x, y): def add(x, y):
@@ -171,6 +176,136 @@ def getagegrouprecord(age,sex='male',weightcategory='hwt',
return power return power
def polygon_to_path(polygon,debug=True):
pid = polygon[0]
query = 'SELECT "rowers_geopoint"."id", "rowers_geopoint"."latitude", "rowers_geopoint"."longitude" FROM "rowers_geopoint" WHERE "rowers_geopoint"."polygon_id" = {pid} ORDER BY "rowers_geopoint"."order_in_poly" ASC'.format(
pid=pid
)
if debug:
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
points = result.fetchall()
conn.close()
engine.dispose()
s = []
for point in points:
s.append([point[1],point[2]])
p = path.Path(s[:-1])
return p
@app.task(bind=True)
def handle_check_race_course(self,
f1,workoutid,courseid,
recordid,**kwargs):
if 'debug' in kwargs:
debug = kwargs['debug']
else:
debug = False
columns = ['time',' latitude',' longitude','cum_dist']
try:
row = rdata(csvfile=f1)
except IOError:
try:
row = rdata(f1 + '.csv')
except IOError:
try:
row = rdata(f1 + '.gz')
except IOError:
return 0
rowdata = row.df
rowdata.rename(columns = {
' latitude':'latitude',
' longitude':'longitude',
' ElapsedTime (sec)': 'time',
}, inplace=True)
rowdata.fillna(method='backfill',inplace=True)
rowdata['time'] = rowdata['time']-rowdata.ix[0,'time']
# we may want to expand the time (interpolate)
rowdata['dt'] = rowdata['time'].apply(
lambda x: timedelta(seconds=x)
)
rowdata = rowdata.resample('100ms',on='dt').mean()
rowdata = rowdata.interpolate()
# initiate database engine
if debug:
engine = create_engine(database_url_debug, echo=False)
else:
engine = create_engine(database_url, echo=False)
# get polygons
query = 'SELECT "rowers_geopolygon"."id" FROM "rowers_geopolygon" WHERE "rowers_geopolygon"."course_id" = {courseid} ORDER BY "rowers_geopolygon"."order_in_course" ASC'.format(
courseid=courseid
)
with engine.connect() as conn, conn.begin():
try:
result = conn.execute(query)
polygons = result.fetchall()
except:
print "Database locked"
conn.close()
engine.dispose()
paths = []
for polygon in polygons:
path = polygon_to_path(polygon,debug=debug)
paths.append(path)
(
coursetimeseconds,
coursemeters,
coursecompleted,
) = coursetime_paths(rowdata,paths)
(
coursetimefirst,
coursemetersfirst,
firstcompleted
) = coursetime_first(
rowdata,paths)
coursetimeseconds = coursetimeseconds-coursetimefirst
coursemeters = coursemeters-coursemetersfirst
if coursecompleted:
query = 'UPDATE "rowers_virtualraceresult" SET "coursecompleted" = 1, "duration" = "{duration}", "distance" = {distance}, "workoutid" = {workoutid} WHERE "id"="{recordid}"'.format(
recordid=recordid,
duration=totaltime_sec_to_string(coursetimeseconds),
distance=int(coursemeters),
workoutid=workoutid,
)
with engine.connect() as conn, conn.begin():
result = conn.execute(query)
conn.close()
engine.dispose()
return 1
else:
return 2
return 0
@app.task(bind=True) @app.task(bind=True)
def handle_getagegrouprecords(self, def handle_getagegrouprecords(self,
@@ -287,8 +422,6 @@ def handle_update_empower(self,
boattype = workoutdict['boattype'] boattype = workoutdict['boattype']
f1 = workoutdict['filename'] f1 = workoutdict['filename']
print wid
# oarlength consistency checks will be done in view # oarlength consistency checks will be done in view
havedata = 1 havedata = 1

View File

@@ -4,6 +4,10 @@
{% block title %}Rowsandall Virtual Race{% endblock %} {% block title %}Rowsandall Virtual Race{% endblock %}
{% block scripts %}
{% include "monitorjobs.html" %}
{% endblock %}
{% block content %} {% block content %}
<div class="grid_12 alpha"> <div class="grid_12 alpha">

View File

@@ -1,3 +1,4 @@
import time import time
import colorsys import colorsys
import timestring import timestring
@@ -371,6 +372,7 @@ verbose_job_status = {
'long_test_task': 'Long Test Task', 'long_test_task': 'Long Test Task',
'long_test_task2': 'Long Test Task 2', 'long_test_task2': 'Long Test Task 2',
'update_empower': 'Correct Empower Inflated Power Bug', 'update_empower': 'Correct Empower Inflated Power Bug',
'submit_race': 'Checking Race Course Result',
} }
def get_job_status(jobid): def get_job_status(jobid):
@@ -13788,13 +13790,21 @@ def virtualevent_submit_result_view(request,id=0):
workouts = Workout.objects.filter(id=selectedworkout) workouts = Workout.objects.filter(id=selectedworkout)
result,comments,errors = add_workout_race(workouts,race,r) result,comments,errors,jobid = add_workout_race(workouts,race,r)
for c in comments: for c in comments:
messages.info(request,c) messages.info(request,c)
for er in errors: for er in errors:
messages.error(request,er) messages.error(request,er)
if jobid:
try:
request.session['async_tasks'] += [(jobid,'submit_race')]
except KeyError:
request.session['async_tasks'] = [(jobid,'submit_race')]
messages.info(request,"We are evaluating your result. The page will reload when we're done. Your result will show up if you adhered to the course")
# redirect to race page # redirect to race page
url = reverse(virtualevent_view, url = reverse(virtualevent_view,
kwargs = { kwargs = {