Pierre Laub endurance & creativity

Python ETL-Pipeline: FIT-Daten effizient in BigQuery analysieren – Teil 2

In Teil 1 habe ich gezeigt, wie ich mit Python eine ETL-Pipeline gebaut habe, die FIT-Dateien von Garmin, Wahoo und Zwift nach BigQuery schiebt – inkl. Hash-basierter Duplikaterkennung, Sessions-/Details-Tabellen und automatischem Archivieren der Files.

In diesem zweiten Teil geht es um das, was nach dem „es läuft!“ passiert ist:

Spoiler: Die Daten sahen gut aus. Bis man genauer hingeschaut hat.
Und dann stellte sich heraus: Die AI hatte mir nicht das offizielle Garmin SDK eingebaut, sondern ein random GitHub-Repo (fitparse).

Dieser Post ist also eine Mischung aus War Story, Debugging-Tagebuch und Lessons Learned zum Thema AI-assistierter Projektaufbau.


Die Symptome: „Sieht irgendwie komisch aus…“

Erstmal sah alles super aus:

  • ETL lief stabil durch
  • BigQuery war voll mit Sessions und Record-Daten
  • Dashboard Tabelle sah gut aus auf den ersten Blick

Bis ich angefangen habe, konkrete Sessions mit der Realität zu vergleichen:

  • Letzer Marathon mit Coros Pace Pro hat komplett gefehlt
  • Manche Felder (z.B. spezielle Power-/HR-Metriken) waren komplett leer

Die erste Reaktion: „Klar, ETL-Bug.“


Phase 1: Exceptions im Parser – „Die Files sind doch okay?!“

Bevor ich überhaupt in BigQuery geschaut habe, ist mir etwas anderes um die Ohren geflogen: viele FIT-Dateien haben den Parser mit kryptischen Exceptions abgeschossen.

Typische Situation:

  • derselbe Export lief für einige Aktivitäten sauber durch
  • andere (z.B. mein letzter Marathon) produzierten reproduzierbar Fehler

Da ich den Aktivitäten in Garmin/Coros/Zwift vertraut habe, war für mich klar: die Dateien sind nicht kaputt – mein Code ist es.

Also habe ich versucht, das Problem direkt im ETL zu „reparieren“:

  • zusätzliche try/except-Blöcke eingebaut
  • einzelne Message-Typen/Fields „weich“ behandelt oder übersprungen
  • an Parsing-Details gedreht, bis die Pipeline „durchlief“

Das Ergebnis: weniger Abstürze, aber dafür stille Datenverluste – einzelne Sessions waren nur teilweise geparst oder hatten Lücken in den Records. Genau das hat mich später in BigQuery eingeholt.

Phase 2: Debugging mit BigQuery-SQL

1. Outlier-Analyse

Erster Schritt: Auffällige Aktivitäten finden.

SELECT
  filename,
  start_time,
  total_distance,
  total_timer_time,
  total_distance / NULLIF(total_timer_time, 0) AS avg_speed_calc,
  avg_speed
FROM `project.fitness_data.sessions`
WHERE sport = 'running'
ORDER BY start_time DESC
LIMIT 50;

Auffälligkeiten:

  • avg_speed passte nicht zu total_distance / total_timer_time
  • Manche total_distance Werte waren zu niedrig oder zu hoch

2. Versuche, das Problem mit SQL zu „reparieren“

Meine erste Intuition: „Vielleicht sind nur ein paar Felder falsch, das repariere ich im Warehouse“:

  • Speed neu berechnen aus distance + timestamp
  • Distance normalisieren (z.B. auf letzte distance im Record-Stream)
  • Sessions recalculaten als Aggregat aus details

Das hat bei der Marathon-Aktivität dazu geführt, dass die Distanz ~16km statt 42km war.

An diesem Punkt war klar: Das Problem sitzt eher vor BigQuery.


Phase 3: Blick zurück in den Parser – was macht fitparse da eigentlich?

In Teil 1 war der Parser so ungefähr aufgebaut:

# (alte Version, vereinfacht)
from fitparse import FitFile

fitfile = FitFile(path_to_file)

for record in fitfile.get_messages('session'):
    data = record.get_values()
    # ... Session-Felder extrahieren

for record in fitfile.get_messages('record'):
    data = record.get_values()
    # ... Zeitreihen-Felder extrahieren

Das kam aus einem AI-Assist (Code-Vervollständigung). Und das war der erste Fehler:

Die AI hat die erste populäre Library vorgeschlagen – fitparse
aber nicht das offizielle Garmin FIT SDK.

Was schief war an fitparse in meinem Setup

fitparse ist nicht grundsätzlich „schlecht“, aber für meine Kombination aus:

  • neuen Geräten (aktuelle Garmin, Wahoo ELEMNT BOLT, Zwift, Coros)
  • neueren FIT-Profilen
  • und Developer Fields

…war es einfach zu „alt“ bzw. nicht mehr vollständig gepflegt:

  • Bestimmte Message-Typen wurden nicht oder nur teilweise unterstützt.
  • Developer Fields und neuere Extensions fehlten komplett.
  • Zwift-spezifische Felder/Interpretationen wurden nicht sauber abgebildet.
  • Der Code in fitparse wird nicht mehr so aktiv gepflegt wie das Garmin SDK.

Damit war klar: Ich parse moderne FIT-Dateien mit einer Library, die für diese Welt nicht mehr wirklich gemacht ist.


Phase 4: Migration auf das offizielle Garmin FIT SDK (garmin-fit-sdk)

Der Wechsel der Library

Statt:

from fitparse import FitFile

verwende ich jetzt:

from garmin_fit_sdk import Decoder, Stream

Die Grundidee im neuen Parser (fit_parser.py):

from garmin_fit_sdk import Decoder, Stream

stream = Stream.from_file(str(file_path))
decoder = Decoder(stream)
messages, errors = decoder.read()

if errors:
    # Logging & Handling

# messages["session"], messages["record"], ...
# enthalten jetzt die sauber geparsten Daten entsprechend der FIT-Spezifikation

Vorteile:

  • Offizielles Garmin FIT SDK → immer auf dem aktuellen Stand der FIT-Spezifikation
  • Bessere Unterstützung für:
    • neue Device-Typen
    • zusätzliche Felder
    • Zwift-/Garmin-spezifische Extensions
  • Weniger „Magie“, mehr klare Strukturen in den zurückgegebenen Messages

Anpassung des ETL

Ich musste im ETL im Prinzip nur eine Schicht austauschen:

  • fit_parser.py komplett neu implementiert (auf garmin-fit-sdk Basis)
  • Schema in BigQuery konnte weitgehend gleich bleiben:
    • Sessions- und Details-Tabellen sind schon sinnvoll entworfen

Danach habe ich einen kompletten Re-Load der FIT-Dateien in BigQuery gemacht und siehe da:

  • Alle Marathon-Aktivitäten waren komplett da
  • Distanz- und Zeitwerte stimmten wieder

Was ich aus dem AI-assistierten Projektaufbau gelernt habe

1. AI-Codevorschläge sind keine Architekturentscheidungen

Die AI hat damals ohne zu zögern fitparse vorgeschlagen:

  • „Du willst FIT parsen? Hier, nimm diese Library aus GitHub.“
  • Kein Hinweis auf:
    • Offizielles Garmin SDK
    • Projekt-Reife
    • Maintenance-Status

Lektion:
AI ist super, um Boilerplate zu generieren, aber Lib-Auswahl ist immer noch deine Aufgabe.

Checkliste fürs nächste Mal:

  • Gibt es ein offizielles SDK vom Hersteller?
  • Ist das Repo aktiv gepflegt (Commits, Issues, Releases)?
  • Passt die Library zur Version des Protokolls/Dateiformats, das ich verarbeite?

2. Data-Quality-Probleme sind selten mit SQL allein lösbar

Mein erster Reflex war:

„Ich fixe das schon in BigQuery mit ein bisschen SQL.“

Das funktioniert, solange:

  • die Rohdaten an sich korrekt sind
  • nur Aggregationen/Logik im ETL falsch sind

Wenn aber schon:

  • Positionen fehlen,
  • Records reduziert sind,
  • oder Felder nie geparst werden,

…kann SQL nichts mehr retten.
Dann muss man den Parser anschauen.

3. Logging und Tests früher ernst nehmen

Was mir geholfen hätte, das Problem früher zu sehen:

  • Ein paar Unit-/Integrationstests, die:
    • eine bekannte Aktivität (mit Expected Values) durch den Parser jagen
    • Distance/Time/Elevation gegen eine „Ground Truth“ vergleichen
  • Strukturierte Logging-Ausgaben im Parser:
    • Anzahl geparster record-Messages pro Datei
    • Fehlermeldungen des Decoders/Parsers

Das habe ich inzwischen nachgerüstet – aber „zu spät“, nachdem schon viel in BigQuery lag.


Fazit: Offizielles SDK, saubere Daten – und ein ehrlicher Blick auf AI-Support

Zusammengefasst:

  • fitparse war in meinem Setup nicht die richtige Wahl:
    • zu alt / nicht vollständig für moderne FIT-Profile
    • Probleme bei bestimmten Geräten / Zwift-Files
  • Das offizielle Garmin FIT SDK (garmin-fit-sdk) hat:
    • die Parsing-Probleme gelöst
    • konsistente Sessions-/Details-Daten geliefert
  • Die Versuche, alles mit SQL in BigQuery zu reparieren, waren nur bedingt erfolgreich:
    • ETL-Bugs im Parser kann man im Warehouse nicht wegoptimieren
  • AI-Assist ist hilfreich, aber:
    • Bibliotheks-Auswahl und Architekturentscheidungen müssen wir Menschen treffen
    • Gerade bei Binärformaten (wie FIT) lohnt sich der Griff zum offiziellen SDK

GitHub: FIT-to-BigQuery-ETL