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 frac_an = totalseconds*df.query(qry)['deltat'].sum()/sumtimehr
datadict = { datadict = {
'<{ut2}'.format(ut2=hrzones[1]): frac_lut2, '&lt;{ut2}'.format(ut2=hrzones[1]): frac_lut2,
'{ut2}'.format(ut2=hrzones[1]): frac_ut2, '{ut2}'.format(ut2=hrzones[1]): frac_ut2,
'{ut1}'.format(ut1=hrzones[2]): frac_ut1, '{ut1}'.format(ut1=hrzones[2]): frac_ut1,
'{at}'.format(at=hrzones[3]): frac_at, '{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['angle'] = data['value']/data['value'].sum() * 2*pi
data['color'] = colors data['color'] = colors
data['zone'] = [ data['zone'] = [
'<{ut2}'.format(ut2=hrzones[1]), '&lt;{ut2}'.format(ut2=hrzones[1]),
'{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]),
'{ut1}'.format(ut1=hrzones[2]), '{ut1}'.format(ut1=hrzones[2]),
'{at}'.format(at=hrzones[3]), '{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']]) data['totaltime'] = pd.Series([pretty_timedelta(v) for v in data['value']])
TOOLS = 'save,hover' data_dict = data.to_dict("records")
chart_data = {
z = figure(title="HR "+title, x_range=(-0.5, 1), height=375, 'data': data_dict,
tools=TOOLS, toolbar_location=None, tooltips="@zone: @totaltime", 'title': "HR "+ title
) }
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)
script, div = get_chart("/hrpie", chart_data, debug=True)
return script, div
def pretty_timedelta(secs): def pretty_timedelta(secs):
hours, remainder = divmod(secs, 3600) hours, remainder = divmod(secs, 3600)

View File

@@ -102,14 +102,11 @@
</li> </li>
{% endif %} {% endif %}
<li class="grid_2"> <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>
{{ interactiveplot |safe }}
{{ the_div|safe }} {{ the_div|safe }}
{{ interactiveplot |safe }}
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -6,12 +6,9 @@
{% block main %} {% block main %}
<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>
{{ interactiveplot |safe }}
@@ -24,6 +21,7 @@
</li> </li>
</ul> </ul>
{{ interactiveplot |safe }}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -12,6 +12,7 @@
<div id="id_js_res"> <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-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://cdn.pydata.org/bokeh/release/bokeh-widgets-3.1.1.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</div> </div>
<script async="true" type="text/javascript"> <script async="true" type="text/javascript">
@@ -157,7 +158,6 @@
var activities_chart = json.activities_chart var activities_chart = json.activities_chart
$("#id_sitready").remove(); $("#id_sitready").remove();
$("#id_chart").append(div); $("#id_chart").append(div);
// $("#id_script").append("<s"+"cript>"+script+"</s"+"cript>");
$("#id_script").append(script); $("#id_script").append(script);
$("#id_activities").append(activities_chart) $("#id_activities").append(activities_chart)
$("#activities_script").append(activities_script) $("#activities_script").append(activities_script)

View File

@@ -23,7 +23,7 @@
</li> </li>
<li class="grid_2"> <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"> <script async="true" type="text/javascript">
Bokeh.set_log_level("info"); Bokeh.set_log_level("info");
</script> </script>

View File

@@ -89,14 +89,11 @@
</p> </p>
</li> </li>
<li class="grid_2"> <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>
{{ interactiveplot |safe }}
{{ the_div |safe }} {{ the_div |safe }}
{{ interactiveplot |safe }}
</li> </li>
<li class="grid_2"> <li class="grid_2">
<h1>Feeling lucky?</h1> <h1>Feeling lucky?</h1>

View File

@@ -98,14 +98,11 @@
</li> </li>
{% endif %} {% endif %}
<li class="grid_2"> <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>
{{ interactiveplot |safe }}
{{ the_div|safe }} {{ the_div|safe }}
{{ interactiveplot |safe }}
</li> </li>
<li class="grid_4"> <li class="grid_4">

View File

@@ -443,9 +443,6 @@ urlpatterns = [
views.trainingzones_view_data, name="trainingzones_view_data"), views.trainingzones_view_data, name="trainingzones_view_data"),
re_path(r'^trainingzones/data/$', views.trainingzones_view_data, re_path(r'^trainingzones/data/$', views.trainingzones_view_data,
name="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, re_path(r'^analysisdata/user/(?P<userid>\d+)/$', views.analysis_view_data,
name='analysis_view_data'), name='analysis_view_data'),
re_path(r'^analysisdata/$', views.analysis_view_data, re_path(r'^analysisdata/$', views.analysis_view_data,
@@ -470,8 +467,6 @@ urlpatterns = [
views.workout_upload_view, name='workout_upload_view'), views.workout_upload_view, name='workout_upload_view'),
re_path(r'^workout/upload/$', views.workout_upload_view, re_path(r'^workout/upload/$', views.workout_upload_view,
name='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, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view,
name='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, 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() @login_required()
def otecp_toadmin_view(request, theuser=0, def otecp_toadmin_view(request, theuser=0,
startdate=timezone.now() - datetime.timedelta(days=365), startdate=timezone.now() - datetime.timedelta(days=365),

View File

@@ -630,50 +630,6 @@ def otw_use_gps(request, id=0):
return HttpResponseRedirect(url) 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 # add a workout manually
@login_required() @login_required()