pe-st / garmin-connect-export

Download a copy of your Garmin Connect data, including stats and GPX tracks.
MIT License
370 stars 76 forks source link

Exporting activity raises "KeyError: 'elevationCorrected'" #112

Closed gustav-b closed 3 months ago

gustav-b commented 3 months ago

Version used: 4.3.0 Python version: 3.8.19

I'm trying to export some old Garmin Connect running activities uploaded from a Garmin Forerunner 410 but I encounter the following error:

$> python gcexport.py --count 1 --start_date 2011-01-01 --end_date 2013-01-01
Welcome to Garmin Connect Exporter!
[WARNING] Output directory ./2024-06-30_garmin_connect_export already exists. Will skip already-downloaded files and append to the CSV file.
Authenticating... Done.
Getting display name... Done. displayName=<redacted>
Fetching user stats... Done.
Querying list of activities 1..1... Done.
Downloading: Garmin Connect activity (1/1) [328565795] <redacted>
        2012-10-13T14:00:52+02:00, 00:51:38, 9.900 km
Traceback (most recent call last):
  File "gcexport.py", line 1319, in <module>
    main(sys.argv)
  File "gcexport.py", line 1303, in main
    process_activity_item(
  File "gcexport.py", line 1225, in process_activity_item
    csv_write_record(csv_filter, extract, actvty, details, activity_type_name, event_type_name)
  File "gcexport.py", line 577, in csv_write_record
    csv_filter.set_column('elevationLossUncorr', str(round(details['summaryDTO']['elevationLoss'], 2)) if not actvty['elevationCorrected'] and present('elevationLoss', details['summaryDTO']) else None)
KeyError: 'elevationCorrected'

I guess the reason is that the activities lack elevation correction and that the code assumes this to be present.

Here's a quick patch that allows these activities to be processed – not sure if it's correct though:

diff --git a/gcexport.py b/gcexport.py
index c054030..1faf69d 100644
--- a/gcexport.py
+++ b/gcexport.py
@@ -574,18 +574,18 @@ def csv_write_record(csv_filter, extract, actvty, details, activity_type_name, e
     csv_filter.set_column('maxSpeedPaceRaw', trunc6(pace_or_speed_raw(type_id, parent_type_id, details['summaryDTO']['maxSpeed'])) if present('maxSpeed', details['summaryDTO']) else None)
     csv_filter.set_column('maxSpeedPace', pace_or_speed_formatted(type_id, parent_type_id, details['summaryDTO']['maxSpeed']) if present('maxSpeed', details['summaryDTO']) else None)
     csv_filter.set_column('elevationLoss', str(round(details['summaryDTO']['elevationLoss'], 2)) if present('elevationLoss', details['summaryDTO']) else None)
-    csv_filter.set_column('elevationLossUncorr', str(round(details['summaryDTO']['elevationLoss'], 2)) if not actvty['elevationCorrected'] and present('elevationLoss', details['summaryDTO']) else None)
-    csv_filter.set_column('elevationLossCorr', str(round(details['summaryDTO']['elevationLoss'], 2)) if actvty['elevationCorrected'] and present('elevationLoss', details['summaryDTO']) else None)
+    csv_filter.set_column('elevationLossUncorr', str(round(details['summaryDTO']['elevationLoss'], 2)) if not present('elevationCorrected', actvty) and present('elevationLoss', details['summaryDTO']) else None)
+    csv_filter.set_column('elevationLossCorr', str(round(details['summaryDTO']['elevationLoss'], 2)) if present('elevationCorrected', actvty) and present('elevationLoss', details['summaryDTO']) else None)
     csv_filter.set_column('elevationGain', str(round(details['summaryDTO']['elevationGain'], 2)) if present('elevationGain', details['summaryDTO']) else None)
-    csv_filter.set_column('elevationGainUncorr', str(round(details['summaryDTO']['elevationGain'], 2)) if not actvty['elevationCorrected'] and present('elevationGain', details['summaryDTO']) else None)
-    csv_filter.set_column('elevationGainCorr', str(round(details['summaryDTO']['elevationGain'], 2)) if actvty['elevationCorrected'] and present('elevationGain', details['summaryDTO']) else None)
+    csv_filter.set_column('elevationGainUncorr', str(round(details['summaryDTO']['elevationGain'], 2)) if not present('elevationCorrected', actvty) and present('elevationGain', details['summaryDTO']) else None)
+    csv_filter.set_column('elevationGainCorr', str(round(details['summaryDTO']['elevationGain'], 2)) if present('elevationCorrected', actvty) and present('elevationGain', details['summaryDTO']) else None)
     csv_filter.set_column('minElevation', str(round(details['summaryDTO']['minElevation'], 2)) if present('minElevation', details['summaryDTO']) else None)
-    csv_filter.set_column('minElevationUncorr', str(round(details['summaryDTO']['minElevation'], 2)) if not actvty['elevationCorrected'] and present('minElevation', details['summaryDTO']) else None)
-    csv_filter.set_column('minElevationCorr', str(round(details['summaryDTO']['minElevation'], 2)) if actvty['elevationCorrected'] and present('minElevation', details['summaryDTO']) else None)
+    csv_filter.set_column('minElevationUncorr', str(round(details['summaryDTO']['minElevation'], 2)) if not present('elevationCorrected', actvty) and present('minElevation', details['summaryDTO']) else None)
+    csv_filter.set_column('minElevationCorr', str(round(details['summaryDTO']['minElevation'], 2)) if present('elevationCorrected', actvty) and present('minElevation', details['summaryDTO']) else None)
     csv_filter.set_column('maxElevation', str(round(details['summaryDTO']['maxElevation'], 2)) if present('maxElevation', details['summaryDTO']) else None)
-    csv_filter.set_column('maxElevationUncorr', str(round(details['summaryDTO']['maxElevation'], 2)) if not actvty['elevationCorrected'] and present('maxElevation', details['summaryDTO']) else None)
-    csv_filter.set_column('maxElevationCorr', str(round(details['summaryDTO']['maxElevation'], 2)) if actvty['elevationCorrected'] and present('maxElevation', details['summaryDTO']) else None)
-    csv_filter.set_column('elevationCorrected', 'true' if actvty['elevationCorrected'] else 'false')
+    csv_filter.set_column('maxElevationUncorr', str(round(details['summaryDTO']['maxElevation'], 2)) if not present('elevationCorrected', actvty) and present('maxElevation', details['summaryDTO']) else None)
+    csv_filter.set_column('maxElevationCorr', str(round(details['summaryDTO']['maxElevation'], 2)) if present('elevationCorrected', actvty) and present('maxElevation', details['summaryDTO']) else None)
+    csv_filter.set_column('elevationCorrected', 'true' if present('elevationCorrected', actvty) else 'false')
     # csv_record += empty_record  # no minimum heart rate in JSON
     csv_filter.set_column('maxHRRaw', str(details['summaryDTO']['maxHR']) if present('maxHR', details['summaryDTO']) else None)
     csv_filter.set_column('maxHR', f"{actvty['maxHR']:.0f}" if present('maxHR', actvty) else None)
pe-st commented 3 months ago

Thanks @gustav-b for reporting this and also finding the reason. I've slightly modified your proposal to assume "no correction" if the field is absent from actvty: https://github.com/pe-st/garmin-connect-export/tree/feature/fix-112

Does that work for you?

gustav-b commented 3 months ago

It works perfectly – thanks!

pe-st commented 3 months ago

Fix integrated in main branch, released in v4.4.0

I noticed the problem also in some of my old activities that were exported still without issues a couple of months ago. No idea if something changed on Garmin Connect....