summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/postrunner/ActivitySummary.rb16
-rw-r--r--lib/postrunner/ChartView.rb31
-rw-r--r--lib/postrunner/HRV_Analyzer.rb147
-rw-r--r--lib/postrunner/LinearPredictor.rb10
-rw-r--r--postrunner.gemspec2
-rw-r--r--spec/HRV_Analyzer_spec.rb95
6 files changed, 158 insertions, 143 deletions
diff --git a/lib/postrunner/ActivitySummary.rb b/lib/postrunner/ActivitySummary.rb
index fb01623..95d776e 100644
--- a/lib/postrunner/ActivitySummary.rb
+++ b/lib/postrunner/ActivitySummary.rb
@@ -159,12 +159,16 @@ module PostRunner
t.row([ 'Suggested Recovery Time:',
rec_time ? secsToDHMS(rec_time * 60) : '-' ])
- rr_intervals = @activity.fit_activity.hrv.map do |hrv|
- hrv.time.compact
- end.flatten
- hrv = HRV_Analyzer.new(rr_intervals)
- if hrv.has_hrv_data?
- t.row([ 'HRV Score:', "%.1f" % hrv.one_sigma(:hrv_score) ])
+ hrv = HRV_Analyzer.new(@activity)
+ # If we have HRV data for more than 120s we compute the PostRunner HRV
+ # Score for the 2nd and 3rd minute. The first minute is ignored as it
+ # often contains erratic data due to body movements and HRM adjustments.
+ # Clinical tests usually recommend a 5 minute measure time, but that's
+ # probably too long for daily tests.
+ if hrv.has_hrv_data? && hrv.duration > 180
+ if (hrv_score = hrv.hrv_score(60, 120)) > 0.0 && hrv_score < 100.0
+ t.row([ 'PostRunner HRV Score:', "%.1f" % hrv_score ])
+ end
end
t
diff --git a/lib/postrunner/ChartView.rb b/lib/postrunner/ChartView.rb
index d5f47d7..94bd407 100644
--- a/lib/postrunner/ChartView.rb
+++ b/lib/postrunner/ChartView.rb
@@ -21,10 +21,7 @@ module PostRunner
@sport = activity.sport
@unit_system = unit_system
@empty_charts = {}
- rr_intervals = @activity.fit_activity.hrv.map do |hrv|
- hrv.time.compact
- end.flatten
- @hrv_analyzer = HRV_Analyzer.new(rr_intervals)
+ @hrv_analyzer = HRV_Analyzer.new(activity)
@charts = [
{
@@ -66,14 +63,12 @@ module PostRunner
:unit => 'ms',
:graph => :line_graph,
:colors => '#900000',
- :show => @hrv_analyzer.has_hrv_data?,
- :min_y => -30,
- :max_y => 30
+ :show => @hrv_analyzer.has_hrv_data?
},
{
:id => 'hrv_score',
- :label => 'HRV Score (30s Window)',
- :short_label => 'HRV Score',
+ :label => 'rMSSD (30s Window)',
+ :short_label => 'rMSSD',
:graph => :line_graph,
:colors => '#900000',
:show => false
@@ -273,22 +268,18 @@ EOT
start_time = @activity.fit_activity.sessions[0].start_time.to_i
min_value = nil
if chart[:id] == 'hrv_score'
- 0.upto(@hrv_analyzer.total_duration.to_i - 30) do |t|
- next unless (hrv_score = @hrv_analyzer.lnrmssdx20(t, 30)) > 0.0
+ window_time = 120
+ 0.upto(@hrv_analyzer.total_duration.to_i - window_time) do |t|
+ next unless (hrv_score = @hrv_analyzer.rmssd(t, window_time)) >= 0.0
min_value = hrv_score if min_value.nil? || min_value > hrv_score
data_set << [ t * 1000, hrv_score ]
end
elsif chart[:id] == 'hrv'
- 1.upto(@hrv_analyzer.rr_intervals.length - 1) do |idx|
- curr_intvl = @hrv_analyzer.rr_intervals[idx]
- prev_intvl = @hrv_analyzer.rr_intervals[idx - 1]
- next unless curr_intvl && prev_intvl
-
- # Convert the R-R interval duration to ms.
- dt = (curr_intvl - prev_intvl) * 1000.0
- min_value = dt if min_value.nil? || min_value > dt
- data_set << [ @hrv_analyzer.timestamps[idx] * 1000, dt ]
+ @hrv_analyzer.hrv.each_with_index do |dt, i|
+ next unless dt
+ data_set << [ @hrv_analyzer.timestamps[i] * 1000, dt * 1000 ]
end
+ min_value = 0
else
@activity.fit_activity.records.each do |r|
value = r.get_as(chart[:id], chart[:unit] || '')
diff --git a/lib/postrunner/HRV_Analyzer.rb b/lib/postrunner/HRV_Analyzer.rb
index 77162bd..ff3eb74 100644
--- a/lib/postrunner/HRV_Analyzer.rb
+++ b/lib/postrunner/HRV_Analyzer.rb
@@ -3,14 +3,14 @@
#
# = HRV_Analyzer.rb -- PostRunner - Manage the data from your Garmin sport devices.
#
-# Copyright (c) 2015 by Chris Schlaeger <cs@taskjuggler.org>
+# Copyright (c) 2015, 2016, 2017 by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
-require 'postrunner/LinearPredictor'
+require 'postrunner/FFS_Activity'
module PostRunner
@@ -19,7 +19,7 @@ module PostRunner
# quality is good enough.
class HRV_Analyzer
- attr_reader :rr_intervals, :timestamps, :errors
+ attr_reader :hrv, :timestamps, :duration, :errors
# According to Nunan et. al. 2010
# (http://www.qeeg.co.uk/HRV/NUNAN-2010-A%20Quantitative%20Systematic%20Review%20of%20Normal%20Values%20for.pdf)
@@ -31,9 +31,24 @@ module PostRunner
LN_RMSSD_MAX = 4.4
# Create a new HRV_Analyzer object.
- # @param rr_intervals [Array of Float] R-R (or NN) time delta in seconds.
- def initialize(rr_intervals)
- @errors = 0
+ # @param arg [Activity, Array<Float>] R-R (or NN) time delta in seconds.
+ def initialize(arg)
+ if arg.is_a?(Array)
+ rr_intervals = arg
+ else
+ activity = arg
+ # Gather the RR interval list from the activity. Note that HRV data
+ # still gets recorded after the activity has been stoped until the
+ # activity gets saved.
+ # Each Fit4Ruby::HRV object has an Array called 'time' that contains up
+ # to 5 R-R interval durations. If less than 5 values are present the
+ # remaining are filled with nil entries.
+ rr_intervals = activity.fit_activity.hrv.map do |hrv|
+ hrv.time.compact
+ end.flatten
+ end
+ #$stderr.puts rr_intervals.inspect
+
cleanup_rr_intervals(rr_intervals)
end
@@ -41,7 +56,11 @@ module PostRunner
# must have HRV data and the measurement duration must be at least 30
# seconds.
def has_hrv_data?
- !@rr_intervals.empty? && total_duration > 30.0
+ @hrv && !@hrv.empty? && total_duration > 30.0
+ end
+
+ def data_quality
+ (@hrv.size - @errors).to_f / @hrv.size * 100.0
end
# Return the total duration of all measured intervals in seconds.
@@ -74,17 +93,15 @@ module PostRunner
end_idx = -1
end
- last_i = nil
sum = 0.0
cnt = 0
- @rr_intervals[start_idx..end_idx].each do |i|
- if i && last_i
+ @hrv[start_idx..end_idx].each do |i|
+ if i
# Input values are in seconds, but rmssd is usually computed from
# milisecond values.
- sum += ((last_i - i) * 1000) ** 2.0
+ sum += (i * 1000) ** 2.0
cnt += 1
end
- last_i = i
end
Math.sqrt(sum / cnt)
@@ -113,84 +130,66 @@ module PostRunner
(ssd - LN_RMSSD_MIN) * (100.0 / (LN_RMSSD_MAX - LN_RMSSD_MIN))
end
- # This method tries to find a window of values that all lie within the
- # TP84 range and then calls the given block for that range.
- def one_sigma(calc_method)
- # Create a new Array that consists of rr_intervals and timestamps
- # tuples.
- set = []
- 0.upto(@rr_intervals.length - 1) do |i|
- set << [ @rr_intervals[i] || 0.0, @timestamps[i] ]
- end
-
- percentiles = Percentiles.new(set)
- # Compile a list of all tuples with rr_intervals that are outside of the
- # PT84 (aka +1sigma range. Sort the list by time.
- not_1sigma = percentiles.not_tp_x(84.13).sort { |e1, e2| e1[1] <=> e2[1] }
-
- # Then find the largest window RR interval list so that all the values
- # in that window are within TP84.
- window_start = window_end = 0
- last = nil
- not_1sigma.each do |e|
- if last
- if (e[1] - last) > (window_end - window_start)
- window_start = last + 1
- window_end = e[1] - 1
- end
- end
- last = e[1]
- end
-
- # That window should be at least 30 seconds long. Otherwise we'll just use
- # all the values.
- if window_end - window_start < 30 || window_end < window_start
- return send(calc_method, 0.0, nil)
- end
-
- send(calc_method, window_start, window_end - window_start)
- end
-
private
def cleanup_rr_intervals(rr_intervals)
- # The rr_intervals Array stores the beat-to-beat time intervals (R-R).
- # If one or move beats have been skipped during measurement, a nil value
- # is inserted.
- @rr_intervals = []
# The timestamps Array stores the relative (to start of sequence) time
# for each interval in the rr_intervals Array.
@timestamps = []
- # Each Fit4Ruby::HRV object has an Array called 'time' that contains up
- # to 5 R-R interval durations. If less than 5 are present, they are
- # filled with nil.
return if rr_intervals.empty?
- window = [ rr_intervals.length / 4, 20 ].min
- intro_mean = rr_intervals[0..4 * window].reduce(:+) / (4 * window)
- predictor = LinearPredictor.new(window, intro_mean)
-
- # The timer accumulates the interval durations.
+ # The timer accumulates the interval durations and keeps track of the
+ # timestamp of the current value with respect to the beging of the
+ # series.
timer = 0.0
- rr_intervals.each do |dt|
- timer += dt
+ clean_rr_intervals = []
+ @errors = 0
+ rr_intervals.each_with_index do |rr, i|
@timestamps << timer
- # Sometimes the hrv data is missing one or more beats. The next
- # detected beat is than listed with the time interval since the last
- # detected beat. We try to detect these skipped beats by looking for
- # time intervals that are 1.5 or more times larger than the predicted
- # value for this interval.
- if (next_dt = predictor.predict) && dt > 1.5 * next_dt
- @rr_intervals << nil
+ # The biggest source of errors are missed beats resulting in intervals
+ # that are twice or more as large as the regular intervals. We look at
+ # a window of values surrounding the current interval to determine
+ # what's normal. We assume that at least half the values are normal.
+ # When we sort the values by size, the middle value must be a good
+ # proxy for a normal value.
+ # Any values that are 1.8 times larger than the normal proxy value
+ # will be discarded and replaced by nil.
+ if rr > 1.8 * median_value(rr_intervals, i, 21)
+ clean_rr_intervals << nil
@errors += 1
else
- @rr_intervals << dt
- # Feed the value into the predictor.
- predictor.insert(dt)
+ clean_rr_intervals << rr
end
+
+ timer += rr
end
+
+ # This array holds the cleanedup heart rate variability values.
+ @hrv = []
+ 0.upto(clean_rr_intervals.length - 2) do |i|
+ rr1 = clean_rr_intervals[i]
+ rr2 = clean_rr_intervals[i + 1]
+ if rr1.nil? || rr2.nil?
+ @hrv << nil
+ else
+ @hrv << (rr1 - rr2).abs
+ end
+ end
+
+ # Save the overall duration of the HRV samples.
+ @duration = timer
+ end
+
+ def median_value(ary, index, half_window_size)
+ low_i = index - half_window_size
+ low_i = 0 if low_i < 0
+ high_i = index + half_window_size
+ high_i = ary.length - 1 if high_i > ary.length - 1
+ values = ary[low_i..high_i].delete_if{ |v| v.nil? }.sort
+
+ median = values[values.length / 2]
end
end
diff --git a/lib/postrunner/LinearPredictor.rb b/lib/postrunner/LinearPredictor.rb
index 6a15407..4ff6da5 100644
--- a/lib/postrunner/LinearPredictor.rb
+++ b/lib/postrunner/LinearPredictor.rb
@@ -18,8 +18,8 @@ module PostRunner
# Create a new LinearPredictor object.
# @param n [Fixnum] The number of coefficients the predictor should use.
- def initialize(n, default = nil)
- @values = Array.new(n, default)
+ def initialize(n)
+ @values = []
@size = n
@next = nil
end
@@ -29,10 +29,12 @@ module PostRunner
def insert(value)
@values << value
- if @values.length > @size
+ if @values.length >= @size
@values.shift
- @next = @values.reduce(:+) / @size
end
+
+ @next = @values.reduce(:+) / @values.size
+ $stderr.puts "insert(#{value}) next: #{@next}"
end
# @return [Float] The predicted value of the next sample.
diff --git a/postrunner.gemspec b/postrunner.gemspec
index c54966e..45f570f 100644
--- a/postrunner.gemspec
+++ b/postrunner.gemspec
@@ -29,7 +29,7 @@ well.}
spec.required_ruby_version = '>=2.0'
spec.add_dependency 'fit4ruby', '~> 1.6.1'
- spec.add_dependency 'perobs', '~> 3.0.1'
+ spec.add_dependency 'perobs', '~> 4.0.0'
spec.add_dependency 'nokogiri', '~> 1.6'
spec.add_development_dependency 'bundler', '~> 1.6'
diff --git a/spec/HRV_Analyzer_spec.rb b/spec/HRV_Analyzer_spec.rb
index 920298e..9ac3200 100644
--- a/spec/HRV_Analyzer_spec.rb
+++ b/spec/HRV_Analyzer_spec.rb
@@ -15,60 +15,79 @@ require 'spec_helper'
describe PostRunner::HRV_Analyzer do
it 'should cleanup the input data' do
- rri = [ 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.5, 0.3,
- 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3,
- 0.3, 0.3, 0.1, 0.3, 0.3, 0.3, 0.3, 0.4,
- 0.5, 0.3, 0.3, 0.2, 0.3, 0.3, 0.3, 0.3 ]
+ rri = [ 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 1.1, 0.6,
+ 0.6, 0.8, 0.6, 0.61, 0.59, 0.6, 0.6, 0.6,
+ 0.6, 0.6, 0.2, 0.6, 0.6, 0.6, 0.6, 0.5,
+ 0.6, 0.6, 0.6, 1.3, 0.6, 0.6, 0.6, 0.6 ]
hrv = PostRunner::HRV_Analyzer.new(rri)
- rro = [ 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, nil, 0.3,
- 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3,
- 0.3, 0.3, 0.1, 0.3, 0.3, 0.3, 0.3, 0.4,
- nil, 0.3, 0.3, 0.2, 0.3, 0.3, 0.3, 0.3 ]
- expect(hrv.rr_intervals).to eql(rro)
expect(hrv.errors).to eql(2)
- ts = [ 0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.3, 2.6,
- 2.9, 3.2, 3.5, 3.8, 4.1, 4.4, 4.7, 5.0,
- 5.3, 5.6, 5.7, 6.0, 6.3, 6.6, 6.9, 7.3,
- 7.8, 8.1, 8.4, 8.6, 8.9, 9.2, 9.5, 9.8 ]
+ ts = [ 0.0, 0.6, 1.2, 1.8, 2.4, 3.0, 3.6, 4.7,
+ 5.3, 5.9, 6.7, 7.3, 7.9, 8.5, 9.1, 9.7,
+ 10.3, 10.9, 11.5, 11.7, 12.3, 12.9, 13.5, 14.1,
+ 14.6, 15.2, 15.8, 16.4, 17.7, 18.3, 18.9, 19.5 ]
hrv.timestamps.each_with_index do |v, i|
expect(v).to be_within(0.01).of(ts[i])
end
expect(hrv.has_hrv_data?).to be false
- expect(hrv.rmssd).to be_within(0.01).of(63.828)
+ expect(hrv.rmssd).to be_within(0.00001).of(124.81096817899)
end
it 'should compute an HRV Score' do
- rri =[
- 0.834, 0.794, 0.789, 0.792, 0.8, 0.795, 0.789, 0.785, 0.783,
- 0.778, 0.737, 0.711, 0.705, 0.717, 0.755, 0.827, 0.885, 0.888, 0.86,
- 0.832, 0.808, 0.755, 0.722, 0.708, 0.693, 0.728, 0.767, 0.838, 0.875,
- 0.888, 0.865, 0.797, 0.75, 0.729, 0.708, 0.733, 0.754, 0.791, 0.803,
- 0.788, 0.76, 0.732, 0.748, 0.754, 0.781, 0.794, 0.787, 0.779, 0.744,
- 0.716, 0.703, 0.7, 0.731, 0.808, 0.793, 0.787, 0.74, 0.716, 0.720,
- 0.724, 0.76, 0.785, 0.817, 0.793, 0.76, 0.741, 0.733, 0.754, 0.785,
- 0.813, 0.833, 0.814, 0.794, 0.78, 0.775
+ rri = [
+ 0.837, 0.831, 0.843, 0.867, 0.788, 0.984, 0.872, 0.891, 0.878, 0.864,
+ 0.844, 0.818, 0.798, 0.791, 0.808, 0.866, 0.927, 0.951, 0.958, 0.943,
+ 0.613, 1.2, 0.884, 0.884, 0.878, 0.873, 0.867, 0.875, 0.872, 0.871,
+ 0.892, 0.943, 1.185, 0.788, 1.255, 0.636, 0.901, 0.896, 0.9, 0.915,
+ 0.698, 1.148, 0.894, 0.872, 0.85, 0.86, 0.893, 0.941, 0.692, 1.233,
+ 0.981, 0.926, 0.93, 0.928, 0.928, 0.93, 1.158, 0.68, 0.877, 0.915,
+ 0.926, 0.933, 0.933, 0.924, 0.681, 1.133, 0.901, 0.892, 0.887, 0.877,
+ 0.732, 0.968, 0.826, 0.824, 0.865, 0.905, 0.915, 0.935, 0.932, 0.924,
+ 0.915, 0.945, 0.96, 0.963, 0.939, 0.92, 0.892, 0.669, 1.037, 0.806,
+ 0.818, 0.847, 0.879, 0.922, 0.938, 0.952, 0.969, 1.018, 1.03, 1.004,
+ 0.98, 0.948, 0.919, 0.894, 0.896, 0.905, 0.913, 0.925, 0.905, 0.879,
+ 0.855, 0.857, 0.866, 0.878, 0.881, 0.884, 0.873, 0.857, 0.851, 0.864,
+ 0.883, 0.895, 0.898, 0.898, 0.876, 0.853, 0.841, 0.85, 0.857, 0.852,
+ 0.861, 0.867, 0.869, 0.858, 0.844, 0.856, 0.869, 0.879, 0.886, 0.89,
+ 0.876, 0.857, 0.843, 0.839, 0.838, 0.843, 0.845, 0.856, 0.856, 0.85,
+ 0.838, 0.842, 0.844, 0.842, 0.834, 0.832, 0.818, 0.81, 0.801, 0.78,
+ 0.797, 0.816, 0.838, 0.85, 0.845, 0.841, 0.84, 0.837, 0.859, 0.874,
+ 0.89, 0.896, 0.893, 0.879, 0.863, 0.855, 0.87, 0.875, 0.861, 0.854,
+ 0.843, 0.836, 0.822, 0.813, 0.806, 0.81, 0.824, 0.834, 0.847, 0.867,
+ 0.877, 0.883, 0.877, 0.856, 0.872, 0.88, 0.87, 0.861, 0.855, 0.852,
+ 0.84, 0.832, 0.82, 0.827, 0.838, 0.854, 0.881, 0.893, 0.857
]
+
hrv = PostRunner::HRV_Analyzer.new(rri)
- expect(hrv.rmssd).to be_within(0.00001).of(29.59341)
- expect(hrv.ln_rmssd).to be_within(0.00001).of(3.38755)
- expect(hrv.hrv_score).to be_within(0.00001).of(32.50346)
+ expect(hrv.data_quality).to be_within(0.00001).of(100)
+ expect(hrv.ln_rmssd(0.0, 90)).to be_within(0.00001).of(5.188390931)
+ expect(hrv.ln_rmssd(90, 90)).to be_within(0.00001).of(2.549616730)
+ expect(hrv.rmssd(0.0, 90)).to be_within(0.00001).of(179.1800079)
+ expect(hrv.hrv_score(0.0, 60)).to be_within(0.00001).of(100.0)
end
it 'should find the right interval for a HRV score computation' do
- rri =[
- 0.999, 0.989, 0.998, 0.989, 0.997, 0.989, 0.999, 0.997, 0.999,
- 0.834, 0.794, 0.789, 0.792, 0.8, 0.795, 0.789, 0.785, 0.783,
- 0.778, 0.737, 0.711, 0.705, 0.717, 0.755, 0.827, 0.885, 0.888, 0.86,
- 0.832, 0.808, 0.755, 0.722, 0.708, 0.693, 0.728, 0.767, 0.838, 0.875,
- 0.888, 0.865, 0.797, 0.75, 0.729, 0.708, 0.733, 0.754, 0.791, 0.803,
- 0.788, 0.76, 0.732, 0.748, 0.754, 0.781, 0.794, 0.787, 0.779, 0.744,
- 0.716, 0.703, 0.7, 0.731, 0.808, 0.793, 0.787, 0.74, 0.716, 0.720,
- 0.724, 0.76, 0.785, 0.817, 0.793, 0.76, 0.741, 0.733, 0.754, 0.785,
- 0.813, 0.833, 0.814, 0.794, 0.78, 0.775,
- 0.997, 0.989, 0.999, 0.998, 0.999, 0.997
+ rri = [
+ 0.752, 0.759, 0.755, 0.741, 0.733, 0.738, 0.751, 0.767, 0.774, 0.777,
+ 0.771, 0.787, 0.795, 0.805, 0.797, 0.78, 0.77, 0.764, 0.766, 0.771,
+ 0.764, 0.763, 0.776, 0.777, 0.785, 0.78, 0.768, 0.757, 0.745, 0.737,
+ 0.724, 0.709, 0.695, 0.699, 0.703, 0.719, 0.726, 0.73, 0.733, 0.739,
+ 0.744, 0.744, 0.738, 0.725, 0.713, 0.706, 0.705, 0.7, 0.694, 0.697,
+ 0.706, 0.716, 0.728, 0.73, 0.731, 0.742, 0.748, 0.742, 0.733, 0.731,
+ 0.729, 0.727, 0.712, 0.712, 0.715, 0.712, 0.706, 0.707, 0.729, 0.762,
+ 0.773, 0.768, 0.78, 0.78, 0.771, 0.75, 0.736, 0.719, 0.704, 0.69, 0.683,
+ 0.688, 0.703, 0.732, 0.742, 0.751, 0.758, 0.783, 0.786, 0.764, 0.752,
+ 0.733, 0.722, 0.711, 0.694, 0.687, 0.69, 0.707, 0.722, 0.732, 0.761,
+ 0.783, 0.805, 0.795, 0.779, 0.76, 0.744, 0.726, 0.707, 0.692, 0.688,
+ 0.694, 0.695, 0.708, 0.729, 0.761, 0.776, 0.787, 0.799, 0.795, 0.773,
+ 0.755, 0.738, 0.721, 0.71, 0.701, 0.692, 0.698, 0.712, 0.73, 0.736,
+ 0.732, 0.722, 0.72, 0.712, 0.709, 0.695, 0.687, 0.688, 0.684, 0.687,
+ 0.685, 0.685, 0.684, 0.689, 0.705, 0.716, 0.712, 0.71, 0.732, 0.75,
+ 0.755, 0.757, 0.758, 0.759, 0.753, 0.748, 0.748, 0.724, 0.715, 0.721,
+ 0.727, 0.743, 0.741, 0.743, 0.757, 0.765, 0.774, 0.781, 0.77, 0.745,
+ 0.729, 0.707
]
hrv = PostRunner::HRV_Analyzer.new(rri)
- expect(hrv.one_sigma(:hrv_score)).to be_within(0.00001).of(32.12369)
+ expect(hrv.hrv_score).to be_within(0.00001).of(0.0)
end
end