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_speedpasste nicht zutotal_distance / total_timer_time- Manche
total_distanceWerte 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
distanceim 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
fitparsewird 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.pykomplett neu implementiert (aufgarmin-fit-sdkBasis)- 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
- Anzahl geparster
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:
fitparsewar 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