Skip to content

Commit cb1d9e6

Browse files
author
Tom Goetz
committed
reduce DB disk usage; better formatting of some values
1 parent 7680d42 commit cb1d9e6

10 files changed

Lines changed: 114 additions & 63 deletions

‎garmindb/activity_fit_file_processor.py‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from .garmindb import File, ActivitiesDb, Activities, ActivityRecords, ActivityLaps, ActivitySplits, ActivityClimbingSplits, ActivitiesDevices, ActivitiesDeviceUsed, \
1414
StepsActivities, SwimmingActivities, CycleActivities, ClimbingActivities, PaddleActivities, ActivityLengths, ActivitySplitSummaries, ActivitiesBestEffort, \
15-
Attributes
15+
Attributes, ActivitiesEvaluations
1616
from .fit_file_processor import FitFileProcessor
1717

1818

@@ -82,12 +82,13 @@ def _write_activity_entry(self, fit_file, message_fields):
8282
self.activity_total_time = message_fields.get('total_timer_time')
8383

8484
def _write_best_effort_entry(self, fit_file, message_fields):
85+
root_logger.info("writing best_effort message %r for %s", message_fields, fit_file.filename)
8586
start_time = fit_file.utc_datetime_to_local(message_fields.start_time)
86-
distance = message_fields.get('distance')
87+
distance = message_fields.get('best_effort_distance')
8788
time = message_fields.get('time')
88-
personal_record = message_fields.get('personal_record')
8989
if start_time and distance and time:
9090
sport = message_fields.get('sport')
91+
personal_record = message_fields.get('personal_record')
9192
best_effort = {
9293
'activity_id' : self.activity_id,
9394
'start_time' : start_time,
@@ -96,12 +97,10 @@ def _write_best_effort_entry(self, fit_file, message_fields):
9697
'time' : time,
9798
'personal_record' : personal_record
9899
}
99-
root_logger.info("writing best_effort %r -> %r for %s", message_fields, best_effort, fit_file.filename)
100-
root_logger.info("writing best_effort distance %r", distance)
100+
root_logger.info("writing best_effort %r for %s", best_effort, fit_file.filename)
101101
ActivitiesBestEffort.s_insert_or_update(self.garmin_act_db_session, best_effort, ignore_none=True, ignore_zero=True)
102102
if personal_record:
103-
distance_int = int(distance)
104-
self.__write_attribute(start_time, f'PR {distance_int} {sport.name}', str(time))
103+
self.__write_attribute(start_time, f'PR {distance} {sport.name}', str(time))
105104

106105
def _write_device_info_entry(self, fit_file, message_fields):
107106
device_serial_number = super()._write_device_info_entry(fit_file, message_fields)
@@ -475,6 +474,7 @@ def _write_session_entry(self, fit_file, message_fields):
475474
'cycles' : message_fields.get('total_cycles'),
476475
'laps' : message_fields.get('num_laps'),
477476
'training_effect' : message_fields.get('total_training_effect'),
477+
'training_load' : message_fields.get('training_load'),
478478
'anaerobic_training_effect' : message_fields.get('total_anaerobic_training_effect'),
479479
'primary_benefit' : message_fields.get('primary_benefit'),
480480
}
@@ -509,6 +509,15 @@ def _write_session_entry(self, fit_file, message_fields):
509509
root_logger.warning("No sport handler for type %s from %s: %s", sport, fit_file.filename, message_fields)
510510
except Exception as e:
511511
root_logger.error("Exception in %s from %s: %s", function_name, fit_file.filename, e)
512+
feel = message_fields.get('workout_feel')
513+
effort = message_fields.get('workout_rpe')
514+
if feel or effort:
515+
evuation = {
516+
'activity_id' : activity_id,
517+
'self_eval_feel' : feel,
518+
'self_eval_effort' : effort
519+
}
520+
ActivitiesEvaluations.s_find_or_create(self.garmin_act_db_session, evuation)
512521

513522
def _write_time_in_zone_entry(self, fit_file, message_fields):
514523
"""Write hz zones message to the database."""

‎garmindb/activity_fit_plugin_base.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
class ActivityFitPluginBase(PluginBase):
1616
"""Base class for GarminDb activity FIT file plugins that handle data based on ids of applications or developer fields, sport or sub-sport ids, etc."""
1717

18+
__plugin_interface_version__ = 1
1819
_type = 'ActivityFit'
1920

2021
@classmethod
@@ -36,6 +37,10 @@ def matches_activity_file(cls, fit_file):
3637
def init_activity(cls, act_db_class, activities_table):
3738
"""Initialize an instance of the plugin as an activity FIT file plugin."""
3839
logger.info("Initializing tables for activity plugin %s with activities table %s", cls.__name__, activities_table)
40+
plugin_interface_implementation_version = getattr(cls, '__plugin_interface_implementation_version__', 0)
41+
if plugin_interface_implementation_version != cls.__plugin_interface_version__:
42+
raise Exception(f'Error loading plugin {cls.__name__} version mismatch: '
43+
+ f'{cls.__plugin_interface_version__} vs {plugin_interface_implementation_version} - update plugin!')
3944
if hasattr(cls, '_records_tablename') and 'record' not in cls._tables:
4045
cls._tables['record'] = activities_table.create(cls._records_tablename, act_db_class, cls._records_version, cls._records_pk, cls._records_cols)
4146
if hasattr(cls, '_laps_tablename') and 'lap' not in cls._tables:

‎garmindb/garmin_json_data.py‎

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from idbutils import JsonFileProcessor
1414

1515
from .garmin_connect_enums import Event, get_summary_sport, get_details_sport
16-
from .garmindb import ActivitiesDb, Activities, StepsActivities, PaddleActivities, CycleActivities
16+
from .garmindb import ActivitiesDb, Activities, ActivitiesCourses, ActivitiesEvaluations, StepsActivities, PaddleActivities, CycleActivities
1717

1818

1919
logger = logging.getLogger(__file__)
@@ -64,20 +64,20 @@ def _process_common(self, json_data):
6464
'stop_long' : self._get_field(json_data, 'endLongitude', float),
6565
'distance' : distance.kms_or_miles(self.measurement_system),
6666
'laps' : self._get_field(json_data, 'lapCount'),
67-
'avg_hr' : self._get_field(json_data, 'averageHR', float),
68-
'max_hr' : self._get_field(json_data, 'maxHR', float),
69-
'calories' : self._get_field(json_data, 'calories', float),
67+
'avg_hr' : self._get_field(json_data, 'averageHR', float, 1),
68+
'max_hr' : self._get_field(json_data, 'maxHR', float, 1),
69+
'calories' : self._get_field(json_data, 'calories', int),
7070
'avg_speed' : avg_speed.kph_or_mph(self.measurement_system) if avg_speed is not None else None,
7171
'max_speed' : max_speed.kph_or_mph(self.measurement_system) if max_speed is not None else None,
7272
'ascent' : ascent.meters_or_feet(self.measurement_system) if ascent is not None else None,
7373
'descent' : descent.meters_or_feet(self.measurement_system) if descent is not None else None,
7474
'max_temperature' : max_temperature.c_or_f(self.measurement_system) if max_temperature is not None else None,
7575
'min_temperature' : min_temperature.c_or_f(self.measurement_system) if min_temperature is not None else None,
7676
'avg_temperature' : avg_temperature.c_or_f(self.measurement_system) if avg_temperature is not None else None,
77-
'training_effect' : self._get_field(json_data, 'aerobicTrainingEffect', float),
78-
'anaerobic_training_effect' : self._get_field(json_data, 'anaerobicTrainingEffect', float),
79-
'max_rr' : self._get_field(json_data, 'maxRespirationRate', float),
80-
'avg_rr' : self._get_field(json_data, 'avgRespirationRate', float),
77+
'training_effect' : self._get_field(json_data, 'aerobicTrainingEffect', float, 1),
78+
'anaerobic_training_effect' : self._get_field(json_data, 'anaerobicTrainingEffect', float, 1),
79+
'max_rr' : self._get_field(json_data, 'maxRespirationRate', float, 1),
80+
'avg_rr' : self._get_field(json_data, 'avgRespirationRate', float, 1),
8181
}
8282

8383
def _process_json(self, json_data):
@@ -114,9 +114,9 @@ def _process_steps_activity(self, activity_id, activity_summary):
114114
'steps' : self._get_field(activity_summary, 'steps', float),
115115
'avg_steps_per_min' : self._get_field(activity_summary, 'averageRunningCadenceInStepsPerMinute', float),
116116
'max_steps_per_min' : self._get_field(activity_summary, 'maxRunningCadenceInStepsPerMinute', float),
117-
'avg_step_length' : avg_step_length.meters_or_feet(self.measurement_system),
117+
'avg_step_length' : avg_step_length.meters_or_feet(self.measurement_system) if avg_step_length else None,
118118
'avg_gct_balance' : self._get_field(activity_summary, 'avgGroundContactBalance', float),
119-
'avg_vertical_oscillation' : avg_vertical_oscillation.meters_or_feet(self.measurement_system),
119+
'avg_vertical_oscillation' : avg_vertical_oscillation.meters_or_feet(self.measurement_system) if avg_vertical_oscillation else None,
120120
'avg_ground_contact_time' : fitfile.conversions.ms_to_dt_time(self._get_field(activity_summary, 'avgGroundContactTime', float)),
121121
'vo2_max' : self._get_field(activity_summary, 'vO2MaxValue', float),
122122
}
@@ -291,39 +291,32 @@ def _process_fitness_equipment(self, sub_sport, activity_id, json_data):
291291
root_logger.debug("fitness_equipment (%s) for %d: %r", sub_sport, activity_id, json_data)
292292
self._call_process_func(sub_sport.name, None, activity_id, json_data)
293293

294-
@classmethod
295-
def get_self_eval_feel(cls, value):
296-
"""Return the Garmin Connect self evaluation 'How did you feel' label for the activity."""
297-
levels = [(100, "Very Strong"), (75, "Strong"), (50, "Normal"), (25, "Weak"), (0, "Very Weak")]
298-
for threshold, label in levels:
299-
if value >= threshold:
300-
return label
301-
302-
@classmethod
303-
def get_self_eval_effort(cls, value):
304-
"""Return the Garmin Connect self evaluation perceived effort label for the activity."""
305-
levels = [(100, "Maximum"), (90, "Extremely Hard"), (70, "Very Hard"), (50, "Hard"),
306-
(40, "Somewhat Hard"), (30, "Moderate"), (20, "Light"), (10, "Very Light"), (0, "None")]
307-
for threshold, label in levels:
308-
if value >= threshold:
309-
return label
310-
311294
def _activities_process_json(self, json_data):
312295
activity_id = json_data['activityId']
313296
metadata_dto = json_data['metadataDTO']
314297
summary_dto = json_data['summaryDTO']
315298
sport, sub_sport = get_details_sport(json_data)
316-
self_eval_feel = self._get_field(summary_dto, 'directWorkoutFeel', int)
317-
self_eval_effort = self._get_field(summary_dto, 'directWorkoutRpe', int)
318299
activity = {
319300
'activity_id' : activity_id,
320-
'course_id' : self._get_field(metadata_dto, 'associatedCourseId', int),
321-
'device_serial_number' : self._get_field(metadata_dto, 'deviceId', int),
322-
'self_eval_feel' : self.get_self_eval_feel(self_eval_feel) if (self_eval_feel is not None) else None,
323-
'self_eval_effort' : self.get_self_eval_effort(self_eval_effort) if (self_eval_effort is not None) else None,
324-
'training_load' : self._get_field(summary_dto, 'activityTrainingLoad', float)
301+
'training_load' : self._get_field(summary_dto, 'activityTrainingLoad', float, 1)
325302
}
326303
activity.update(self._process_common(summary_dto))
327304
Activities.s_insert_or_update(self.garmin_act_db_session, activity, ignore_none=True)
305+
course_id = self._get_field(metadata_dto, 'associatedCourseId', int)
306+
if course_id:
307+
activity_course = {
308+
'activity_id' : activity_id,
309+
'course_id' : course_id,
310+
}
311+
ActivitiesCourses.s_insert_or_update(self.garmin_act_db_session, activity_course, ignore_none=True)
312+
self_eval_feel = self._get_field(summary_dto, 'directWorkoutFeel', int)
313+
self_eval_effort = self._get_field(summary_dto, 'directWorkoutRpe', int)
314+
if self_eval_feel or self_eval_effort:
315+
evuation = {
316+
'activity_id' : activity_id,
317+
'self_eval_feel' : fitfile.fields.ActivityEvalFeel.get_self_eval_feel(self_eval_feel) if (self_eval_feel is not None) else None,
318+
'self_eval_effort' : fitfile.fields.ActivityEvalEffort.get_self_eval_effort(self_eval_effort) if (self_eval_effort is not None) else None
319+
}
320+
ActivitiesEvaluations.s_insert_or_update(self.garmin_act_db_session, evuation)
328321
self._call_process_func(sport.name, sub_sport, activity_id, json_data)
329322
return 1

‎garmindb/garmindb/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
from .sleep_db import SleepDb, SleepEvents, SleepAssessments, Sleep, Naps
1313
from .hrv_db import HrvDb, HrvValue, HrvStatusSummary
1414
from .activities_db import ActivitiesDb, Activities, ActivityLaps, ActivityRecords, ActivitiesDevices, ActivitiesDeviceUsed, ActivitySplits, ActivityClimbingSplits, \
15-
SportActivities, StepsActivities, PaddleActivities, CycleActivities, ClimbingActivities, ActivityLengths, ActivitySplitSummaries, SwimmingActivities, ActivitiesBestEffort
15+
SportActivities, StepsActivities, PaddleActivities, CycleActivities, ClimbingActivities, ActivityLengths, ActivitySplitSummaries, SwimmingActivities, ActivitiesBestEffort, \
16+
ActivitiesCourses, ActivitiesEvaluations
1617
from .garmin_summary_db import GarminSummaryDb, Summary, YearsSummary, MonthsSummary, WeeksSummary, DaysSummary, IntensityHR

‎garmindb/garmindb/activities_db.py‎

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ class ActivityClimbingSplits(ActivitiesDb.Base, idbutils.DbObject):
249249

250250
activity_id = Column(String, ForeignKey('activities.activity_id'))
251251
split = Column(Integer)
252-
253252
grade = Column(String) # climbing grade
254253
completed = Column(Boolean) # climbing route
255254
falls = Column(Integer) # climbing number of falls
@@ -323,16 +322,9 @@ class Activities(ActivitiesDb.Base, ActivitiesLapsCommon):
323322
name = Column(String)
324323
description = Column(String)
325324
type = Column(String)
326-
course_id = Column(Integer)
327325
laps = Column(Integer)
328326
sport = Column(String)
329327
sub_sport = Column(String)
330-
331-
device_serial_number = Column(BigInteger)
332-
333-
self_eval_feel = Column(String)
334-
self_eval_effort = Column(String)
335-
336328
training_load = Column(Float)
337329
training_effect = Column(Float)
338330
anaerobic_training_effect = Column(Float)
@@ -510,7 +502,7 @@ class ActivitiesBestEffort(ActivitiesDb.Base, idbutils.DbObject):
510502
activity_id = Column(String, ForeignKey('activities.activity_id'), primary_key=True)
511503
start_time = Column(DateTime)
512504
sport = Column(Enum(fitfile.fields.Sport))
513-
distance = Column(Float)
505+
distance = Column(String)
514506
time = Column(Time, nullable=False, default=datetime.time.min)
515507
personal_record = Column(Boolean)
516508

@@ -526,6 +518,30 @@ def get_activity(cls, db, activity_id):
526518
return cls.s_get_activity(session, activity_id)
527519

528520

521+
class ActivitiesCourses(ActivitiesDb.Base, idbutils.DbObject):
522+
"""Class represents a database table that maps activities to course ids."""
523+
524+
__tablename__ = 'activities_courses'
525+
__get_col_name__ = "activity_id"
526+
527+
db = ActivitiesDb
528+
table_version = 2
529+
530+
activity_id = Column(String, primary_key=True)
531+
course_id = Column(Integer)
532+
533+
@classmethod
534+
def s_get_activity(cls, session, activity_id):
535+
"""Return all activity devices records for a given activity_id."""
536+
return session.query(cls).filter(cls.activity_id == activity_id).all()
537+
538+
@classmethod
539+
def get_activity(cls, db, activity_id):
540+
"""Return all activity devices records for a given activity_id."""
541+
with db.managed_session() as session:
542+
return cls.s_get_activity(session, activity_id)
543+
544+
529545
class ActivitiesDevices(ActivitiesDb.Base, idbutils.DbObject):
530546
"""Class represents a database table that maps device ids to activities (by id) that they were used in."""
531547

@@ -552,6 +568,31 @@ def get_activity(cls, db, activity_id):
552568
return cls.s_get_activity(session, activity_id)
553569

554570

571+
class ActivitiesEvaluations(ActivitiesDb.Base, idbutils.DbObject):
572+
"""Class represents a database table that that stores self evaluations for activities."""
573+
574+
__tablename__ = 'activities_evaluations'
575+
__get_col_name__ = "activity_id"
576+
577+
db = ActivitiesDb
578+
table_version = 1
579+
580+
activity_id = Column(String, ForeignKey('activities.activity_id'), primary_key=True)
581+
self_eval_feel = Column(String)
582+
self_eval_effort = Column(String)
583+
584+
@classmethod
585+
def s_get_activity(cls, session, activity_id):
586+
"""Return all activity devices records for a given activity_id."""
587+
return session.query(cls).filter(cls.activity_id == activity_id).all()
588+
589+
@classmethod
590+
def get_activity(cls, db, activity_id):
591+
"""Return all activity devices records for a given activity_id."""
592+
with db.managed_session() as session:
593+
return cls.s_get_activity(session, activity_id)
594+
595+
555596
class ActivitiesDeviceUsed(ActivitiesDb.Base, idbutils.DbObject):
556597
"""Class represents a database table that show which devices were used for metrics during activities (by serial number)."""
557598

@@ -598,20 +639,20 @@ def _create_activity_view(cls, db, selectable):
598639
"""Create a database view for a activity type."""
599640
view_name = cls._get_default_view_name()
600641
logger.debug("Creating activity view %s if needed.", view_name)
601-
cls.create_join_view(db, view_name, selectable, Activities, order_by=Activities.start_time.desc())
642+
cls.create_join_view(db, view_name, selectable, [Activities], order_by=Activities.start_time.desc())
602643

603644
@classmethod
604645
def _create_sport_view(cls, db, selectable, sport):
605646
"""Create a database view for a sport based activity type."""
606647
# SQL equality is `=` and string literals use single quotes. sqlite
607648
# tolerates `==` and double-quoted literals; postgres does not.
608649
filter = literal_column(f"{Activities.sport} = '{sport}'")
609-
cls.create_join_view(db, f'{sport}_activities_view', selectable, Activities, filter, Activities.start_time.desc())
650+
cls.create_join_view(db, f'{sport}_activities_view', selectable, [Activities], filter, Activities.start_time.desc())
610651

611652
@classmethod
612653
def _create_course_view(cls, db, selectable, course_id):
613-
filter = literal_column(f'{Activities.course_id} = {course_id}')
614-
cls.create_join_view(db, f'course_{course_id}_view', selectable, Activities, filter, Activities.start_time.desc())
654+
filter = literal_column(f'{ActivitiesCourses.course_id} = {course_id}')
655+
cls.create_join_view(db, f'course_{course_id}_view', selectable, [Activities, ActivitiesCourses], filter, Activities.start_time.desc())
615656

616657
@classmethod
617658
def create_view(cls, db):
@@ -669,7 +710,7 @@ def _view_selectable(cls, include_sport=False, include_subsport=False, include_t
669710
if include_type:
670711
selectable.append(Activities.type.label('type'))
671712
if include_course:
672-
selectable.append(Activities.course_id.label('course_id'))
713+
selectable.append(ActivitiesCourses.course_id.label('course_id'))
673714
selectable += [
674715
Activities.start_time.label('start_time'),
675716
Activities.stop_time.label('stop_time'),

0 commit comments

Comments
 (0)