From a74ba4ba972c07e4a0f5272508a13f8dcb003f70 Mon Sep 17 00:00:00 2001 From: Chris Schlaeger Date: Mon, 31 Oct 2016 21:59:15 +0100 Subject: New: Weekly report for monitoring data. --- lib/postrunner/FitFileStore.rb | 22 ++++- lib/postrunner/Main.rb | 11 +++ lib/postrunner/MonitoringStatistics.rb | 175 ++++++++++++++++++++++++++++++++- 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 update-gps Download the current set of GPS Extended Prediction Orbit (EPO) data and store them on the device. +weekly [ ] + + Print a table with various statistics for each day of the specified + week. If no date is given, yesterday's week will be used. + 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)) -- cgit v1.2.3