From 4a61f3eb3103aa512bef1b0d82295403bc4bc977 Mon Sep 17 00:00:00 2001 From: Martin Kagamino Lehoux Date: Thu, 27 Aug 2026 19:07:33 +0200 Subject: Swap ride read path from GPX to FIT for performance Always use FIT for detail, official climb creation and backtest; keep GPX fallback for legacy rides pending backfill. FIT is 10-20x smaller and parses without XML. --- cli/cli.go | 8 ++++---- cli/cli_test.go | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'cli') diff --git a/cli/cli.go b/cli/cli.go index c71fd9e..672cc34 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -145,12 +145,12 @@ func runBacktest(db *sql.DB) error { if !isBacktestEligible(item) { continue } - parsed, err := ride.ParseFile(ride.GPXRideParser{}, item.GPXPath) + parsed, err := ride.ParseFile(ride.FITRideParser{}, item.FitPath) if err != nil { if errors.Is(err, ride.ErrNoTrackPoints) { continue } - slog.Warn("Failed to parse ride GPX", "ride", item.ExternalID, "file", item.GPXPath, "error", err) + slog.Warn("Failed to parse ride FIT", "ride", item.ExternalID, "file", item.FitPath, "error", err) continue } reference := time.Duration(item.ExternalMovingTimeS) * time.Second @@ -164,7 +164,7 @@ func runBacktest(db *sql.DB) error { }) } if len(samples) == 0 { - slog.Info("No rides with GPX to backtest", "rides", len(items)) + slog.Info("No rides with FIT to backtest", "rides", len(items)) return nil } sort.Slice(samples, func(i, j int) bool { @@ -195,7 +195,7 @@ func runBacktest(db *sql.DB) error { } func isBacktestEligible(item ride.StoredRide) bool { - if item.GPXPath == "" || item.ExternalMovingTimeS <= 0 || item.ElapsedTimeS <= 0 { + if item.FitPath == "" || item.ExternalMovingTimeS <= 0 || item.ElapsedTimeS <= 0 { return false } if !item.IsCategorised() { diff --git a/cli/cli_test.go b/cli/cli_test.go index 28260e0..e12ff7a 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -8,17 +8,17 @@ import ( ) func TestBacktestEligibilityExcludesShortDistances(t *testing.T) { - short := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000 - 1, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} - minimum := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + short := ride.StoredRide{FitPath: "ride.fit", DistanceM: 10_000 - 1, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + minimum := ride.StoredRide{FitPath: "ride.fit", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} assert.True(t, isBacktestEligible(short)) assert.True(t, isBacktestEligible(minimum)) } func TestBacktestEligibilityExcludesLowMovingRatio(t *testing.T) { - low := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 7_999, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} - boundary := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} - noElapsed := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 1, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + low := ride.StoredRide{FitPath: "ride.fit", DistanceM: 10_000, ExternalMovingTimeS: 7_999, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + boundary := ride.StoredRide{FitPath: "ride.fit", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + noElapsed := ride.StoredRide{FitPath: "ride.fit", DistanceM: 10_000, ExternalMovingTimeS: 1, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} assert.True(t, isBacktestEligible(low)) assert.True(t, isBacktestEligible(boundary)) @@ -26,11 +26,11 @@ func TestBacktestEligibilityExcludesLowMovingRatio(t *testing.T) { } func TestBacktestEligibilityExcludesUncategorisedAndIndoorTransport(t *testing.T) { - base := ride.StoredRide{GPXPath: "ride.gpx", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} - uncategorised := ride.StoredRide{GPXPath: "ride.gpx", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000} - indoor := ride.StoredRide{GPXPath: "ride.gpx", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeIndoor} - transport := ride.StoredRide{GPXPath: "ride.gpx", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTransport} - race := ride.StoredRide{GPXPath: "ride.gpx", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeMTB, RideType: ride.RideTypeRace} + base := ride.StoredRide{FitPath: "ride.fit", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTraining} + uncategorised := ride.StoredRide{FitPath: "ride.fit", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000} + indoor := ride.StoredRide{FitPath: "ride.fit", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeIndoor} + transport := ride.StoredRide{FitPath: "ride.fit", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeRoad, RideType: ride.RideTypeTransport} + race := ride.StoredRide{FitPath: "ride.fit", ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000, BikeType: ride.BikeTypeMTB, RideType: ride.RideTypeRace} assert.True(t, isBacktestEligible(base)) assert.False(t, isBacktestEligible(uncategorised)) -- cgit v1.3