instroke interactive as much polars as works
This commit is contained in:
@@ -1177,440 +1177,6 @@ def interactive_agegroup_plot(df, distance=2000, duration=None,
|
||||
|
||||
return script, div
|
||||
|
||||
|
||||
def interactive_cpchart(rower, thedistances, thesecs, theavpower,
|
||||
theworkouts, promember=0,
|
||||
wcpower=[], wcdurations=[]):
|
||||
|
||||
message = 0
|
||||
# plot tools
|
||||
if (promember == 1):
|
||||
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
||||
else: # pragma: no cover
|
||||
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair'
|
||||
|
||||
x_axis_type = 'log'
|
||||
|
||||
thesecs = pd.Series(thesecs)
|
||||
|
||||
velo = thedistances/thesecs
|
||||
p = pd.Series(500./velo)
|
||||
|
||||
p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
|
||||
source = ColumnDataSource(
|
||||
data=dict(
|
||||
dist=thedistances,
|
||||
duration=thesecs,
|
||||
spm=0*theavpower,
|
||||
tim=niceformat(
|
||||
thesecs.fillna(method='ffill').apply(
|
||||
lambda x: timedeltaconv(x))
|
||||
),
|
||||
|
||||
power=theavpower,
|
||||
fpace=nicepaceformat(p2),
|
||||
)
|
||||
)
|
||||
|
||||
# fitting the data to Paul
|
||||
if len(thedistances) >= 2:
|
||||
paulslope, paulintercept, r, p, stderr = linregress(
|
||||
np.log10(thedistances), p)
|
||||
else: # pragma: no cover
|
||||
paulslope = 5.0/np.log10(2.0)
|
||||
paulintercept = p[0]-paulslope*np.log10(thedistances[0])
|
||||
|
||||
fitx = pd.Series(np.arange(100)*2*max(np.log10(thedistances))/100.)
|
||||
|
||||
fitp = paulslope*fitx+paulintercept
|
||||
|
||||
fitvelo = 500./fitp
|
||||
fitpower = 2.8*(fitvelo**3)
|
||||
fitt = 10**fitx/fitvelo
|
||||
fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
|
||||
sourcepaul = ColumnDataSource(
|
||||
data=dict(
|
||||
dist=10**fitx,
|
||||
duration=fitt,
|
||||
power=fitpower,
|
||||
spm=0*fitpower,
|
||||
tim=niceformat(
|
||||
fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
),
|
||||
fpace=nicepaceformat(fitp2),
|
||||
)
|
||||
)
|
||||
|
||||
def fitfunc(pars, x):
|
||||
return pars[0] / (1+(x/pars[2])) + pars[1]/(1+(x/pars[3]))
|
||||
|
||||
def errfunc(pars, x, y):
|
||||
return fitfunc(pars, x)-y
|
||||
|
||||
# p0 = [500,350,10,8000]
|
||||
wcpower = pd.Series(wcpower, dtype='float')
|
||||
wcdurations = pd.Series(wcdurations, dtype='float')
|
||||
|
||||
# fitting WC data to three parameter CP model
|
||||
if len(wcdurations) >= 4:
|
||||
p1wc, success = optimize.leastsq(errfunc, p0[:],
|
||||
args=(wcdurations, wcpower))
|
||||
else: # pragma: no cover
|
||||
p1wc = None
|
||||
|
||||
# fitting the data to three parameter CP model
|
||||
|
||||
success = 0
|
||||
p1 = p0
|
||||
if len(thesecs) >= 4:
|
||||
try:
|
||||
p1, success = optimize.leastsq(
|
||||
errfunc, p0[:], args=(thesecs, theavpower))
|
||||
except (RuntimeError, RuntimeWarning): # pragma: no cover
|
||||
factor = fitfunc(p0, thesecs.mean())/theavpower.mean()
|
||||
p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]]
|
||||
success = 0
|
||||
else: # pragma: no cover
|
||||
factor = fitfunc(p0, thesecs.mean())/theavpower.mean()
|
||||
p1 = [p0[0]/factor, p0[1]/factor, p0[2], p0[3]]
|
||||
success = 0
|
||||
|
||||
# Get stayer score
|
||||
if success == 1: # pragma: no cover
|
||||
power4min = fitfunc(p1, 240.)
|
||||
power1h = fitfunc(p1, 3600.)
|
||||
power10sec = fitfunc(p1, 10.)
|
||||
r10sec4min = 100.*power10sec/power4min
|
||||
r1h4min = 100.*power1h/power4min
|
||||
|
||||
combined = r1h4min-0.2*(r10sec4min-100)
|
||||
|
||||
dataset = pd.read_csv('static/stats/combined_set.csv')
|
||||
|
||||
stayerscore = int(percentileofscore(dataset['combined'], combined))
|
||||
else:
|
||||
stayerscore = None
|
||||
|
||||
fitt = pd.Series(10**(4*np.arange(100)/100.))
|
||||
|
||||
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: # pragma: no cover
|
||||
fitpowerwc = 0*fitpower
|
||||
fitpowerexcellent = 0*fitpower
|
||||
fitpowergood = 0*fitpower
|
||||
fitpowerfair = 0*fitpower
|
||||
fitpoweraverage = 0*fitpower
|
||||
|
||||
message = ""
|
||||
if len(fitpower[fitpower < 0]) > 0: # pragma: no cover
|
||||
message = "CP model fit didn't give correct results"
|
||||
|
||||
fitvelo = (fitpower/2.8)**(1./3.)
|
||||
fitdist = fitt*fitvelo
|
||||
fitp = 500./fitvelo
|
||||
fitp2 = fitp.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
|
||||
sourcecomplex = ColumnDataSource(
|
||||
data=dict(
|
||||
dist=fitdist,
|
||||
duration=fitt,
|
||||
tim=niceformat(
|
||||
fitt.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
),
|
||||
spm=0*fitpower,
|
||||
power=fitpower,
|
||||
fitpowerwc=fitpowerwc,
|
||||
fitpowerexcellent=fitpowerexcellent,
|
||||
fitpowergood=fitpowergood,
|
||||
fitpowerfair=fitpowerfair,
|
||||
fitpoweraverage=fitpoweraverage,
|
||||
fpace=nicepaceformat(fitp2),
|
||||
)
|
||||
)
|
||||
|
||||
# making the plot
|
||||
plot = figure(tools=TOOLS, x_axis_type=x_axis_type,
|
||||
width=900,
|
||||
toolbar_location="above",
|
||||
toolbar_sticky=False)
|
||||
|
||||
# add watermark
|
||||
watermarkurl = "/static/img/logo7.png"
|
||||
watermarkrange = Range1d(start=0, end=1)
|
||||
watermarkalpha = 0.6
|
||||
watermarky = 0.01
|
||||
watermarkw = 184
|
||||
watermarkh = 35
|
||||
watermarkanchor = 'bottom_right'
|
||||
plot.extra_y_ranges = {"watermark": watermarkrange}
|
||||
#plot.sizing_mode = 'scale_both'
|
||||
|
||||
plot.image_url([watermarkurl], 1.8*max(thesecs), watermarky,
|
||||
watermarkw, watermarkh,
|
||||
global_alpha=watermarkalpha,
|
||||
w_units='screen',
|
||||
h_units='screen',
|
||||
anchor=watermarkanchor,
|
||||
dilate=True,
|
||||
y_range_name="watermark",
|
||||
)
|
||||
|
||||
plot.circle('duration', 'power', source=source, fill_color='red', size=15,
|
||||
legend_label='Power')
|
||||
plot.xaxis.axis_label = "Duration (seconds)"
|
||||
plot.yaxis.axis_label = "Power (W)"
|
||||
|
||||
if stayerscore is not None: # pragma: no cover
|
||||
plot.add_layout(
|
||||
Label(x=100, y=100, x_units='screen', y_units='screen',
|
||||
text='Stayer Score '+str(stayerscore)+'%',
|
||||
background_fill_alpha=0.7,
|
||||
background_fill_color='white',
|
||||
text_color='black')
|
||||
)
|
||||
# plot.add_layout(
|
||||
# Label(x=100,y=120,x_units='screen',y_units='screen',
|
||||
# text='Stayer Score (6min) '+str(stayerscore2)+'%',
|
||||
# background_fill_alpha=0.7,
|
||||
# background_fill_color='white',
|
||||
# text_color='black')
|
||||
# )
|
||||
|
||||
cpdata = dataprep.fetchcperg(rower, theworkouts)
|
||||
|
||||
if cpdata.empty: # pragma: no cover
|
||||
message = 'Calculations are running in the background. Please refresh this page to see updated results'
|
||||
return ['', '', paulslope, paulintercept, p1, message, p1wc]
|
||||
|
||||
velo = cpdata['distance']/cpdata['delta']
|
||||
|
||||
p = 500./velo
|
||||
|
||||
p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
|
||||
|
||||
source2 = ColumnDataSource(
|
||||
data=dict(
|
||||
duration=cpdata['delta'],
|
||||
power=cpdata['cp'],
|
||||
tim=niceformat(
|
||||
cpdata['delta'].fillna(method='ffill').apply(
|
||||
lambda x: timedeltaconv(x))
|
||||
),
|
||||
dist=cpdata['distance'],
|
||||
pace=nicepaceformat(p2),
|
||||
)
|
||||
)
|
||||
|
||||
plot.circle('duration', 'power', source=source2,
|
||||
fill_color='blue', size=3,
|
||||
legend_label='Power from segments')
|
||||
|
||||
hover = plot.select(dict(type=HoverTool))
|
||||
|
||||
hover.tooltips = OrderedDict([
|
||||
('Duration ', '@tim'),
|
||||
('Power (W)', '@power{int}'),
|
||||
('Distance (m)', '@dist{int}'),
|
||||
('Pace (/500m)', '@fpace'),
|
||||
])
|
||||
|
||||
hover.mode = 'mouse'
|
||||
|
||||
plot.y_range = Range1d(0, 1.5*max(theavpower))
|
||||
plot.x_range = Range1d(1, 2*max(thesecs))
|
||||
plot.legend.orientation = "vertical"
|
||||
|
||||
plot.line('duration', 'power', source=sourcepaul,
|
||||
legend_label="Paul's Law")
|
||||
plot.line('duration', 'power', source=sourcecomplex, legend_label="CP Model",
|
||||
color='green')
|
||||
if p1wc is not None:
|
||||
plot.line('duration', 'fitpowerwc', source=sourcecomplex,
|
||||
legend_label="World Class",
|
||||
color='Maroon', line_dash='dotted')
|
||||
|
||||
plot.line('duration', 'fitpowerexcellent', source=sourcecomplex,
|
||||
legend_label="90% percentile",
|
||||
color='Purple', line_dash='dotted')
|
||||
|
||||
plot.line('duration', 'fitpowergood', source=sourcecomplex,
|
||||
legend_label="75% percentile",
|
||||
color='Olive', line_dash='dotted')
|
||||
|
||||
plot.line('duration', 'fitpowerfair', source=sourcecomplex,
|
||||
legend_label="50% percentile",
|
||||
color='Gray', line_dash='dotted')
|
||||
|
||||
plot.line('duration', 'fitpoweraverage', source=sourcecomplex,
|
||||
legend_label="25% percentile",
|
||||
color='SkyBlue', line_dash='dotted')
|
||||
|
||||
script, div = components(plot)
|
||||
|
||||
return [script, div, paulslope, paulintercept, p1, message, p1wc]
|
||||
|
||||
|
||||
def interactive_windchart(id=0, promember=0):
|
||||
# check if valid ID exists (workout exists)
|
||||
row = Workout.objects.get(id=id)
|
||||
# g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
|
||||
|
||||
f1 = row.csvfilename
|
||||
|
||||
# create interactive plot
|
||||
plot = figure(width=400, height=300)
|
||||
|
||||
# get user
|
||||
# u = User.objects.get(id=row.user.id)
|
||||
r = row.user
|
||||
|
||||
rr = rrower(hrmax=r.max, hrut2=r.ut2,
|
||||
hrut1=r.ut1, hrat=r.at,
|
||||
hrtr=r.tr, hran=r.an, ftp=r.ftp)
|
||||
|
||||
rowdata = rdata(f1, rower=rr)
|
||||
if rowdata == 0: # pragma: no cover
|
||||
return 0
|
||||
|
||||
try:
|
||||
dist = rowdata.df.loc[:, 'cum_dist']
|
||||
except KeyError:
|
||||
return ['', 'No Data Found']
|
||||
|
||||
try: # pragma: no cover
|
||||
vwind = rowdata.df.loc[:, 'vwind']
|
||||
winddirection = rowdata.df.loc[:, 'winddirection']
|
||||
bearing = rowdata.df.loc[:, 'bearing']
|
||||
except KeyError:
|
||||
rowdata.add_wind(0, 0)
|
||||
rowdata.add_bearing()
|
||||
vwind = rowdata.df.loc[:, 'vwind']
|
||||
winddirection = rowdata.df.loc[:, 'winddirection']
|
||||
bearing = rowdata.df.loc[:, 'winddirection']
|
||||
rowdata.write_csv(f1, gzip=True)
|
||||
dataprep.update_strokedata(id, rowdata.df)
|
||||
|
||||
winddirection = winddirection % 360
|
||||
winddirection = (winddirection + 360) % 360
|
||||
|
||||
tw = tailwind(bearing, vwind, 1.0*winddirection)
|
||||
|
||||
source = ColumnDataSource(
|
||||
data=dict(
|
||||
dist=dist,
|
||||
vwind=vwind,
|
||||
tw=tw,
|
||||
winddirection=winddirection,
|
||||
)
|
||||
)
|
||||
|
||||
# plot tools
|
||||
if (promember == 1):
|
||||
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
||||
else: # pragma: no cover
|
||||
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
||||
|
||||
# making the plot
|
||||
plot = figure(tools=TOOLS, width=400, height=500,
|
||||
# toolbar_location="below",
|
||||
toolbar_sticky=False,
|
||||
)
|
||||
plot.line('dist', 'vwind', source=source, legend_label="Wind Speed (m/s)")
|
||||
plot.line('dist', 'tw', source=source,
|
||||
legend_label="Tail (+)/Head (-) Wind (m/s)", color='black')
|
||||
try:
|
||||
plot.title.text = row.name
|
||||
except ValueError: # pragma: no cover
|
||||
plot.title.text = ""
|
||||
# plot.title.text_font_size="1.0em"
|
||||
plot.title.text_font = "1.0em"
|
||||
plot.xaxis.axis_label = "Distance (m)"
|
||||
plot.yaxis.axis_label = "Wind Speed (m/s)"
|
||||
plot.y_range = Range1d(-7, 7)
|
||||
#plot.sizing_mode = 'stretch_both'
|
||||
|
||||
plot.extra_y_ranges = {"winddirection": Range1d(start=0, end=360)}
|
||||
plot.line('dist', 'winddirection', source=source,
|
||||
legend_label='Wind Direction', color="red",
|
||||
y_range_name="winddirection")
|
||||
plot.add_layout(LinearAxis(y_range_name="winddirection",
|
||||
axis_label="Wind Direction (degree)"), 'right')
|
||||
|
||||
script, div = components(plot)
|
||||
|
||||
return [script, div]
|
||||
|
||||
|
||||
def interactive_streamchart(id=0, promember=0):
|
||||
# check if valid ID exists (workout exists)
|
||||
row = Workout.objects.get(id=id)
|
||||
# g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime")
|
||||
|
||||
f1 = row.csvfilename
|
||||
|
||||
# create interactive plot
|
||||
plot = figure(width=400,
|
||||
)
|
||||
# get user
|
||||
# u = User.objects.get(id=row.user.id)
|
||||
r = row.user
|
||||
|
||||
rr = rrower(hrmax=r.max, hrut2=r.ut2,
|
||||
hrut1=r.ut1, hrat=r.at,
|
||||
hrtr=r.tr, hran=r.an, ftp=r.ftp)
|
||||
|
||||
rowdata = rdata(f1, rower=rr)
|
||||
if rowdata == 0: # pragma: no cover
|
||||
return "", "No Valid Data Available"
|
||||
|
||||
try:
|
||||
dist = rowdata.df.loc[:, 'cum_dist']
|
||||
except KeyError:
|
||||
return ['', 'No Data found']
|
||||
|
||||
try:
|
||||
vstream = rowdata.df.loc[:, 'vstream']
|
||||
except KeyError:
|
||||
rowdata.add_stream(0)
|
||||
vstream = rowdata.df.loc[:, 'vstream']
|
||||
rowdata.write_csv(f1, gzip=True)
|
||||
dataprep.update_strokedata(id, rowdata.df)
|
||||
|
||||
# plot tools
|
||||
if (promember == 1):
|
||||
TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
||||
else: # pragma: no cover
|
||||
TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair'
|
||||
|
||||
# making the plot
|
||||
plot = figure(tools=TOOLS, width=400, height=500,
|
||||
# toolbar_location="below",
|
||||
toolbar_sticky=False,
|
||||
)
|
||||
plot.line(dist, vstream, legend_label="River Stream Velocity (m/s)")
|
||||
try:
|
||||
plot.title.text = row.name
|
||||
except ValueError: # pragma: no cover
|
||||
plot.title.text = ""
|
||||
plot.title.text_font_size = "1.0em"
|
||||
plot.xaxis.axis_label = "Distance (m)"
|
||||
plot.yaxis.axis_label = "River Current (m/s)"
|
||||
plot.y_range = Range1d(-2, 2)
|
||||
#plot.sizing_mode = 'stretch_both'
|
||||
|
||||
script, div = components(plot)
|
||||
|
||||
return [script, div]
|
||||
|
||||
def forcecurve_multi_interactive_chart(selected): # pragma: no cover
|
||||
ids = [analysis.id for analysis in selected]
|
||||
workoutids = [analysis.workout.id for analysis in selected]
|
||||
@@ -1678,12 +1244,13 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co
|
||||
maximum_values[analysis.metric] = mean_vals.max()
|
||||
xvals = np.arange(len(mean_vals))
|
||||
|
||||
data2 = pd.DataFrame({
|
||||
'x': pd.Series(xvals),
|
||||
'y': pd.Series(mean_vals),
|
||||
data2 = pl.DataFrame({
|
||||
'x': pl.Series(xvals),
|
||||
'y': pl.Series(mean_vals),
|
||||
|
||||
})
|
||||
data2['id'] = cntr
|
||||
data2 = data2.with_columns((pl.lit(cntr)).alias("id"))
|
||||
|
||||
df2.append(data2)
|
||||
|
||||
legendlabel = '{name} - {metric} - {workout}'.format(
|
||||
@@ -1704,9 +1271,9 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co
|
||||
ytitle = 'Scaled'
|
||||
cntr = cntr+1
|
||||
|
||||
df2 = pd.concat(df2, axis=0)
|
||||
df2 = pl.concat(df2)
|
||||
|
||||
data_dict = df2.to_dict("records")
|
||||
data_dict = df2.to_dicts()
|
||||
|
||||
chart_data = {
|
||||
'title': '',
|
||||
@@ -1725,24 +1292,25 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
||||
individual_curves,
|
||||
name='',notes=''): # pragma: no cover
|
||||
|
||||
df_pos = (df+abs(df))/2.
|
||||
df_min = -(-df+abs(-df))/2.
|
||||
|
||||
if df.empty:
|
||||
return "", "No data in selection"
|
||||
|
||||
mean_vals = df.median().replace(0, np.nan)
|
||||
q75 = df_pos.quantile(q=0.75).replace(0,np.nan)
|
||||
q25 = df_pos.quantile(q=0.25).replace(0,np.nan)
|
||||
q75min = df_min.quantile(q=0.75).replace(0,np.nan)
|
||||
q25min = df_min.quantile(q=0.25).replace(0,np.nan)
|
||||
|
||||
df_pos = (df+abs(df))/2.
|
||||
df_min = -(-df+abs(-df))/2.
|
||||
|
||||
|
||||
mean_vals = df.median().replace(0, np.nan)
|
||||
q75 = df_pos.quantile(q = 0.75).replace(0, np.nan)
|
||||
q25 = df_pos.quantile(q=0.25).replace(0, np.nan)
|
||||
q75min = df_min.quantile(q=0.75).replace(0, np.nan)
|
||||
q25min = df_min.quantile(q=0.25).replace(0, np.nan)
|
||||
|
||||
mean_vals = mean_vals.interpolate()
|
||||
|
||||
xvals = np.arange(len(mean_vals))
|
||||
|
||||
df_plot = pd.DataFrame({
|
||||
df_plot = pl.DataFrame({
|
||||
'x':xvals,
|
||||
'median':mean_vals,
|
||||
'high':q75,
|
||||
@@ -1751,14 +1319,17 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
||||
'low 2': q25,
|
||||
})
|
||||
|
||||
df_plot['high'].update(df_plot.pop('high 2'))
|
||||
df_plot['low'].update(df_plot.pop('low 2'))
|
||||
try:
|
||||
df_plot.interpolate(axis=1,inplace=True)
|
||||
except TypeError:
|
||||
pass
|
||||
df_plot = df_plot.with_columns(
|
||||
pl.coalesce(["high", "high 2"]).alias("high")
|
||||
)
|
||||
|
||||
df_plot = df_plot.with_columns(
|
||||
pl.coalesce("low", "low 2").alias("low")
|
||||
)
|
||||
|
||||
df_plot = df_plot.drop(["high 2", "low 2"])
|
||||
df_plot = df_plot.drop_nulls()
|
||||
|
||||
|
||||
if metric == 'boat accelerator curve':
|
||||
ytitle = "Boat acceleration (m/s^2)"
|
||||
elif metric == 'instroke boat speed':
|
||||
@@ -1771,7 +1342,7 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
||||
|
||||
|
||||
lines_dict = df.to_dict("records")
|
||||
data_dict = df_plot.to_dict("records")
|
||||
data_dict = df_plot.to_dicts()
|
||||
|
||||
chart_data = {
|
||||
'lines': lines_dict,
|
||||
@@ -1790,7 +1361,6 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max,
|
||||
'analysis_name': name,
|
||||
}
|
||||
|
||||
|
||||
script, div = get_chart("/instroke", chart_data, debug=False)
|
||||
|
||||
return script, div
|
||||
|
||||
@@ -1523,19 +1523,6 @@ def otwcp_toadmin_view(request, theuser=0,
|
||||
return response
|
||||
|
||||
|
||||
def agegroupcpview(request, age, normalize=0, userid=0):
|
||||
script, div = interactive_agegroupcpchart(age, normalized=normalize)
|
||||
|
||||
response = render(request, 'agegroupcp.html',
|
||||
{
|
||||
'active': 'nav-analysis',
|
||||
'interactiveplot': script,
|
||||
'the_div': div,
|
||||
}
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def agegrouprecordview(request, sex='male', weightcategory='hwt',
|
||||
distance=2000, duration=None):
|
||||
|
||||
Reference in New Issue
Block a user