summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Schlaeger <chris@linux.com>2016-10-31 21:59:15 +0100
committerChris Schlaeger <chris@linux.com>2016-10-31 21:59:15 +0100
commita74ba4ba972c07e4a0f5272508a13f8dcb003f70 (patch)
treea50dc9af1706d7d12746933a05f8c64be5e9077c
parent799e20f9d44aa2d76243f899b7a4b6f43864ffe5 (diff)
downloadpostrunner-a74ba4ba972c07e4a0f5272508a13f8dcb003f70.zip
New: Weekly report for monitoring data.
-rw-r--r--lib/postrunner/FitFileStore.rb22
-rw-r--r--lib/postrunner/Main.rb11
-rw-r--r--lib/postrunner/MonitoringStatistics.rb175
3 files changed, 206 insertions, 2 deletions
diff --git a/lib/postrunner/FitFileStore.rb b/lib/postrunner/FitFileStore.rb
index 162cd76..fb86d76 100644
--- a/lib/postrunner/FitFileStore.rb
+++ b/lib/postrunner/FitFileStore.rb
@@ -353,8 +353,28 @@ module PostRunner
puts MonitoringStatistics.new(monitoring_files).daily(day)
end
+ def weekly_report(day)
+ # 'day' specifies the current week. It must be in the form of
+ # YYYY-MM-DD and references a day in the specific week. But we don't
+ # know what timezone the watch was set to for a given date. The files
+ # are always named after the moment of finishing the recording expressed
+ # as GMT time. Each file contains information about the time zone for
+ # the specific file. Recording is always flipped to a new file at
+ # midnight GMT but there are usually multiple files per
+ # GMT day.
+ day_as_time = Time.parse(day).gmtime
+ start_day = day_as_time -
+ (24 * 60 * 60 * (day_as_time.wday - @store['config']['week_start_day']))
+ # To get weekly intensity minutes we need 7 days of data prior to the
+ # current month start and 1 after to include the following night. We add
+ # at least 12 extra hours to accomondate time zone changes.
+ monitoring_files = monitorings(start_day - 8 * 24 * 60 * 60,
+ start_day + 8 * 24 * 60 * 60)
+
+ puts MonitoringStatistics.new(monitoring_files).weekly(start_day)
+ end
+
def monthly_report(day)
- monitorings = []
# 'day' specifies the current month. It must be in the form of
# YYYY-MM-01. But we don't know what timezone the watch was set to for a
# given date. The files are always named after the moment of finishing
diff --git a/lib/postrunner/Main.rb b/lib/postrunner/Main.rb
index 1544fd2..8bc1aa3 100644
--- a/lib/postrunner/Main.rb
+++ b/lib/postrunner/Main.rb
@@ -63,6 +63,8 @@ module PostRunner
cfg = (@db['config'] ||= @db.new(PEROBS::Hash))
cfg['unit_system'] ||= :metric
cfg['version'] ||= VERSION
+ # First day of the week. 0 means Sunday, 1 Monday and so on.
+ cfg['week_start_day'] ||= 1
# We always override the data_dir as the user might have moved the data
# directory. The only reason we store it in the DB is to have it
# available throught the application.
@@ -241,6 +243,11 @@ htmldir <directory>
update-gps Download the current set of GPS Extended Prediction Orbit (EPO)
data and store them on the device.
+weekly [ <YYYY-MM-DD> ]
+
+ Print a table with various statistics for each day of the specified
+ week. If no date is given, yesterday's week will be used.
+
<fit file> An absolute or relative name of a .FIT file.
@@ -313,6 +320,10 @@ EOT
# Get the date of requested day in 'YY-MM-DD' format. If no argument
# is given, use the current date.
@ffs.daily_report(day_in_localtime(args, '%Y-%m-%d'))
+ when 'weekly'
+ # Get the date of requested day in 'YY-MM-DD' format. If no argument
+ # is given, use the current date.
+ @ffs.weekly_report(day_in_localtime(args, '%Y-%m-%d'))
when 'monthly'
# Get the date of requested day in 'YY-MM-DD' format. If no argument
# is given, use the current date.
diff --git a/lib/postrunner/MonitoringStatistics.rb b/lib/postrunner/MonitoringStatistics.rb
index 3655b04..867d55c 100644
--- a/lib/postrunner/MonitoringStatistics.rb
+++ b/lib/postrunner/MonitoringStatistics.rb
@@ -55,6 +55,15 @@ module PostRunner
str
end
+ # Generate a report for a certain week.
+ # @param day [String] Date of a day in that week as YYYY-MM-DD string.
+ def weekly(start_day)
+ "Monitoring Statistics for the week of " +
+ "#{start_day.strftime('%Y-%m-%d')}\n\n" +
+ weekly_goal_table(start_day).to_s + "\n" +
+ weekly_sleep_table(start_day).to_s
+ end
+
# Generate a report for a certain month.
# @param day [String] Date of a day in that months as YYYY-MM-DD string.
def monthly(day)
@@ -193,6 +202,100 @@ module PostRunner
t
end
+ def weekly_goal_table(start_day)
+ t = FlexiTable.new
+ left = { :halign => :left }
+ right = { :halign => :right }
+ t.set_column_attributes([ left ] + [ right ] * 10)
+ t.head
+ t.row([ 'Day', 'Steps', '% of', 'Goal', 'Intens.', '% of',
+ 'Floors', '% of', 'Floors', 'Dist.', 'Cals.' ])
+ t.row([ '', '', 'Goal', 'Steps', 'Minutes', '150', 'clmbd.', '10',
+ 'descd.', 'm', 'kCal' ])
+ t.body
+ totals = Hash.new(0)
+ counted_days = 0
+ intensity_minutes_sum = 0
+ 0.upto(7) do |dow|
+ break if (time = start_day + 24 * 60 * 60 * dow) > Time.now
+
+ day_str = time.strftime('%a')
+ t.cell(day_str)
+
+ analyzer = DailyMonitoringAnalyzer.new(@monitoring_files,
+ time.strftime('%Y-%m-%d'))
+
+ steps_distance_calories = analyzer.steps_distance_calories
+ steps = steps_distance_calories[:steps]
+ totals[:steps] += steps
+ steps_goal = analyzer.steps_goal
+ totals[:steps_goal] += steps_goal
+ t.cell(steps)
+ t.cell(percent(steps, steps_goal))
+ t.cell(steps_goal)
+
+ intensity_minutes =
+ analyzer.intensity_minutes[:moderate_minutes] +
+ 2 * analyzer.intensity_minutes[:vigorous_minutes]
+ intensity_minutes_sum += intensity_minutes
+ totals[:intensity_minutes] += intensity_minutes
+ t.cell(intensity_minutes.to_i)
+ t.cell(percent(intensity_minutes_sum, 150))
+
+ floors = analyzer.total_floors
+ floors_climbed = floors[:floors_climbed]
+ totals[:floors_climbed] += floors_climbed
+ t.cell(floors_climbed)
+ t.cell(percent(floors_climbed, 10))
+
+ floors_descended = floors[:floors_descended]
+ totals[:floors_descended] += floors_descended
+ t.cell(floors_descended)
+
+
+ distance = steps_distance_calories[:distance]
+ totals[:distance] += distance
+ t.cell(distance.to_i)
+
+ calories = steps_distance_calories[:calories]
+ totals[:calories] += calories
+ t.cell(calories.to_i)
+
+ t.new_row
+ counted_days += 1
+ end
+
+ t.foot
+ t.cell('Totals')
+ t.cell(totals[:steps])
+ t.cell('')
+ t.cell(totals[:steps_goal])
+ t.cell(totals[:intensity_minutes].to_i)
+ t.cell('')
+ t.cell(totals[:floors_climbed])
+ t.cell('')
+ t.cell(totals[:floors_descended])
+ t.cell(totals[:distance].to_i)
+ t.cell(totals[:calories].to_i)
+ t.new_row
+
+ if counted_days > 0
+ t.cell('Averages')
+ t.cell((totals[:steps] / counted_days).to_i)
+ t.cell(percent(totals[:steps], totals[:steps_goal]))
+ t.cell((totals[:steps_goal] / counted_days).to_i)
+ t.cell((totals[:intensity_minutes] / counted_days).to_i)
+ t.cell(percent(totals[:intensity_minutes], (counted_days / 7.0) * 150))
+ t.cell((totals[:floors_climbed] / counted_days).to_i)
+ t.cell(percent(totals[:floors_climbed] / counted_days, 10))
+ t.cell((totals[:floors_descended] / counted_days).to_i)
+ t.cell('%.0f' % (totals[:distance] / counted_days))
+ t.cell((totals[:calories] / counted_days).to_i)
+ end
+
+ t
+ end
+
def monthly_goal_table(year, month, last_day_of_month)
t = FlexiTable.new
left = { :halign => :left }
@@ -292,6 +395,76 @@ module PostRunner
t
end
+ def weekly_sleep_table(start_day)
+ t = FlexiTable.new
+ left = { :halign => :left }
+ right = { :halign => :right }
+ t.set_column_attributes([ left ] + [ right ] * 6)
+ t.head
+ t.row([ 'Date', 'Total Sleep', 'Cycles', 'REM Sleep', 'Light Sleep',
+ 'Deep Sleep', 'RHR' ])
+ t.body
+ totals = Hash.new(0)
+ counted_days = 0
+ rhr_days = 0
+
+ 0.upto(7) do |dow|
+ break if (time = start_day + 24 * 60 * 60 * dow) > Time.now
+
+ day_str = time.strftime('%a')
+ t.cell(day_str)
+
+ analyzer = DailySleepAnalyzer.new(@monitoring_files,
+ time.strftime('%Y-%m-%d'),
+ -12 * 60 * 60)
+
+ if (analyzer.sleep_cycles.empty?)
+ 5.times { t.cell('-') }
+ else
+ totals[:total_sleep] += analyzer.total_sleep
+ totals[:cycles] += analyzer.sleep_cycles.length
+ totals[:rem_sleep] += analyzer.rem_sleep
+ totals[:light_sleep] += analyzer.light_sleep
+ totals[:deep_sleep] += analyzer.deep_sleep
+ counted_days += 1
+
+ t.cell(secsToHM(analyzer.total_sleep))
+ t.cell(analyzer.sleep_cycles.length)
+ t.cell(secsToHM(analyzer.rem_sleep))
+ t.cell(secsToHM(analyzer.light_sleep))
+ t.cell(secsToHM(analyzer.deep_sleep))
+ end
+
+ if (rhr = analyzer.resting_heart_rate) && rhr > 0
+ t.cell(rhr)
+ totals[:rhr] += rhr
+ rhr_days += 1
+ else
+ t.cell('-')
+ end
+ t.new_row
+ end
+ t.foot
+ t.cell('Averages')
+ if counted_days > 0
+ t.cell(secsToHM(totals[:total_sleep] / counted_days))
+ t.cell('%.1f' % (totals[:cycles].to_f / counted_days))
+ t.cell(secsToHM(totals[:rem_sleep] / counted_days))
+ t.cell(secsToHM(totals[:light_sleep] / counted_days))
+ t.cell(secsToHM(totals[:deep_sleep] / counted_days))
+ else
+ 5.times { t.cell('-') }
+ end
+ if rhr_days > 0
+ t.cell('%.0f' % (totals[:rhr] / rhr_days))
+ else
+ t.cell('-')
+ end
+ t.new_row
+
+ t
+ end
+
def monthly_sleep_table(year, month, last_day_of_month)
t = FlexiTable.new
left = { :halign => :left }
@@ -345,7 +518,7 @@ module PostRunner
t.cell('Averages')
if counted_days > 0
t.cell(secsToHM(totals[:total_sleep] / counted_days))
- t.cell('%.1f' % (totals[:cycles] / counted_days))
+ t.cell('%.1f' % (totals[:cycles].to_f / counted_days))
t.cell(secsToHM(totals[:rem_sleep] / counted_days))
t.cell(secsToHM(totals[:light_sleep] / counted_days))
t.cell(secsToHM(totals[:deep_sleep] / counted_days))