Private
Public Access
1
0

Merge branch 'release/v5.35'

This commit is contained in:
Sander Roosendaal
2017-12-14 16:45:46 +01:00
12 changed files with 1178 additions and 10 deletions
+6 -1
View File
@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
from .models import ( from .models import (
Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement, Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement,
Team,TeamInvite,TeamRequest, Team,TeamInvite,TeamRequest,
WorkoutComment, WorkoutComment,C2WorldClassAgePerformance,
) )
# Register your models here so you can use them in the Admin module # Register your models here so you can use them in the Admin module
@@ -26,6 +26,9 @@ class WorkoutAdmin(admin.ModelAdmin):
class FavoriteChartAdmin(admin.ModelAdmin): class FavoriteChartAdmin(admin.ModelAdmin):
list_display = ('user','xparam','yparam1','yparam2','plottype','workouttype','reststrokes') list_display = ('user','xparam','yparam1','yparam2','plottype','workouttype','reststrokes')
class C2WorldClassAgePerformanceAdmin(admin.ModelAdmin):
list_display = ('sex','weightcategory','age','distance','power','name','season')
class SiteAnnouncementAdmin(admin.ModelAdmin): class SiteAnnouncementAdmin(admin.ModelAdmin):
list_display = ('announcement','created','modified','expires','dotweet') list_display = ('announcement','created','modified','expires','dotweet')
@@ -51,3 +54,5 @@ admin.site.register(SiteAnnouncement,SiteAnnouncementAdmin)
admin.site.register(TeamInvite,TeamInviteAdmin) admin.site.register(TeamInvite,TeamInviteAdmin)
admin.site.register(TeamRequest,TeamRequestAdmin) admin.site.register(TeamRequest,TeamRequestAdmin)
admin.site.register(WorkoutComment,WorkoutCommentAdmin) admin.site.register(WorkoutComment,WorkoutCommentAdmin)
admin.site.register(C2WorldClassAgePerformance,
C2WorldClassAgePerformanceAdmin)
+4
View File
@@ -473,6 +473,10 @@ def strfdelta(tdelta):
return res return res
def timedelta_to_seconds(tdelta):
return 60.*tdelta.minute+tdelta.second
# A nice printable format for pace values # A nice printable format for pace values
+366 -5
View File
@@ -53,6 +53,7 @@ import stravastuff
from rowers.dataprep import rdata from rowers.dataprep import rdata
import rowers.dataprep as dataprep import rowers.dataprep as dataprep
import rowers.metrics as metrics
from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima
@@ -976,6 +977,255 @@ def googlemap_chart(lat,lon,name=""):
return [script,div] return [script,div]
def interactive_agegroupcpchart(age,normalized=False):
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
fhduration = []
fhpower = []
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex='female',
distance=distance,
weightcategory='hwt'
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
fhduration.append(duration)
fhpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex='female',
duration=duration,
weightcategory='hwt'
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
fhduration.append(60.*duration)
fhpower.append(worldclasspower)
except ValueError:
pass
flduration = []
flpower = []
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex='female',
distance=distance,
weightcategory='lwt'
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
flduration.append(duration)
flpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex='female',
duration=duration,
weightcategory='lwt'
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
flduration.append(60.*duration)
flpower.append(worldclasspower)
except ValueError:
pass
mlduration = []
mlpower = []
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex='male',
distance=distance,
weightcategory='lwt'
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
mlduration.append(duration)
mlpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex='male',
duration=duration,
weightcategory='lwt'
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
mlduration.append(60.*duration)
mlpower.append(worldclasspower)
except ValueError:
pass
mhduration = []
mhpower = []
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex='male',
distance=distance,
weightcategory='hwt'
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
mhduration.append(duration)
mhpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex='male',
duration=duration,
weightcategory='hwt'
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
mhduration.append(60.*duration)
mhpower.append(worldclasspower)
except ValueError:
pass
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
errfunc = lambda pars,x,y: fitfunc(pars,x)-y
p0 = [500,350,10,8000]
# fitting WC data to three parameter CP model
if len(fhduration)>=4:
p1fh, success = optimize.leastsq(errfunc, p0[:],
args = (fhduration,fhpower))
else:
p1fh = None
# fitting WC data to three parameter CP model
if len(flduration)>=4:
p1fl, success = optimize.leastsq(errfunc, p0[:],
args = (flduration,flpower))
else:
p1fl = None
# fitting WC data to three parameter CP model
if len(mlduration)>=4:
p1ml, success = optimize.leastsq(errfunc, p0[:],
args = (mlduration,mlpower))
else:
p1ml = None
if len(mhduration)>=4:
p1mh, success = optimize.leastsq(errfunc, p0[:],
args = (mhduration,mhpower))
else:
p1mh = None
fitt = pd.Series(10**(4*np.arange(100)/100.))
fitpowerfh = fitfunc(p1fh,fitt)
fitpowerfl = fitfunc(p1fl,fitt)
fitpowerml = fitfunc(p1ml,fitt)
fitpowermh = fitfunc(p1mh,fitt)
if normalized:
facfh = fitfunc(p1fh,60)
facfl = fitfunc(p1fl,60)
facml = fitfunc(p1ml,60)
facmh = fitfunc(p1mh,60)
fitpowerfh /= facfh
fitpowerfl /= facfl
fitpowermh /= facmh
fitpowerml /= facml
fhpower /= facfh
flpower /= facfl
mlpower /= facml
mhpower /= facmh
source = ColumnDataSource(
data = dict(
duration = fitt,
fitpowerfh = fitpowerfh,
fitpowerfl = fitpowerfl,
fitpowerml = fitpowerml,
fitpowermh = fitpowermh,
flduration = flduration,
flpower = flpower,
fhduration = fhduration,
fhpower = fhpower,
mlduration = mlduration,
mlpower = mlpower,
mhduration = mhduration,
mhpower = mhpower,
)
)
x_axis_type = 'log'
y_axis_type = 'linear'
plot = Figure(plot_width=900,x_axis_type=x_axis_type)
plot.line('duration','fitpowerfh',source=source,
legend='Female HW',color='blue')
plot.line('duration','fitpowerfl',source=source,
legend='Female LW',color='red')
plot.line('duration','fitpowerml',source=source,
legend='Male LW',color='green')
plot.line('duration','fitpowermh',source=source,
legend='Male HW',color='orange')
plot.circle('flduration','flpower',source=source,
fill_color='red',size=15)
plot.circle('fhduration','fhpower',source=source,
fill_color='blue',size=15)
plot.circle('mlduration','mlpower',source=source,
fill_color='green',size=15)
plot.circle('mhduration','mhpower',source=source,
fill_color='orange',size=15)
plot.title.text = 'age '+str(age)
plot.xaxis.axis_label = "Duration (seconds)"
if normalized:
plot.yaxis.axis_label = "Power (normalized)"
else:
plot.yaxis.axis_label = "Power (W)"
script,div = components(plot)
return script,div
def interactive_otwcpchart(powerdf,promember=0): def interactive_otwcpchart(powerdf,promember=0):
powerdf = powerdf[~(powerdf == 0).any(axis=1)] powerdf = powerdf[~(powerdf == 0).any(axis=1)]
@@ -1069,8 +1319,69 @@ def interactive_otwcpchart(powerdf,promember=0):
return [script,div,p1,ratio,message] return [script,div,p1,ratio,message]
def interactive_agegroup_plot(df,distance=2000,duration=None,
sex='male',weightcategory='hwt'):
age = df['age']
power = df['power']
name = df['name']
season = df['season']
# poly_coefficients = np.polyfit(age,power,6)
age2 = np.linspace(11,95)
# poly_vals = np.polyval(poly_coefficients,age2)
# poly_vals = 0.5*(np.abs(poly_vals)+poly_vals)
fitfunc = lambda pars, x: 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])))
errfunc = lambda pars, x,y: fitfunc(pars,x)-y
p0 = [700,120,700,10,100,100]
p1, success = optimize.leastsq(errfunc,p0[:],
args = (age,power))
expo_vals = fitfunc(p1, age2)
expo_vals = 0.5*(np.abs(expo_vals)+expo_vals)
source = ColumnDataSource(
data = dict(
age = age,
power = power,
age2 = age2,
expo_vals = expo_vals,
season = season,
name=name,
)
)
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair'
plot = Figure(tools=TOOLS,plot_width=900)
plot.circle('age','power',source=source,fill_color='red',size=15,
legend='World Record')
plot.line(age2,expo_vals)
plot.xaxis.axis_label = "Age"
plot.yaxis.axis_label = "Concept2 power"
hover = plot.select(dict(type=HoverTool))
hover.tooltips = OrderedDict([
('Name ','@name'),
('Season ','@season'),
])
hover.mode = 'mouse'
script,div = components(plot)
return script,div
def interactive_cpchart(rower,thedistances,thesecs,theavpower, def interactive_cpchart(rower,thedistances,thesecs,theavpower,
theworkouts,promember=0): theworkouts,promember=0,
wcpower=[],wcdurations=[]):
message = 0 message = 0
# plot tools # plot tools
@@ -1136,11 +1447,22 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower,
) )
# fitting the data to three parameter CP model
fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3])) fitfunc = lambda pars,x: pars[0]/(1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
errfunc = lambda pars,x,y: fitfunc(pars,x)-y errfunc = lambda pars,x,y: fitfunc(pars,x)-y
p0 = [500,350,10,8000] p0 = [500,350,10,8000]
wcpower = pd.Series(wcpower)
wcdurations = pd.Series(wcdurations)
# fitting WC data to three parameter CP model
if len(wcdurations)>=4:
p1wc, success = optimize.leastsq(errfunc, p0[:],
args = (wcdurations,wcpower))
else:
p1wc = None
# fitting the data to three parameter CP model
p1 = p0 p1 = p0
if len(thesecs)>=4: if len(thesecs)>=4:
@@ -1153,6 +1475,21 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower,
fitt = pd.Series(10**(4*np.arange(100)/100.)) fitt = pd.Series(10**(4*np.arange(100)/100.))
fitpower = fitfunc(p1,fitt) fitpower = fitfunc(p1,fitt)
if p1wc is not None:
fitpowerwc = 0.95*fitfunc(p1wc,fitt)
fitpowerexcellent = 0.7*fitfunc(p1wc,fitt)
fitpowergood = 0.6*fitfunc(p1wc,fitt)
fitpowerfair = 0.5*fitfunc(p1wc,fitt)
fitpoweraverage = 0.4*fitfunc(p1wc,fitt)
else:
fitpowerwc = 0*fitpower
fitpowerexcellent = 0*fitpower
fitpowergood = 0*fitpower
fitpowerfair = 0*fitpower
fitpoweraverage = 0*fitpower
message = "" message = ""
if len(fitpower[fitpower<0]) > 0: if len(fitpower[fitpower<0]) > 0:
@@ -1172,6 +1509,11 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower,
), ),
spm = 0*fitpower, spm = 0*fitpower,
power = fitpower, power = fitpower,
fitpowerwc = fitpowerwc,
fitpowerexcellent = fitpowerexcellent,
fitpowergood = fitpowergood,
fitpowerfair = fitpowerfair,
fitpoweraverage = fitpoweraverage,
fpace = nicepaceformat(fitp2), fpace = nicepaceformat(fitp2),
) )
) )
@@ -1207,7 +1549,7 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower,
if cpdata.empty: if cpdata.empty:
message = 'Calculations are running in the background. Please refresh this page to see updated results' message = 'Calculations are running in the background. Please refresh this page to see updated results'
return ['','',paulslope,paulintercept,p1,message] return ['','',paulslope,paulintercept,p1,message,p1wc]
velo = cpdata['distance']/cpdata['delta'] velo = cpdata['distance']/cpdata['delta']
@@ -1250,12 +1592,31 @@ def interactive_cpchart(rower,thedistances,thesecs,theavpower,
plot.line('duration','power',source=sourcepaul,legend="Paul's Law") plot.line('duration','power',source=sourcepaul,legend="Paul's Law")
plot.line('duration','power',source=sourcecomplex,legend="CP Model", plot.line('duration','power',source=sourcecomplex,legend="CP Model",
color='green') color='green')
plot.line('duration','fitpowerwc',source=sourcecomplex,
legend="World Class",
color='Maroon',line_dash='dotted')
plot.line('duration','fitpowerexcellent',source=sourcecomplex,
legend="90% percentile",
color='Purple',line_dash='dotted')
plot.line('duration','fitpowergood',source=sourcecomplex,
legend="75% percentile",
color='Olive',line_dash='dotted')
plot.line('duration','fitpowerfair',source=sourcecomplex,
legend="50% percentile",
color='Gray',line_dash='dotted')
plot.line('duration','fitpoweraverage',source=sourcecomplex,
legend="25% percentile",
color='SkyBlue',line_dash='dotted')
script, div = components(plot) script, div = components(plot)
return [script,div,paulslope,paulintercept,p1,message] return [script,div,paulslope,paulintercept,p1,message,p1wc]
def interactive_windchart(id=0,promember=0): def interactive_windchart(id=0,promember=0):
# check if valid ID exists (workout exists) # check if valid ID exists (workout exists)
+50
View File
@@ -1,5 +1,8 @@
from utils import lbstoN from utils import lbstoN
import numpy as np import numpy as np
from models import C2WorldClassAgePerformance
import pandas as pd
from scipy import optimize
rowingmetrics = ( rowingmetrics = (
('time',{ ('time',{
@@ -316,3 +319,50 @@ def calc_trimp(df,sex,hrmax,hrmin):
trimp = trimpdata.sum() trimp = trimpdata.sum()
return trimp return trimp
def getagegrouprecord(age,sex='male',weightcategory='hwt',
distance=2000,duration=None):
if not duration:
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
distance=distance,
sex=sex,
weightcategory=weightcategory
).values()
)
)
else:
duration=60*int(duration)
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
duration=duration,
sex=sex,
weightcategory=weightcategory
).values()
)
)
if not df.empty:
ages = df['age']
powers = df['power']
#poly_coefficients = np.polyfit(ages,powers,6)
fitfunc = lambda pars, x: 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])))
errfunc = lambda pars, x,y: fitfunc(pars,x)-y
p0 = [700,120,700,10,100,100]
p1, success = optimize.leastsq(errfunc,p0[:],
args = (ages,powers))
power = fitfunc(p1, float(age))
#power = np.polyval(poly_coefficients,age)
power = 0.5*(np.abs(power)+power)
else:
power = 0
return power
+99
View File
@@ -21,6 +21,8 @@ from sqlalchemy import create_engine
import sqlalchemy as sa import sqlalchemy as sa
from sqlite3 import OperationalError from sqlite3 import OperationalError
from django.utils import timezone from django.utils import timezone
import pandas as pd
from dateutil import parser
import datetime import datetime
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from rowers.rows import validate_file_extension from rowers.rows import validate_file_extension
@@ -149,6 +151,103 @@ class PowerZonesField(models.TextField):
value = self._get_val_from_obj(obj) value = self._get_val_from_obj(obj)
return self.get_deb_prep_value(value) return self.get_deb_prep_value(value)
c2url = 'http://www.concept2.com/indoor-rowers/racing/records/world?machine=1&event=All&gender=All&age=All&weight=All'
def update_records(url=c2url):
try:
dfs = pd.read_html(url,attrs={'class':'views-table'})
df = dfs[0]
df.columns = df.columns.str.strip()
success = 1
except:
df = pd.DataFrame()
if not df.empty:
C2WorldClassAgePerformance.objects.all().delete()
df.Gender = df.Gender.apply(lambda x: 'male' if x=='M' else 'female')
df['Distance'] = df['Event']
df['Duration'] = 0
for nr,row in df.iterrows():
if 'm' in row['Record']:
df.ix[nr,'Distance'] = row['Record'][:-1]
df.ix[nr,'Duration'] = 60*row['Event']
else:
df.ix[nr,'Distance'] = row['Event']
try:
tobj = datetime.datetime.strptime(row['Record'],'%M:%S.%f')
except ValueError:
tobj = datetime.datetime.strptime(row['Record'],'%H:%M:%S.%f')
df.ix[nr,'Duration'] = 3600.*tobj.hour+60.*tobj.minute+tobj.second+tobj.microsecond/1.e6
print row.Duration
for nr,row in df.iterrows():
try:
weightcategory = row.Weight.lower()
except AttributeError:
weightcategory = 'hwt'
sex = row.Gender
name = row.Name
age = int(row.Age)
distance = int(row.Distance)
duration = float(row.Duration)
season = int(row.Season)
velo = distance/duration
power = int(2.8*velo**3)
record = C2WorldClassAgePerformance(
age = age,
weightcategory = weightcategory,
sex=sex,
distance = distance,
duration = duration,
power = power,
season = season,
name = name,
)
try:
record.save()
except:
print record
class C2WorldClassAgePerformance(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
('male','male'),
('female','female'),
)
weightcategory = models.CharField(default="hwt",
max_length=30,
choices=weightcategories)
sex = models.CharField(default="female",
max_length=30,
choices=sexcategories)
age = models.IntegerField(default=19,verbose_name="Age")
distance = models.IntegerField(default=2000)
name = models.CharField(max_length=200,blank=True)
duration = models.FloatField(default=1,blank=True)
season = models.IntegerField(default=2013)
power = models.IntegerField(default=200)
class Meta:
unique_together = ('age','sex','weightcategory','distance')
def __unicode__(self):
return self.sex+' '+self.weightcategory+' '+self.name+':'+str(self.age)+' ('+str(self.season)+')'
# For future Team functionality # For future Team functionality
class Team(models.Model): class Team(models.Model):
choices = ( choices = (
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall {% endblock %}
{% block content %}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script>
// Set things up to resize the plot on a window resize. You can play with
// the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
var plot = Bokeh.index[plotid];
var plotresizer = function() {
// arguments: use width, use height, maintain aspect ratio
plot.resize_width_height(true, false, false);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
<div id="workouts" class="grid_12 alpha">
<h1>Interactive Plot</h1>
{{ the_div|safe }}
</div>
{% endblock %}
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Rowsandall {% endblock %}
{% block content %}
<script type="text/javascript" src="/static/js/bokeh-0.12.3.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script>
// Set things up to resize the plot on a window resize. You can play with
// the arguments of resize_width_height() to change the plot's behavior.
var plot_resize_setup = function () {
var plotid = Object.keys(Bokeh.index)[0]; // assume we have just one plot
var plot = Bokeh.index[plotid];
var plotresizer = function() {
// arguments: use width, use height, maintain aspect ratio
plot.resize_width_height(true, false, false);
};
window.addEventListener('resize', plotresizer);
plotresizer();
};
window.addEventListener('load', plot_resize_setup);
</script>
<style>
/* Need this to get the page in "desktop mode"; not having an infinite height.*/
html, body {height: 100%; margin:5px;}
</style>
<div id="workouts" class="grid_12 alpha">
<h1>Interactive Plot</h1>
{{ the_div|safe }}
</div>
{% endblock %}
+7 -2
View File
@@ -79,8 +79,13 @@
<div class="grid_12 alpha"> <div class="grid_12 alpha">
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<p>&nbsp;</p> <div class="grid_2 suffix_4 alpha">
</div> <p>
<a class="button blue small" href="/rowers/ote-bests2">
Ranking Pieces 2.0</a></p>
<p>Analyze your Concept2 ranking pieces over a date range and predict your pace on other pieces.</p>
</div>
</div>
<div class="grid_6 omega"> <div class="grid_6 omega">
<div class="grid_2 alpha"> <div class="grid_2 alpha">
+114
View File
@@ -160,6 +160,19 @@
{{ the_div|safe }} {{ the_div|safe }}
{% if age %}
<p>The dashed lines are based on the
<a href="https://log.concept2.com/rankings">Concept2</a>
rankings for your age, gender
and weight category. World class means within 5% of
<a href="http://www.concept2.com/indoor-rowers/racing/records/world">
World Record</a> in terms
of power.
The percentile lines are estimates of where the percentiles
of the Concept2 rankings historically are for those of exactly
your age, gender and weight class.
</p>
{% endif %}
</div> </div>
<div id="predictions" class="grid_12 alpha"> <div id="predictions" class="grid_12 alpha">
@@ -259,6 +272,107 @@
{% endif %} {% endif %}
</div> </div>
<div class="grid_6 alpha">
{% if age %}
<h1>World Records</h1>
<table width = "70%" class="listtable">
<tbody>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/100m">100m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/500m">500m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1000m">1000m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/2000m">2000m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/5000m">5000m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/6000m">6000m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/10000m">10000m
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/21097m">Half Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/42195m">Full Marathon
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/1min">1 minute
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/4min">4 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/30min">30 minutes
</a>
</td>
</tr>
<tr>
<td>
<a
href="/rowers/agegrouprecords/{{ sex }}/{{ weightcategory }}/60min">1 hour
</a>
</td>
</tr>
</tbody>
</table>
{% endif %}
</div>
</div> </div>
{% endblock %} {% endblock %}
+14
View File
@@ -121,6 +121,14 @@ urlpatterns = [
url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'),
url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'), url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'),
url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'),
url(r'^agegroupcp/(?P<age>\d+)$',views.agegroupcpview),
url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)$',views.agegroupcpview),
url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<distance>\d+)m$',
views.agegrouprecordview),
url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)/(?P<duration>\d+)min$',
views.agegrouprecordview),
url(r'^agegrouprecords/(?P<sex>\w+.*)/(?P<weightcategory>\w+.*)$',
views.agegrouprecordview),
url(r'^list-workouts/ranking$',views.workouts_view,{'rankingonly':True}), url(r'^list-workouts/ranking$',views.workouts_view,{'rankingonly':True}),
url(r'^list-workouts/team/(?P<teamid>\d+)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view), url(r'^list-workouts/team/(?P<teamid>\d+)/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
url(r'^list-workouts/team/(?P<teamid>\d+)/$',views.workouts_view), url(r'^list-workouts/team/(?P<teamid>\d+)/$',views.workouts_view),
@@ -159,6 +167,12 @@ urlpatterns = [
url(r'^ote-bests/(?P<deltadays>\d+)$',views.rankings_view), url(r'^ote-bests/(?P<deltadays>\d+)$',views.rankings_view),
url(r'^ote-bests/$',views.rankings_view), url(r'^ote-bests/$',views.rankings_view),
url(r'^(?P<theuser>\d+)/ote-bests/$',views.rankings_view), url(r'^(?P<theuser>\d+)/ote-bests/$',views.rankings_view),
url(r'^(?P<theuser>\d+)/ote-bests2/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.rankings_view),
url(r'^(?P<theuser>\d+)/ote-bests2/(?P<deltadays>\d+)$',views.rankings_view2),
url(r'^ote-bests2/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.rankings_view2),
url(r'^ote-bests2/(?P<deltadays>\d+)$',views.rankings_view2),
url(r'^ote-bests2/$',views.rankings_view2),
url(r'^(?P<theuser>\d+)/ote-bests2/$',views.rankings_view2),
url(r'^(?P<theuser>\d+)/otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view), url(r'^(?P<theuser>\d+)/otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view),
url(r'^(?P<theuser>\d+)/otw-bests/(?P<deltadays>\d+)$',views.otwrankings_view), url(r'^(?P<theuser>\d+)/otw-bests/(?P<deltadays>\d+)$',views.otwrankings_view),
url(r'^otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view), url(r'^otw-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.otwrankings_view),
+5
View File
@@ -263,3 +263,8 @@ def myqueue(queue,function,*args,**kwargs):
return job return job
from datetime import date
def calculate_age(born):
today = date.today()
return today.year - born.year - ((today.month, today.day) < (born.month, born.day))
+419 -2
View File
@@ -710,7 +710,8 @@ def splitstdata(lijst):
from utils import ( from utils import (
geo_distance,serialize_list,deserialize_list,uniqify, geo_distance,serialize_list,deserialize_list,uniqify,
str2bool,range_to_color_hex,absolute,myqueue,get_call str2bool,range_to_color_hex,absolute,myqueue,get_call,
calculate_age
) )
import datautils import datautils
@@ -3220,6 +3221,16 @@ def rankings_view(request,theuser=0,
promember=0 promember=0
if not request.user.is_anonymous(): if not request.user.is_anonymous():
r = getrower(request.user) r = getrower(request.user)
if r.birthdate:
age = calculate_age(r.birthdate)
worldclasspower = int(metrics.getagegrouprecord(
age,
sex=r.sex,
weightcategory=r.weightcategory
))
else:
worldclasspower = None
result = request.user.is_authenticated() and ispromember(request.user) result = request.user.is_authenticated() and ispromember(request.user)
if result: if result:
promember=1 promember=1
@@ -3488,6 +3499,7 @@ def rankings_view(request,theuser=0,
'form':form, 'form':form,
'dateform':dateform, 'dateform':dateform,
'deltaform':deltaform, 'deltaform':deltaform,
'worldclasspower':worldclasspower,
'id': theuser, 'id': theuser,
'theuser':uu, 'theuser':uu,
'startdate':startdate, 'startdate':startdate,
@@ -3495,6 +3507,358 @@ def rankings_view(request,theuser=0,
'teams':get_my_teams(request.user), 'teams':get_my_teams(request.user),
}) })
# Show ranking distances including predicted paces
@login_required()
def rankings_view2(request,theuser=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays>0:
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "":
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "":
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate:
s = enddate
enddate = startdate
startdate = s
if theuser == 0:
theuser = request.user.id
promember=0
if not request.user.is_anonymous():
r = getrower(request.user)
wcdurations = []
wcpower = []
if r.birthdate:
age = calculate_age(r.birthdate)
durations = [1,4,30,60]
distances = [100,500,1000,2000,5000,6000,10000,21097,42195]
for distance in distances:
worldclasspower = metrics.getagegrouprecord(
age,
sex=r.sex,
distance=distance,
weightcategory=r.weightcategory
)
velo = (worldclasspower/2.8)**(1./3.)
try:
duration = distance/velo
wcdurations.append(duration)
wcpower.append(worldclasspower)
except ZeroDivisionError:
pass
for duration in durations:
worldclasspower = metrics.getagegrouprecord(
age,
sex=r.sex,
duration=duration,
weightcategory=r.weightcategory
)
try:
velo = (worldclasspower/2.8)**(1./3.)
distance = int(60*duration*velo)
wcdurations.append(60.*duration)
wcpower.append(worldclasspower)
except ValueError:
pass
else:
worldclasspower = None
age = 0
result = request.user.is_authenticated() and ispromember(request.user)
if result:
promember=1
# get all indoor rows in date range
# process form
if request.method == 'POST' and "daterange" in request.POST:
dateform = DateRangeForm(request.POST)
deltaform = DeltaDaysForm(request.POST)
if dateform.is_valid():
startdate = dateform.cleaned_data['startdate']
enddate = dateform.cleaned_data['enddate']
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST:
deltaform = DeltaDaysForm(request.POST)
if deltaform.is_valid():
deltadays = deltaform.cleaned_data['deltadays']
if deltadays:
enddate = timezone.now()
startdate = enddate-datetime.timedelta(days=deltadays)
if startdate > enddate:
s = enddate
enddate = startdate
startdate = s
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
else:
dateform = DateRangeForm()
deltaform = DeltaDaysForm()
else:
dateform = DateRangeForm(initial={
'startdate': startdate,
'enddate': enddate,
})
deltaform = DeltaDaysForm()
# get all 2k (if any) - this rower, in date range
try:
r = getrower(theuser)
except Rower.DoesNotExist:
allergworkouts = []
r=0
try:
uu = User.objects.get(id=theuser)
except User.DoesNotExist:
uu = ''
# test to fix bug
startdate = datetime.datetime.combine(startdate,datetime.time())
enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59))
enddate = enddate+datetime.timedelta(days=1)
rankingdistances = [100,500,1000,2000,5000,6000,10000,21097,42195,100000]
rankingdurations = []
rankingdurations.append(datetime.time(minute=1))
rankingdurations.append(datetime.time(minute=4))
rankingdurations.append(datetime.time(minute=30))
rankingdurations.append(datetime.time(hour=1,minute=15))
rankingdurations.append(datetime.time(hour=1))
thedistances = []
theworkouts = []
thesecs = []
rankingdistances.sort()
rankingdurations.sort()
for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(user=r,distance=rankingdistance,
workouttype__in=['rower','dynamic','slides'],
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('duration')
if workouts:
thedistances.append(rankingdistance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-6*workouts[0].duration.microsecond
thesecs.append(timesecs)
for rankingduration in rankingdurations:
workouts = Workout.objects.filter(user=r,duration=rankingduration,
workouttype='rower',
startdatetime__gte=startdate,
startdatetime__lte=enddate).order_by('-distance')
if workouts:
thedistances.append(workouts[0].distance)
theworkouts.append(workouts[0])
timesecs = 3600*workouts[0].duration.hour
timesecs += 60*workouts[0].duration.minute
timesecs += workouts[0].duration.second
timesecs += 1.e-5*workouts[0].duration.microsecond
thesecs.append(timesecs)
thedistances = np.array(thedistances)
thesecs = np.array(thesecs)
thevelos = thedistances/thesecs
theavpower = 2.8*(thevelos**3)
# create interactive plot
if len(thedistances) !=0 :
res = interactive_cpchart(
r,thedistances,thesecs,theavpower,
theworkouts,promember=promember,
wcdurations=wcdurations,wcpower=wcpower
)
script = res[0]
div = res[1]
paulslope = res[2]
paulintercept = res[3]
p1 = res[4]
message = res[5]
try:
testcalc = pd.Series(res[6])*3
except TypeError:
age = 0
else:
script = ''
div = '<p>No ranking pieces found.</p>'
paulslope = 1
paulintercept = 1
p1 = [1,1,1,1]
message = ""
if request.method == 'POST' and "piece" in request.POST:
form = PredictedPieceForm(request.POST)
if form.is_valid():
value = form.cleaned_data['value']
hourvalue,value = divmod(value,60)
if hourvalue >= 24:
hourvalue = 23
pieceunit = form.cleaned_data['pieceunit']
if pieceunit == 'd':
rankingdistances.append(value)
else:
rankingdurations.append(datetime.time(minute=int(value),hour=int(hourvalue)))
else:
form = PredictedPieceForm()
rankingdistances.sort()
rankingdurations.sort()
predictions = []
cpredictions = []
for rankingdistance in rankingdistances:
# Paul's model
p = paulslope*np.log10(rankingdistance)+paulintercept
velo = 500./p
t = rankingdistance/velo
pwr = 2.8*(velo**3)
a = {'distance':rankingdistance,
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
predictions.append(a)
# CP model -
pwr2 = p1[0]/(1+t/p1[2])
pwr2 += p1[1]/(1+t/p1[3])
if pwr2 <= 0:
pwr2 = 50.
velo2 = (pwr2/2.8)**(1./3.)
if np.isnan(velo2) or velo2 <= 0:
velo2 = 1.0
t2 = rankingdistance/velo2
pwr3 = p1[0]/(1+t2/p1[2])
pwr3 += p1[1]/(1+t2/p1[3])
if pwr3 <= 0:
pwr3 = 50.
velo3 = (pwr3/2.8)**(1./3.)
if np.isnan(velo3) or velo3 <= 0:
velo3 = 1.0
t3 = rankingdistance/velo3
p3 = 500./velo3
a = {'distance':rankingdistance,
'duration':timedeltaconv(t3),
'pace':timedeltaconv(p3),
'power':int(pwr3)}
cpredictions.append(a)
for rankingduration in rankingdurations:
t = 3600.*rankingduration.hour
t += 60.*rankingduration.minute
t += rankingduration.second
t += rankingduration.microsecond/1.e6
# Paul's model
ratio = paulintercept/paulslope
u = ((2**(2+ratio))*(5.**(3+ratio))*t*np.log(10))/paulslope
d = 500*t*np.log(10.)
d = d/(paulslope*lambertw(u))
d = d.real
velo = d/t
p = 500./velo
pwr = 2.8*(velo**3)
a = {'distance':int(d),
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
predictions.append(a)
# CP model
pwr = p1[0]/(1+t/p1[2])
pwr += p1[1]/(1+t/p1[3])
if pwr <= 0:
pwr = 50.
velo = (pwr/2.8)**(1./3.)
if np.isnan(velo) or velo <=0:
velo = 1.0
d = t*velo
p = 500./velo
a = {'distance':int(d),
'duration':timedeltaconv(t),
'pace':timedeltaconv(p),
'power':int(pwr)}
cpredictions.append(a)
messages.error(request,message)
return render(request, 'rankings.html',
{'rankingworkouts':theworkouts,
'interactiveplot':script,
'the_div':div,
'predictions':predictions,
'cpredictions':cpredictions,
'nrdata':len(thedistances),
'form':form,
'dateform':dateform,
'deltaform':deltaform,
'id': theuser,
'theuser':uu,
'age':age,
'sex':r.sex,
'weightcategory':r.weightcategory,
'startdate':startdate,
'enddate':enddate,
'teams':get_my_teams(request.user),
})
@user_passes_test(ispromember,login_url="/",redirect_field_name=None) @user_passes_test(ispromember,login_url="/",redirect_field_name=None)
def workout_update_cp_view(request,id=0): def workout_update_cp_view(request,id=0):
try: try:
@@ -7556,7 +7920,10 @@ def workout_flexchart3_view(request,*args,**kwargs):
rowdata = rdata(row.csvfilename) rowdata = rdata(row.csvfilename)
additionalmetrics = rowdata.get_additional_metrics() try:
additionalmetrics = rowdata.get_additional_metrics()
except AttributeError:
additionalmetrics = []
extrametrics = {m.replace('/','_slsh_'):m for m in additionalmetrics} extrametrics = {m.replace('/','_slsh_'):m for m in additionalmetrics}
@@ -10917,3 +11284,53 @@ def team_members_stats_view(request,id):
}) })
return response return response
from rowers.models import C2WorldClassAgePerformance
def agegroupcpview(request,age,normalize=0):
script,div = interactive_agegroupcpchart(age,normalized=normalize)
response = render(request,'agegroupcp.html',
{
'interactiveplot':script,
'the_div':div,
}
)
return response
def agegrouprecordview(request,sex='male',weightcategory='hwt',
distance=2000,duration=None):
if not duration:
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
distance=distance,
sex=sex,
weightcategory=weightcategory
).values()
)
)
else:
duration = int(duration)*60
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
duration=duration,
sex=sex,
weightcategory=weightcategory
).values()
)
)
script,div = interactive_agegroup_plot(df,sex=sex,distance=distance,
duration=duration,
weightcategory=weightcategory)
return render(request, 'agegroupchart.html',
{
'interactiveplot':script,
'the_div':div,
})