postrunner 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|