postrunner 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -6
- data/lib/postrunner/DailyMonitoringAnalyzer.rb +239 -0
- data/lib/postrunner/DailySleepAnalyzer.rb +418 -125
- data/lib/postrunner/FFS_Device.rb +3 -7
- data/lib/postrunner/FFS_Monitoring.rb +0 -2
- data/lib/postrunner/FitFileStore.rb +10 -9
- data/lib/postrunner/FlexiTable.rb +6 -1
- data/lib/postrunner/Main.rb +12 -12
- data/lib/postrunner/MonitoringStatistics.rb +372 -0
- data/lib/postrunner/SleepCycle.rb +198 -0
- data/lib/postrunner/version.rb +1 -1
- data/postrunner.gemspec +10 -2
- data/spec/PostRunner_spec.rb +8 -0
- metadata +17 -10
- data/lib/postrunner/SleepStatistics.rb +0 -117
@@ -129,14 +129,10 @@ module PostRunner
|
|
129
129
|
# @param to_time [Time] end time of the interval (not included)
|
130
130
|
# @return [Array] list of overlapping FFS_Monitoring objects.
|
131
131
|
def monitorings(from_time, to_time)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
(from_time <= m.period_end && m.period_end < to_time)
|
136
|
-
list << m
|
137
|
-
end
|
132
|
+
@monitorings.select do |m|
|
133
|
+
(from_time <= m.period_start && m.period_start < to_time) ||
|
134
|
+
(from_time <= m.period_end && m.period_end < to_time)
|
138
135
|
end
|
139
|
-
list
|
140
136
|
end
|
141
137
|
|
142
138
|
end
|
@@ -18,7 +18,7 @@ require 'postrunner/DirUtils'
|
|
18
18
|
require 'postrunner/FFS_Device'
|
19
19
|
require 'postrunner/ActivityListView'
|
20
20
|
require 'postrunner/ViewButtons'
|
21
|
-
require 'postrunner/
|
21
|
+
require 'postrunner/MonitoringStatistics'
|
22
22
|
|
23
23
|
module PostRunner
|
24
24
|
|
@@ -329,16 +329,17 @@ module PostRunner
|
|
329
329
|
# there are usually multiple files per GMT day.
|
330
330
|
day_as_time = Time.parse(day).gmtime
|
331
331
|
@store['devices'].each do |id, device|
|
332
|
-
#
|
333
|
-
#
|
334
|
-
|
332
|
+
# To get weekly intensity minutes we need 7 days of data prior to the
|
333
|
+
# current date and 1 day after to include the following night. We add
|
334
|
+
# at least 12 extra hours to accomodate time zone changes.
|
335
|
+
monitorings += device.monitorings(day_as_time - 8 * 24 * 60 * 60,
|
335
336
|
day_as_time + 36 * 60 * 60)
|
336
337
|
end
|
337
|
-
monitoring_files = monitorings.map do |m|
|
338
|
+
monitoring_files = monitorings.reverse.map do |m|
|
338
339
|
read_fit_file(File.join(fit_file_dir(m.fit_file_name, m.device.long_uid,
|
339
340
|
'monitor'), m.fit_file_name))
|
340
341
|
end
|
341
|
-
puts
|
342
|
+
puts MonitoringStatistics.new(monitoring_files).daily(day)
|
342
343
|
end
|
343
344
|
|
344
345
|
def monthly_report(day)
|
@@ -354,14 +355,14 @@ module PostRunner
|
|
354
355
|
@store['devices'].each do |id, device|
|
355
356
|
# We are looking for all files that potentially overlap with our
|
356
357
|
# localtime day.
|
357
|
-
monitorings += device.monitorings(day_as_time -
|
358
|
+
monitorings += device.monitorings(day_as_time - 8 * 24 * 60 * 60,
|
358
359
|
day_as_time + 33 * 24 * 60 * 60)
|
359
360
|
end
|
360
|
-
monitoring_files = monitorings.map do |m|
|
361
|
+
monitoring_files = monitorings.sort.map do |m|
|
361
362
|
read_fit_file(File.join(fit_file_dir(m.fit_file_name, m.device.long_uid,
|
362
363
|
'monitor'), m.fit_file_name))
|
363
364
|
end
|
364
|
-
puts
|
365
|
+
puts MonitoringStatistics.new(monitoring_files).monthly(day)
|
365
366
|
end
|
366
367
|
|
367
368
|
private
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# = FlexiTable.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
5
|
#
|
6
|
-
# Copyright (c) 2014, 2015 by Chris Schlaeger <cs@taskjuggler.org>
|
6
|
+
# Copyright (c) 2014, 2015, 2016 by Chris Schlaeger <cs@taskjuggler.org>
|
7
7
|
#
|
8
8
|
# This program is free software; you can redistribute it and/or modify
|
9
9
|
# it under the terms of version 2 of the GNU General Public License as
|
@@ -191,6 +191,11 @@ module PostRunner
|
|
191
191
|
end
|
192
192
|
|
193
193
|
def new_row
|
194
|
+
if @current_row && @head_rows[0] &&
|
195
|
+
@current_row.length != @head_rows[0].length
|
196
|
+
Log.fatal "Row has #{@current_row.length} cells instead of " +
|
197
|
+
"#{@head_rows[0].length} cells in head row."
|
198
|
+
end
|
194
199
|
@current_row = nil
|
195
200
|
end
|
196
201
|
|
data/lib/postrunner/Main.rb
CHANGED
@@ -69,9 +69,6 @@ module PostRunner
|
|
69
69
|
cfg['html_dir'] = File.join(@db_dir, 'html')
|
70
70
|
|
71
71
|
setup_directories
|
72
|
-
if $DEBUG && (errors = @db.check) != 0
|
73
|
-
Log.abort "Postrunner database is corrupted: #{errors} errors found"
|
74
|
-
end
|
75
72
|
return execute_command(args)
|
76
73
|
|
77
74
|
rescue Exception => e
|
@@ -173,6 +170,13 @@ check [ <fit file> | <ref> ... ]
|
|
173
170
|
Check the provided FIT file(s) for structural errors. If no file or
|
174
171
|
reference is provided, the complete archive is checked.
|
175
172
|
|
173
|
+
daily [ <YYYY-MM-DD> ]
|
174
|
+
Print the monitoring statistics for the requested day and the
|
175
|
+
following night. If no date is given, yesterday's date will be used.
|
176
|
+
|
177
|
+
delete <ref>
|
178
|
+
Delete the activity from the archive.
|
179
|
+
|
176
180
|
dump <fit file> | <ref>
|
177
181
|
Dump the content of the FIT file.
|
178
182
|
|
@@ -184,18 +188,13 @@ import [ <fit file> | <directory> ]
|
|
184
188
|
file or directory is provided, the directory that was used for the
|
185
189
|
previous import is being used.
|
186
190
|
|
187
|
-
daily [ <date> ]
|
188
|
-
Print a report summarizing the current day or the specified day.
|
189
|
-
|
190
|
-
delete <ref>
|
191
|
-
Delete the activity from the archive.
|
192
|
-
|
193
191
|
list
|
194
192
|
List all FIT files stored in the data base.
|
195
193
|
|
196
|
-
monthly [ <
|
194
|
+
monthly [ <YYYY-MM-DD> ]
|
195
|
+
|
197
196
|
Print a table with various statistics for each day of the specified
|
198
|
-
month.
|
197
|
+
month. If no date is given, yesterday's month will be used.
|
199
198
|
|
200
199
|
records
|
201
200
|
List all personal records.
|
@@ -296,6 +295,7 @@ EOT
|
|
296
295
|
case (cmd = args.shift)
|
297
296
|
when 'check'
|
298
297
|
if args.empty?
|
298
|
+
@db.check(true)
|
299
299
|
@ffs.check
|
300
300
|
Log.info "Datebase cleanup started. Please wait ..."
|
301
301
|
@db.gc
|
@@ -556,7 +556,7 @@ EOT
|
|
556
556
|
|
557
557
|
def day_in_localtime(args, format)
|
558
558
|
begin
|
559
|
-
(args.empty? ? Time.now : Time.parse(args[0])).
|
559
|
+
(args.empty? ? Time.now - 24 * 60 * 60 : Time.parse(args[0])).
|
560
560
|
localtime.strftime(format)
|
561
561
|
rescue ArgumentError
|
562
562
|
Log.abort("#{args[0]} is not a valid date. Use YYYY-MM-DD format.")
|
@@ -0,0 +1,372 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = MonitoringStatistics.rb -- PostRunner - Manage the data from your Garmin sport devices.
|
5
|
+
#
|
6
|
+
# Copyright (c) 2016 by Chris Schlaeger <cs@taskjuggler.org>
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of version 2 of the GNU General Public License as
|
10
|
+
# published by the Free Software Foundation.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'fit4ruby'
|
14
|
+
|
15
|
+
require 'postrunner/DailySleepAnalyzer'
|
16
|
+
require 'postrunner/DailyMonitoringAnalyzer'
|
17
|
+
require 'postrunner/FlexiTable'
|
18
|
+
|
19
|
+
module PostRunner
|
20
|
+
|
21
|
+
# This class can be used to generate reports for sleep data. It uses the
|
22
|
+
# DailySleepAnalyzer class to compute the data and generates the report for
|
23
|
+
# a certain time period.
|
24
|
+
class MonitoringStatistics
|
25
|
+
|
26
|
+
include Fit4Ruby::Converters
|
27
|
+
|
28
|
+
# Create a new MonitoringStatistics object.
|
29
|
+
# @param monitoring_files [Array of Fit4Ruby::Monitoring_B] FIT files
|
30
|
+
def initialize(monitoring_files)
|
31
|
+
@monitoring_files = monitoring_files
|
32
|
+
# Week starts on Monday
|
33
|
+
@first_day_of_week = 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate a report for a certain day.
|
37
|
+
# @param day [String] Date of the day as YYYY-MM-DD string.
|
38
|
+
def daily(day)
|
39
|
+
sleep_analyzer = DailySleepAnalyzer.new(@monitoring_files, day,
|
40
|
+
+12 * 60 * 60)
|
41
|
+
monitoring_analyzer = DailyMonitoringAnalyzer.new(@monitoring_files, day)
|
42
|
+
|
43
|
+
str = "Daily Monitoring Report for #{day}\n\n" +
|
44
|
+
"#{daily_goals_table(monitoring_analyzer)}\n" +
|
45
|
+
"#{daily_stats_table(monitoring_analyzer, sleep_analyzer)}\n"
|
46
|
+
if sleep_analyzer.sleep_cycles.empty?
|
47
|
+
str += 'No sleep data available for this day'
|
48
|
+
else
|
49
|
+
str += "Sleep Statistics for " +
|
50
|
+
"#{sleep_analyzer.window_start_time.strftime('%Y-%m-%d')} - " +
|
51
|
+
"#{sleep_analyzer.window_end_time.strftime('%Y-%m-%d')}\n\n" +
|
52
|
+
daily_sleep_cycle_table(sleep_analyzer).to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
str
|
56
|
+
end
|
57
|
+
|
58
|
+
# Generate a report for a certain month.
|
59
|
+
# @param day [String] Date of a day in that months as YYYY-MM-DD string.
|
60
|
+
def monthly(day)
|
61
|
+
day_as_time = Time.parse(day)
|
62
|
+
year = day_as_time.year
|
63
|
+
month = day_as_time.month
|
64
|
+
last_day_of_month = Date.new(year, month, -1).day
|
65
|
+
|
66
|
+
"Monitoring Statistics for #{day_as_time.strftime('%B %Y')}\n\n" +
|
67
|
+
monthly_goal_table(year, month, last_day_of_month).to_s + "\n" +
|
68
|
+
monthly_sleep_table(year, month, last_day_of_month).to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def percent(value, total)
|
74
|
+
"#{'%.0f' % ((value * 100.0) / total)}%"
|
75
|
+
end
|
76
|
+
|
77
|
+
def cell_right_aligned(table, text)
|
78
|
+
table.cell(text, { :halign => :right })
|
79
|
+
end
|
80
|
+
|
81
|
+
def time_as_hm(t, utc_offset)
|
82
|
+
t.localtime(utc_offset).strftime('%H:%M')
|
83
|
+
end
|
84
|
+
|
85
|
+
def daily_sleep_cycle_table(analyzer)
|
86
|
+
ti = FlexiTable.new
|
87
|
+
ti.head
|
88
|
+
ti.row([ 'Cycle', 'From', 'To', 'Duration', 'REM Sleep',
|
89
|
+
'Light Sleep', 'Deep Sleep'])
|
90
|
+
ti.body
|
91
|
+
utc_offset = analyzer.utc_offset
|
92
|
+
format = { :halign => :right }
|
93
|
+
totals = Hash.new(0)
|
94
|
+
last_to_time = nil
|
95
|
+
analyzer.sleep_cycles.each_with_index do |c, idx|
|
96
|
+
if last_to_time && c.from_time > last_to_time
|
97
|
+
# We have a gap in the sleep cycles.
|
98
|
+
ti.cell('Wake')
|
99
|
+
cell_right_aligned(ti, time_as_hm(last_to_time, utc_offset))
|
100
|
+
cell_right_aligned(ti, time_as_hm(c.from_time, utc_offset))
|
101
|
+
cell_right_aligned(ti, "(#{secsToHM(c.from_time - last_to_time)})")
|
102
|
+
ti.cell('')
|
103
|
+
ti.cell('')
|
104
|
+
ti.cell('')
|
105
|
+
ti.new_row
|
106
|
+
end
|
107
|
+
|
108
|
+
ti.cell((idx + 1).to_s, format)
|
109
|
+
ti.cell(c.from_time.localtime(utc_offset).strftime('%H:%M'), format)
|
110
|
+
ti.cell(c.to_time.localtime(utc_offset).strftime('%H:%M'), format)
|
111
|
+
|
112
|
+
duration = c.to_time - c.from_time
|
113
|
+
totals[:duration] += duration
|
114
|
+
ti.cell(secsToHM(duration), format)
|
115
|
+
|
116
|
+
totals[:rem] += c.total_seconds[:rem]
|
117
|
+
ti.cell(secsToHM(c.total_seconds[:rem]), format)
|
118
|
+
|
119
|
+
light_sleep = c.total_seconds[:nrem1] + c.total_seconds[:nrem2]
|
120
|
+
totals[:light_sleep] += light_sleep
|
121
|
+
ti.cell(secsToHM(light_sleep), format)
|
122
|
+
|
123
|
+
totals[:deep_sleep] += c.total_seconds[:nrem3]
|
124
|
+
ti.cell(secsToHM(c.total_seconds[:nrem3]), format)
|
125
|
+
|
126
|
+
ti.new_row
|
127
|
+
last_to_time = c.to_time
|
128
|
+
end
|
129
|
+
ti.foot
|
130
|
+
ti.cell('Totals')
|
131
|
+
ti.cell(analyzer.sleep_cycles[0].from_time.localtime(utc_offset).
|
132
|
+
strftime('%H:%M'), format)
|
133
|
+
ti.cell(analyzer.sleep_cycles[-1].to_time.localtime(utc_offset).
|
134
|
+
strftime('%H:%M'), format)
|
135
|
+
ti.cell(secsToHM(totals[:duration]), format)
|
136
|
+
ti.cell(secsToHM(totals[:rem]), format)
|
137
|
+
ti.cell(secsToHM(totals[:light_sleep]), format)
|
138
|
+
ti.cell(secsToHM(totals[:deep_sleep]), format)
|
139
|
+
ti.new_row
|
140
|
+
|
141
|
+
ti
|
142
|
+
end
|
143
|
+
|
144
|
+
def daily_goals_table(monitoring_analyzer)
|
145
|
+
t = FlexiTable.new
|
146
|
+
|
147
|
+
t.head
|
148
|
+
t.row([ 'Steps', 'Intensity Minutes', 'Floors Climbed' ])
|
149
|
+
|
150
|
+
t.body
|
151
|
+
t.set_column_attributes(Array.new(3, { :halign => :center}))
|
152
|
+
|
153
|
+
steps_distance_calories = monitoring_analyzer.steps_distance_calories
|
154
|
+
steps = steps_distance_calories[:steps]
|
155
|
+
steps_goal = monitoring_analyzer.steps_goal
|
156
|
+
t.cell(steps)
|
157
|
+
|
158
|
+
intensity_minutes = weekly_intensity_minutes(monitoring_analyzer)
|
159
|
+
t.cell(intensity_minutes)
|
160
|
+
|
161
|
+
floors = monitoring_analyzer.total_floors
|
162
|
+
floors_climbed = floors[:floors_climbed]
|
163
|
+
t.cell(floors_climbed)
|
164
|
+
t.new_row
|
165
|
+
|
166
|
+
t.cell("#{percent(steps, steps_goal)} of daily goal #{steps_goal}")
|
167
|
+
t.cell("#{percent(intensity_minutes, 150)} of weekly goal 150")
|
168
|
+
t.cell("#{percent(floors_climbed, 10)} of daily goal 10")
|
169
|
+
t.new_row
|
170
|
+
|
171
|
+
t
|
172
|
+
end
|
173
|
+
|
174
|
+
def daily_stats_table(monitoring_analyzer, sleep_analyzer)
|
175
|
+
t = FlexiTable.new
|
176
|
+
t.set_column_attributes(Array.new(4, { :halign => :center}))
|
177
|
+
|
178
|
+
t.head
|
179
|
+
t.row([ 'Distance', 'Calories', 'Floors descended',
|
180
|
+
'Resting Heart Rate' ])
|
181
|
+
|
182
|
+
t.body
|
183
|
+
steps_distance_calories = monitoring_analyzer.steps_distance_calories
|
184
|
+
t.cell("#{'%.1f' % (steps_distance_calories[:distance] / 1000.0)} km")
|
185
|
+
|
186
|
+
t.cell("#{steps_distance_calories[:calories].to_i}")
|
187
|
+
|
188
|
+
floors = monitoring_analyzer.total_floors
|
189
|
+
t.cell("#{floors[:floors_descended]}")
|
190
|
+
|
191
|
+
t.cell("#{sleep_analyzer.resting_heart_rate} BPM")
|
192
|
+
|
193
|
+
t
|
194
|
+
end
|
195
|
+
|
196
|
+
def monthly_goal_table(year, month, last_day_of_month)
|
197
|
+
t = FlexiTable.new
|
198
|
+
left = { :halign => :left }
|
199
|
+
right = { :halign => :right }
|
200
|
+
t.set_column_attributes([ left ] + [ right ] * 7)
|
201
|
+
t.head
|
202
|
+
t.row([ 'Day', 'Steps', '%', 'Goal', 'Intensity', '%',
|
203
|
+
'Floors', '% of 10' ])
|
204
|
+
t.row([ '', '', '', '', 'Minutes', 'Week', '', '' ])
|
205
|
+
t.body
|
206
|
+
totals = Hash.new(0)
|
207
|
+
counted_days = 0
|
208
|
+
intensity_minutes_sum = 0
|
209
|
+
1.upto(last_day_of_month).each do |dom|
|
210
|
+
break if (time = Time.new(year, month, dom)) > Time.now
|
211
|
+
|
212
|
+
day_str = time.strftime('%d %a')
|
213
|
+
t.cell(day_str)
|
214
|
+
|
215
|
+
analyzer = DailyMonitoringAnalyzer.new(@monitoring_files, day_str)
|
216
|
+
|
217
|
+
steps_distance_calories = analyzer.steps_distance_calories
|
218
|
+
steps = steps_distance_calories[:steps]
|
219
|
+
totals[:steps] += steps
|
220
|
+
steps_goal = analyzer.steps_goal
|
221
|
+
totals[:steps_goal] += steps_goal
|
222
|
+
t.cell(steps)
|
223
|
+
t.cell(percent(steps, steps_goal))
|
224
|
+
t.cell(steps_goal)
|
225
|
+
|
226
|
+
if dom == 1
|
227
|
+
intensity_minutes = weekly_intensity_minutes(analyzer)
|
228
|
+
else
|
229
|
+
intensity_minutes_sum = 0 if time.wday == @first_day_of_week
|
230
|
+
intensity_minutes =
|
231
|
+
analyzer.intensity_minutes[:moderate_minutes] +
|
232
|
+
2 * analyzer.intensity_minutes[:vigorous_minutes]
|
233
|
+
end
|
234
|
+
intensity_minutes_sum += intensity_minutes
|
235
|
+
totals[:intensity_minutes] += intensity_minutes
|
236
|
+
t.cell(intensity_minutes_sum.to_i)
|
237
|
+
t.cell(percent(intensity_minutes_sum, 150))
|
238
|
+
|
239
|
+
floors = analyzer.total_floors
|
240
|
+
floors_climbed = floors[:floors_climbed]
|
241
|
+
totals[:floors] += floors_climbed
|
242
|
+
t.cell(floors_climbed)
|
243
|
+
t.cell(percent(floors_climbed, 10))
|
244
|
+
t.new_row
|
245
|
+
counted_days += 1
|
246
|
+
end
|
247
|
+
|
248
|
+
t.foot
|
249
|
+
t.cell('Totals')
|
250
|
+
t.cell(totals[:steps])
|
251
|
+
t.cell('')
|
252
|
+
t.cell(totals[:steps_goal])
|
253
|
+
t.cell(totals[:intensity_minutes].to_i)
|
254
|
+
t.cell('')
|
255
|
+
t.cell(totals[:floors])
|
256
|
+
t.cell('')
|
257
|
+
t.new_row
|
258
|
+
|
259
|
+
if counted_days > 0
|
260
|
+
t.cell('Averages')
|
261
|
+
t.cell((totals[:steps] / counted_days).to_i)
|
262
|
+
t.cell(percent(totals[:steps], totals[:steps_goal]))
|
263
|
+
t.cell((totals[:steps_goal] / counted_days).to_i)
|
264
|
+
t.cell((totals[:intensity_minutes] / counted_days).to_i)
|
265
|
+
t.cell(percent(totals[:intensity_minutes], (counted_days / 7.0) * 150))
|
266
|
+
t.cell((totals[:floors] / counted_days).to_i)
|
267
|
+
t.cell(percent(totals[:floors] / counted_days, 10))
|
268
|
+
end
|
269
|
+
|
270
|
+
t
|
271
|
+
end
|
272
|
+
|
273
|
+
def monthly_sleep_table(year, month, last_day_of_month)
|
274
|
+
t = FlexiTable.new
|
275
|
+
left = { :halign => :left }
|
276
|
+
right = { :halign => :right }
|
277
|
+
t.set_column_attributes([ left ] + [ right ] * 6)
|
278
|
+
t.head
|
279
|
+
t.row([ 'Date', 'Total Sleep', 'Cycles', 'REM Sleep', 'Light Sleep',
|
280
|
+
'Deep Sleep', 'RHR' ])
|
281
|
+
t.body
|
282
|
+
totals = Hash.new(0)
|
283
|
+
counted_days = 0
|
284
|
+
rhr_days = 0
|
285
|
+
|
286
|
+
1.upto(last_day_of_month).each do |dom|
|
287
|
+
break if (time = Time.new(year, month, dom)) > Time.now
|
288
|
+
|
289
|
+
day_str = time.strftime('%d %a')
|
290
|
+
t.cell(day_str)
|
291
|
+
|
292
|
+
analyzer = DailySleepAnalyzer.new(@monitoring_files, day_str,
|
293
|
+
-12 * 60 * 60)
|
294
|
+
|
295
|
+
if (analyzer.sleep_cycles.empty?)
|
296
|
+
5.times { t.cell('-') }
|
297
|
+
else
|
298
|
+
totals[:total_sleep] += analyzer.total_sleep
|
299
|
+
totals[:cycles] += analyzer.sleep_cycles.length
|
300
|
+
totals[:rem_sleep] += analyzer.rem_sleep
|
301
|
+
totals[:light_sleep] += analyzer.light_sleep
|
302
|
+
totals[:deep_sleep] += analyzer.deep_sleep
|
303
|
+
counted_days += 1
|
304
|
+
|
305
|
+
t.cell(secsToHM(analyzer.total_sleep))
|
306
|
+
t.cell(analyzer.sleep_cycles.length)
|
307
|
+
t.cell(secsToHM(analyzer.rem_sleep))
|
308
|
+
t.cell(secsToHM(analyzer.light_sleep))
|
309
|
+
t.cell(secsToHM(analyzer.deep_sleep))
|
310
|
+
end
|
311
|
+
|
312
|
+
if (rhr = analyzer.resting_heart_rate) && rhr > 0
|
313
|
+
t.cell(rhr)
|
314
|
+
totals[:rhr] += rhr
|
315
|
+
rhr_days += 1
|
316
|
+
else
|
317
|
+
t.cell('-')
|
318
|
+
end
|
319
|
+
t.new_row
|
320
|
+
end
|
321
|
+
t.foot
|
322
|
+
t.cell('Averages')
|
323
|
+
if counted_days > 0
|
324
|
+
t.cell(secsToHM(totals[:total_sleep] / counted_days))
|
325
|
+
t.cell('%.1f' % (totals[:cycles] / counted_days))
|
326
|
+
t.cell(secsToHM(totals[:rem_sleep] / counted_days))
|
327
|
+
t.cell(secsToHM(totals[:light_sleep] / counted_days))
|
328
|
+
t.cell(secsToHM(totals[:deep_sleep] / counted_days))
|
329
|
+
else
|
330
|
+
5.times { t.cell('-') }
|
331
|
+
end
|
332
|
+
if rhr_days > 0
|
333
|
+
t.cell('%.0f' % (totals[:rhr] / rhr_days))
|
334
|
+
else
|
335
|
+
t.cell('-')
|
336
|
+
end
|
337
|
+
t.new_row
|
338
|
+
|
339
|
+
t
|
340
|
+
end
|
341
|
+
|
342
|
+
def weekly_intensity_minutes(monitoring_analyzer)
|
343
|
+
current_date = monitoring_analyzer.window_start_time.localtime
|
344
|
+
|
345
|
+
intensity_minutes = 0
|
346
|
+
# Get intensity minutes for previous days of the current week.
|
347
|
+
if current_date.wday != @first_day_of_week
|
348
|
+
1.upto(5) do |i|
|
349
|
+
date = current_date - 24 * 60 * 60 * i
|
350
|
+
|
351
|
+
ma = DailyMonitoringAnalyzer.new(@monitoring_files,
|
352
|
+
date.strftime('%Y-%m-%d'))
|
353
|
+
intensity_minutes +=
|
354
|
+
ma.intensity_minutes[:moderate_minutes] +
|
355
|
+
2 * ma.intensity_minutes[:vigorous_minutes]
|
356
|
+
|
357
|
+
break if current_date.wday == @first_day_of_week
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# Finally add the intensity minutes of the current day.
|
362
|
+
intensity_minutes +=
|
363
|
+
monitoring_analyzer.intensity_minutes[:moderate_minutes] +
|
364
|
+
2 * monitoring_analyzer.intensity_minutes[:vigorous_minutes]
|
365
|
+
|
366
|
+
intensity_minutes
|
367
|
+
end
|
368
|
+
|
369
|
+
end
|
370
|
+
|
371
|
+
end
|
372
|
+
|