diff --git a/rowers/datautils.py b/rowers/datautils.py index 8c7727c7..8489af1f 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -468,7 +468,6 @@ def getfastest(df,thevalue,mode='distance'): starttime = griddata(restime,starttimes,[thevalue*60*1000],method='linear',rescale=True) duration = griddata(restime,restime,[thevalue*60*1000],method='linear',rescale=True) endtime = starttime+duration - print(distance,starttime,endtime ) return distance[0],starttime[0]/1000.,endtime[0]/1000. return 0 # pragma: no cover diff --git a/rowers/forms.py b/rowers/forms.py index 76d00219..2661e1a3 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -58,6 +58,24 @@ class FlexibleDecimalField(forms.DecimalField): value = value.replace('.', '').replace(',', '.') return super(FlexibleDecimalField, self).to_python(value) +class TrainingZonesForm(forms.Form): + zoneschoices = ( + ('power','Power Zones'), + ('hr','Heart Rate Zones') + ) + + zones = forms.ChoiceField(initial='hr',label='Training Zones',choices=zoneschoices) + startdate = forms.DateField( + initial=timezone.now()-datetime.timedelta(days=365), + widget=AdminDateWidget(), #format='%Y-%m-%d'), + label='Start Date') + enddate = forms.DateField( + initial=timezone.now(), + # widget=SelectDateWidget(years=range(1990,2050)), + widget=AdminDateWidget(), #format='%Y-%m-%d'), + label='End Date') + + class InstantPlanSelectForm(forms.Form): datechoices = ( ('startdate','start date'), diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c4a92124..d8d0c035 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -715,13 +715,14 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l 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 trimp = w.trimp rscore = w.rscore if rscore == 0: # pragma: no cover rscore = w.hrtss - if totaldays<30: # pragma: no cover + if totaldays<=30: # pragma: no cover dates.append(dd) dates_sorting.append(dd2) else: @@ -759,7 +760,7 @@ def interactive_activitychart2(workouts,startdate,enddate,stack='type',toolbar_l dd = d.strftime('%d') - if totaldays<30: + if totaldays<=30: dates.append(d.strftime('%m/%d')) dates_sorting.append(d.strftime('%Y/%m/%d')) else: @@ -6557,3 +6558,318 @@ def interactive_otw_advanced_pace_chart(id=0,promember=0): div = '' return [script,div] + +def get_zones_report(rower,startdate,enddate,trainingzones='hr'): + duration = enddate-startdate + + totaldays = duration.total_seconds()/(24*3600) + + dates = [] + dates_sorting = [] + minutes = [] + hours = [] + zones = [] + + workouts = Workout.objects.filter( + user=rower, + startdatetime__gte=startdate, + startdatetime__lte=enddate, + duplicate=False, + ).order_by("-startdatetime") + + ids = [w.id for w in workouts] + + columns = ['workoutid','hr','power','time'] + + df = dataprep.getsmallrowdata_db(columns,ids=ids) + try: + df['deltat'] = df['time'].diff().clip(lower=0) + except KeyError: # pragma: no cover + pass + + df = dataprep.clean_df_stats(df,workstrokesonly=False, + ignoreadvanced=True,ignorehr=False) + + #totalmeters,totalhours, totalminutes, totalseconds = get_totals(workouts) + + hrzones = rower.hrzones + powerzones = rower.powerzones + + for w in workouts: + dd = w.date.strftime('%m/%d') + dd2 = w.date.strftime('%Y/%m/%d') + dd3 = w.date.strftime('%Y/%m') + dd4 = arrow.get(w.date).isocalendar()[1] + #print(w.date,arrow.get(w.date),arrow.get(w.date).isocalendar()) + + qryw = 'workoutid == {workoutid}'.format(workoutid=w.id) + + qry = 'hr < {ut2}'.format(ut2=rower.ut2) + if trainingzones == 'power': + qry = 'power < {ut2}'.format(ut2=rower.pw_ut2) + timeinzone = df.query(qry).query(qryw)['deltat'].sum()/(60*1e3) + if totaldays<=30: + dates.append(dd) + dates_sorting.append(dd2) + elif totaldays<=121: # pragma: no cover + dates.append(dd4) + dates_sorting.append(dd4) + else: # pragma: no cover + dates.append(dd3) + dates_sorting.append(dd3) + minutes.append(timeinzone) + hours.append(timeinzone/60.) + if trainingzones == 'hr': + zones.append('<{ut2}'.format(ut2=hrzones[1])) + else: + zones.append('<{ut2}'.format(ut2=powerzones[1])) + #print(w,dd,timeinzone,' 30: + df.drop('minutes',inplace=True,axis='columns') + else: + df.drop('hours',inplace=True,axis='columns') + + + source = ColumnDataSource(df) + + df.sort_values('date_sorting',inplace=True) + df.drop('date_sorting',inplace=True,axis='columns') + + hv.extension('bokeh') + + + bars = hv.Bars(df, kdims = ['date','zones']).aggregate(function=np.sum).redim.values(zones=zones_order) + + #bars = table.to.bars(['date','zones'],['minutes']) + bars.opts( + opts.Bars(cmap=color_map,show_legend=True,stacked=True, + tools=['tap','hover'],width=550,padding=(0,(0,.1)), + legend_position='bottom', + show_frame=False) + ) + + p = hv.render(bars) + + p.title.text = 'Activity {d1} to {d2} for {r}'.format( + d1 = startdate.strftime("%Y-%m-%d"), + d2 = enddate.strftime("%Y-%m-%d"), + r = str(rower), + ) + + if totaldays >= 30: + p.xaxis.axis_label = 'Week' + if totaldays >= 121: + p.xaxis.axis_label = 'Month' + + p.plot_width=550 + p.plot_height=350 + p.toolbar_location = 'above' + p.y_range.start = 0 + p.sizing_mode = 'stretch_both' + + script,div = components(p) + + return script,div diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 60e82e56..e57d9d2d 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -359,6 +359,8 @@ def get_todays_micro(plan,thedate=date.today()): enddate__gte = thedate ) + + if thismacro: thismeso = TrainingMesoCycle.objects.filter( plan=thismacro[0], @@ -366,6 +368,8 @@ def get_todays_micro(plan,thedate=date.today()): enddate__gte = thedate ) + + if thismeso: thismicro = TrainingMicroCycle.objects.filter( plan=thismeso[0], diff --git a/rowers/tasks.py b/rowers/tasks.py index 38df2741..427ce4b9 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -2806,7 +2806,7 @@ def handle_update_wps(rid,types,ids,mode,debug=False,**kwargs): return 0 try: wps_median = int(df.loc[mask,'driveenergy'].median()) - except ValueError: + except ValueError: # pragma: no cover return 0 if mode == 'water': @@ -3029,7 +3029,7 @@ def df_from_summary(data): weightclass = data['weight_class'] try: title = data['name'] - except KeyError: + except KeyError: # pragma: no cover title = "" try: t = data['comments'].split('\n', 1)[0] @@ -3043,13 +3043,22 @@ def df_from_summary(data): startdatetime,starttime,workoutdate,duration,starttimeunix,timezone = utils.get_startdatetime_from_c2data(data) - splits = data['workout']['splits'] + try: + splits = data['workout']['splits'] + except KeyError: + splits = [0] time = starttimeunix elapsed_distance = 0 times = [0] distances = [0] - spms = [splits[0]['stroke_rate']] - hrs = [splits[0]['heart_rate']['average']] + try: + spms = [splits[0]['stroke_rate']] + except KeyError: + spms = [0] + try: + hrs = [splits[0]['heart_rate']['average']] + except KeyError: # pragma: no cover + hrs = [0] for split in splits: time += split['time']/10. @@ -3057,7 +3066,10 @@ def df_from_summary(data): times.append(time) distances.append(elapsed_distance) spms.append(split['stroke_rate']) - hrs.append(split['heart_rate']['average']) + try: + hrs.append(split['heart_rate']['average']) + except KeyError: # pragma: no cover + hrs.append(0) df = pd.DataFrame({ 'TimeStamp (sec)': times, @@ -3090,7 +3102,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone try: has_strokedata = data['stroke_data'] - except KeyError: + except KeyError: # pragma: no cover has_strokedata = True @@ -3146,7 +3158,7 @@ def handle_c2_async_workout(alldata,userid,c2token,c2id,delaysec,defaulttimezone dologging('debuglog.log',s.text) has_strokedata = False - if not has_strokedata: + if not has_strokedata: # pragma: no cover df = df_from_summary(data) else: dologging('debuglog.log',json.dumps(s.json())) diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index fb021a59..ee84aaa6 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -41,24 +41,79 @@ title="Jump to following workout">Next {% endif %}

+

+ + + + + + + + + + +
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. +