Private
Public Access
1
0

hr pie v1

This commit is contained in:
2024-03-24 14:30:16 +01:00
parent e80fa1e4f9
commit 2fee1fcdb6
10 changed files with 226 additions and 688 deletions

View File

@@ -284,7 +284,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
datadict = {
'<{ut2}'.format(ut2=hrzones[1]): frac_lut2,
'&lt;{ut2}'.format(ut2=hrzones[1]): frac_lut2,
'{ut2}'.format(ut2=hrzones[1]): frac_ut2,
'{ut1}'.format(ut1=hrzones[2]): frac_ut1,
'{at}'.format(at=hrzones[3]): frac_at,
@@ -299,7 +299,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
data['angle'] = data['value']/data['value'].sum() * 2*pi
data['color'] = colors
data['zone'] = [
'<{ut2}'.format(ut2=hrzones[1]),
'&lt;{ut2}'.format(ut2=hrzones[1]),
'{ut2}'.format(ut2=hrzones[1]),
'{ut1}'.format(ut1=hrzones[2]),
'{at}'.format(at=hrzones[3]),
@@ -309,24 +309,14 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0):
data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
TOOLS = 'save,hover'
z = figure(title="HR "+title, x_range=(-0.5, 1), height=375,
tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime",
)
z.wedge(x=0, y=1, radius=0.4,
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color='white', fill_color='color', source=data, legend_group='zone')
z.axis.axis_label = None
z.axis.visible = False
z.grid.grid_line_color = None
z.outline_line_color = None
z.toolbar_location = 'right'
return components(z)
data_dict = data.to_dict("records")
chart_data = {
'data': data_dict,
'title': "HR "+ title
}
script, div = get_chart("/hrpie", chart_data, debug=True)
return script, div
def pretty_timedelta(secs):
hours, remainder = divmod(secs, 3600)

View File

@@ -102,14 +102,11 @@
</li>
{% endif %}
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_4">

View File

@@ -6,12 +6,9 @@
{% block main %}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
<script src="https://d3js.org/d3.v6.js"></script>
{{ interactiveplot |safe }}
@@ -24,6 +21,7 @@
</li>
</ul>
{{ interactiveplot |safe }}
{% endif %}
{% endblock %}

View File

@@ -12,6 +12,7 @@
<div id="id_js_res">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</div>
<script async="true" type="text/javascript">
@@ -52,35 +53,35 @@
<input type="submit" value="Submit"/>
</form>
</p>
</li>
<li class="grid_2">
</li>
<li class="grid_2">
<h2>All workouts</h2>
<p>
<table class="listtable shortpadded">
<tbody>
<tr>
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
</tr>
<tr>
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td>
</tr>
<tr>
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
</tr>
<tr>
<td>Average heart rate</td><td><span id="total_hr"></span> bpm</td>
</tr>
<tr>
<td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td>
</tr>
<tr>
<td>Average power</td><td><span id="total_power"></span> W</td>
</tr>
<tr>
<td>Maximum power</td><td><span id="total_maxpower"></span> W</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Total Distance</td><td>{{ totalsdict|lookup:"distance"}} meters</td>
</tr>
<tr>
<td>Total Duration</td><td>{{ totalsdict|lookup:"duration"}} </td>
</tr>
<tr>
<td>Number of workouts</td><td>{{ totalsdict|lookup:"nrworkouts"}}</td>
</tr>
<tr>
<td>Average heart rate</td><td><span id="total_hr"></span> bpm</td>
</tr>
<tr>
<td>Maximum heart rate</td><td><span id="total_maxhr"></span> bpm</td>
</tr>
<tr>
<td>Average power</td><td><span id="total_power"></span> W</td>
</tr>
<tr>
<td>Maximum power</td><td><span id="total_maxpower"></span> W</td>
</tr>
</tbody>
</table>
</p>
</li>
@@ -96,40 +97,40 @@
{{ totaldiv|safe }}
</div>
</li>
{% for ddict in typedicts %}
<li class="grid_1">
<h2>{{ ddict|lookup:"wtype"}}</h2>
<p>
<table class="listtable shortpadded">
<tbody>
<tr>
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
</tr>
<tr>
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td>
</tr>
<tr>
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
</tr>
<tr>
<td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td>
</tr>
<tr>
<td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td>
</tr>
<tr>
<td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td>
</tr>
<tr>
<td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Total Distance</td><td>{{ ddict|lookup:"distance"}} meters</td>
</tr>
<tr>
<td>Total Duration</td><td>{{ ddict|lookup:"duration"}} </td>
</tr>
<tr>
<td>Number of workouts</td><td>{{ ddict|lookup:"nrworkouts"}}</td>
</tr>
<tr>
<td>Average heart rate</td><td><span id="{{ ddict|lookup:'id'}}_hr"></span> bpm</td>
</tr>
<tr>
<td>Maximum heart rate</td><td><span id="{{ ddict|lookup:'id' }}_hrmax"></span> bpm</td>
</tr>
<tr>
<td>Average power</td><td><span id="{{ ddict|lookup:'id'}}_power"></span> W</td>
</tr>
<tr>
<td>Maximum power</td><td><span id="{{ ddict|lookup:'id'}}_powermax"></span> W</td>
</tr>
</tbody>
</table>
</p>
</li>
{% endfor %}
</ul>
@@ -142,40 +143,39 @@
<script>
$(function($) {
console.log('loading script for chart');
var ed = '{{ senddate|date:"Y-m-d" }}'
console.log('End',ed);
console.log(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}')
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}', function(json) {
var script = json.script;
var div = json.div;
var totalsdict = json.totalsdict
var listofdicts = json.listofdicts
var activities_script = json.activities_script
var activities_chart = json.activities_chart
$("#id_sitready").remove();
$("#id_chart").append(div);
// $("#id_script").append("<s"+"cript>"+script+"</s"+"cript>");
$("#id_script").append(script);
$("#id_activities").append(activities_chart)
$("#activities_script").append(activities_script)
$("#total_hr").append(totalsdict.hrmean);
$("#total_maxhr").append(totalsdict.hrmax);
$("#total_power").append(totalsdict.powermean);
$("#total_maxpower").append(totalsdict.powermax);
listofdicts.forEach(function(item){
var id = "#"+item.id+"_hr";
$(id).append(item.hrmean);
id = "#"+item.id+"_hrmax";
$(id).append(item.hrmax);
id = "#"+item.id+"_power";
$(id).append(item.powermean);
id = "#"+item.id+"_powermax";
$(id).append(item.powermax);
})
});
console.log('loading script for chart');
var ed = '{{ senddate|date:"Y-m-d" }}'
console.log('End',ed);
console.log(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}')
$.getJSON(window.location.protocol + '//'+window.location.host + '/rowers/history/user/{{ rower.user.id }}/data/?startdate={{ sstartdate|date:"Y-m-d" }}&enddate={{ senddate|date:"Y-m-d" }}&workouttype={{ workouttype }}&yaxis={{ yaxis }}', function(json) {
var script = json.script;
var div = json.div;
var totalsdict = json.totalsdict
var listofdicts = json.listofdicts
var activities_script = json.activities_script
var activities_chart = json.activities_chart
$("#id_sitready").remove();
$("#id_chart").append(div);
$("#id_script").append(script);
$("#id_activities").append(activities_chart)
$("#activities_script").append(activities_script)
$("#total_hr").append(totalsdict.hrmean);
$("#total_maxhr").append(totalsdict.hrmax);
$("#total_power").append(totalsdict.powermean);
$("#total_maxpower").append(totalsdict.powermax);
listofdicts.forEach(function(item){
var id = "#"+item.id+"_hr";
$(id).append(item.hrmean);
id = "#"+item.id+"_hrmax";
$(id).append(item.hrmax);
id = "#"+item.id+"_power";
$(id).append(item.powermean);
id = "#"+item.id+"_powermax";
$(id).append(item.powermax);
})
});
});
</script>
{% endblock %}

View File

@@ -23,7 +23,7 @@
</li>
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v6.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>

View File

@@ -8,26 +8,26 @@
{% block scripts %}
{% include "monitorjobs.html" %}
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
$( "#slider-range" ).slider({
range: true,
min: 0,
max: {{ maxminutes }},
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
slide: function( event, ui ) {
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
$("#id_activeminutesmin").val(ui.values[0]);
$("#id_activeminutesmax").val(ui.values[1]);
}
});
$( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) +
" min - " + $( "#slider-range" ).slider( "values", 1 ) + " min ");
console.log({{ activeminutesmin }}, {{ activeminutesmax}}, 'active range');
$( "#slider-range" ).slider({
range: true,
min: 0,
max: {{ maxminutes }},
values: [ {{ activeminutesmin }}, {{ activeminutesmax }} ],
slide: function( event, ui ) {
$( "#amount" ).val(ui.values[ 0 ] + " min - " + ui.values[ 1 ] + " min " );
$("#id_activeminutesmin").val(ui.values[0]);
$("#id_activeminutesmax").val(ui.values[1]);
}
});
$( "#amount" ).val($( "#slider-range" ).slider( "values", 0 ) +
" min - " + $( "#slider-range" ).slider( "values", 1 ) + " min ");
} );
</script>
</script>
{% endblock %}
{% block main %}
@@ -42,78 +42,75 @@
{% endif %}
</p>
<p>
<table width=100%>
<tr>
<th>Name</th><td>{{ workout.name }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Public link to this workout</th>
<td>
<table width=100%>
<tr>
<th>Name</th><td>{{ workout.name }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Public link to this workout</th>
<td>
<a href="/rowers/workout/{{ workout.id|encode }}/">https://rowsandall.com/rowers/workout/{{ workout.id|encode }}</a>
<td>
</tr>
</table>
</p>
<td>
</tr>
</table>
</p>
<h1>Edit Workout Interval Data</h1>
<p>
Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest
intervals. To save your work, press the Save button above the updated summary.
Use "Interval Shorthand", "Feeling lucky?", or "Intervals by power/pace" methods below to edit work and rest
intervals. To save your work, press the Save button above the updated summary.
</p>
<ul class="main-content">
<li class="grid_2">
<h1>Updated Summary</h1>
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
{% csrf_token %}
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
{% for key,value in formvalues.items %}
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
{% endfor %}
<p>
<input type="submit" value="Save">
</p>
<span>
<a href="">Reset to last saved</a>
&nbsp;
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
</span>
</form>
<p>
<pre>
{{ intervalstats }}
</pre>
</p>
</li>
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
{{ the_div |safe }}
</li>
<li class="grid_2">
<h1>Feeling lucky?</h1>
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
section below, where you can edit it.
<form enctype="multipart/form-data" method="post">
<input type="hidden" name="ruptures" value="ruptures">
<table width=100%>
{{ ruptureform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="I'm feeling lucky">
</form>
</p>
</li>
<li class="grid_2">
<li class="grid_2">
<h1>Updated Summary</h1>
<form enctype="multipart/form-data" action="/rowers/workout/{{ workout.id|encode }}/editintervals/" method="post">
{% csrf_token %}
<input type="hidden" name="{{ savebutton }}" value="{{ intervalstring }}">
<input type="hidden" name="nrintervals" value={{ nrintervals }}>
{% for key,value in formvalues.items %}
<input type="hidden" name="{{ key }}" value="{{ value|safe }}">
{% endfor %}
<p>
<input type="submit" value="Save">
</p>
<span>
<a href="">Reset to last saved</a>
&nbsp;
<a href="/rowers/workout/{{ workout.id|encode }}/restore/">Restore Original data</a>
</span>
</form>
<p>
<pre>
{{ intervalstats }}
</pre>
</p>
</li>
<li class="grid_2">
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div |safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_2">
<h1>Feeling lucky?</h1>
<p>This new, experimental feature tries to find intervals by looking for patterns in the data. It does not
autodetect rest and work intervals yet. The resulting "interval string" is copied to the Interval Shorthand
section below, where you can edit it.
<form enctype="multipart/form-data" method="post">
<input type="hidden" name="ruptures" value="ruptures">
<table width=100%>
{{ ruptureform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="I'm feeling lucky">
</form>
</p>
</li>
<li class="grid_2">
<h1>Interval Shorthand</h1>
<p>
See the how-to <a href="#howto">at the bottom of this page</a> for details on how to use this form.
@@ -125,26 +122,26 @@
{% csrf_token %}
<input class="button" type="submit" value="Update">
</form>
</li>
<li class="grid_2">
</li>
<li class="grid_2">
<h1>Intervals by Power/Pace</h1>
<p>With this form, you can specify a power or pace level. Everything faster/harder than the
specified pace/power will become a work interval. Everything slower will become a rest
interval. Use the slider to limit the active range. Everything outside the active range will
become rest (warming up and cooling down).
become rest (warming up and cooling down).
</p>
<form ecntype="multipart/form-data" method="post">
<div id="slider-range"></div>
<p>
<label for="amount">Active Range:</label>
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p>
<div id="slider-range"></div>
<p>
<label for="amount">Active Range:</label>
<input type="text" id="amount" readonly style="border:0; color:#1c75bc; font-weight:bold;">
</p>
<table>
{{ powerupdateform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="Submit">
</form>
@@ -153,11 +150,11 @@
<h1 id="howto">Interval Shorthand How-To</h1>
<p>This is a quick way to enter the intervals using a special mini-language.</p>
<p>You enter something like <em>8x500m/3min</em>, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.</p>
<p>Special characters are <em>x</em> (times), <em>+</em> and <em>/</em> (denotes a rest interval), as well as <em>(</em> and <em>)</em>. Units are <em>min</em> (minutes), <em>sec</em> (seconds), <em>m</em> (meters) and <em>km</em> (km). </p>
<p>A typical interval is described as "<b>10min/5min</b>", with the work part before the "<b>/</b>" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "<b>1km</b>" or "<b>1000m</b>". The basic units can be combined with "<b>+</b>" and "<b>Nx</b>". You can use parentheses as in the example below.</p>
<p>Here are a few examples</p>
<table class="listtable" width=100%>
<tr>
@@ -182,32 +179,32 @@
</tr>
</table>
</li>
{% if courses %}
<li>
<h1>Interval by Course</h1>
<p>
This functionality allows you to record a time on a set course that you've rowed during the workout.
The summary will be updated to show time on course, and you can compare this with other
attempts.
</p>
<p>
{% if rower.share_course_results %}
You are currently sharing your course results with all Rowsandall users.
Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results.
{% else %}
You are currently hiding your course results (except for your participation in online challenges).
Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results.
{% endif %}
</p>
<form ecntype="multipart/form-data" method="post">
<table>
{{ courseselectform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="Select Course">
</form>
</li>
{% endif %}
{% if courses %}
<li>
<h1>Interval by Course</h1>
<p>
This functionality allows you to record a time on a set course that you've rowed during the workout.
The summary will be updated to show time on course, and you can compare this with other
attempts.
</p>
<p>
{% if rower.share_course_results %}
You are currently sharing your course results with all Rowsandall users.
Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results.
{% else %}
You are currently hiding your course results (except for your participation in online challenges).
Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results.
{% endif %}
</p>
<form ecntype="multipart/form-data" method="post">
<table>
{{ courseselectform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="Select Course">
</form>
</li>
{% endif %}
</ul>
{% endblock %}

View File

@@ -98,14 +98,11 @@
</li>
{% endif %}
<li class="grid_2">
<script src="https://cdn.pydata.org/bokeh/release/bokeh-3.1.1.min.js"></script>
<script async="true" type="text/javascript">
Bokeh.set_log_level("info");
</script>
{{ interactiveplot |safe }}
<script src="https://d3js.org/d3.v6.js"></script>
{{ the_div|safe }}
{{ interactiveplot |safe }}
</li>
<li class="grid_4">

View File

@@ -443,9 +443,6 @@ urlpatterns = [
views.trainingzones_view_data, name="trainingzones_view_data"),
re_path(r'^trainingzones/data/$', views.trainingzones_view_data,
name="trainingzones_view_data"),
re_path(r'^ote-bests2/user/(?P<userid>\d+)/$',
views.rankings_view2, name='rankings_view2'),
re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'),
re_path(r'^analysisdata/user/(?P<userid>\d+)/$', views.analysis_view_data,
name='analysis_view_data'),
re_path(r'^analysisdata/$', views.analysis_view_data,
@@ -470,8 +467,6 @@ urlpatterns = [
views.workout_upload_view, name='workout_upload_view'),
re_path(r'^workout/upload/$', views.workout_upload_view,
name='workout_upload_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view,
name='workout_histo_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view,
name='workout_forcecurve_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/(?P<analysis>\d+)/$', views.workout_forcecurve_view,

View File

@@ -1382,398 +1382,6 @@ def ajax_agegrouprecords(request,
)
# Show ranking distances including predicted paces
@login_required()
@permission_required('rower.is_coach', fn=get_user_by_userid, raise_exception=True)
def rankings_view2(request, userid=0,
startdate=timezone.now()-datetime.timedelta(days=365),
enddate=timezone.now(),
deltadays=-1,
startdatestring="",
enddatestring=""):
if deltadays > 0: # pragma: no cover
startdate = enddate-datetime.timedelta(days=int(deltadays))
if startdatestring != "": # pragma: no cover
startdate = iso8601.parse_date(startdatestring)
if enddatestring != "": # pragma: no cover
enddate = iso8601.parse_date(enddatestring)
if enddate < startdate: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
if userid == 0:
userid = request.user.id
else:
lastupdated = "1900-01-01"
promember = 0
r = getrequestrower(request, userid=userid)
theuser = r.user
wcdurations = []
wcpower = []
lastupdated = "1900-01-01"
userid = 0
if 'options' in request.session:
options = request.session['options']
try:
wcdurations = options['wcdurations']
wcpower = options['wcpower']
lastupdated = options['lastupdated']
except KeyError: # pragma: no cover
pass
try:
userid = options['userid']
except KeyError: # pragma: no cover
userid = 0
else:
options = {}
lastupdatedtime = arrow.get(lastupdated).timestamp()
current_time = arrow.utcnow().timestamp()
deltatime_seconds = current_time - lastupdatedtime
recalc = False
if str(userid) != str(theuser) or deltatime_seconds > 3600:
recalc = True
options['lastupdated'] = arrow.utcnow().isoformat()
else: # pragma: no cover
recalc = False
options['userid'] = theuser.id
if r.birthdate:
age = calculate_age(r.birthdate)
else:
age = 0
agerecords = CalcAgePerformance.objects.filter(
age=age,
sex=r.sex,
weightcategory=r.weightcategory)
if len(agerecords) == 0:
recalc = True
wcpower = []
wcdurations = []
else:
wcdurations = []
wcpower = []
for record in agerecords:
wcdurations.append(record.duration)
wcpower.append(record.power)
options['wcpower'] = wcpower
options['wcdurations'] = wcdurations
if theuser:
options['userid'] = theuser.id
request.session['options'] = options
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: # pragma: no cover
s = enddate
enddate = startdate
startdate = s
elif request.method == 'POST' and "datedelta" in request.POST: # pragma: no cover
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: # pragma: no cover
r = 0
uu = theuser
# test to fix bug
startdate = datetime.datetime.combine(startdate, datetime.time())
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
startdate = arrow.get(startdate).datetime
enddate = arrow.get(enddate).datetime
thedistances = []
theworkouts = []
thesecs = []
rankingdistances.sort()
rankingdurations.sort()
for rankingdistance in rankingdistances:
workouts = Workout.objects.filter(
user=r, distance=rankingdistance,
workouttype__in=['rower', 'dynamic', 'slides'],
rankingpiece=True,
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',
rankingpiece=True,
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]
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: # pragma: no cover
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)
try:
pwr = int(pwr)
except (ValueError, AttributeError): # pragma: no cover
pwr = 0
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: # pragma: no cover
pwr2 = 50.
velo2 = (pwr2/2.8)**(1./3.)
if np.isnan(velo2) or velo2 <= 0: # pragma: no cover
velo2 = 1.0
t2 = rankingdistance/velo2
pwr3 = p1[0]/(1+t2/p1[2])
pwr3 += p1[1]/(1+t2/p1[3])
if pwr3 <= 0: # pragma: no cover
pwr3 = 50.
velo3 = (pwr3/2.8)**(1./3.)
if np.isnan(velo3) or velo3 <= 0: # pragma: no cover
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)
try:
a = {'distance': int(d),
'duration': timedeltaconv(t),
'pace': timedeltaconv(p),
'power': int(pwr)}
predictions.append(a)
except: # pragma: no cover
pass
# CP model
pwr = p1[0] / (1 + t / p1[2])
pwr += p1[1] / (1 + t / p1[3])
if pwr <= 0: # pragma: no cover
pwr = 50.
velo = (pwr / 2.8)**(1. / 3.)
if np.isnan(velo) or velo <= 0: # pragma: no cover
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)
if recalc:
wcdurations = []
wcpower = []
durations = [1, 4, 30, 60]
distances = [100, 500, 1000, 2000, 5000, 6000, 10000, 21097, 42195]
df = pd.DataFrame(
list(
C2WorldClassAgePerformance.objects.filter(
sex=r.sex,
weightcategory=r.weightcategory
).values()
)
)
jsondf = df.to_json()
job = myqueue(queue,
handle_getagegrouprecords,
jsondf, distances, durations, age, r.sex, r.weightcategory)
try:
request.session['async_tasks'] += [(job.id, 'agegrouprecords')]
except KeyError:
request.session['async_tasks'] = [(job.id, 'agegrouprecords')]
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,
'rower': r,
'active': 'nav-analysis',
'age': age,
'sex': r.sex,
'recalc': recalc,
'weightcategory': r.weightcategory,
'startdate': startdate,
'enddate': enddate,
'teams': get_my_teams(request.user),
})
@login_required()
def otecp_toadmin_view(request, theuser=0,
startdate=timezone.now() - datetime.timedelta(days=365),

View File

@@ -630,50 +630,6 @@ def otw_use_gps(request, id=0):
return HttpResponseRedirect(url)
# Show Stroke power histogram for a workout
@login_required()
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
def workout_histo_view(request, id=0):
w = get_workoutuser(id, request)
r = getrequestrower(request)
mayedit = 0
if w.user == r:
mayedit = 1
res = interactive_histoall([w], 'power', False)
script = res[0]
div = res[1]
breadcrumbs = [
{
'url': '/rowers/list-workouts/',
'name': 'Workouts'
},
{
'url': get_workout_default_page(request, id),
'name': w.name
},
{
'url': reverse('workout_histo_view', kwargs={'id': id}),
'name': 'Histogram'
}
]
return render(request,
'histo_single.html',
{'interactiveplot': script,
'breadcrumbs': breadcrumbs,
'active': 'nav-workouts',
'workout': w,
'rower': r,
'the_div': div,
'id': id,
'mayedit': mayedit,
'teams': get_my_teams(request.user),
})
# add a workout manually
@login_required()