From 3040997317d5dfa285da93fa266501f80c072d24 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
{% if workout|previousworkout:rower.user %}
@@ -35,9 +25,7 @@
{{ searchform }} No workouts found {{ searchform }} No workouts found The dashed lines are based on the
- Concept2
- rankings for your age ({{ age }}), gender ({{ sex }})
- and weight category ({{ weightcategory }}). The Gold Medal
- Standard power is derived from the
-
- World Record for your category.
- 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.
-
- For rowing on the water, the results are corrected for the expected
- difference in power between the Concept2 indoor rower and power
- values on the water.
- The dashed lines are based on the
+ Concept2
+ rankings for your age ({{ age }}), gender ({{ sex }})
+ and weight category ({{ weightcategory }}). The Gold Medal
+ Standard power is derived from the
+
+ World Record for your category.
+ 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.
+
+ For rowing on the water, the results are corrected for the expected
+ difference in power between the Concept2 indoor rower and power
+ values on the water.
+
{% if workout|previousworkout:rower.user %}
@@ -34,6 +26,7 @@
- Sleep
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_analytics.html' %}
+{% endblock %}
From 092ce85214b66111a1c098e7a0ac02a841be971e Mon Sep 17 00:00:00 2001
From: Sander Roosendaal *(XTU1S2bhe^*EY{r6s5Jn`1C5O?D+`BHw|y<^Sq!*BCoRN8K7vy
zW=5lZO)@Xmq-P7HA&)p2-M&R>hsitR1%oC^%Y;Sk`8koV$ezy@8e7#o&x|Hcggmx+
z{%-P|b8i`tG@hLFqLj?Kwyyc@M
{{ course.name }}
Interactive Comparison
Film Deaths
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_analytics.html' %}
+{% endblock %}
From f935db347fb78d937b69c4d4ce295492bdafacca Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
+
+
+{{ the_script |safe }}
+
{% endblock %}
{% endlocaltime %}
diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html
index 6d8ff847..49c9f361 100644
--- a/rowers/templates/menu_workout.html
+++ b/rowers/templates/menu_workout.html
@@ -101,11 +101,6 @@
Force Curve
-
- {{ form.as_table }}
-
- {% else %}
-
+
+ {{ form.as_table }}
+
+ {% else %}
+ Gold Medal Standards
-
-
-
-
-
-
- 100m
-
-
-
-
-
- 500m
-
-
-
-
-
- 1000m
-
-
-
-
-
- 2000m
-
-
-
-
-
- 5000m
-
-
-
-
-
- 6000m
-
-
-
-
-
- 10000m
-
-
-
-
-
- Half Marathon
-
-
-
-
-
- Full Marathon
-
-
-
-
-
- 1 minute
-
-
-
-
-
- 4 minutes
-
-
-
-
-
- 30 minutes
-
-
-
-
-
-
- 1 hour
-
-
- Gold Medal Standards
+
+
+
+
+
+
+ 100m
+
+
+
+
+
+ 500m
+
+
+
+
+
+ 1000m
+
+
+
+
+
+ 2000m
+
+
+
+
+
+ 5000m
+
+
+
+
+
+ 6000m
+
+
+
+
+
+ 10000m
+
+
+
+
+
+ Half Marathon
+
+
+
+
+
+ Full Marathon
+
+
+
+
+
+ 1 minute
+
+
+
+
+
+ 4 minutes
+
+
+
+
+
+ 30 minutes
+
+
+
+
+
+
+ 1 hour
+
+
+ Flexible Chart
+
Chart Stack
+
diff --git a/rowers/templates/workout_view.html b/rowers/templates/workout_view.html
index af45ac7e..eaa3184c 100644
--- a/rowers/templates/workout_view.html
+++ b/rowers/templates/workout_view.html
@@ -104,59 +104,53 @@
{% endif %}
- {% for course in courses %}
- In Stroke Metrics
@@ -172,10 +169,16 @@ $( function() {
{{ the_div|safe }}
-
- {% endfor %}
-
-
-
- Timed Course:
-
-
- {{ course }}
-
- Workout Summary
-
-
- {{ summary }}
-
-
+
+ {{ summary }}
+
+
+I;Pa}Yfnbh@rtNU`X`rL!9F zsUq~wfF)f-w7Gs1^n@l7fhs|ZX*z3~H6Q*kdTRnJ zt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7YWdYThX5*$80NNxch` z8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_&EpuGnm7Fc z---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO9Q16WL(-wQ zJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbv IKR!SI;w2^PnR1 zcH3G(rRGWDHcE4 +oZMUA`GRbw3VAxpj24fviXBlT^-A4Jb+g0L=LFTobo@LSM! z9Q4*WL{xRvkZ7AXZ~Bg0_Qo?=RnXRfn@p-kQ}YJVCp*nL)Me>7bZx+&dTw4q&qgRa zX*B3*IwcsbjnXiBXDsAIOAUGsZNQ(tBiF`+o=8=AKYLZ4T%1`Im%94CXmtzSQko=p(a zMefpzQc}auohTam5zss1*+!L1CpawW(wP-{sR(_{YubWN>OB@6@O>-67z4eKaTbFr zODBY)mA1D+-)ioL(Mztr395|T5Q|pY-fF;ay>y1rhrFh(E^-&4=wfHH((r4^(h=CM zWDV$5j@*bc8J7;UzIlV_w<}re*13yRHfRfNt{(-x&($|d^z0|Ix@DW|2hr!1tQkq= z&5PC-t-hhwH*Xa5yj{dxW!y!HZFS3ud-F1SmkY0y=v^!-^{r9rIOt<8ywchAF+gjc zHgRuWLcd+fS{b>E5nE^M8KAc&&Gf1TPza#)$h|3gKNk8ATR(0%SUm2|FF!mz>zA9u z?@sz3e>qv6F4z6u_uEb1?{)niyz_tBqZbq`A;Wja{f`HW-2r!YXZrXX^XJP?tKIhp z4=xhNvx^V=KJC)mb8+@|)2&~2Cnwvdl|HW4$1l6SOXux&Z>9~XYulrYL7gt`B z`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yp5DiF=#_`_Vat>5Kk1U$S#b69*SoFO zX|tD4`)xN|KH4wNfABor^x<#IlV!JACJy=2hevniz78ID>*Gfc4}W=@*=+yZZT~gB z#ZO +~ERB$UgGhv{j&NS8jm^FKot>Vw39 F0RT%=7v%r| literal 4000 zcmV;R4`1*fiwFn@ uIcmVC4NpBoC7J%>m6@m}RVG#CP__!#t zf^35^U}u8Z$mBMtNFBLnq?XWKUh?mU?6zguRv~`U6u~Oc2SZ(5Th~{Qd^?Z6d2?}o z@M^i)t}fS)9-@JV2j3h&IC{S6R_oRJ?EB@qUv7Tr`tJ8`yF5rcueXmLzUcd_!^PtD z>(}P2+gz;H=H&8Xk-prXyjWgz+r{mNFJ95Z{V5K+H=l8qZ%_Kwt5yH!k8k?rI^W=p z6TI23J~+c`HkX^T1p(mVx2I2@?N0I4YQ62d^~o|FwOt+V&->)^r0aLT_D(l&pg-yr znK#?hzdrEqUH^S|ez{pKw~JT!*Zcp`^(XuNm)DoV&%4whpt~Nr|BVli9~>MUy?p@b zXXKCXH~e|5_haQ|ic`tj*80lKe1z9M*zd}#5IoQaM&!ruT60ge`F zr+525>8=i*_1&g_zPeaGdiYnzUSB93`uwu*&huf 6ct@ ze!Tu}+4b9>mz$@n^~-+w@BYS*NT>O=JAb)6Mw>3O|MUG77PnmD#@A1~H>-=67yqXl z5O>QB+;WK &Ue*53 F_9Sjc@WBIyTb}Q3hwIU6`^jaW|NPr$&jh!Q;UAwdo%3H^-zTw&M~nSmA768k zF8cA$yZsjPi*OrWEp-Nyq+P6nI Pg?r}EUovny_O!98UYq-af^3E_D?!F@KmUiR3Q*gJzP(ki^5AIkM z_W%`fm&82@l}4NQ#!ZBKUGg5dBJSP<4^E@cJ6F<-W10_lUk`Ty;{#fayn8V+5$=_Z z^A*`~!X|=^8ty?#+V69c_fQdcXB-e{^tvcT_4)b8J3>W@okYW|bJHS}CC~RDPj!)} zYNmKPgk;Kz&XhxxlD-?Z^O1R$ipX0NlTxpec@h%}l9w5gj}?(m=A0l0Rd~h~wd37< z$Xizrc?9Dul1k>2@n6z&=jSue3sgj&jb#+pkT2}Ji!&lm6_Ixaoz97a&X=6$Ews^o z6!O7D5~~T%p${efclRRSM*BhXgvJqg)o35elAlk64y7Wij;s+tRB3oImbB;RMBavq z$R|CW3028g5KBhsEX`=N$BM{D6A-*rrQJ31Ga%1oT*Rm%pObS7lAn>^zNXQh#jpdf zndixtC2wa$zM_Z?2Lk|JG~@+JM(6E){Px_ex*_sOeyu}Q8Wu?`>BE~9`HFmamJK6X zO_Y{=(G2@WekAhFfK`$YFd3PzZJw_vN^67h=~YzO^AU`18s60Bc|nszUS-cSK+%ZJ zj7IyKWL~UE&lX5S9&s|deT&i#lXu1o22GTf35(kEb0S}nJ)bQ!wyJra8BLrBd2I9i z?c_P<-ZCI*JUQt_DVcR`UGv+ILS76o1eMGS6^+<5^5c;ACM1bR4SB~+=I= miR| zBtTF#$?#lM=6%!cM gB=55)m z>)2{(M GorgM=%kIHQrkX*m7^DFZ0Mo0+A+4>u@^c zxp{I1$-A6&t?Gp62#f0Wb0S~UXdewA22Fh4F%_+%_ANd?2Kk(O8!%}4-a1P~b8qKF zz9M^`tRaW2n&*WklY7gpetwX=H)xSmqrIb|_WX> 2L2m7oF$lM7|>3 zK51(lQMGEB3*4f#GaxSpKvZ4MUE~&}o&IRg-iYWrA$n-!?@GQR-QI#>FDg%tM_W?E zyH_4w)9{ALbMAhs^4|Jn3C5OzIeGG8ymhFF)%m0w6pYmg&d8pxC_Zn4vB;>&$?@rJ zu@fO*n-4G4LtZe~(yG?9@KUsXeoiu9lZGd3ybx-9c;2~^e*0O?^A*|iNq#w!s>a59 z*D5OSNWP*dtwaM%pc?Iin~ZMXytf17 g15oSSD3w zMnrDpryj9M msax;c@r$dhA(p-7;~w~yWz9qnCP zi+T+5``laIJRd?)t8Px@EAr$>ve1}7MLve2IXSM)^P`Y=CRk!klokQXhH2-7z9LtS z#GG&sL4}^6p}%AQ(`v?hmpk9Os2lGQ7>lOd-v>QZhF&T{FS%g~=mvaClR=NI-}NB+ zU<3(Cg+8f qkLvjTKb+a1p#TslOw7stCQ$EwVhS z&<8gesjmz@yL!+kJIw|Ht3n@rQ8jN)13p%Sp3_`UpqlFu!er3bHsIGJ_0BLLspfjb zvgI~&LeCYUk6FWyI;k(aYGFe^4*J{))v+ow3)76%H2fK$mptIBLbixPMWgj|Qu8V@ z?l>6o7*rZQy5`Nht8o|VL7&EMaJfI82E8xZ2lrkXnb3^8LG;!D5bLBq*vX*BX50;; z_X)kOiAK)mDvF*P`ccqF6RpyvL*R-UchtOjW1vsUdPH5>4N`XeH>saIdNIyx(No!0 zX>*c#s0e*9BBW}7jg%}x-_TFnrQ>sFOzT8c#vLUEK_=2Z2ivmrgXpbcuWO%^7si-O z=cH!W52N>nf&|@w@1ba6JvKFO4D^@;SXpp~6mT*faj~Uk4x(p+PS-UHDHdJ0bXEgC zRfOIdu%ye#W#GxIant7dVf4X7QC8nD+M+I )cSQxf=t0&af2J)z`C>ZKpy_%^L+h8v%?usdr&A z13tC{twHq8Sk$d$i0ot0#%a{-`ccqFLlLb`!w+S{H{85=W1zQ``)BFAc^qR?^QJ%G zTQMNY!Yhj~nRtC|$=DSIZEe!nkYZG!Cu(#3^wA5Ls9d-hEmP6RUGw3OgPu)vNILYE zC$qd+bL0-A-?dEiDmBlFZ7!YZJ92j|6VdDDdMA@f&}wO!1L(u9Wg_U7yg}=w8Fy2+ z>qBmtXhl%v$hDzW-`o{_s0e+?9kg^^HEb*!zTr0DkAWW1FpE=3y+GOJ>Ul Us=mSosp-NNp2GGacG7%7?&aRKt(lTd&o=pIyH_t=a z<&2^A(isK4%jl!ZuJ_1AYwn;KccY+>#-lP1-&5HV3}`{yG0-FBhAK*_=dvXj(1Nz3 zpqJcGg>+JHrD)_1nwmEX`e0Zv=%ij;(NvA5=8b}$l5t1M)(YOa=E$A8ODE=*iJ)E6 z)_Xshxt?0kb`X6ovSv_)Z+!60uAc$=Xe3#8I;oF-GIl*RHE#$#lM!om;hO-V+4VC( zFS+mvbm*}t`n*(78}LU!9}F<+`r(J1ygw22)Vz5^=<`a}fI)|z<7Cjc_3MYxvk5}F z$X$9-N^1DI6GcNm0(xgW+o*Es1cxPEI 4Z?U()M2HTg}}tddbx{L6wmkV$n+5dky%lm(DQykk_=;MeZULUF>XD8h$NVIs&_u ztO32sksDDa1o=8b}$cZ-;-jJqhYt!_DSZ(c_4a^aN{y^BSqzBNi62Yt+iS30{s258OG zChpBk=yxkwD $?p{iznUH^@pD>`{nld zi}U{LAJ3N;%XNS7%Tr)><@UbKhwM4m_J{CTJ66- z`hJr*UT)s*`%{ 8B=#7W-cFWW5Kk1U$S#b07r@O7z zX|tF2`|UPdKiZGZfAl=v^zkpt^JTYPCJy=2+edffzK))B>$Ar{9RK)JX0!Wqzy0U* z7XS3};`e2`Gk~uE_WvI(ZhWzS^1H`${9uQAwNB6B`-F12`5`@x-_xa!AN&t;cnbK$ GfB^utL_q8S From 0ec34774c1d9e612dff09d40b658a90e3cf96f39 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 15:04:40 +0100 Subject: [PATCH 030/121] f --- rowers/interactiveplots.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 6fa0ae28..fa637c32 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3557,6 +3557,8 @@ def interactive_chart(id=0, promember=0, intervaldata={}): data_dict = datadf.to_dict("records") metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] + + print(intervaldata) chart_data = { 'title': row.name, @@ -3565,6 +3567,7 @@ def interactive_chart(id=0, promember=0, intervaldata={}): 'y2': "spm", 'data': data_dict, 'metrics': metrics_list, + 'intervals': intervaldata, } script, div = get_chart("/interactive", chart_data) From e80fa1e4f91c76dcc1b39aad03f0e83f0960b8aa Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 22 Mar 2024 16:27:04 +0100 Subject: [PATCH 031/121] interactive_chart op twee plaatsen --- rowers/interactiveplots.py | 23 ++++++++++++++++++---- rowers/templates/instroke_interactive.html | 23 +++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index fa637c32..cccd3486 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -3558,8 +3558,23 @@ def interactive_chart(id=0, promember=0, intervaldata={}): metrics_list = [{'name': name, 'rowingmetrics':d } for name, d in metrics.rowingmetrics] - print(intervaldata) - + + intervals = [] + # add shaded bar chart areas + if intervaldata != {}: + intervaldf = pd.DataFrame(intervaldata) + intervaldf['itime'] = intervaldf['itime']*1.e3 + intervaldf['time'] = intervaldf['itime'].cumsum() + intervaldf['time'] = intervaldf['time'].shift(1) + intervaldf.loc[0, 'time'] = 0 + intervaldf['time_r'] = intervaldf['time'] + intervaldf['itime'] + intervaldf['value'] = 100 + mask = intervaldf['itype'] == 3 + intervaldf.loc[mask, 'value'] = 0 + intervaldf['bottom'] = 0 + + intervals = intervaldf.to_dict("records") + chart_data = { 'title': row.name, 'x': "time", @@ -3567,9 +3582,9 @@ def interactive_chart(id=0, promember=0, intervaldata={}): 'y2': "spm", 'data': data_dict, 'metrics': metrics_list, - 'intervals': intervaldata, + 'intervals': intervals, } - + script, div = get_chart("/interactive", chart_data) return script, div diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index 6f3be39c..9c86fdb9 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -11,7 +11,6 @@ jQuery UI Slider - Range slider - @@ -30,11 +29,14 @@ dataType: 'json', success: function(data) { - console.log(data); - $("#id_script").replaceWith(''+data.script+''); - $("#id_chart").replaceWith(''+data.div+''); - $("#ds").replaceWith(''+data.ds+''); - $("#dd").replaceWith(''+data.dd+''); + $("#id_script").replaceWith(''+data.script+''); + $("#id_chart").replaceWith(''+data.div+''); + + + $("#dd").empty(); + $("#dd").html(data.dd); + $("#ds").empty(); + $('#ds').html(data.ds) } }); } @@ -169,11 +171,10 @@ $( function() { {{ the_div|safe }}-- - - {{ dd|safe }} - + ++ + {{ dd|safe }}{{ ds |safe }} From 2fee1fcdb6fa3f3bf575a1938361ff5d2ae6411c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal+Date: Sun, 24 Mar 2024 14:30:16 +0100 Subject: [PATCH 032/121] hr pie v1 --- rowers/interactiveplots.py | 28 +- rowers/templates/disqualification_view.html | 9 +- rowers/templates/histo_single.html | 8 +- rowers/templates/history.html | 172 ++++----- rowers/templates/splitworkout.html | 2 +- rowers/templates/summary_edit.html | 245 ++++++------ rowers/templates/withdraw_view.html | 9 +- rowers/urls.py | 5 - rowers/views/analysisviews.py | 392 -------------------- rowers/views/workoutviews.py | 44 --- 10 files changed, 226 insertions(+), 688 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index cccd3486..9946f22c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -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, + '<{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]), + '<{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) diff --git a/rowers/templates/disqualification_view.html b/rowers/templates/disqualification_view.html index dfacbe67..5a6406cb 100644 --- a/rowers/templates/disqualification_view.html +++ b/rowers/templates/disqualification_view.html @@ -102,14 +102,11 @@ {% endif %} - - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} + diff --git a/rowers/templates/histo_single.html b/rowers/templates/histo_single.html index 4d6111d3..868f2f34 100644 --- a/rowers/templates/histo_single.html +++ b/rowers/templates/histo_single.html @@ -6,12 +6,9 @@ {% block main %} - - + + -{{ interactiveplot |safe }} @@ -24,6 +21,7 @@ +{{ interactiveplot |safe }} {% endif %} {% endblock %} diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 358b91a0..52aaa542 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -12,6 +12,7 @@+{% endblock %} diff --git a/rowers/templates/splitworkout.html b/rowers/templates/splitworkout.html index bd7c0130..845162b2 100644 --- a/rowers/templates/splitworkout.html +++ b/rowers/templates/splitworkout.html @@ -23,7 +23,7 @@- + diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 4117c89d..886194e6 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -8,26 +8,26 @@ {% block scripts %} {% include "monitorjobs.html" %} - - - + + + {% endblock %} {% block main %} @@ -42,78 +42,75 @@ {% endif %} {% endif %}-
-
+- Name {{ workout.name }} -- Distance: {{ workout.distance }}m -- Duration: {{ workout.duration |durationprint:"%H:%M:%S.%f" }} -- +Public link to this workout -+ +
- ++ Name {{ workout.name }} ++ Distance: {{ workout.distance }}m ++ Duration: {{ workout.duration |durationprint:"%H:%M:%S.%f" }} ++ -Public link to this workout +https://rowsandall.com/rowers/workout/{{ workout.id|encode }} - - + Edit Workout Interval Data
- 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.
-
{% endblock %} diff --git a/rowers/templates/withdraw_view.html b/rowers/templates/withdraw_view.html index a5df85ce..2929761c 100644 --- a/rowers/templates/withdraw_view.html +++ b/rowers/templates/withdraw_view.html @@ -98,14 +98,11 @@- -
-Updated Summary
- --
- {{ intervalstats }} -- -- - - - - {{ interactiveplot |safe }} - - {{ the_div |safe }} -
-- -
-Feeling lucky?
-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. -
- -- +
- +
+Updated Summary
+ ++
+ {{ intervalstats }} ++ +- + + + {{ the_div |safe }} + {{ interactiveplot |safe }} + +
+- +
+Feeling lucky?
+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. +
+ +- -
Interval Shorthand
See the how-to at the bottom of this page for details on how to use this form. @@ -125,26 +122,26 @@ {% csrf_token %} -
- +
+- - {% if courses %} -
Intervals by Power/Pace
- +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).
- + @@ -153,11 +150,11 @@Interval Shorthand How-To
This is a quick way to enter the intervals using a special mini-language.
You enter something like 8x500m/3min, 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.
- +Special characters are x (times), + and / (denotes a rest interval), as well as ( and ). Units are min (minutes), sec (seconds), m (meters) and km (km).
- +A typical interval is described as "10min/5min", with the work part before the "/" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "1km" or "1000m". The basic units can be combined with "+" and "Nx". You can use parentheses as in the example below.
- +Here are a few examples
@@ -182,32 +179,32 @@ - -
- {% endif %} + {% if courses %} +Interval by Course
-- 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. -
-- {% if rower.share_course_results %} - You are currently sharing your course results with all Rowsandall users. - Click here to hide your course results. - {% else %} - You are currently hiding your course results (except for your participation in online challenges). - Click here to hide your course results. - {% endif %} -
- -- +
+ {% endif %}Interval by Course
++ 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. +
++ {% if rower.share_course_results %} + You are currently sharing your course results with all Rowsandall users. + Click here to hide your course results. + {% else %} + You are currently hiding your course results (except for your participation in online challenges). + Click here to hide your course results. + {% endif %} +
+ +- - - - {{ interactiveplot |safe }} + {{ the_div|safe }} + {{ interactiveplot |safe }} + diff --git a/rowers/urls.py b/rowers/urls.py index 2ebcf93f..4fdbf66b 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -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 + + +\d+)/$', - views.rankings_view2, name='rankings_view2'), - re_path(r'^ote-bests2/$', views.rankings_view2, name='rankings_view2'), re_path(r'^analysisdata/user/(?P \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 \b[0-9A-Fa-f]+\b)/histo/$', views.workout_histo_view, - name='workout_histo_view'), re_path(r'^workout/(?P \b[0-9A-Fa-f]+\b)/forcecurve/$', views.workout_forcecurve_view, name='workout_forcecurve_view'), re_path(r'^workout/(?P \b[0-9A-Fa-f]+\b)/forcecurve/(?P \d+)/$', views.workout_forcecurve_view, diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 2ebdbb72..03bedb87 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -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 = ' No ranking pieces found.
' - 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), diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index edf0b572..7fb0c248 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -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() From b35d080c48695ce1de143dcc7e1e9e01d9739233 Mon Sep 17 00:00:00 2001 From: Sander RoosendaalDate: Mon, 25 Mar 2024 08:04:03 +0100 Subject: [PATCH 033/121] marker workouts --- rowers/interactiveplots.py | 29 ++++++++++++++++++++++++--- rowers/templates/goldmedalscores.html | 8 +++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9946f22c..9b436a6d 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -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, + '<{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]), + '<{ut2}'.format(ut2=hrzones[1]), '{ut2}'.format(ut2=hrzones[1]), '{ut1}'.format(ut1=hrzones[2]), '{at}'.format(at=hrzones[3]), @@ -1573,7 +1573,6 @@ def goldmedalscorechart(user, startdate=None, enddate=None): enddate = arrow.get(enddate).datetime.replace( hour=0, minute=0, second=0, microsecond=0) - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' # marker workouts workouts = Workout.objects.filter(user=user.rower, date__gte=startdate, @@ -1664,6 +1663,30 @@ def goldmedalscorechart(user, startdate=None, enddate=None): idx = df.groupby(['date'])['score'].transform(max) == df['score'] df = df[idx] + df.fillna(value=0, inplace=True) + df['dat1'] = df['date'].map(lambda x: x.to_pydatetime(x).strftime("%Y-%m-%d")) + + df2 = pd.DataFrame({ + 'markerscore': df['markerscore'], + 'score': df['score'], + 'markerduration':df['markerduration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + 'duration': df['duration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + 'date': df['dat1'], + 'url':df['url'], + 'workout':df['workout'] + }) + + data_dicts = df2.to_dict("records") + chart_data = { + 'data': data_dicts + } + + script, div = get_chart("/markerworkouts", chart_data) + return script, div, outids + + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' source = ColumnDataSource( data=dict( markerscore=df['markerscore'], diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index e56f3624..e623a746 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -65,9 +65,6 @@ Bokeh.set_log_level("info"); - - {{ chartscript |safe }} -@@ -136,6 +133,11 @@+ {{ chartscript |safe }} +{% endblock %} From fa62212739f74f45c09ec865fa61e02aa00ae435 Mon Sep 17 00:00:00 2001 From: Sander RoosendaalDate: Mon, 25 Mar 2024 08:09:43 +0100 Subject: [PATCH 034/121] removing redundant code --- rowers/interactiveplots.py | 78 --------------------------- rowers/templates/goldmedalscores.html | 9 +--- 2 files changed, 1 insertion(+), 86 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 9b436a6d..8555400e 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1686,84 +1686,6 @@ def goldmedalscorechart(user, startdate=None, enddate=None): script, div = get_chart("/markerworkouts", chart_data) return script, div, outids - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' - source = ColumnDataSource( - data=dict( - markerscore=df['markerscore'], - score=df['score'], - markerduration=df['markerduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - duration=df['duration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - url=df['url'], - workout=df['workout'] - ) - ) - - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=600, - toolbar_location='above', - toolbar_sticky=False) - - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - plot.xaxis.axis_label = 'Date' - plot.yaxis.axis_label = 'Gold Medal Score' - - plot.circle('date', 'score', source=source, fill_color='blue', - size=10, - legend_label='Workouts') - - plot.circle('date', 'markerscore', source=source, fill_color='red', - size=10, - legend_label='Marker Workouts') - - plot.legend.location = "bottom_left" - - plot.x_range = Range1d( - startdate, enddate+datetime.timedelta(days=5), - ) - - hover = plot.select(dict(type=HoverTool)) - - hover.tooltips = OrderedDict([ - ('Marker', '@markerscore{int}'), - ('Test', '@markerduration'), - ('Score', '@score{int}'), - ('Duration', '@duration'), - ('Date', '@fdate'), - ('Workout', '@workout') - ]) - - taptool = plot.select(type=TapTool) - taptool.callback = OpenURL(url='@url') - - script, div = components(plot) - - return script, div, outids def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue=7, diff --git a/rowers/templates/goldmedalscores.html b/rowers/templates/goldmedalscores.html index e623a746..b0cb042c 100644 --- a/rowers/templates/goldmedalscores.html +++ b/rowers/templates/goldmedalscores.html @@ -60,14 +60,6 @@ {% block main %} - - - - - - {% if rower.user %} Gold Medal Scores for {{ rower.user.first_name }}
{% else %} @@ -76,6 +68,7 @@+
Hover over the workout to see details, click on the workout to open in a separate page.
From 02c998af3e54cb7e4618b62749e4abd91f6cd14b Mon Sep 17 00:00:00 2001 From: Sander RoosendaalDate: Mon, 25 Mar 2024 19:57:57 +0100 Subject: [PATCH 035/121] removing obsolete code --- rowers/interactiveplots.py | 177 +++-------------------- rowers/templates/performancemanager.html | 34 +++-- 2 files changed, 39 insertions(+), 172 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8555400e..c83f0f88 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1797,174 +1797,37 @@ def performance_chart(user, startdate=None, enddate=None, kfitness=42, kfatigue= tz=datetime.timezone.utc).replace(tzinfo=None)) df = df.loc[mask] - source = ColumnDataSource( - data=dict( - testpower=df['testpower'], - testduration=df['testduration'].apply( - lambda x: totaltime_sec_to_string(x, shorten=True)), - date=df['date'], - fdate=df['date'].map(lambda x: x.strftime('%d-%m-%Y')), - fitness=df['fitness'], - fatigue=df['fatigue'], - form=df['form'], - impulse=df['impulse'] - ) - ) + df2 = pd.DataFrame({ + "testpower" :df['testpower'], + "testduration":df['testduration'].apply( + lambda x: totaltime_sec_to_string(x, shorten=True)), + "fitness":df['fitness'], + "fatigue":df['fatigue'], + "form":df['form'], + "impulse":df['impulse'], + "date": df['date'].map(lambda x: x.strftime('%Y-%m-%d')), + }) - plot = figure(tools=TOOLS, x_axis_type='datetime', - width=900, height=300, - toolbar_location="above", - toolbar_sticky=False) - # add watermark - watermarkurl = "/static/img/logo7.png" - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} + df2.fillna(value=0, inplace=True) - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) + data_dict = df2.to_dict("records") - fitlabel = 'Fitness' - fatiguelabel = 'Fatigue' - formlabel = 'Freshness' - rightaxlabel = 'Freshness' - if dofatigue: # pragma: no cover - yaxlabel = 'Fitness/Fatigue' - else: # pragma: no cover - yaxlabel = 'Fitness' - - if modelchoice == 'banister': # pragma: no cover - fitlabel = 'PTE (fitness)' - fatiguelabel = 'NTE (fatigue)' - formlabel = 'Performance' - rightaxlabel = 'Performance' - if dofatigue: - yaxlabel = 'PTE/NTE' - else: - yaxlabel = 'PTE' + chart_data = { + 'data': data_dict, + 'title': 'Performance Manager '+user.first_name, + 'plotform' : doform, + 'plotfatigue': dofatigue, + } - plot.xaxis.axis_label = None - plot.yaxis.axis_label = yaxlabel - - y2rangemin = df.loc[:, ['form']].min().min() - y2rangemax = df.loc[:, ['form']].max().max() - - if dofatigue: # pragma: no cover - y1rangemax = df.loc[:, ['fitness', 'fatigue']].max().max()*1.02 - else: # pragma: no cover - y1rangemax = df.loc[:, ['fitness']].max().max()*1.02 - - if doform: # pragma: no cover - plot.extra_y_ranges["yax2"] = Range1d(start=y2rangemin, end=y2rangemax) - plot.add_layout(LinearAxis(y_range_name="yax2", - axis_label=rightaxlabel), "right") - - plot.line('date', 'fitness', source=source, color='blue', - legend_label=fitlabel) - band = Band(base='date', upper='fitness', lower=0, source=source, level='underlay', - fill_alpha=0.2, fill_color='blue') - plot.add_layout(band) - - if dofatigue: # pragma: no cover - plot.line('date', 'fatigue', source=source, color='red', - legend_label=fatiguelabel) - if doform: # pragma: no cover - plot.line('date', 'form', source=source, color='green', - legend_label=formlabel, y_range_name="yax2") - - plot.legend.location = "top_left" - - #plot.sizing_mode = 'scale_both' - - startdate = datetime.datetime.combine( - startdate, datetime.datetime.min.time()) - enddate = datetime.datetime.combine(enddate, datetime.datetime.min.time()) - - xrange = Range1d( - startdate, enddate, - ) - plot.x_range = xrange - plot.y_range = Range1d( - start=0, end=y1rangemax, - ) - plot.title.text = 'Performance Manager '+user.first_name - - hover = plot.select(dict(type=HoverTool)) - - linked_crosshair = CrosshairTool(dimensions='height') - - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}') - ]) - - if showtests: - hover.tooltips = OrderedDict([ - ('Date', '@fdate'), - (fitlabel, '@fitness{int}'), - (fatiguelabel, '@fatigue{int}'), - (formlabel, '@form{int}'), - ('Impulse', '@impulse{int}'), - ('Gold Medal Score', '@testpower{int}'), - ('Test', '@testduration'), - ]) - - plot2 = figure(tools=TOOLS2, x_axis_type='datetime', - width=900, height=150, - toolbar_location=None, - toolbar_sticky=False) - - plot2.x_range = xrange - plot2.y_range = Range1d(0, df['impulse'].max()) - - plot2.vbar(x=df['date'], top=df['impulse'], color='gray') - plot2.vbar(x=df['date'], top=0*df['testpower']+df['impulse'], color='red') - - #plot2.sizing_mode = 'scale_both' - plot2.yaxis.axis_label = 'Impulse' - plot2.xaxis.axis_label = 'Date' - - plot.add_tools(linked_crosshair) - plot2.add_tools(linked_crosshair) - - mylayout = layoutcolumn([plot, plot2]) - - try: - script, div = components(mylayout) - except Exception as e: # pragma: no cover - df.dropna(inplace=True, axis=0, how='any') - return ( - '', - 'Something went wrong with the chart ({nrworkouts} workouts, {nrdata} datapoints, error {e})'.format( - nrworkouts=workouts.count(), - nrdata=len(df), - e=e, - ), 0, 0, 0, [] - ) + script, div = get_chart("/performance", chart_data) return [script, div, endfitness, endfatigue, endform, outids] + def interactive_histoall(theworkouts, histoparam, includereststrokes, spmmin=0, spmmax=55, extratitle='', diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html index c0fd3040..11b38315 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -8,6 +8,15 @@ +{% endblock %} + +{% block main %} + + + + + -{% endblock %} - -{% block main %} - - - - - - - {{ chartscript |safe }} -@@ -83,10 +82,12 @@+
{{ the_div|safe }} -++ + {{ chartscript |safe }} +{% endblock %} From 892d6e4a836bc3aa21524a7f20ac99a55e612c6e Mon Sep 17 00:00:00 2001 From: Sander RoosendaalDate: Mon, 25 Mar 2024 21:31:08 +0100 Subject: [PATCH 036/121] removed tests of removed urls --- rowers/interactiveplots.py | 2 +- rowers/tests/test_analysis.py | 30 ------------- rowers/tests/test_cpchart.py | 62 -------------------------- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/test_uploads.py | 4 -- rowers/tests/test_urls.py | 3 -- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4000 bytes rowers/tests/viewnames.csv | 1 - 8 files changed, 2 insertions(+), 102 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c83f0f88..f1eed721 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -315,7 +315,7 @@ def interactive_hr_piechart(df, rower, title, totalseconds=0): 'title': "HR "+ title } - script, div = get_chart("/hrpie", chart_data, debug=True) + script, div = get_chart("/hrpie", chart_data) return script, div def pretty_timedelta(secs): diff --git a/rowers/tests/test_analysis.py b/rowers/tests/test_analysis.py index 8c7bc3e0..f7101bb7 100644 --- a/rowers/tests/test_analysis.py +++ b/rowers/tests/test_analysis.py @@ -1356,36 +1356,6 @@ class MarkerPerformanceTest(TestCase): response = self.c.post(url,form_data) self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.create_engine') - @patch('rowers.dataprep.getsmallrowdata_db') - def test_ranking_view2(self, mocked_sqlalchemy, - mocked_getsmallrowdata_db): - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - startdate = (self.user_workouts[0].startdatetime-datetime.timedelta(days=3)).date() - enddate = (self.user_workouts[0].startdatetime+datetime.timedelta(days=3)).date() - - url = reverse('rankings_view2') - response = self.c.get(url) - self.assertEqual(response.status_code,200) - - form_data = { - 'startdate':startdate.strftime("%Y-%m-%d"), - 'enddate': enddate.strftime("%Y-%m-%d"), - 'doform': True, - 'dofatigue': True, - 'metricchoice':'rscore', - 'modelchoice': 'coggan', - 'daterange': '', - } - form = DateRangeForm(form_data) - if not form.is_valid(): - print(form.errors) - self.assertTrue(form.is_valid()) - - response = self.c.post(url,form_data) - self.assertEqual(response.status_code,200) class AlertTest(TestCase): def setUp(self): diff --git a/rowers/tests/test_cpchart.py b/rowers/tests/test_cpchart.py index 6752450c..6f46c0cf 100644 --- a/rowers/tests/test_cpchart.py +++ b/rowers/tests/test_cpchart.py @@ -166,65 +166,3 @@ class CPChartTest(TestCase): self.assertEqual(response.status_code,200) - @patch('rowers.dataprep.fetchcperg', side_effect = mocked_fetchcperg) - @patch('rowers.dataprep.create_engine') - def test_rankingpieces(self, mocked_fetchcperg, mocked_sqlalchemy): - url = '/rowers/ote-bests2/' - - login = self.c.login(username=self.u.username,password=self.password) - self.assertTrue(login) - - # update_records(url='rowers/tests/c2worldrecords.html',verbose=False) - - form_data = { - 'name': faker.word(), - 'date': nu.date(), - 'timezone': 'UTC', - 'duration': '00:02:00.0', - 'starttime': '10:01:43', - 'distance': 500, - 'workouttype': 'rower', - 'boattype': '1x', - 'weightcategory': 'hwt', - 'adaptiveclass': 'None', - 'notes': faker.text(), - 'rankingpiece': True, - 'duplicate': False, - 'avghr': '160', - 'avgpwr': 0, - 'avgspm': 40, - } - - url2 = '/rowers/workout/addmanual/' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 100 - form_data['duration'] = '00:00:18.5' - - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 2000 - form_data['duration'] = '00:06:56.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 5000 - form_data['duration'] = '00:20:48.0' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 6000 - form_data['duration'] = '00:22:17.6' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - form_data['distance'] = 10000 - form_data['duration'] = '00:38:16.5' - response = self.c.post(url2, form_data, follow=True) - self.assertEqual(response.status_code, 200) - - response = self.c.get(url) - self.assertEqual(response.status_code, 200) diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index a4984a1a..0492f3e5 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -739,6 +739,6 @@ class InteractivePlotTests(TestCase): workout = Workout.objects.filter(user=self.r,workouttype__in=mytypes.rowtypes)[0] id = workout.id - script, div, js_res, css_res, comment = interactiveplots.interactive_flexchart_stacked(id,self.r) + script, div = interactiveplots.interactive_flexchart_stacked(id,self.r) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 68a61119..b36335c1 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -74,10 +74,6 @@ class ViewTest(TestCase): self.assertEqual(response.status_code, 200) - response = self.c.get('/rowers/workout/'+encoded1+'/histo/', form_data, follow=True) - self.assertEqual(response.status_code, 200) - - f.close() diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index c798445f..53a2d978 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -122,8 +122,6 @@ class URLTests(TestCase): '/rowers/me/workflowconfig2/', '/rowers/me/workflowconfig2/user/1/', '/rowers/me/workflowdefault/', - '/rowers/ote-bests2/', - '/rowers/ote-bests2/user/1/', '/rowers/partners/', '/rowers/physics/', '/rowers/planrequired/', @@ -155,7 +153,6 @@ class URLTests(TestCase): '/rowers/workout/'+encoded1+'/flexchart/', '/rowers/workout/'+encoded1+'/forcecurve/', '/rowers/workout/'+encoded1+'/get-thumbnails/', - '/rowers/workout/'+encoded1+'/histo/', '/rowers/workout/'+encoded1+'/image/', '/rowers/workout/'+encoded1+'/instroke/', '/rowers/workout/'+encoded1+'/interactiveotwplot/', diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 74d4512c54218bb2e5d6196c85237a0e52864745..929f46eab568644c34249b30a4049599aa725fc2 100644 GIT binary patch literal 4000 zcmV;R4`1*fiwFp>$pK~p|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m}RVG#CP__!#F zf^35^U}u8Z$mBMt$Q8L~q?VvAFZuUFcH6RStB^ctieMG!gQ2djt?R2tzMV&3zd1iU zc-3ER*B6_w9-@JV2VWmQIC^%~tvBn<>398R=&!!-hVHj++aIKzH`}isz8Hqf!`15b z>(}PAyE _D(l&pg-yr znK#?#e}3TKyWzX;?BZ(OZ&$DI&-eem8&3B7_t%%gzjmonKzBWK{~I43KR7rzdiwy< z&&VI&Z}{Wj>E*@M@YTbA4lnwvhx;$n*N>hb6QKJ7 g{>Cjyb&(`PtR}cT}*y{_WL!Vs?-B~{DX@7FDdA>as_D8Evw!S~$`}O_} zdeWyKyG{3*@8zS7@ArO(?-(&0t*$@4-{G4iYTar7k3QY~&)v{}dwITlHHSYg(=WN+ z{AlxS-woTJ`l~1F&C8+x_i*D!q|^M;oxSXj(WZ;+|9pRi)h(B}@%5AL&HDW1`Tyw# z#NBcOw_M`J*N-;4S9SQ~c=uNHiR|^3n-B7NpC0frVSbGJzi&{yTA!Y#TkD4x7tfEM zbeosmS)%yC)|-2GC;$E6?sr=*FV>sk=H~oGdI$c(ySUr!!y`X>CB)eA*JO9VJqg^T zK>ia7dlI)J_}~G Pg?r|~Povny_O!98UYq-af^3Jdr?!F@KmUiR3Q*gJzP(ki^5AIkM z_W%`fm&82@l}4NQ#?6F#UGg5dBJSP<4^E@cJ6F<-V_FV(Uk`Ty;{#fayn8V+6YiCb z^A*`~!X|=^8ty?#+V4w}_fQdcXB-e{^tvcT_4(z W@okYW|bJHS}CC~RDPj!)} zYNmKPgk;Kz&XhxxlD-?Z%aM7OipX0NlTxpec@h%}l9vUMj}?(m=A0l0Rd~h~wd37# z$Xizrc?9Dul1k>2@n6z&=a)0j3sgj&jb#+pkT2}Jiwh!86_Ixaoz97a&X=6$Ews^o z67s=B5~~T%p${efclRRSM*C6ngvJqg)o35el3z}Q4y7Wij;s+tRB3oImbB-WMBavq z$R|CW3028g5KBhsEG=lX$BM{D6A-*rrQJ313n0&AT*Rm%pObS7l3$SDzNXQh#jpdf zndixtC2to*zM_Z?2Lk|JG~@+JM(6E){Px_ex-s%eeyu}Q8Wu?`>BCzT`HFmamJK6X zO_Y{=(G2@Wej@VDfK`$YFdLb#ZJw_vN^67h=~YzO^AU`18s6OJc|nszUS-cSK+%ZJ zf=2tAWL~UE&lX5S9&t9heT&kLlXu1o22GTf35(kEOCn#9J)bQ!wyJra8BLrCd2I9i z?c_P<-ZCI*JUQt_DVcR`UGv*dLS76o1eMGS6^+<5^3#y_CM1bR4SB~+=I= miR| zBtTF#$?#lM=6%!cCn3* #a2X~jd!{zt&Oo{PR^3Z*HdG| zIrr9iM9pX~2(yv-X3vk4kA?!opdufeZa@EsjZp5EqKeWAL(}c&FY{teNV>gB=55)m z>)2{(Cn4{Rur6XFQkH!4;Y~o^CfEkORb^d^Yvk`r-d04OOz^tuD)Bg*_ Go5QM=%kIHQrkX*m7^@FZ0Mo0+A+4>u^5g zxp{I%$-A6&t?Gp62#f0WOCn#>XdewA22Fh4F%_+%_ANd?1^JwN8!%}4-a1P~b8nYK zz9M^`tRaW2n&*XPlY7gpetwj^H)xSmqrIb|_WXj#JF19$?wmlZ>2L2m7oF#qM7|>3 zK51(lQMGEB3*4f#3m`8BKvZ4MUE~&}o&RXh-iYWrA$n-!?@GQR-QI#>FDg%tM_W?E zyH_4w)9}X0bMAhs^4|Jn3C5OzIeYSAymhFF)%m0w6pYmgF36s*C_Zn4vB;>&$?@rJ zu`?lGn-4G4LtZe~(yG?9@KUsXen~Q4lZGd3ybx-9c;2~^e)~nu^A*|iNq#w!s>a59 z*D5OSNWP*dtwaM%pc?Iin~iSYytgCd g15oSSD3w zMnrDp=N_?1 msax+RgX$dhA(p-7;~w~yWz9qnCP zi+T$3``laIJRd?)t8Pi;EAr$>ve1}7MLve2IXSM)^OKNwCRk!klokQXhH00Cz9LtS z#GG&sL4}^6p}%AQ(`v?hmpk9Os2lGQ7>lOd-v>QZhF&T{FS%g~=mvaCvq6uo-}NZ^ zU<3(Cg+8f nA~PjTKb+a1p#TslOw7stCQ$EwVhS z&<8ggsjmz@yL!+kJIw|Ht3n@rQ8jN#13p%Sp3_`UpqlFu!fepjHsIGJ_0BLLspfjb zvgI~QLeCYUk6FWyI;k(aYGFe^4f@;()v+ow3)76%H2ejimptIBLbixPMWgjgQu8V@ z?l>6o7*rZQy5`Nht8o|VL7&EMaJfI82E8xZ2lrkXnb3^8QS{aT5bLBq*x8`RX55XU z_X)kOiAK)mDvF*P`bp476RpyvL*R-UchtOjQ=m`EdPH5>4N`XeH>saJdNIyx(No!0 zX-krNs0e*9BBW}7jg%}x-_Xz7rQ>sFOzT8c#vLUEL1xlE2ivmrqv)++uWO%^7si-P z=cH!WkE8d7f&|@w@1ba6JvKFO3iOx+SXpp~6mT{jaj~Ukj-qFSPS-UHDHdJ0bWsC7 zRfOIdu%ye#W#HMYant7darD7NQC8nD+M+I )cSQxtjug&af2J)z`C>ZKpy_&6@;08v%?usdr&E z13tC{tx@#OSk$d$i0ot0#%a{-`bp47LlLb`!w+S{H{85=Q=qq$`)BFAc^qR?^X5O` zTQMNY!Yhj~n|OU~$=DSIZEe!nkYZG!Cu(#3{Lu@Ts9d-hEmP6RUGw2jgPu)vNILYE zXS2LnbL5Vr-?dEiDmBlFZ7!YpJ92j|6VdDDdMC3<&}wO!Bk04fWg_U7yg}=w8FzEH z>qBmtXhl%v$hDzW-`o{_s0e+?9kg^^HEb*!zTr0DPk|oMFpE=3y+GOJ>UlwG9#n+h z?piCT)I2HNMrld)^|m7P-jG!WZH1wzH_x^Ke+u*j28oeX=q=1 Us=mXBCp-NNpM$pIHG7%7?&aRKt(lQr-o=pIyH_t=a z<&2^A(wPLk%jl!ZuJ_1AYwn;Kcaxxx#-lP1-&5HV3}`{yDbOS3hAK*_=dvXj(1Ny; zpqJcGg>+JHrD)_1nwmEW`e0Zv=%ij;(NvA5=1qd0l5t1M)(YOa=E$A9ODE=*iJ)E6 z)_Xsjxt?0kb`*UsvSv_)Z+!60u3rH9Xe3#8I;oF-Hg-KVHE#?(lM!om;hO-V+4T!R zFS+mvbm*}t`n*(78}KJU9}F<+`r(J1ygw84)Vz6P=<`a}fI)|z<808k_3Ovcvk5}F z$X$9-N^1DI6GcNm0eWXV+o*Es1cxPEI*URt6`_xLO 4Z?U()M2HTg}}#ddbx{L6wmkV$n+5dky%lm(Do)kk_=;MeZULUF>X88h$NVIs&_u ztO32sksDEF6Yx%x(lp8afAw`_C$DEhpTH6y9K zdC~f!)i>1o=1qd0cZ-;-jJqhYt!_DUZ(c_4a^aN{y^BSqzBNjn27SzhS30{s258OG zX70^P=yxkwD $?p{tH<5t^@krXhJJhe z+1c>r4`=;(zZnib-);K*pc@Y0jsM;E5_{kM9^tdo;mf1d{(uMjGrjwb`SbOs_5S;# z@2(QZi>tT${@A6r=j!71w%a`K&dzpED}7vVPM>!}m(JVm->yXE{c%U%yc}L!T-|t0 zzPY?STc318`svp%#`U>BIa&Jd+n4LJ=hr{?m-IfSLvK8sw_Bcc|4En3&VrktKizG; zNt?aA-*30!`q6%L{-bB c8Ne3+`~QztH@?_E`Q2kWey~Hm-lXU7T|(JkeV?AjZ|Ty<5B>*Ei(duA GfB^s+jzCxd literal 3999 zcmV;Q4`A>giwFoSjQwQ-|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!zwlBQl~DC zaYP`iMFKVoD4VxcV&?BpuloLcf3bM` z_N_VYE>2f#b98pPNMCM_UM)|%&EopQ7jNk9?iBmoyKgwl{iA;MX4Sv{ fJ=Ir8lK>)b; iX@kz0nQq>5qCt z=H2GyUmy7Qu7A*-oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dJ3qVV@7?`p|7v-0clTxb`q9fn0(3us{6O#=dEeqbITIamguemo0~{>U zP9OJw+@0?|>${8o`Ra6e@9tk6dwHRB=<~C_JIRMVTOOUQUv3VC{lVg^t)C8fxZ1rz zPnPM&Zqj|`d--DHr@cSoJ46fzi_1@+cDSEJtvg=+W0~&$x2|9Qe15upHT(DH>6ct? zezg91+4Y-Wmlsb~>(~AA-~E*zkxuh_ck+69h&Ekh_vfc8EUvl4m9L+4?^dU;PybIh zAnuwQxaJa9zJ9dczN-EE!|hwKOk^*&Tz!x)m+1i?66VLa`}+#Ti`DUQy0vBh>g?s= zlWzUGJ4qBj+j@2H?&!at-TijU`PpjSU)`L)NbkTmyo=l2K0oq{S3-;(eoeLq+>*dm z3go|{up@Cjg3lf>T=RUlJ6w)lo3AeW{MSFec_z4a4FB?s>74)S`aX$GJXq}h`tq8K zbkQ$=-u1VbUxe%MYN<1rB<*4q)R7HN1ABhZ6>%q55%&ZU!370(N_ta4?&0R#sV;f9 zQW1A&h*34lry+t1a*wm&?rcTeW0H3(Uc)_>ly`>NaQ78)x3nGSor1d!h6-}WTX4s! zxCf|+yCm*Os5IKVH*O-_>yr1t6>;|_cyJnh-no)y9MgQb`+B$w7$4AT ?9gyotqY+EP1{Kd8&&% zRWrrYJ|t64bfz4ll=R)OosZ13R7BpIn3Q^z%#)Z Bb!3eIqDsSyv7|jeC-OE_ zL_X>1OsGn}f><(2XK6;GJyt|Mnt wZS#CZQCb^}Pp_iNo{wOB)9|J~&kLF)@+y0t0g6U! zW;EK@B=cfTdbU6s@`#hs?OT*~n7lJyFleH*Ojy*OpA-3t?D=e=u~p6U%xL07$YY!5 zuP4tr_m%-k zdzw6!Kz#A*f_tsA$BdkspV=Hz7$pYREfoGJiwzTn~8! zBLRY{Nrva5GVhyiKMHv^Y*AG6eBh!Qo^LWg3VCNRqNrp(+M?duCiCNv-{#(`TA Z3-tx8#b-d}vnPDCE7Nh)(0Zwd{)K z+lMyVk3pVc+Y(Eo+gt03>h__<=SLw=#xX=y6FezPeohsYt|IbctT3zQc~`WSc2?x2 zBJ#l`?y3w-TehN7pvn9g E)pb8_ZHzMdKz z&bhbFBWgx_L70rpH+z1Vd^8jw1{L|(bo=Q?Y=m;R6jhW~7@BTBeVG?)LelM3GH=Uf zUB^~SI|_Mkgmn=ck+S5Q4{rqWHo-RNtt#tUTqA!|^0p%KWP;aKSBb~T#OIsL50e*T z302iq63W)qwH}z`kPn6sRh<(gmaVbzO}8I|Jc5ZxtnuDDz?OSEeVIo#5{NWWT8Gmi z&&`uFNZ#eFYgH#iM_5$1pA-3-M*C<0F=*oRj;UxBwQuqHG05lK+kipS_tse|ntMAZ z@)g Gl>3dr^6EJlc{P z-mUWRnua$_o^$t8mG{;sOE9(!%*m4% &^;Z!Fe$azfcPDuo*PQOL7F z()GX$q3Eh*b0S~UJWpbRVANz_hKNNoGiXNhyswCSFp^|mQ)v-_n#@l zJ2t*=xFcS zTGV5Z-{s!w=J^ncT6J?GUy&zAl7+?uD)KQD&B<|Xo*#w0Gr i5Z8t}0q^ql5;0@YlP5GI4ZwgJB;sdt6}Nj29a zmMyoL6MC))easqu)Jc8WRSO&XanR>ZsE$>cS(s+5rs2;3z2pI36|zMXDjKbylbTnN zamT@s$Dq>i(KT=0O^v%y5BfBAgUkK#H0XWNKDf8a$b@Fx4WhROfLJH>!A=G}Hsfv( zy-( gS5frb(2s&XnrM|S9RgR>xTEIH8v}h()+6f5ZjiF$ze)Y%(Tj0ji=N81 zN}H3^Lq+I=5g}CrY@}on`i6emE*+mcV_GMoGVUlT2r`lOIoOt^A4G2rdtLjSyfDUO zIwv)|ei*$s6eQ>dd=Etn>#?bMW1z<*z{-L I;Pa}Yfnbh@rtNU`X`rL!9F zsUq~wfF)f-w7Gs1^n@l7fhs|ZX*z3~H6Q*kdTRnJ zt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7YWdYThX5*$80NNxch` z8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_&EpuGnm7Fc z---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO9Q16WL(-wQ zJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbv IKR!SI;w2^PnR1 zcH3G(rRGWDHcE4 +oZMUA`GRbw3VAxpj24fviXBlT^-A4Jb+g0L=LFTobo@LSM! z9Q4*WL{xRvkZ7AXZ~Bg0_Qo?=RnXRfn@p-kQ}YJVCp*nL)Me>7bZx+&dTw4q&qgRa zX*B3*IwcsbjnXiBXDsAIOAUGsZNQ(tBiF`+o=8=AKYLZ4T%1`Im%94CXmtzSQko=p(a zMefpzQc}auohTam5zss1*+!L1CpawW(wP-{sR(_{YubWN>OB@6@O>-67z4eKaTbFr zODBY)mA1D+-)ioL(Mztr395|T5Q|pY-fF;ay>y1rhrFh(E^-&4=wfHH((r4^(h=CM zWDV$5j@*bc8J7;UzIlV_w<}re*13yRHfRfNt{(-x&($|d^z0|Ix@DW|2hr!1tQkq= z&5PC-t-hhwH*Xa5yj{dxW!y!HZFS3ud-F1SmkY0y=v^!-^{r9rIOt<8ywchAF+gjc zHgRuWLcd+fS{b>E5nE^M8KAc&&Gf1TPza#)$h|3gKNk8ATR(0%SUm2|FF!mz>zA9u z?@sz3e>qv6F4z6u_uEb1?{)niyz_tBqZbq`A;Wja{f`HW-2r!YXZrXX^XJP?tKIhp z4=xhNvx^V=KJC)mb8+@|)2&~2Cnwvdl|HW4$1l6SOXux&Z>9~XYulrYL7gt`B z`{(B;tD~+@KmGE>xIFh)CrjV`{Cair^77~Yp5DiF=#_`_Vat>5Kk1U$S#b69*SoFO zX|tD4`)xN|KH4wNfABor^x<#IlV!JACJy=2hevniz78ID>*Gfc4}W=@*=+yZZT~gB z#ZO +~ERB$UgGhv{j&NS8jm^FKot>Vw39 F0RT%=7v%r| diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index 9fa22e20..f0f76651 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -31,7 +31,6 @@ 33,35,kill_async_job,kill job,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 34,36,post_progress,post progress,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 35,37,graphs_view,view charts,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, -38,40,rankings_view2,view ranking,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,TRUE,FALSE,TRUE,TRUE, 41,43,cum_flex,flex all chart,TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, 42,44,analysis_view_data,redirects to analysis direct,TRUE,302,pro,200,302,pro,200,302,coach,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 43,47,cum_flex_data,flex all chart data (json),TRUE,200,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,FALSE,FALSE,FALSE, From 14b0540042dab49249829499990c6bceae2e5ade Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 26 Mar 2024 08:01:28 +0100 Subject: [PATCH 037/121] testing testing --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4001 bytes rowers/tests/viewnames.csv | 1 - rowers/views/workoutviews.py | 12 ++---------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 929f46eab568644c34249b30a4049599aa725fc2..7629a55f6cfd11306d8c23fa57a260aa2a4fcfaa 100644 GIT binary patch literal 4001 zcmV;S4_@#eiwFqt?g3^3|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVPN)K__!#F z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7Xtb{`Gw?|pal;PB;Dw_2~(r;nHGetGqz>$~5&&2lg8yxx4f|EljV4;G8J zZ{M2J?&^HCHpds|i}dB@_|@{f+bnKBeDQ|%cc(b$-u;EMJUZ@IZ&v;LAKvxLb-uwH zCwRA6eR77kY%Z=&7X*NdU!Onwd3%b7tM#Vu*2l|q)Mj 5_~8Mh zpOHU)-0*(y=gW(${@eY3_OF`@1jG*H2H52+%zQc}VaQ`M}} |L$-6h;*7?y0h2IBedxvyFWi(VR6ePZhZZ`d$&4&eg1#C z0dcq7z%7@!@%7X7_EqiQA8p@?Wg>gM<>rGtS*8bkM3^7r?(Z8EuU4n0>DHG0tBaGP z=iT~sca|uAvi0WP-SK}vx%=&w%Zt^zzqvVok=}v7@Gfq5`}D}qUI{UF_%+!ca8Cj^ zDUkny!j8o42tIkhaLe=E?r=SNZN9kd%O8LJ%QL~PWBBK1Oy~Sp*Y` Fz3^KQSz{36_jS4*A2Bxx6`ppI;C8rbuLu82Fiinu3;2rekNQ_`CXau0XsPIbw< zm5R7KLyW3XJ`E9Ekb9gBcV{c&9+SLV@fz;2q`WiChP$tbyQS?o?-bl^FjSB`-h(?< z#XUep+$C{OLZ#8>y>S!aUYEQFu86xg!GqK2^Ujqt )f;mWy$kB$WvY9 zshTOC4j`FwqBG?XrKInM?R;dOr6Tgy#H7@#WS+!?g5+gJ kIWc-)(-1+&;^8yu-XJZ+KHRKEX?&6HdQ$^&RL8o)#pz|f?c?)f{ zABB7{k;H1kbLc}!|J}XFx6yu(JfU#}UNzc>vgGFzp+l+2sv~Oz5LFspj3w>)Igz)a zBJxR3XF^r-6~vNJI!iMe?Xe>A(F6o _d0x;YkyqLC3{W&; zGo#VICYcv&(z6B9kVl-1Zr`G`!{nXuf {jjd{)XGRkzLLS>Z ze>-{3xwi~R8c$AoQA%cATi5*dqmUN^3_&IHLPaAsjr=&|y$MO;QA6HwlleQ6=X%H^ z7zq$mO)@+em3iNE`%%cVVT+=g=K~kj@O+c`QOG-k5k)2Q(H8aIHkluX{5JPi)dJ0y zt*E>gd2BwsA@Vu*R;)_5w`D6TZD=w-3VCbNP#-nAy(L#v=0mgUMj`JFMRXePtz}m< z-#)a_ehl&q+m=`w-QHSPRJRW;K0gY1GL9jtn&3%U@^h-FbQO^oV})5Y&%2_vw6h{F z6_F1naaUzv+Oidu0!`+}AaBtmjnk2;C3!W6OwMPl6hM; z>pHet+EK`RBdm+qh?FJYe0U>}w+XgEZ&g{>;u`t8lD8F+ClkD`x=K7wCO+R}ewe%% zOQ@=@l2Ep;uJynihkP)EsOp>`v22ZvZ@T>$ B~H_kwB!0(mI?D zd2XJZLGmtVU8_1FI>Ms5{hY|xG}=c4h(QydcT7dAsC|pik3l}?-UbYszPHX&(cIfP zk*~;}Cu_(dtLAy3$>iR0tDhev?+scc)oAais69U;@{TGZpF1ZIYx>(e&qe3?IgzhO zw@=y{M^vp^<^s1U?F`6^0T5M}a~HWqX{SHhvo|8TPKX{F`MZ*@NVm6O*o(@OT5>LE{%dt 25_xMV{Zo;rqU+e?`qP$~F#>s?^YB#t@FEvo$42aox}VlWKAPY#s${-! zHkNIDZ^s~S$vDwfSH;5FSaVvJwxTF)l3!t^Ztq-Ct8Px@EAr&nU?>u3^6jJdMMrzr z)}kJR{4V!aH_wMq)T)~k`HDO_k}NbPP?3+JXiko6^ZY2}oe7p$6QxCfvSHdep|8l5 zBQYo3Lr|e7Xz1_Q|FoL%-sR4>F6zd61jeE%_xC{$m7$l4&`WNZ0=fa;(qzzM>vuhf zJ{UnlQlU?(Ay34pZ&E*qKDRR^WF2~Ei`sV5=K4|4TVn-PK3oJZP3rH6o+?7`bBipG zD)hlkM(Qg=oTy$xgF@z^c$kUsTPT(}0f^q31N$6R75TgfJQOwGH?+Nxd@+NUFIW zv23}`oX~Sc=wsIKqfY9}u3Fg8kApsULUpXl%)&HdH4T3T=p_&Ms*o+BP|;}poYcIE zj5`j7JO-79kFI(1?rPkHdeEn_8(i*>r$O(F_QAbZMkX}lZV u^D%R z=zT)3Yod{}xr(CahJF bZx^~31Bp&&sw;Cm=qSdUH38v{Ki0ag~=AqAXFM_g=anS^e8pYVO8BpEE24b@lZuW!tGxQ}aea&qe^FPU>Bl z%z%$AL2D4bGZuAg86x{wv~e0WyM7e((NILI)9^#t@C`R_-WcdD<^EYZZyv|k)V%2r z_*M*vvhd0xOeS7mTQYV )*QLR=(jBsy-LlqVw+26`i|Uf%S80Lx!%cS60}-c<^cMzZJ7wVC2!DrX~x~u z?fQ^gCR!0xIdW}i)i-xVA1Xp0atAG4R}CA>hHtnH_+y|)G|b{uQZG<;xq6 dmulz#juWfk9$q6?zMkS=+FA^M=uHi>yf% zuTK_1E3%$})VthOBkIsQDr(%dt{UT@4_WHHZov0E8L4jr{vdir6NGi~dI`3uhTnp= z 0QhD6)EdDC~~vNxW|s)Du-++ CN*{ zb~$5cy>v!F?=t$RvgbYzQ2DG5< zDCi|OR3V+zTPYg3gQn( imBi>w(`;Ts=(v+HMoJ{n2ZolfebpNw5kP0brZ&t$|}UHB$IXm BK{m(HxvOGW5oUegwIQtz?ofbUxg#u(^@jI$V2 zSvnyUt+c%t`c`u{j9zl}O;BazhFG-H_Fe;i>!mY{KIAoRb&6TjMzG3&j7tOX{J{#fI ChVw=fjrg-G9<0v$Npl=P!3# zt NANSjCxPG*so&WG (-}FpB(-0BD2~4x!e9@ zdW&DYKL2f*?hN1|!0!LU#f>j^Pk#HDjvj1Luh!`~JWeQ=S5MN@_$^)f=)wO01h)rU H#DD<+1bRFE literal 4000 zcmV;R4`1*fiwFp>$pK~p|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m}RVG#CP__!#F zf^35^U}u8Z$mBMt$Q8L~q?VvAFZuUFcH6RStB^ctieMG!gQ2djt?R2tzMV&3zd1iU zc-3ER*B6_w9-@JV2VWmQIC^%~tvBn<>398R=&!!-hVHj++aIKzH`}isz8Hqf!`15b z>(}PAyE _D(l&pg-yr znK#?#e}3TKyWzX;?BZ(OZ&$DI&-eem8&3B7_t%%gzjmonKzBWK{~I43KR7rzdiwy< z&&VI&Z}{Wj>E*@M@YTbA4lnwvhx;$n*N>hb6QKJ7 g{>Cjyb&(`PtR}cT}*y{_WL!Vs?-B~{DX@7FDdA>as_D8Evw!S~$`}O_} zdeWyKyG{3*@8zS7@ArO(?-(&0t*$@4-{G4iYTar7k3QY~&)v{}dwITlHHSYg(=WN+ z{AlxS-woTJ`l~1F&C8+x_i*D!q|^M;oxSXj(WZ;+|9pRi)h(B}@%5AL&HDW1`Tyw# z#NBcOw_M`J*N-;4S9SQ~c=uNHiR|^3n-B7NpC0frVSbGJzi&{yTA!Y#TkD4x7tfEM zbeosmS)%yC)|-2GC;$E6?sr=*FV>sk=H~oGdI$c(ySUr!!y`X>CB)eA*JO9VJqg^T zK>ia7dlI)J_}~G Pg?r|~Povny_O!98UYq-af^3Jdr?!F@KmUiR3Q*gJzP(ki^5AIkM z_W%`fm&82@l}4NQ#?6F#UGg5dBJSP<4^E@cJ6F<-V_FV(Uk`Ty;{#fayn8V+6YiCb z^A*`~!X|=^8ty?#+V4w}_fQdcXB-e{^tvcT_4(z W@okYW|bJHS}CC~RDPj!)} zYNmKPgk;Kz&XhxxlD-?Z%aM7OipX0NlTxpec@h%}l9vUMj}?(m=A0l0Rd~h~wd37# z$Xizrc?9Dul1k>2@n6z&=a)0j3sgj&jb#+pkT2}Jiwh!86_Ixaoz97a&X=6$Ews^o z67s=B5~~T%p${efclRRSM*C6ngvJqg)o35el3z}Q4y7Wij;s+tRB3oImbB-WMBavq z$R|CW3028g5KBhsEG=lX$BM{D6A-*rrQJ313n0&AT*Rm%pObS7l3$SDzNXQh#jpdf zndixtC2to*zM_Z?2Lk|JG~@+JM(6E){Px_ex-s%eeyu}Q8Wu?`>BCzT`HFmamJK6X zO_Y{=(G2@Wej@VDfK`$YFdLb#ZJw_vN^67h=~YzO^AU`18s6OJc|nszUS-cSK+%ZJ zf=2tAWL~UE&lX5S9&t9heT&kLlXu1o22GTf35(kEOCn#9J)bQ!wyJra8BLrCd2I9i z?c_P<-ZCI*JUQt_DVcR`UGv*dLS76o1eMGS6^+<5^3#y_CM1bR4SB~+=I= miR| zBtTF#$?#lM=6%!cCn3* #a2X~jd!{zt&Oo{PR^3Z*HdG| zIrr9iM9pX~2(yv-X3vk4kA?!opdufeZa@EsjZp5EqKeWAL(}c&FY{teNV>gB=55)m z>)2{(Cn4{Rur6XFQkH!4;Y~o^CfEkORb^d^Yvk`r-d04OOz^tuD)Bg*_ Go5QM=%kIHQrkX*m7^@FZ0Mo0+A+4>u^5g zxp{I%$-A6&t?Gp62#f0WOCn#>XdewA22Fh4F%_+%_ANd?1^JwN8!%}4-a1P~b8nYK zz9M^`tRaW2n&*XPlY7gpetwj^H)xSmqrIb|_WXj#JF19$?wmlZ>2L2m7oF#qM7|>3 zK51(lQMGEB3*4f#3m`8BKvZ4MUE~&}o&RXh-iYWrA$n-!?@GQR-QI#>FDg%tM_W?E zyH_4w)9}X0bMAhs^4|Jn3C5OzIeYSAymhFF)%m0w6pYmgF36s*C_Zn4vB;>&$?@rJ zu`?lGn-4G4LtZe~(yG?9@KUsXen~Q4lZGd3ybx-9c;2~^e)~nu^A*|iNq#w!s>a59 z*D5OSNWP*dtwaM%pc?Iin~iSYytgCd g15oSSD3w zMnrDp=N_?1 msax+RgX$dhA(p-7;~w~yWz9qnCP zi+T$3``laIJRd?)t8Pi;EAr$>ve1}7MLve2IXSM)^OKNwCRk!klokQXhH00Cz9LtS z#GG&sL4}^6p}%AQ(`v?hmpk9Os2lGQ7>lOd-v>QZhF&T{FS%g~=mvaCvq6uo-}NZ^ zU<3(Cg+8f nA~PjTKb+a1p#TslOw7stCQ$EwVhS z&<8ggsjmz@yL!+kJIw|Ht3n@rQ8jN#13p%Sp3_`UpqlFu!fepjHsIGJ_0BLLspfjb zvgI~QLeCYUk6FWyI;k(aYGFe^4f@;()v+ow3)76%H2ejimptIBLbixPMWgjgQu8V@ z?l>6o7*rZQy5`Nht8o|VL7&EMaJfI82E8xZ2lrkXnb3^8QS{aT5bLBq*x8`RX55XU z_X)kOiAK)mDvF*P`bp476RpyvL*R-UchtOjQ=m`EdPH5>4N`XeH>saJdNIyx(No!0 zX-krNs0e*9BBW}7jg%}x-_Xz7rQ>sFOzT8c#vLUEL1xlE2ivmrqv)++uWO%^7si-P z=cH!WkE8d7f&|@w@1ba6JvKFO3iOx+SXpp~6mT{jaj~Ukj-qFSPS-UHDHdJ0bWsC7 zRfOIdu%ye#W#HMYant7darD7NQC8nD+M+I )cSQxtjug&af2J)z`C>ZKpy_&6@;08v%?usdr&E z13tC{tx@#OSk$d$i0ot0#%a{-`bp47LlLb`!w+S{H{85=Q=qq$`)BFAc^qR?^X5O` zTQMNY!Yhj~n|OU~$=DSIZEe!nkYZG!Cu(#3{Lu@Ts9d-hEmP6RUGw2jgPu)vNILYE zXS2LnbL5Vr-?dEiDmBlFZ7!YpJ92j|6VdDDdMC3<&}wO!Bk04fWg_U7yg}=w8FzEH z>qBmtXhl%v$hDzW-`o{_s0e+?9kg^^HEb*!zTr0DPk|oMFpE=3y+GOJ>UlwG9#n+h z?piCT)I2HNMrld)^|m7P-jG!WZH1wzH_x^Ke+u*j28oeX=q=1 Us=mXBCp-NNpM$pIHG7%7?&aRKt(lQr-o=pIyH_t=a z<&2^A(wPLk%jl!ZuJ_1AYwn;Kcaxxx#-lP1-&5HV3}`{yDbOS3hAK*_=dvXj(1Ny; zpqJcGg>+JHrD)_1nwmEW`e0Zv=%ij;(NvA5=1qd0l5t1M)(YOa=E$A9ODE=*iJ)E6 z)_Xsjxt?0kb`*UsvSv_)Z+!60u3rH9Xe3#8I;oF-Hg-KVHE#?(lM!om;hO-V+4T!R zFS+mvbm*}t`n*(78}KJU9}F<+`r(J1ygw84)Vz6P=<`a}fI)|z<808k_3Ovcvk5}F z$X$9-N^1DI6GcNm0eWXV+o*Es1cxPEI*URt6`_xLO 4Z?U()M2HTg}}#ddbx{L6wmkV$n+5dky%lm(Do)kk_=;MeZULUF>X88h$NVIs&_u ztO32sksDEF6Yx%x(lp8afAw`_C$DEhpTH6y9K zdC~f!)i>1o=1qd0cZ-;-jJqhYt!_DUZ(c_4a^aN{y^BSqzBNjn27SzhS30{s258OG zX70^P=yxkwD $?p{tH<5t^@krXhJJhe z+1c>r4`=;(zZnib-);K*pc@Y0jsM;E5_{kM9^tdo;mf1d{(uMjGrjwb`SbOs_5S;# z@2(QZi>tT${@A6r=j!71w%a`K&dzpED}7vVPM>!}m(JVm->yXE{c%U%yc}L!T-|t0 zzPY?STc318`svp%#`U>BIa&Jd+n4LJ=hr{?m-IfSLvK8sw_Bcc|4En3&VrktKizG; zNt?aA-*30!`q6%L{-bB c8Ne3+`~QztH@?_E`Q2kWey~Hm-lXU7T|(JkeV?AjZ|Ty<5B>*Ei(duA GfB^s+jzCxd diff --git a/rowers/tests/viewnames.csv b/rowers/tests/viewnames.csv index f0f76651..d6d99cfb 100644 --- a/rowers/tests/viewnames.csv +++ b/rowers/tests/viewnames.csv @@ -42,7 +42,6 @@ 52,58,workout_toggle_ranking,toggle ranking,TRUE,302,basic,302,302,basic,403,302,coach,302,302,FALSE,FALSE,TRUE,TRUE,TRUE, 53,59,team_workout_upload_view,upload workout for team member,TRUE,302,coach,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, 54,60,workout_upload_view,upload a workout,TRUE,302,basic,200,302,FALSE,200,302,FALSE,200,302,FALSE,FALSE,FALSE,TRUE,TRUE, -55,61,workout_histo_view,histogram ,TRUE,302,basic,200,302,basic,403,403,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 56,62,workout_forcecurve_view,force curve,TRUE,302,pro,200,302,pro,403,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 57,63,workout_unsubscribe_view,unsubscribe from comments,TRUE,302,basic,200,302,basic,200,302,basic,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, 58,64,workout_comment_view,comment on workout,TRUE,302,basic,200,302,basic,200,302,coach,200,302,FALSE,FALSE,TRUE,TRUE,TRUE, diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 7fb0c248..3cca4b75 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1620,15 +1620,10 @@ def course_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { @@ -1802,15 +1797,12 @@ def virtualevent_compare_view(request, id=0): except: # pragma: no cover labeldict = {} - res = interactive_multiple_compare_chart(workoutids, xparam, yparam, + script, div = interactive_multiple_compare_chart(workoutids, xparam, yparam, promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) script = res[0] div = res[1] - errormessage = res[3] - if errormessage != '': # pragma: no cover - messages.error(request, errormessage) breadcrumbs = [ { From cccb0be36c0a4081c45bf7a69fd6b7cac5d6feb8 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 07:14:34 +0100 Subject: [PATCH 038/121] running tests, fixed issues --- rowers/interactiveplots.py | 10 ++++++++-- rowers/tests/testdata/testdata.tcx.gz | Bin 4001 -> 4000 bytes rowers/views/workoutviews.py | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f1eed721..d8f455c5 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -4175,10 +4175,16 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', compute=compute, workstrokesonly=workstrokesonly, for_chart=True) + + # check if dataframe not empty + if datadf.empty: # pragma: no cover + return [' No non-zero data in selection
', ''] + datadf['workoutid'] = datadf['workoutid'].astype(int) datadf.dropna(axis=1, how='all', inplace=True) datadf.dropna(axis=0, how='all', inplace=True) + nrworkouts = len(ids) try: @@ -4187,11 +4193,11 @@ def interactive_multiple_compare_chart(ids, xparam, yparam, plottype='line', try: tseconds = datadf.loc[:, xparam] except: - return ['', 'A chart data error occurred
', '', 'A chart data error occurred'] + return ['A chart data error occurred
', ''] # check if dataframe not empty if datadf.empty: # pragma: no cover - return ['', 'No non-zero data in selection
', '', 'No non-zero data in selection'] + return ['No non-zero data in selection
', ''] if (xparam == 'time'): diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 7629a55f6cfd11306d8c23fa57a260aa2a4fcfaa..9156a4deb611e39e9dc57da148806d85e1ea78c0 100644 GIT binary patch literal 4000 zcmV;R4`1*fiwFo+F9T)*|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!zwlBQl~DC zaYP`iMFKVoD4VxcV&?BpuloLcf3bM` z_N_VYE>2f#b98pPNMCM_UM)|%&EopQ7jNk9?iBmoyKgwl{iA;MX4Sv{fJ=Ir8lK>)b; iX@kz0nQq>5qCt z=H2GyUmy7Qu7A*-oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dJ3qVV@7?`p|7v-0clTxb`q9fn0(3us{6O#=dEeqbITIamguemo0~{>U zP9OJw+@0?|>${8o`Ra6e@9tk6dwHRB=<~C_JIRMVTOOUQUv3VC{lVg^t)C8fxZ1rz zPnPM&Zqj|`d--DHr@cSoJ46fzi_1@+cDSEJtvg=+W0~&$x2|9Qe15upHT(DH>6ct? zezg91+4Y-Wmlsb~>(~AA-~E*zkxuh_ck+69h&Ekh_vfc8EUvl4m9L+4?^dU;PybIh zAnuwQxaJa9zJ9dczN-EE!|hwKOk^*&Tz!x)m+1i?66VLa`}+#Ti`DUQy0vBh>g?s= zlWzUGJ4qBj+j@2H?&!at-TijU`PpjSU)`L)NbkTmyo=l2K0oq{S3-;(eoeLq+>*dm z3go|{up@Cjg3lf>T=RUlJ6w)lo3AeW{MSFec_z4a4FB?s>74)S`aX$GJXq}h`tq8K zbkQ$=-u1VbUxe%MYN<1rB<*4q)R7HN1ABhZ6>%q55%&ZU!370(N_ta4?&0R#sV;f9 zQW1A&h*34lry+t1a*wm&?rcTeW0H3(Uc)_>ly`>NaQ78)x3nGSor1d!h6-}WTX4s! zxCf|+yCm*Os5IKVH*O-_>yr1t6>;|_cyJnh-no)y9MgQb`+B$w7$4AT ?9gyotqY+EP1{Kd8&&% zRWrrYJ|t64bfz4ll=R)OosZ13R7BpIn3Q^z%#)Z Bb!3eIqDsSyv7|jeC-OE_ zL_X>1OsGn}f><(2XK6;GJyt|Mnt wZS#CZQCb^}Pp_iNo{wOB)9|J~&kLF)@+y0t0g6U! zW;EK@B=cfTdbU6s@`#hs?OT*~n7lJyFleH*Ojy*OpA-3t?D=e=u~p6U%xL07$YY!5 zuP4tr_m%-k zdzw6!Kz#A*f_tsA$BdkspV=Hz7$pYREfoGJiwzTn~8! zBLRY{Nrva5GVhyiKMHv^Y*AG6eBh!Qo^LWg3VCNRqNrp(+M?duCiCNv-{#(`TA Z3-tx8#b-d}vnPDCE7Nh)(0Zwd{)K z+lMyVk3pVc+Y(Eo+gt03>h__<=SLw=#xX=y6FezPeohsYt|IbctT3zQc~`WSc2?x2 zBJ#l`?y3w-TehN7pvn9g E)pb8_ZHzMdKz z&bhbFBWgx_L70rpH+z1Vd^8jw1{L|(bo=Q?Y=m;R6jhW~7@BTBeVG?)LelM3GH=Uf zUB^~SI|_Mkgmn=ck+S5Q4{rqWHo-RNtt#tUTqA!|^0p%KWP;aKSBb~T#OIsL50e*T z302iq63W)qwH}z`kPn6sRh<(gmaVbzO}8I|Jc5ZxtnuDDz?OSEeVIo#5{NWWT8Gmi z&&`uFNZ#eFYgH#iM_5$1pA-3-M*C<0F=*oRj;UxBwQuqHG05lK+kipS_tse|ntMAZ z@)g Gl>3dr^6EJlc{P z-mUWRnua$_o^$t8mG{;sOE9(!%*m4% &^;Z!Fe$azfcPDuo*PQOL7F z()GX$q3Eh*b0S~UJWpbRVANz_hKNNoGiXNhyswCSFp^|mQ)v-_n#@l zJ2t*=xFcS zTGV5Z-{s!w=J^ncT6J?GUy&zAl7+?uD)KQD&B<|Xo*#w0Gr i5Z8t}0q^ql5;0@YlP5GI4ZwgJB;sdt6}Nj29a zmMyoL6MC))easqu)Jc8WRSO&XanR>ZsE$>cS(s+5rs2;3z2pI36|zMXDjKbylbTnN zamT@s$Dq>i(KT=0O^v%y5BfBAgUkK#H0XWNKDf8a$b@Fx4WhROfLJH>!A=G}Hsfv( zy-( gS5frb(2s&XnrM|S9RgR>xTEIH8v}h()+6f5ZjiF$ze)Y%(Tj0ji=N81 zN}H3^Lq+I=5g}CrY@}on`i6emE*+mcV_GMoGVUlT2r`lOIoOt^A4G2rdtLjSyfDUO zIwv)|ei*$s6eQ>dd=Etn>#?bMW1z<*z{-L I;Pa}Yfnbh@rtNU`X`rL!9F zsUq~wfF)f-w7Gs1^n@l7fhs|ZX*z3~H6Q*kdTRnJ zt8bXyWSXY6ES+KW-gqml((u`qUFU{c&D|L2bB3j$uD+h7YWdYThX5*$80NNxch` z8St?sXbqxw#-eU5Lu4O|Hcq2v*N=ie8j5Ij8h$7nzTxK08w0(i+&@d_&EpuGnm7Fc z---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO9Q16WL(-wQ zJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbv IKR!SI;w2^PnR1 zcH3G(rRGWDHcE4 +oZMUA`GRbw3VAxpj24fviXBlT^-A4Jb+g0L=LFTobo@LSM! z9Q4*WL{xRvkZ7AXZ~Bg0_Qo?=RnXRfn@p-kQ}YJVCp*nL)Me>7bZx+&dTw4q&qgRa zX*B3*IwcsbjnXiBXDsAIOAUGsZNQ(tBiF`+o=8=AKYLZ4T%1`Im%94CXmtzSQko=p(a zMefpzQc}auohTam5zss1*+!L1CpawW(wP-{sR(_{YubWN>OB@6@O>-67z4eKaTbFr zODBY)mA1D+-)ioL(Mztr395|T5Q|pY-fF;ay>y1rhrFh(E^-&4=wfHH((r4^(h=CM zWDV$5j@*bc8J7;UzIlV_w<}re*13yRHfRfNt{(-x&($|d^z0|Ix@DW|2hr!1tQkq= z&5PC-t-hhwH*Xa5yj{dxW!y!HZFS3ud-F1SmkY0y=v^!-^{r9rIOt<8ywchAF+gjc zHgRuWLcd+fS{b>E5nE^M8KAc&&Gf1TPza#)$h|3gKNk8ATR(0%SUm2|FF!mz>zA9u z?@sz3e>qv6F4z6u_uEb1?{)niyz_s$qZfn_V+Y?I_dgyib_d+qo$2Fm%%3kmt#;oZ zJh(_4&n`af`?O1M&&Ao>O}Bp8ot$i+R{FSFAHVGSE}ggAy RK|D;Q1XTjCaU+=bB zr_EkI?YG@<`Dnj5|H1Qg(}%w;PnO+gnK x4B!WV-Tw!RD_`uM{Pr;&-r1sFt=tGK literal 4001 zcmV;S4_@#eiwFqt?g3^3|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVPN)K__!#F z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7Xtb{`Gw?|pal;PB;Dw_2~(r;nHGetGqz>$~5&&2lg8yxx4f|EljV4;G8J zZ{M2J?&^HCHpds|i}dB@_|@{f+bnKBeDQ|%cc(b$-u;EMJUZ@IZ&v;LAKvxLb-uwH zCwRA6eR77kY%Z=&7X*NdU!Onwd3%b7tM#Vu*2l|q)Mj 5_~8Mh zpOHU)-0*(y=gW(${@eY3_OF`@1jG*H2H52+%zQc}VaQ`M}} |L$-6h;*7?y0h2IBedxvyFWi(VR6ePZhZZ`d$&4&eg1#C z0dcq7z%7@!@%7X7_EqiQA8p@?Wg>gM<>rGtS*8bkM3^7r?(Z8EuU4n0>DHG0tBaGP z=iT~sca|uAvi0WP-SK}vx%=&w%Zt^zzqvVok=}v7@Gfq5`}D}qUI{UF_%+!ca8Cj^ zDUkny!j8o42tIkhaLe=E?r=SNZN9kd%O8LJ%QL~PWBBK1Oy~Sp*Y` Fz3^KQSz{36_jS4*A2Bxx6`ppI;C8rbuLu82Fiinu3;2rekNQ_`CXau0XsPIbw< zm5R7KLyW3XJ`E9Ekb9gBcV{c&9+SLV@fz;2q`WiChP$tbyQS?o?-bl^FjSB`-h(?< z#XUep+$C{OLZ#8>y>S!aUYEQFu86xg!GqK2^Ujqt )f;mWy$kB$WvY9 zshTOC4j`FwqBG?XrKInM?R;dOr6Tgy#H7@#WS+!?g5+gJ kIWc-)(-1+&;^8yu-XJZ+KHRKEX?&6HdQ$^&RL8o)#pz|f?c?)f{ zABB7{k;H1kbLc}!|J}XFx6yu(JfU#}UNzc>vgGFzp+l+2sv~Oz5LFspj3w>)Igz)a zBJxR3XF^r-6~vNJI!iMe?Xe>A(F6o _d0x;YkyqLC3{W&; zGo#VICYcv&(z6B9kVl-1Zr`G`!{nXuf {jjd{)XGRkzLLS>Z ze>-{3xwi~R8c$AoQA%cATi5*dqmUN^3_&IHLPaAsjr=&|y$MO;QA6HwlleQ6=X%H^ z7zq$mO)@+em3iNE`%%cVVT+=g=K~kj@O+c`QOG-k5k)2Q(H8aIHkluX{5JPi)dJ0y zt*E>gd2BwsA@Vu*R;)_5w`D6TZD=w-3VCbNP#-nAy(L#v=0mgUMj`JFMRXePtz}m< z-#)a_ehl&q+m=`w-QHSPRJRW;K0gY1GL9jtn&3%U@^h-FbQO^oV})5Y&%2_vw6h{F z6_F1naaUzv+Oidu0!`+}AaBtmjnk2;C3!W6OwMPl6hM; z>pHet+EK`RBdm+qh?FJYe0U>}w+XgEZ&g{>;u`t8lD8F+ClkD`x=K7wCO+R}ewe%% zOQ@=@l2Ep;uJynihkP)EsOp>`v22ZvZ@T>$ B~H_kwB!0(mI?D zd2XJZLGmtVU8_1FI>Ms5{hY|xG}=c4h(QydcT7dAsC|pik3l}?-UbYszPHX&(cIfP zk*~;}Cu_(dtLAy3$>iR0tDhev?+scc)oAais69U;@{TGZpF1ZIYx>(e&qe3?IgzhO zw@=y{M^vp^<^s1U?F`6^0T5M}a~HWqX{SHhvo|8TPKX{F`MZ*@NVm6O*o(@OT5>LE{%dt 25_xMV{Zo;rqU+e?`qP$~F#>s?^YB#t@FEvo$42aox}VlWKAPY#s${-! zHkNIDZ^s~S$vDwfSH;5FSaVvJwxTF)l3!t^Ztq-Ct8Px@EAr&nU?>u3^6jJdMMrzr z)}kJR{4V!aH_wMq)T)~k`HDO_k}NbPP?3+JXiko6^ZY2}oe7p$6QxCfvSHdep|8l5 zBQYo3Lr|e7Xz1_Q|FoL%-sR4>F6zd61jeE%_xC{$m7$l4&`WNZ0=fa;(qzzM>vuhf zJ{UnlQlU?(Ay34pZ&E*qKDRR^WF2~Ei`sV5=K4|4TVn-PK3oJZP3rH6o+?7`bBipG zD)hlkM(Qg=oTy$xgF@z^c$kUsTPT(}0f^q31N$6R75TgfJQOwGH?+Nxd@+NUFIW zv23}`oX~Sc=wsIKqfY9}u3Fg8kApsULUpXl%)&HdH4T3T=p_&Ms*o+BP|;}poYcIE zj5`j7JO-79kFI(1?rPkHdeEn_8(i*>r$O(F_QAbZMkX}lZV u^D%R z=zT)3Yod{}xr(CahJF bZx^~31Bp&&sw;Cm=qSdUH38v{Ki0ag~=AqAXFM_g=anS^e8pYVO8BpEE24b@lZuW!tGxQ}aea&qe^FPU>Bl z%z%$AL2D4bGZuAg86x{wv~e0WyM7e((NILI)9^#t@C`R_-WcdD<^EYZZyv|k)V%2r z_*M*vvhd0xOeS7mTQYV )*QLR=(jBsy-LlqVw+26`i|Uf%S80Lx!%cS60}-c<^cMzZJ7wVC2!DrX~x~u z?fQ^gCR!0xIdW}i)i-xVA1Xp0atAG4R}CA>hHtnH_+y|)G|b{uQZG<;xq6 dmulz#juWfk9$q6?zMkS=+FA^M=uHi>yf% zuTK_1E3%$})VthOBkIsQDr(%dt{UT@4_WHHZov0E8L4jr{vdir6NGi~dI`3uhTnp= z 0QhD6)EdDC~~vNxW|s)Du-++ CN*{ zb~$5cy>v!F?=t$RvgbYzQ2DG5< zDCi|OR3V+zTPYg3gQn( imBi>w(`;Ts=(v+HMoJ{n2ZolfebpNw5kP0brZ&t$|}UHB$IXm BK{m(HxvOGW5oUegwIQtz?ofbUxg#u(^@jI$V2 zSvnyUt+c%t`c`u{j9zl}O;BazhFG-H_Fe;i>!mY{KIAoRb&6TjMzG3&j7tOX{J{#fI ChVw=fjrg-G9<0v$Npl=P!3# zt NANSjCxPG*so&WG (-}FpB(-0BD2~4x!e9@ zdW&DYKL2f*?hN1|!0!LU#f>j^Pk#HDjvj1Luh!`~JWeQ=S5MN@_$^)f=)wO01h)rU H#DD<+1bRFE diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 3cca4b75..79a151f9 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1801,8 +1801,6 @@ def virtualevent_compare_view(request, id=0): promember=promember, plottype=plottype, labeldict=labeldict, startenddict=startenddict) - script = res[0] - div = res[1] breadcrumbs = [ { From c4dc809168da0cd7183079d8fc5727a03349cba9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 07:22:49 +0100 Subject: [PATCH 039/121] tests run --- rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 3999 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 9156a4deb611e39e9dc57da148806d85e1ea78c0..804b53755ed4b7816219c4f1afae8d7520a63c74 100644 GIT binary patch delta 3912 zcmV-O54Z53AD -%@nz}>wc5APhjxad~v)%y71a@{X49(8^9N4Ht-rJdKC`*&aU{rUc4@%HUo zbKG5=uGZ%0>~xX7+#J1Ho_3qX^@lIs(B0iB_PclAaFz#0{p!uCfB*Ble!0#!c;y7| zHmlFh@RrTl#qojwaPj-o6(`TPr?|ITZ~AV1v`j~BR{Oj2J~@%_7XjFl2mvvFKR>(Z z@8A7r|7v-0clTxb`ti#{0(AF4?h(8|-nY0<&O}EX;co!@00)b-)5ravbmx1|`|hHD zu{vGezx!9mUS232`r@qbPV!;Tmq%yomzzUjf3Wy!>!$-At#)tF(`EXxn{=P~UcT7) zY44Bt4iUq_;_}m{9Udf6>yDRy|5&EG|F!FvKb@a$U(NpgdHN-nn;)-#T6X>Bm*vIN z)%tb6{C9ulN2JsI)}6dw9->Vb+5P$H3X5wlapmi$-MiK4>(l?!4T!tu2Clipm9HPK zx36md{&4$NEECzwEmt4p%Vm1NhlKet?*6_)@nUs+oNjH|zdCz)__SMpzwS;F#m}~0 z-Mc&b?`L D zI=ouy3?@mtSOs-tgVVr&o*#5Y+{snMJwZfpLBXAp-c*o#xH)&KOWv(i#N8QURE_d! zh~R?U<7~J)TM_q| I+_5U|0V?7yiF*<% zjW+L%n+W&1 @1Y{@&Nv{@=yg$w>htrFcZ7-*JBfx_=cYv{OP+5*p6Vh`)lBiU56P4h zohgSXC4Dz+=Ognh6_K|lCZ%2_^CTt|Brh`}A1flC%sD|0s_={}YR9|zkhiWL@(9LR zB$do3 tRY|6cNb?wo+={m3_6_?2c0iD&s%7t{V3#vi6mAN zo `tNQ 1lqEl(2pvjARvlR*fT+^&Vk~LT&xyPZ6_HPRIuojr zuOOC;(pj3(Xpa? A^Bfou3qdkjZ2VOJJlPyc$ z&WL e;$(FD7Ns2~?~E4=nkX$37PaT+M7|<>K3iyPRr5SEnm7^i*yj1`$#c%V zWkAw+a?*=ZGV9v9=C>b(ycl2zDw!858nJ2Q$06@cND_}4@{XI#-;g}lLmt6MfS_uU z;kl^H`=;BELY@s<6xBQ*xTuEbo6L_w-WiN2Dw&Uewy5{E$^1Cvx4E~f7HGC?Mdhu? zWAot+k AnX`GH!El-Q~x|JDKQH!mJJR9$HQCb^g$()=yk*}x5hI8(% z^N503DFT2)$Qj*zNXPW8bA!1_`G8(T1D+!e0~h_IrlbT(Dc1^mWt-y&WU_~ zMfN;dLk?Lr&kIc^_m*4z{2+O6&?2cudq+j>`5BRSR1x{yIe}Qy-`;sHI?vCEd_}r_ z($+YlYSl6qxJ7AaKwb=hsJfiH$Sq1c{n4Ji5z%!*^w7xPlzc_Hy#>QwRGu7 ;SH1L-2GJLz4gfwj4cCm^5n&Tc W~~oRK|WQGDJ8W06slljGCd zVkbhrHXmN7hrD2}rB$tI;iYK({G4RICJj&6cp=pI@Vs*+{r0n(=PR=3ll*cdRgI1J zu2odtkbFf^T8RdjKsDM2HyPc&d2a{E%RZp-N*|t&MOPu8mCQ>; @|H}b>eqj71l*NpaoLQ#8uPNRLOhdh$87NaKXI&f? =AqUx3sT+!7@=j6ThR1bNA+#8EEo}5s2jY^?LeiZU- zkaRsTLnyjx*__DNG|!WmAQ&}&8JHnr(aa2*(LC=fA|H$-nb%ZWM4%?~Q;*fjKBKWr zs?3at+{jNoVw1>QL+PK2JQZEXCfA>~%#0Dp`<#cT>W3G(=sGrHXVm?)Ci2k)hfyW- zg|o42>w7x}c}vELuDU7~&c>S4y0jHVX_Nd4D|LJ4iduDZB43dw#|A@xkwBAgAH6R+ z+Pk(E^%&%LxwpD`K7^uH-JHl* EqR4fvKOgC1MI z>p}Fv2ojPCeNqj1B1V0GllnpQxt%E?>(Dz})V7m0*N=kU8Y`&s;UaixQh!7AR1tcg zTV#1up$~2{QePQ*cJ-i7cA5 YZUg zQqA> Rk0x5BONYP}HSVZ+^Tt4*l=X -lt{(+Gp@~GGO3-4O&YEV; zhd+$on!w8H8)i3|rfDrpXBfRV-U_QUe70rRxuI5bHwOBgVJWDquV*RSPKBD9Hwt<- z0vL5t@4{pTd~69?gXo>Hs9Vbr*~g-d)2P|?qo9w5B3hk>AIgSrxOwx&KyNAc&(eAG zIL4;tO@F|Dw_-q)g;y3~GV%J_lCdia+S;VCA;qXdPt@l6>7y4gQMqt2TBf3ryXM0m z2R)nUkaXxRPiA?u=ExmJzipZ5Rcf9U+gv)+cjRtcCZgBP^-d;}pw-ec2hfLY%S6yE zd4twVGw!Bt*N5CP(TbqTk!wS%zPTy-P!al&J80>Dx@y>1HhjZvz#juWqG1-Nl6rx% z%hmIY)I6vNz1_A}P^o!RxQ)`B>g#Pq=)EDU4B84qQE#4Y1O6E32@Db=tI%7R%-V*{ zn>UPpTVzeDczv=6T9Ne(q~7JO8c~PdQBmWrb=4RLeaKSpbpyWV$w+-0@CVT|njox; z*GsT}MK$~uv>gY%H4YI~T{R@y=FOYFBbUALOjZ@Nb>JqGs?pTELG;N^vkrAxIu2bM z@TZ=em(a5j%1#;$dYVoNMr)%qjNTawInh#so @STG;*UtdG7$U8j=b>ffPQCCd 7?FD(a0S%HE$I3!LVS^NxitDsTxhq8wEWj (>vXXA^{Uk-PMwl+^HZCyIuDegyQ+c(zgH(g_Yrx^!lRUMfN#^P0AxlX{Ou z2Yla3FvdVHWSqsI%F+p;Xr=9~(6^eqVf2!#Z-Oc#H^icqwznGaTQ8kq^dYZltBc%4 zD7x6$tTg;uvUCKtD_H}2l_NKzOva@Ht#95S`t3^Ax^?a%l?~cLo9jnG?{oEkjS@Zk z$*gYK=K4YOc_nK`QhD>D^+l_1sP)Yo1wC&UF;^LPQDR%&a^l{+jNawKD ;b5^l z;Lh$$AAe*1eEDg$`~KkJMdEmN@nPR*U3z;i&fadi^~>($Wc#$z$JP4yW!HD n}_cktkK|LW}G%4_oA{QP8f)b;77U%nWZ=l<$s>ARm^uTEZG{@mYx()*YWz4CBA zY63KKylgvg|g?#36tB@aV4G*TIu+ zef;>*;m^-9o9&;w?Z2eA_}S~zKbGmv0PX?o{y$h;`C|9vw~y)Y&KC7*ou0$PgmQWD WC_Rlo(xngY{14elt2e|0fB^s^zyyN; delta 3913 zcmV-P54P~1AD|xwABzYGRWFeSAQLJz=TfIGj&VdFt3?7f3MiYmRgpO(f1lo+p6tC@ zUTjup>w9<6z}>x{4(}X1zvx!$)%y6sa@{X49(H~AqT4L@($4G6y}Pga{(OJ2c>DIP zIqoh_S8H>0cDhJkZjN3pPrJ?H`okA*= 5ILrN`e)VS6zyIZ3zg*`VymEqf zo7HD$c+2MO;&?#-xcKAA72{{yQ~a=6Z~AV1v`j~BR{Oj2K0cA~7Xi?d2mvvFJ3qVV z@7?`p|7v-0clTxb`q9fn0(3us{6O#=dEeqbITIamguemo0~{>UP9OJw+@0?|>${8o z`Ra6e@9tk6dwHRB=<~C_JIRMVTOOUQUv3VC{lVg^t)C8fxZ1rzPnPM&Zqj|`d--DH zr@cSoJ46fzi_1@+cDSEJtvg dD-=wUzZn8 zR_oXO^56ZHACXS;dw24Bd5AV$WcTN%D=e+ou+GngdpVinYp4Ne1pdw$RraVJ+1_XH8a1qF9XdQ(B};pW__E_t_75qD>ZQ8mh^ zA%Y8XkF(+KY(?B-l6Na!!#$RicZS(;_Z4xsv>oT2g1Zfd3UbF=aL1~+2dId?9gyotqY+EP1{Kd8&&%RWrrYJ|t64 zbfz4ll=R)OosZ13R7BpIn3Q^z%#)Z A$-b`8L`Qk|#8dz^g|4P?r3BB6KJfS#@NM0HR96i?O6VKPU1wR75`M=}f3f zzJgdXN@r NMcDJ-mJ)1 v@+SerW zVoiFsKpOIYh?CLnTa Z3-tx8#b-d}vnPDCE7Nh)(0Zwd{)K+lMyV zk3pVc+Y(Eo+gt03>h__<=SLw=#xX=y6FezPeohsYt|IbctT3zQc~`WSc2?x2BJ#l` z?y3xbOk1|1QlQEF800OQq;Wb@wLC4_>sDq|MJ=`>@@%}*MQLq}C3AA-M82LH8_v16 z&Le6@dqJ3t%r|>}n0z!8AO;or*mV2pM{I<0w-i;BRv4OYKYf`OYeLfPRWfhOW?jct zOFIgAZ-jLb8xCiAb#R-a5dRdpmuZM>Z0OG*McI(;?5z zlQT%(<*aK}CqzeBRJWfK`I<)iXaF&2;`5HFXce_@@%b^x=iJ+XLDTovSt^=)J16pg z71{G-4LM}hJTEkv+*@w-^MmBQL5rjs?Hv`h=VwITQAOl)=LBL+e|zV-=sZ6s@)ha! zNn7KHs#VKe;1;Ew0eLY1qUv()BDW~*^hbO4Mnu;M(L*DDQ}Pw*_7)6#QF(Ga+L9XH zt@7}ihBr)}bN5q~_tqy%Ft!ZL$&(j< kH)3JUNhPU3PtVtIgR$A9`Z=WT8x^k>%g(e{0+&+ zipYzBpd9UO(Ul5k<;kgNv`25eh^kvka79-qos;+0Q$6Gfa&IiwcydD7H7bP~`BBKT zLDKcW458?%Wpg55(>zaNf?(8tWMGDfMKd#KM)SO{h Fykn_Pd|GBZXX?{glWsvlnDqU+d*ol*DGn#e~J97dJQ z7tY4At?%s^(W*frA_iHtkmtDD{9rviF`$#92*ROMFLH}ee}NQ zXz$uu)MJp}<=*P%`4Ea)b#o$Lktauzg~kLb@-Y<6$#HF-ABDU#!4hktv $+ zt_RTvBS=Up^hq`3i5T^NP3i~H=XR!qtV8c?QQJ=1Tt5nWYpkHkhl}8)N&OAcQ$^@~ zZjt3tg+92+NPT7K+0}zS*=aTqSQYx{i>i5Z8t}0q^ql5;0@YlP5GI4ZwgJB;sdt6} zNj29amMyoL6MC))easqu)Jc8WRSO&XanR>ZsE$>cS(s+5rs2 Z@Y$AK=Z0F%-5BU|hNYmczMiFQI~8hb-YDqV z2w>Dny$h2W@UbOm4Wf6(qHZliWFLz*PNQbmkAglLifDBjekdEh;pWX71HGl(KTGG$ z;~1NoH~j&B---cI7G7C|$;9hxOUAA!Xls+kh7_X;JyDzMr;lF1MCHQ8Xqk#e?wSvO z9Q16WL(-wQJelRqnj?1@{kCPISE+ecY;);M-;uj*nTTFD*E^X^f>uk*96%qoEfYbv z #AX6+3*dw0e=kih=y65O6mp5 zE?3VpQuCl9^mf}?L8azN;WkQhs;{>dq4$QYGH5FdMZI~p4ftcACoo8itU_;LGHV+) zZ{9HaZILyp;`PZQXhqgDkb0N9YD67+M@5ah)>UI1^dU>V*A4idCnNQ3z#l} TOzj$HP}Gg(#8)`6Q$szy`u2GJ)w%{tU&={R(4 zz@K_ qaTt5T!Vu-YAo`;r^JN3e=kXt5yV%nxdA8;}aRhpVNfIjAyiGUb&c73Fl zmN^6TYyv2~c^=9xXAG^E&M4?zMjusny+ KYLZ4T%1`Im% z94CXmtzSQko=p(aMefpzQc}auohTZA`Vr7O C%}MdZ`F~%xl_$PU<}t z9q@fC!59O*kZ~4+DoZDXqLsF{Lf>lchS5u|z6q*~+z^Xa+TLowZ@qMe(TBXItuAsG zq3B{~v(oTu$ (;r8R5oY}ZLS{$z0cKuH%j#E zC$qX`o9hSB=asA(N#)Ip))%e5q1HEV6!g4Z#9U?EMTu>7%ZYpQGJ2N_uaxLrEGqS_ zQR+D8V=lbX+4V6%Yo0c7Z(c&bUCCM*xr-57XY3iEw ss&I8p!LYTDSAH^`VU(_ zZa7#x?#?eiJU#1|o5Sx;`X7IPIa!`A*Zto2+fCo^b^RW^^MAUd7laUF2j3m{KOQW0 z2i)16>EmzApD#bHcHbX7xJVq&E ?q8i ($B2%b)vydwL(!p;sQx zhb>RK|D;Q1XTjCaU+=bBr_EkI?YG@<`Dnj5|H1Qg(}%w;PnO+gnK x4B!WV-Tw!RD_`uM{Pr;&-r1sFt Date: Wed, 27 Mar 2024 18:43:06 +0100 Subject: [PATCH 040/121] testing --- rowers/interactiveplots.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d8f455c5..318a12b2 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -365,6 +365,17 @@ def interactive_workouttype_piechart(workouts): except KeyError: # pragma: no cover pass + data_dict = data.to_dict("records") + + chart_data = { + "data": data_dict, + "title": "Types" + } + + + div, script = get_chart("/workouttypepie", chart_data, debug=True) + return script, div + p = figure(height=350, title="Types", toolbar_location=None, tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) From 672cfab0c46b68ebc2b14ed9ad7be7b909744a1c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 27 Mar 2024 18:58:40 +0100 Subject: [PATCH 041/121] workouttype pie chart v1 --- rowers/interactiveplots.py | 17 ++--------------- rowers/templates/history.html | 7 ++++--- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 318a12b2..728f8219 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -373,23 +373,10 @@ def interactive_workouttype_piechart(workouts): } - div, script = get_chart("/workouttypepie", chart_data, debug=True) + script, div = get_chart("/workouttypepie", chart_data, debug=True) + return script, div - p = figure(height=350, title="Types", toolbar_location=None, - tools="hover,save", tooltips="@type: @totaltime", x_range=(-0.5, 1.0)) - - p.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='type', ) - - p.axis.axis_label = None - p.axis.visible = False - p.grid.grid_line_color = None - p.outline_line_color = None - p.toolbar_location = 'right' - - return components(p) def interactive_boxchart(datadf, fieldname, extratitle='', diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 52aaa542..55ddbb5b 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -22,9 +22,6 @@ -- {{ tscript|safe}} -@@ -91,6 +88,10 @@{{ tdiv|safe }} + + {{ tscript|safe}} ++ From c0042445068d93d8fa3a71b98f748e1e90c19f55 Mon Sep 17 00:00:00 2001 From: Sander RoosendaalDate: Thu, 28 Mar 2024 18:30:38 +0100 Subject: [PATCH 042/121] v1 activity_chart --- rowers/interactiveplots.py | 50 +++++++++++++++++++++++++++++------ rowers/templates/history.html | 2 ++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 728f8219..e915de3b 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -373,7 +373,7 @@ def interactive_workouttype_piechart(workouts): } - script, div = get_chart("/workouttypepie", chart_data, debug=True) + script, div = get_chart("/workouttypepie", chart_data, debug=False) return script, div @@ -567,6 +567,9 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba df.sort_values('date_sorting', inplace=True) + data_dict = df.to_dict("records") + + hv.extension('bokeh') if stack == 'type': @@ -706,12 +709,9 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb while d <= enddate: dd = d.strftime('%d') - if totaldays <= 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) + dates.append(d.strftime('%Y-%m-%d')) + dates_sorting.append(d.strftime('%Y/%m/%d')) + durations.append(0) trimps.append(0) rscores.append(0) @@ -743,6 +743,41 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb df = pd.DataFrame(thedict) + data_dict = df.to_dict("records") + + if totaldays < 30: + datebin = "day" + elif totaldays < 50: + datebin = "week" + else: + datebin = "month" + + stacknames = { + 'TRIMP': 'trimp', + 'distance': 'distance', + 'time': 'duration', + 'rScore': 'rscore', + 'duration': 'duration', + } + + chart_data = { + 'data': data_dict, + 'title': 'Activity {d1} to {d2}'.format( + d1=startdate.strftime("%Y-%m-%d"), + d2=enddate.strftime("%Y-%m-%d"), + ), + 'datebin': datebin, + 'colorby': 'type', + 'stackby': stacknames[yaxis], + 'doreduce': True, + 'dosort': True, + 'colors': mytypes.color_map, + } + + script, div = get_chart("/activity_bar", chart_data, debug=True) + + return script, div + if totaldays > 30 and yaxis == 'duration': # pragma: no cover df['duration'] = df['duration']/60 elif yaxis == 'TRIMP': @@ -4739,7 +4774,6 @@ def get_zones_report(rower, startdate, enddate, trainingzones='hr', date_agg='we 'hours': hours, } - # print(pd.DataFrame(data).head()) return data diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 55ddbb5b..28635bc5 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -166,6 +166,8 @@ $("#total_maxhr").append(totalsdict.hrmax); $("#total_power").append(totalsdict.powermean); $("#total_maxpower").append(totalsdict.powermax); + $("#selectors").hide(); + listofdicts.forEach(function(item){ var id = "#"+item.id+"_hr"; $(id).append(item.hrmean); From 742fca831a88221792684486fc7e9fe4c14f62eb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:07:27 +0100 Subject: [PATCH 043/121] small speed up --- rowers/interactiveplots.py | 200 +++++-------------------------------- 1 file changed, 24 insertions(+), 176 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index e915de3b..614a8f7a 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -623,47 +623,22 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, yaxis='duration'): - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - distances = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] + print(timezone.now()) startdate = datetime.datetime( year=startdate.year, month=startdate.month, day=startdate.day) enddate = datetime.datetime( year=enddate.year, month=enddate.month, day=enddate.day) - duration = enddate-startdate + totaldays = (enddate-startdate).days - totaldays = duration.total_seconds()/(24*3600) + data_dicts = [] + aantal = 1 for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') + rr = w.user + rowersinitials = rr.user.first_name[0:aantal]+rr.user.last_name[0:aantal] + dd = w.date.strftime('%Y-%m-%d') du = w.duration.hour*60+w.duration.minute trimp = w.trimp @@ -672,78 +647,24 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb if rscore == 0: # pragma: no cover rscore = w.hrtss - if totaldays <= 30: # pragma: no cover - dates.append(dd) - dates_sorting.append(dd2) - else: - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - trimps.append(trimp) - rscores.append(rscore) - distances.append(distance) - links.append( - "{siteurl}/rowers/workout/{code}/".format( + + link = "{siteurl}/rowers/workout/{code}/".format( siteurl=settings.SITE_URL, code=encoder.encode_hex(w.id) ) - ) - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) + data_dicts.append({ + 'date': dd, + 'duration': du, + 'distance': distance, + 'trimp': trimp, + 'rscore': rscore, + 'type': w.workouttype, + 'link': link, + 'rower': rowersinitials + }) - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate - - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass - - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - dates.append(d.strftime('%Y-%m-%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - - durations.append(0) - trimps.append(0) - rscores.append(0) - distances.append(0) - links.append('') - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: # pragma: no cover - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'distance': distances, - 'link': links, - } - - df = pd.DataFrame(thedict) - - data_dict = df.to_dict("records") + if totaldays < 30: datebin = "day" @@ -761,7 +682,7 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb } chart_data = { - 'data': data_dict, + 'data': data_dicts, 'title': 'Activity {d1} to {d2}'.format( d1=startdate.strftime("%Y-%m-%d"), d2=enddate.strftime("%Y-%m-%d"), @@ -774,83 +695,10 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb 'colors': mytypes.color_map, } - script, div = get_chart("/activity_bar", chart_data, debug=True) + print(timezone.now()) + + script, div = get_chart("/activity_bar", chart_data, debug=False) - return script, div - - if totaldays > 30 and yaxis == 'duration': # pragma: no cover - df['duration'] = df['duration']/60 - elif yaxis == 'TRIMP': - df.drop('duration', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'rScore': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('distance', inplace=True, axis='columns') - elif yaxis == 'distance': # pragma: no cover - df.drop('duration', inplace=True, axis='columns') - df.drop('trimp', inplace=True, axis='columns') - df.drop('rscore', inplace=True, axis='columns') - - df['color'] = df['type'].apply(lambda x: mapcolors(x)) - - df.sort_values('date_sorting', inplace=True) - - hv.extension('bokeh') - - # table = hv.Table(df,[('date','Date'),('type','Workout Type')], - # [('duration','Minutes'),('trimp','TRIMP'),('rscore','rScore'),('link','link')]) - - types_order = mytypes.workouttypes_ordered - # bars=table.to.bars(['date',stack],[yaxis]) - bars = hv.Bars(df, kdims=['date', stack]).aggregate( - function=np.sum).redim.values(types=types_order) - - # print(mytypes.color_map) - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.xaxis.axis_label = 'Period' - if yaxis == 'duration': - p.yaxis.axis_label = 'Duration (min)' - if totaldays > 30: # pragma: no cover - p.yaxis.axis_label = 'Duration (h)' - elif yaxis == 'TRIMP': - p.yaxis.axis_label = 'TRIMP' - elif yaxis == 'distance': # pragma: no cover - p.yaxis.axis_label = 'Distance (m)' - else: # pragma: no cover - p.yaxis.axis_label = 'rScore' - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - #p.sizing_mode = 'stretch_both' - p.y_range.start = 0 - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df['link']}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) - return script, div def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', From 26ad051cf54c88bc9740aed1e7fb3b3bb3af515c Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:10:02 +0100 Subject: [PATCH 044/121] fix --- rowers/interactiveplots.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 614a8f7a..09626c66 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -623,7 +623,6 @@ def interactive_activitychart(workouts, startdate, enddate, stack='type', toolba def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, yaxis='duration'): - print(timezone.now()) startdate = datetime.datetime( year=startdate.year, month=startdate.month, day=startdate.day) @@ -695,11 +694,10 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb 'colors': mytypes.color_map, } - print(timezone.now()) script, div = get_chart("/activity_bar", chart_data, debug=False) - + return script, div def interactive_forcecurve(theworkouts, workstrokesonly=True, plottype='scatter', spm_min=15, spm_max=45, From 7ab56936dac0e5d57394e8a36f9ca2e1acfa1165 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 29 Mar 2024 08:57:39 +0100 Subject: [PATCH 045/121] removed activity_chart --- rowers/interactiveplots.py | 182 +--------------------------- rowers/templates/history.html | 9 -- rowers/templates/list_workouts.html | 19 +-- rowers/views/workoutviews.py | 2 +- 4 files changed, 7 insertions(+), 205 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 09626c66..905e0fe3 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -441,186 +441,8 @@ def interactive_planchart(data, startdate, enddate): return script, div -def interactive_activitychart(workouts, startdate, enddate, stack='type', toolbar_location=None, - yaxis='trimp'): - dates = [] - dates_sorting = [] - types = [] - rowers = [] - durations = [] - rscores = [] - trimps = [] - links = [] - - rowersinitials = {} - seen = ['seen'] - idseen = [] - - startdate = datetime.datetime( - year=startdate.year, month=startdate.month, day=startdate.day) - enddate = datetime.datetime( - year=enddate.year, month=enddate.month, day=enddate.day) - - duration = enddate-startdate - - totaldays = duration.total_seconds()/(24*3600) - - for w in workouts: - aantal = 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - if w.user.id not in idseen: - while initials in seen: # pragma: no cover - aantal += 1 - initials = w.user.user.first_name[0:aantal] + \ - w.user.user.last_name[0:aantal] - - seen.append(initials) - idseen.append(w.user.id) - rowersinitials[w.user.id] = initials - - for w in workouts: - dd = w.date.strftime('%m/%d') - dd2 = w.date.strftime('%Y/%m/%d') - dd3 = w.date.strftime('%Y/%m') - du = w.duration.hour*60+w.duration.minute - rscore = w.rscore - trimp = w.trimp - - if rscore == 0: # pragma: no cover - rscore = w.hrtss - - if totaldays < 30: - dates.append(dd) - dates_sorting.append(dd2) - else: # pragma: no cover - dates.append(dd3) - dates_sorting.append(dd3) - durations.append(du) - rscores.append(rscore) - trimps.append(trimp) - links.append( - "{siteurl}/rowers/workout/{code}/".format( - siteurl=settings.SITE_URL, - code=encoder.encode_hex(w.id) - ) - ) - - types.append(w.workouttype) - try: - rowers.append(rowersinitials[w.user.id]) - except IndexError: # pragma: no cover - rowers.append(str(w.user)) - - try: - d = utc.localize(startdate) - except (ValueError, AttributeError): # pragma: no cover - d = startdate - - try: - enddate = utc.localize(enddate) - except (ValueError, AttributeError): # pragma: no cover - pass - - # add dates with no activity - while d <= enddate: - dd = d.strftime('%d') - - if totaldays < 30: - dates.append(d.strftime('%m/%d')) - dates_sorting.append(d.strftime('%Y/%m/%d')) - else: # pragma: no cover - dates.append(d.strftime('%Y/%m')) - dates_sorting.append(d.strftime('%Y/%m')) - durations.append(0) - rscores.append(0) - trimps.append(0) - links.append('') - try: - types.append(types[0]) - except IndexError: - types.append('rower') - - try: - rowers.append(rowers[0]) - except IndexError: - try: - rowers.append(str(workouts[0].user)) - except IndexError: - rowers.append(' ') - - d += datetime.timedelta(days=1) - - thedict = { - 'date': dates, - 'date_sorting': dates_sorting, - 'duration': durations, - 'trimp': trimps, - 'rscore': rscores, - 'type': types, - 'rower': rowers, - 'link': links, - } - - df = pd.DataFrame(thedict) - - df.sort_values('date_sorting', inplace=True) - - data_dict = df.to_dict("records") - - - hv.extension('bokeh') - - if stack == 'type': - table = hv.Table(df, [('date', 'Date'), ('type', 'Workout Type')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - else: - table = hv.Table(df, [('date', 'Date'), ('rower', 'Rower')], - [('duration', 'Minutes'), ('rscore', 'rScore'), ('trimp', 'TRIMP'), ('link', 'link')]) - - bars = table.to.bars(['date', stack], [yaxis]) - if stack == 'type': - bars.opts( - opts.Bars(cmap=mytypes.color_map, show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - else: - bars.opts( - opts.Bars(cmap='Category10', show_legend=True, stacked=True, - tools=['tap', 'hover'], width=550, xrotation=45, padding=(0, (0, .1)), - legend_position='bottom', show_frame=True)) - - p = hv.render(bars) - - p.title.text = 'Activity {d1} to {d2}'.format( - d1=startdate.strftime("%Y-%m-%d"), - d2=enddate.strftime("%Y-%m-%d"), - ) - - p.width = 550 - p.height = 350 - p.toolbar_location = toolbar_location - p.y_range.start = 0 - #p.sizing_mode = 'stretch_both' - taptool = p.select(type=TapTool) - - callback = CustomJS(args={'links': df.link}, code=""" - var index = cb_data.source.selected['1d'].indices[0]; - console.log(links); - console.log(index); - console.log(links[index]); - window.location.href = links[index] - """) - - taptool.js_on_event('tap', callback) - - script, div = components(p) - return script, div - - -def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolbar_location=None, +def interactive_activitychart2(workouts, startdate, enddate, stack='type', yaxis='duration'): @@ -687,7 +509,7 @@ def interactive_activitychart2(workouts, startdate, enddate, stack='type', toolb d2=enddate.strftime("%Y-%m-%d"), ), 'datebin': datebin, - 'colorby': 'type', + 'colorby': stack, 'stackby': stacknames[yaxis], 'doreduce': True, 'dosort': True, diff --git a/rowers/templates/history.html b/rowers/templates/history.html index 28635bc5..fc89929e 100644 --- a/rowers/templates/history.html +++ b/rowers/templates/history.html @@ -5,19 +5,10 @@ {% block title %}Rowsandall {% endblock %} {% block main %} - - - -- --diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index beeafdad..51e40308 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -10,7 +10,7 @@ {% endblock %} @@ -229,22 +229,11 @@ Total meters: {{ totalmeters }}. Total time {{ totalhours }}:{{ totalminutes }}h. Dig deeper. -- Activity chart by - TRIMP, - rScore, - Time. -
- - - - - - {{ interactiveplot |safe }} + {{ the_div |safe }} + {{ interactiveplot |safe }} + {% if announcements %}diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 79a151f9..d0e77543 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2221,7 +2221,7 @@ def workouts_view(request, message='', successmessage='', if yaxis not in ['duration', 'trimp', 'rscore']: # pragma: no cover yaxis = 'duration' - script, div = interactive_activitychart(g_workouts, + script, div = interactive_activitychart2(g_workouts, g_startdate, g_enddate, stack=stack, From 7f3fea5f37838a91d0d3b443897fa55d723d4003 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 1 Apr 2024 12:46:29 +0200 Subject: [PATCH 046/121] v1 instroke_compare --- rowers/interactiveplots.py | 99 +++++++++---------------- rowers/templates/instroke_analysis.html | 10 +-- 2 files changed, 38 insertions(+), 71 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 905e0fe3..ba018f19 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2756,12 +2756,14 @@ def forcecurve_multi_interactive_chart(selected): # pragma: no cover return (script, div) def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no cover - df_plot = pd.DataFrame() + df2 = [] ids = [analysis.id for analysis in selected] metrics = list(set([analysis.metric for analysis in selected])) maximum_values = {} + workouts = [] for metric in metrics: maximum_values[metric] = 0 + cntr = 1 for analysis in selected: #start_second, end_second, spm_min, spm_max, name activeminutesmin = int(analysis.start_second/60.) @@ -2781,79 +2783,48 @@ def instroke_multi_interactive_chart(selected, *args, **kwargs): # pragma: no co if mean_vals.max() > maximum_values[analysis.metric]: maximum_values[analysis.metric] = mean_vals.max() xvals = np.arange(len(mean_vals)) - xname = 'x_'+str(analysis.id) - yname = 'y_'+str(analysis.id) - df_plot[xname] = pd.Series(xvals) - df_plot[yname] = pd.Series(mean_vals) - if len(metrics) > 1: - for analysis in selected: - yname = 'y_'+str(analysis.id) - df_plot[yname] = df_plot[yname] / maximum_values[analysis.metric] + data2 = pd.DataFrame({ + 'x': pd.Series(xvals), + 'y': pd.Series(mean_vals), + + }) + data2['id'] = cntr + df2.append(data2) - source = ColumnDataSource( - df_plot - ) - - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - if len(metrics)>1: - plot.yaxis.axis_label = 'Scaled' - else: - plot.yaxis.axis_label = metrics[0] - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - colors = itertools.cycle(palette) - - try: - items = itertools.izip(ids, colors) - except AttributeError: - items = zip(ids, colors) - - for id, color in items: - xname = 'x_'+str(id) - yname = 'y_'+str(id) - analysis = InStrokeAnalysis.objects.get(id=id) legendlabel = '{name} - {metric} - {workout}'.format( name = analysis.name, metric = analysis.metric, date = analysis.date, workout = str(analysis.workout) ) - plot.line(xname,yname,source=source,legend_label=legendlabel, - line_width=2, color=color) + workouts.append({'id': cntr, 'label': legendlabel}) + cntr = cntr + 1 - script, div = components(plot) + + ytitle = metrics[0] + if len(metrics) > 1: + cntr = 1 + for analysis in selected: + df2[cntr-1]['y'] = df2[cntr-1]['y'] / maximum_values[analysis.metric] + ytitle = 'Scaled' + cntr = cntr+1 - return (script, div) + df2 = pd.concat(df2, axis=0) + + data_dict = df2.to_dict("records") + + chart_data = { + 'title': '', + 'data': data_dict, + 'ytitle': ytitle, + 'workouts': workouts, + } + + script, div = get_chart("/instroke_compare", chart_data) + + return script, div + def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, activeminutesmin, activeminutesmax, diff --git a/rowers/templates/instroke_analysis.html b/rowers/templates/instroke_analysis.html index 0ced8ffb..301e3ddb 100644 --- a/rowers/templates/instroke_analysis.html +++ b/rowers/templates/instroke_analysis.html @@ -6,24 +6,20 @@ {% block main %} -{{ js_res | safe }} -{{ css_res| safe }} - - - + -{{ the_script |safe }} - In-Stroke Analysis for {{ rower.user.first_name }} {{ rower.user.last_name }}
+ {% if the_div %}
- {% endif %} From 3792a6f7b67d70b131277c4d631c966fa620215d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal
{{ the_div|safe }} + {{ the_script |safe }}Date: Mon, 1 Apr 2024 16:35:20 +0200 Subject: [PATCH 047/121] v1 instroke interactive chart --- rowers/interactiveplots.py | 148 ++++----------------- rowers/templates/instroke_interactive.html | 11 +- rowers/tests/test_unit_tests.py | 2 +- rowers/tests/testdata/testdata.tcx.gz | Bin 3999 -> 4001 bytes 4 files changed, 34 insertions(+), 127 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index ba018f19..3b74b7b8 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -2864,136 +2864,42 @@ def instroke_interactive_chart(df,metric, workout, spm_min, spm_max, except TypeError: pass - TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,crosshair' - plot = figure(width=920,tools=TOOLS, - toolbar_location='above', - toolbar_sticky=False) - - #plot.sizing_mode = 'stretch_both' - - plot.title.text = str(workout) + ' - ' + metric - - # add watermark - watermarkurl = "/static/img/logo7.png" - - watermarkrange = Range1d(start=0, end=1) - watermarkalpha = 0.6 - watermarkx = 0.99 - watermarky = 0.01 - watermarkw = 184 - watermarkh = 35 - watermarkanchor = 'bottom_right' - plot.extra_y_ranges = {"watermark": watermarkrange} - plot.extra_x_ranges = {"watermark": watermarkrange} - - plot.image_url([watermarkurl], watermarkx, watermarky, - watermarkw, watermarkh, - global_alpha=watermarkalpha, - w_units='screen', - h_units='screen', - anchor=watermarkanchor, - dilate=True, - x_range_name="watermark", - y_range_name="watermark", - ) - - - source = ColumnDataSource( - df_plot - ) - - - TIPS = OrderedDict([ - ('x','@x'), - ('median','@median'), - ('high','@high'), - ('low','@low') - ]) - - hover = plot.select(type=HoverTool) - hover.tooltips = TIPS - - s = 'SPM: {spm_min} - {spm_max}'.format( - spm_min = spm_min, - spm_max = spm_max, - ) - - label = Label(x=50, y=450, x_units='screen',y_units='screen', - text=s, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - s2 = 'Time: {activeminutesmin} - {activeminutesmax}'.format( - activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), - activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) - ) - - label2 = Label(x=50,y=400, x_units='screen', y_units='screen', - text=s2, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - - plot.add_layout(label) - plot.add_layout(label2) - - if name: - namelabel = Label(x=50, y=480, x_units='screen', y_units='screen', - text=name, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(namelabel) - - if notes: - noteslabel = Label(x=50, y=50, x_units='screen', y_units='screen', - text=notes, - background_fill_alpha=0.7, - background_fill_color='white', - text_color='black', - ) - plot.add_layout(noteslabel) - - if individual_curves: - for index,row in df.iterrows(): - plot.line(xvals,row,color='lightgray',line_width=1) - else: - plot.varea('x', y1='high', y2='low',source=source,fill_color="lightgray",alpha=0.5) - - plot.line('x','median',source=source,legend_label='median',color="black", - line_width=3) - - medrange = mean_vals.max()-mean_vals.min() - yrange = Range1d(start=mean_vals.min()-0.2*medrange, - end=mean_vals.max()+0.2*medrange,) - plot.y_range = yrange - - plot.add_tools(HoverTool(tooltips=TIPS)) - + if metric == 'boat accelerator curve': - plot.yaxis.axis_label = "Boat acceleration (m/s^2)" + ytitle = "Boat acceleration (m/s^2)" elif metric == 'instroke boat speed': - plot.yaxis.axis_label = "Boat Speed (m/s)" + ytitle = "Boat Speed (m/s)" vavg = mean_vals.median() elif metric == 'oar angle velocity curve': - plot.yaxis.axis_label = "Oar Angular Velocity (degree/s)" + ytitle = "Oar Angular Velocity (degree/s)" elif metric == 'seat curve': - plot.yaxis.axis_label = "Seat Speed (m/s)" + ytitle = "Seat Speed (m/s)" + - plot.xaxis.axis_label = 'Time (%)' + lines_dict = df.to_dict("records") + data_dict = df_plot.to_dict("records") - try: - script, div = components(plot) - except ValueError: - script = "" - div = "Something went wrong with the chart" + chart_data = { + 'lines': lines_dict, + 'data': data_dict, + 'ytitle': ytitle, + 'title': str(workout) + ' - ' + metric, + 'individual_curves': individual_curves, + 'spmmin': spm_min, + 'spmmax': spm_max, + 'timemin' :'{activeminutesmin}'.format( + activeminutesmin=datetime.timedelta(seconds=60*activeminutesmin), + ), + 'timemax': '{activeminutesmax}'.format( + activeminutesmax=datetime.timedelta(seconds=60*activeminutesmax) + ), + 'analysis_name': name, + } - return (script, div) + + script, div = get_chart("/instroke", chart_data, debug=False) + return script, div def interactive_chart(id=0, promember=0, intervaldata={}): diff --git a/rowers/templates/instroke_interactive.html b/rowers/templates/instroke_interactive.html index 9c86fdb9..acc2a8d1 100644 --- a/rowers/templates/instroke_interactive.html +++ b/rowers/templates/instroke_interactive.html @@ -13,7 +13,6 @@ - - - {{ the_script |safe }} -In Stroke Metrics
+
- {% if user.rower|is_basic %} @@ -167,9 +164,13 @@ $( function() {
- +
{{ the_div|safe }}++ {{ the_script |safe }} +- diff --git a/rowers/tests/test_unit_tests.py b/rowers/tests/test_unit_tests.py index 0492f3e5..195b9ec1 100644 --- a/rowers/tests/test_unit_tests.py +++ b/rowers/tests/test_unit_tests.py @@ -685,7 +685,7 @@ class InteractivePlotTests(TestCase): startdate = workouts[0].date-datetime.timedelta(days=5) enddate = workouts2[0].date+datetime.timedelta(days=5) - script, div = interactiveplots.interactive_activitychart(workouts,startdate,enddate) + script, div = interactiveplots.interactive_activitychart2(workouts,startdate,enddate) self.assertFalse(len(script)==0) self.assertFalse(len(div)==0) diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 804b53755ed4b7816219c4f1afae8d7520a63c74..3c5912231fea528068d9b2afd89b73de6d3b218f 100644 GIT binary patch literal 4001 zcmV;S4_@#eiwFo2kqTx4|8!+@bYx+4VJ>uIcmVC4NpBoC7J%>m6@m`QVG#CP__!#t z0>{o^4A`C^F*3OgDpE)88L1_x%iF&nvfGhmTZQCFQv|C(9}IPMZCzhI^6fnQ?%ny> z-kassW_7WCxQ_<*_r5!NaQO17Tdh~?)5ptozr1?V_1$mXX1SMkUT+@mzv}zTgT>
n~ST{1p(mV*XPfE-k#!{)q2x+>*HlQYO^}no%h+rao2Bu?VWC5Pk+=K zGVeAge}3TKyZ&)^c5$^@ZWeFw&-eem>yLN)FRw3!f9+C(fbM$e?l_ww1sk9+@t?+7s*F0Ma)+~H9YweEEJk7c_1pSphe{pI=g)$HG2reAWs z`RV%mW!G OWukG5~cGLgOBa`Qo+EYkx%BFv9*_xBBoSF6+0bZg80)y2ut z^KSjRJ4+Nl*?M#D?)bl--2HaT<;7~<-`t$PNbkU3co(<3eR||)uY?#o{F-bJxF>;| z6v%%;VMpS21fM)$xaIk7ceozCHeX!!<&VGq<(c5tG5qs0rgQ$Q>-!`&@o=&G>+@?a z(nUZ2dAHwUei3fNtEJ9hlC+CeP)9a64ea?rSHzuMMcfla1Q!(CDd|lGxre)Rr@G|b zN=4kAAx70GpN0r7$UV-6yR#K>k4fIGcn$YhQr;P6!`)ZJ-O_fPcM9${7%Ipe@4+3b z;vS$P?vl7Cq0(sc-nfZyuS?zoSH#_$;K6D1dFM)+aZK~!?(5+$V0=KUk#{dfCc?e4 zalRruPS`}SQNuk*N&9_H@*XPU?u-Khjb0a}s6Ia*c}J*7v6E<+b#7XOvgG+5 3jEfjm Z zHS;{#vgGZI$X67x;a~v3i-x>F$>_YjkKdk~RX0RF$**;&O2Z DdBl$Rkchw{KC}Ve-y+!JvuKGGS4Beoo{ovgfmf##S}YGoy(UA&+gI zznwhi+*<}DjVC9)C?&J5t!sY!QOJt{hM zIOpCvkEj{#1z|EW-|YEe^3hO$7*ynA)9t4pu@TDMQdCh|VQ9Mj^krVG2}!qC$-FI_ zbsbwR?I`5E5!OX)M9PwHKD-gg+XUO7x2mjbagF?4$=iy^lL=l|T_qkT6Q6G~KTKYX zB~(>cNhn)a*Lq-%Lp~TnRCP{}ShmK-H{E^=@(3m(vBrDr09)?u^kp8|NFdTgX&p|7 zJU36yAbFRwu2r279br-3eoo|T8ttP2#Gr}KJEo#l)V{^%#~`0`ZvzHR-&<#?XzuNt z$X8^~lQra!Rr9>iWO8r0)z1%-_XaJJYP5G$)SjOac}EqI&z%#9HT~_K=c4ocoXA(C z+b3;}BdS&{bAelwb_V3d0Enu~xr^MQw9_B$*&7jECqxg8{9Vacq}y9C>_z3t@n}nG zc=yV~YZ~4#dCuKWRo+{lEWy|^Fegu5jJFOou{xi0gMzU-!5P`}6~*UmFcuj#IXOPP zEp{U0YxCiSddLgrT3Xeb7G8?h&(BHbYtrz9jTb_V56?SS(r-VjdA=fhKFKdfQq|aa z?^;FW9m!V|rIl!a2~?wfaFfyPoA-8ryc_@;uk_*hSacQgS;@RqMBW+;qLTRlMc1O9 zmCU<}$a{l8pt0%#hRMi$BR@<&8kYup&1fGe6t(B)G}?!H$RinRF>11|1IH%wcO)Mx zA} D!!86p n>)pWB%dvJSnoMQuB2bNwjjt+9eCA1;EICiQnjPZgo}xkZ*o z75d;NBlVS`XIBsUWT)9cU{&a&FRJFvX~4&d&~uvW2~=}ELYNHt+6MfZq}~|@B-LDx zShn0|PUyKJ^f7DrQ783fS1oMl$3dSvp*mJ&W?`DKnub3E^pXdBRmc`msA#l)PHJ97 z#vKPk9)n85N7uZ0cQx)pJ?PWe4KDY`)1dc7`{3RyBNLi&H;CRE0Aiig2Rj+`*o?bD z^gf~2HPOh~Tt(4yLq7`oXrfiRbO>Bg CEqW^3 zDs4_u4;7&gMub!iu#u8Q=o|WJyL5c+jA@;S%DAJXAjm}8=U`iweh|Gi>~-yP^1>LC z>73N;`eF3mP>`S-@I4eQtjDJ2je#DM04odbkOEGoBQCbI%t7>Q(CNBnA;qE#m(FUy zr;5-!1D13dxePp+HE!BmKa4(@D9Y*^MqAXS)8_h7&=Z 8L43T{-+Bl7xT|Wx?Xegr9Y51XR_=cM|Zw&O7a{nxyH;-d%YTon* zd@BY-S$Jg;CKIo(Eg8F_psh_B8&Zra^h9m0pFVm46O{`Wqh%@@xobZBanQ4g4oQdJ z@?@4bYmVGu^xKw+UZv()vCXA3eMj!LWg>drT<>Hu30f^Ja{ztVwoC-wk~e6*G~;gS zc74b#6RilU9Jw~M>YKZw4;7&gxr3IjtA>qb!#CUp{4vlY8fI}SsTU}_Ts_Z7&4Y^2 z+ihzFm6|7o+bGSczTQ@Z-W#&Ypsg?z_2$_&;E#cxz#uWQ3cZENtZmr5dBf z*C&gh6 Rs-t5q0Pt6*cZ!SB-Jdhb;A8H{g4ojMTRQe-J&R3BtN~y#!lS!*4;` zanM`i5K+}tL!xcoyy-h~*&EMfRY6+^ZZfGFP0brbpX@a2P?x3S(6s@7>bZFdJsY9y zq|u 6Bo!HcG?jow1M;Ej8#lv;lwmj$9iPdLmWfm9ud&m9{NQXAr%RVXY6}IcRhJ z4A6@q(yDnLT1M{F3$H?MnTTnd4t>DMG*oG7-T?ZTTP6Zx)Y In*BrT1cj?63G7+?E z+IsINGuKlK+76=6Mb-?e@Qn|?+4VC(AB`mIPAB!zPsXmNrsfTyXEI`~E_@RpG`oHV z=p`3kfet+uMW2@nY6JcV=z{@9T|fMgllLcro|-pr2z_438ZhY4bDRwNwtoFEdNx5w z7r9F>N=XerccN(MM?mk4XB$;6o#3#fOJ`Q-r6Tk(uW1W9srOiP!1t{LV+`~{##s!i zES(UFR@&YReXF?}MlZSgCa5xULo8Zpd#?e%_0kzeAM%>Ey2xFGqKlo)O2e-uOGjY4 zk~N@LIdUV)WL!GX`sNLy->zh>TjwrP*`O`7xqcM%K3Cr;(X*e->XvP;A4H#5vSuWe zH!oUWwEBix-@H-K^L7z)m2np(w$&{s?#;{ST`s&*qIa>V)VD^d a_p%aIrh!!R}0d{KowG`qOIn z{o&)Q#PQ )%x_L>$`N`ZufR2Dj$zKeDu11b#Zm$ zHF e{r((-S@9oXD8P`_m}iOrbBN$oDW-`cmGM3%+7+FpTFE~ zwN9J8eB5ul;rh{jcK*Yc>86i giwFpyxdUbb|8!+@bYx+4VJ>uIcmVC4TW=Ic7J%RR6&4T4!-|@7sZ$rn zI3keMA^{r(l+D|!ViX@kz0nQq>5qCt z=H2GyUmy7Qu7B8_oL#Jzo5dUa>;3=i`lH?c%gamQw_R!w&`l5B{l>e8clP!UK0JW* zGxEnz8{Y3dKR>(Z@8A7r|7v-0clTxb`ti#{0(AF4?h(8|-nY0<&O}EX;co!@00)b- z)5ravbmx1|`|hHDu{vGezx!9mUS232`r@qbPV!;Tmq%yomzzUjf3Wy!>!$-At#)tF z(`EXxn{=P~UcT7)Y44Bt4iUq_;_}m{9Udf6>yDTISf;!Gwd ds{rTw%i)$`%A zh`Z(nuDQgOuOF|suWJAPaQjv)6WPlxS0CidWqQDeg!wV<{=P!-Vs(6+Zf)7WI(vEe zv|GRKP7=k>wqD)4JNoZucfZ|oezscoS2yP`(mU`C@8Wj1&yW1#l@McxUz6