diff --git a/rowers/dataprep.py b/rowers/dataprep.py index b7043a7f..e055f8ef 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -553,7 +553,10 @@ def setcp(workout, background=False, recurrance=True): # check dts tarr = datautils.getlogarr(4000) if df['delta'][0] in tarr: - return(df, df['delta'], df['cp']) + try: + return(df, df['delta'], df['cp'], df['cr']) + except KeyError: + return(df, df['delta'], df['cp'], 0*df['cp']) except Exception as e: try: os.remove(filename) @@ -565,7 +568,7 @@ def setcp(workout, background=False, recurrance=True): strokesdf = remove_nulls_pl(strokesdf) if strokesdf.is_empty(): - return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.DataFrame({'delta': [], 'cp': [], 'cr': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) totaltime = strokesdf['time'].max() maxt = totaltime/1000. @@ -580,7 +583,7 @@ def setcp(workout, background=False, recurrance=True): elif os.path.exists(csvfilename+'.gz'): # pragma: no cover csvfile = csvfilename+'.gz' else: # pragma: no cover - return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.DataFrame({'delta': [], 'cp': [], 'cr': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) csvfile = os.path.abspath(csvfile) with grpc.insecure_channel( @@ -593,7 +596,7 @@ def setcp(workout, background=False, recurrance=True): grpc.channel_ready_future(channel).result(timeout=10) except grpc.FutureTimeoutError: # pragma: no cover dologging('metrics.log','grpc channel time out in setcp') - return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.DataFrame({'delta': [], 'cp': [], 'cr':[]}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) stub = metrics_pb2_grpc.MetricsStub(channel) req = metrics_pb2.CPRequest(filename = csvfile, filetype = "CSV", tarr = logarr) @@ -602,16 +605,18 @@ def setcp(workout, background=False, recurrance=True): response = stub.GetCP(req, timeout=60) except Exception as e: dologging('metrics.log', traceback.format_exc()) - return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.DataFrame({'delta': [], 'cp': [], 'cr': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) delta = pl.Series(np.array(response.delta)) cpvalues = pl.Series(np.array(response.power)) powermean = response.avgpower + spmvalues = pl.Series(np.array(response.spm)) try: df = pl.DataFrame({ 'delta': delta, 'cp': cpvalues, + 'cr': spmvalues, 'id': workout.id, }) @@ -620,7 +625,7 @@ def setcp(workout, background=False, recurrance=True): except Exception as e: dologging("metrics.log", "setcp: "+ str(e)) - return pl.DataFrame({'delta': [], 'cp': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.DataFrame({'delta': [], 'cp': [], 'cr': []}), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) #df.to_parquet(filename, engine='fastparquet', compression='GZIP') @@ -631,7 +636,7 @@ def setcp(workout, background=False, recurrance=True): workout.goldmedalduration = goldmedalduration workout.save() - return df, delta, cpvalues + return df, delta, cpvalues, spmvalues # pragma: no cover @@ -772,7 +777,7 @@ def fetchcp_new(rower, workouts): data = [] for workout in workouts: - df, delta, cpvalues = setcp(workout) + df, delta, cpvalues, cpvalues_spm = setcp(workout) df = df.drop('id') df = df.with_columns((pl.lit(str(workout))).alias("workout")) df = df.with_columns((pl.lit(workout.url())).alias("url")) @@ -780,7 +785,7 @@ def fetchcp_new(rower, workouts): data.append(df) if len(data) == 0: - return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) if len(data) > 1: df = pl.concat(data) @@ -791,11 +796,16 @@ def fetchcp_new(rower, workouts): pl.all().sort_by('cp').last(), ]) except (KeyError, ColumnNotFoundError): # pragma: no cover - return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) + return pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64), 0, pl.Series(dtype=pl.Float64), pl.Series(dtype=pl.Float64) df = df.filter(pl.col("cp")>20) - return df['delta'], df['cp'], 0, df['workout'], df['url'] + try: + testje = df['cr'] + except KeyError: + return df['delta'], df['cp'], 0*df['cp'], 0, df['workout'], df['url'] + + return df['delta'], df['cp'], df['cr'], 0, df['workout'], df['url'] diff --git a/rowers/datautils.py b/rowers/datautils.py index 0b24d252..2eeac953 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -84,7 +84,10 @@ def cpfit(powerdf, fraclimit=0.0001, nmax=1000): p1 = p0 thesecs = powerdf['Delta'].to_numpy() - theavpower = powerdf['CP'].to_numpy() + try: + theavpower = powerdf['CP'].to_numpy() + except: # pragma: no cover + theavpower = powerdf['CR'].to_numpy() if len(thesecs) >= 4: diff --git a/rowers/forms.py b/rowers/forms.py index face5583..a6a1c9d7 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1299,6 +1299,7 @@ analysischoices = ( ('stats', 'Statistics'), ('compare', 'Compare'), ('cp', 'CP chart'), + ('cr', 'CR chart'), ) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 2f0d2a5d..ae3a25ae 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1113,6 +1113,75 @@ def interactive_otwcpchart(powerdf, promember=0, rowername="", r=None, return [script, div, p1, ratio, message] +def interactive_otwcrchart(powerdf, promember=0, rowername="", r=None, + cpfit='data', + title='', type='water'): + + powerdf2 = powerdf.filter((pl.col("Delta") > 0) & (pl.col("CR") > 0)) + + # plot tools + if (promember == 1): # pragma: no cover + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,hover,crosshair' + + x_axis_type = 'log' + + deltas = powerdf2['Delta'].apply(lambda x: timedeltaconv(x)) + powerdf2 = powerdf2.with_columns( + ftime = deltas.apply(lambda x: strfdelta(x)), + Deltaminutes = pl.col("Delta")/60. + ) + + # there is no Paul's law for OTW + + thesecs = powerdf2['Delta'] + theavpower = powerdf2['CR'] + + p1, fitt, fitpower, ratio = datautils.cpfit(powerdf2) + if cpfit == 'automatic' and r is not None: + if type == 'water': + p1 = [r.r0, r.r1, r.r2, r.r3] + ratio = r.cpratio + elif type == 'erg': # pragma: no cover + p1 = [r.er0, r.er1, r.er2, r.er3] + ratio = r.ecrratio + + def fitfunc(pars, x): + return abs(pars[0])/(1+(x/abs(pars[2]))) + abs(pars[1])/(1+(x/abs(pars[3]))) + + fitpower = fitfunc(p1, fitt) + + message = "" + # if len(fitpower[fitpower<0]) > 0: + # message = "CP model fit didn't give correct results" + + deltas = fitt.apply(lambda x: timedeltaconv(x)) + ftime = niceformat(deltas) + + + + fit_data = pl.DataFrame(dict( + CR=fitpower, + CRmax=ratio*fitpower, + duration=fitt/60., + ftime=ftime, + )) + + if not title: + title = "Critical StrokeRate for "+rowername + + chart_dict = { + 'data': powerdf2.to_dicts(), + 'fitdata': fit_data.to_dicts(), + 'title': title, + } + + script, div = get_chart("/cr", chart_dict) + + return [script, div, p1, ratio, message] + + def interactive_agegroup_plot(df, distance=2000, duration=None, sex='male', weightcategory='hwt'): diff --git a/rowers/models.py b/rowers/models.py index f1451085..ca4d864c 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -1104,6 +1104,18 @@ class Rower(models.Model): ep3 = models.FloatField(default=1.0, verbose_name="erg CP p4") ecpratio = models.FloatField(default=1.0, verbose_name="erg CP fit ratio") + r0 = models.FloatField(default=1.0, verbose_name="CR r1") + r1 = models.FloatField(default=1.0, verbose_name="CR r2") + r2 = models.FloatField(default=1.0, verbose_name="CR r3") + r3 = models.FloatField(default=1.0, verbose_name="CR r4") + crratio = models.FloatField(default=1.0, verbose_name="CR fit ratio") + + er0 = models.FloatField(default=1.0, verbose_name="erg CR r1") + er1 = models.FloatField(default=1.0, verbose_name="erg CR r2") + er2 = models.FloatField(default=1.0, verbose_name="erg CR r3") + er3 = models.FloatField(default=1.0, verbose_name="erg CR r4") + ecpratio = models.FloatField(default=1.0, verbose_name="erg CP fit ratio") + cprange = models.IntegerField(default=42, verbose_name="Range for calculation of breakthrough workouts and fitness (CP)", choices=cppresets) diff --git a/rowers/rowing_workout_metrics_pb2.py b/rowers/rowing_workout_metrics_pb2.py index 80b29670..132e63c3 100644 --- a/rowers/rowing_workout_metrics_pb2.py +++ b/rowers/rowing_workout_metrics_pb2.py @@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1crowing-workout-metrics.proto\x12\x16rowing_workout_metrics\"\x80\x01\n\x15WorkoutMetricsRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x0b\n\x03sex\x18\x02 \x01(\t\x12\x0b\n\x03\x66tp\x18\x03 \x01(\x01\x12\r\n\x05hrftp\x18\x04 \x01(\x01\x12\r\n\x05hrmax\x18\x05 \x01(\x01\x12\r\n\x05hrmin\x18\x06 \x01(\x01\x12\x0e\n\x06wpsavg\x18\x07 \x01(\x01\"\x80\x01\n\x16WorkoutMetricsResponse\x12\x0b\n\x03tss\x18\x01 \x01(\x01\x12\r\n\x05normp\x18\x02 \x01(\x01\x12\r\n\x05trimp\x18\x03 \x01(\x01\x12\r\n\x05hrtss\x18\x04 \x01(\x01\x12\r\n\x05normv\x18\x05 \x01(\x01\x12\r\n\x05normw\x18\x06 \x01(\x01\x12\x0e\n\x06spmtss\x18\x07 \x01(\x01\"=\n\tCPRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x10\n\x08\x66iletype\x18\x02 \x01(\t\x12\x0c\n\x04tarr\x18\x03 \x03(\x01\"<\n\nCPResponse\x12\r\n\x05\x64\x65lta\x18\x01 \x03(\x01\x12\r\n\x05power\x18\x02 \x03(\x01\x12\x10\n\x08\x61vgpower\x18\x03 \x01(\x01\x32\xc7\x01\n\x07Metrics\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest\x1a..rowing_workout_metrics.WorkoutMetricsResponse\x12N\n\x05GetCP\x12!.rowing_workout_metrics.CPRequest\x1a\".rowing_workout_metrics.CPResponseB\x1aZ\x18./rowing-workout-metricsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1crowing-workout-metrics.proto\x12\x16rowing_workout_metrics\"\x80\x01\n\x15WorkoutMetricsRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x0b\n\x03sex\x18\x02 \x01(\t\x12\x0b\n\x03\x66tp\x18\x03 \x01(\x01\x12\r\n\x05hrftp\x18\x04 \x01(\x01\x12\r\n\x05hrmax\x18\x05 \x01(\x01\x12\r\n\x05hrmin\x18\x06 \x01(\x01\x12\x0e\n\x06wpsavg\x18\x07 \x01(\x01\"\x80\x01\n\x16WorkoutMetricsResponse\x12\x0b\n\x03tss\x18\x01 \x01(\x01\x12\r\n\x05normp\x18\x02 \x01(\x01\x12\r\n\x05trimp\x18\x03 \x01(\x01\x12\r\n\x05hrtss\x18\x04 \x01(\x01\x12\r\n\x05normv\x18\x05 \x01(\x01\x12\r\n\x05normw\x18\x06 \x01(\x01\x12\x0e\n\x06spmtss\x18\x07 \x01(\x01\"=\n\tCPRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x10\n\x08\x66iletype\x18\x02 \x01(\t\x12\x0c\n\x04tarr\x18\x03 \x03(\x01\"I\n\nCPResponse\x12\r\n\x05\x64\x65lta\x18\x01 \x03(\x01\x12\r\n\x05power\x18\x02 \x03(\x01\x12\x10\n\x08\x61vgpower\x18\x03 \x01(\x01\x12\x0b\n\x03spm\x18\x04 \x03(\x01\x32\xc7\x01\n\x07Metrics\x12l\n\x0b\x43\x61lcMetrics\x12-.rowing_workout_metrics.WorkoutMetricsRequest\x1a..rowing_workout_metrics.WorkoutMetricsResponse\x12N\n\x05GetCP\x12!.rowing_workout_metrics.CPRequest\x1a\".rowing_workout_metrics.CPResponseB\x1aZ\x18./rowing-workout-metricsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -39,7 +39,7 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['_CPREQUEST']._serialized_start=318 _globals['_CPREQUEST']._serialized_end=379 _globals['_CPRESPONSE']._serialized_start=381 - _globals['_CPRESPONSE']._serialized_end=441 - _globals['_METRICS']._serialized_start=444 - _globals['_METRICS']._serialized_end=643 + _globals['_CPRESPONSE']._serialized_end=454 + _globals['_METRICS']._serialized_start=457 + _globals['_METRICS']._serialized_end=656 # @@protoc_insertion_point(module_scope) diff --git a/rowers/templates/otwcr.html b/rowers/templates/otwcr.html new file mode 100644 index 00000000..f4d7d849 --- /dev/null +++ b/rowers/templates/otwcr.html @@ -0,0 +1,20 @@ + + {{ the_div|safe }} +

+ + + + + + + + + + + + + + + +
Duration Stroke Estimate 1 Stroke Estimate 2
{{ duration }}{{ power }}{{ upper }}
+

diff --git a/rowers/templates/user_analysis_select.html b/rowers/templates/user_analysis_select.html index 853039d7..65722c5c 100644 --- a/rowers/templates/user_analysis_select.html +++ b/rowers/templates/user_analysis_select.html @@ -124,13 +124,13 @@