aboutsummaryrefslogtreecommitdiff
path: root/ride/moving_time_test.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-20 15:47:52 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-20 15:47:52 +0200
commit57e48b23987630936c84300d87ea5be682cb64bd (patch)
tree96e840749bb658c2162788cedca627165747a254 /ride/moving_time_test.go
parent992c36c6c7ce7e1e732866b3cd4fb8269a905875 (diff)
feat(ride): compute moving time from GPX with backtest
Add ride.MovingTime, a speed-threshold algorithm over track timestamps. Add a -backtest CLI command comparing computed values against Strava's reference across all stored rides, with a percentile distribution summary. Baseline: mean err 3.9%, p50 1.6, p75 4.6, p90 8.4, p95 12.1, p99 44.9.
Diffstat (limited to 'ride/moving_time_test.go')
-rw-r--r--ride/moving_time_test.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/ride/moving_time_test.go b/ride/moving_time_test.go
new file mode 100644
index 0000000..402726f
--- /dev/null
+++ b/ride/moving_time_test.go
@@ -0,0 +1,72 @@
+package ride_test
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/martinlehoux/biking_home/ride"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func gpxWithPoints(t *testing.T, points string) ride.Ride {
+ t.Helper()
+ data := parserTestGPXPrefix + points + parserTestGPXSuffix
+ parsed, err := (ride.GPXRideParser{}).Parse(strings.NewReader(data))
+ require.NoError(t, err)
+ return parsed
+}
+
+func trackPoint(lat, lon string, timestamp string) string {
+ return `<trkpt lat="` + lat + `" lon="` + lon + `">` +
+ `<ele>100</ele>` +
+ `<time>` + timestamp + `</time>` +
+ `</trkpt>`
+}
+
+func TestMovingTimeCountsSteadyRiding(t *testing.T) {
+ parsed := gpxWithPoints(t,
+ trackPoint("43.0", "5.0", "2026-01-01T10:00:00Z")+
+ trackPoint("43.009", "5.0", "2026-01-01T10:01:00Z")+
+ trackPoint("43.018", "5.0", "2026-01-01T10:02:00Z"),
+ )
+
+ assert.Equal(t, 2*time.Minute, ride.MovingTime(parsed))
+}
+
+func TestMovingTimeExcludesSlowInterval(t *testing.T) {
+ parsed := gpxWithPoints(t,
+ trackPoint("43.0", "5.0", "2026-01-01T10:00:00Z")+
+ trackPoint("43.0005", "5.0", "2026-01-01T10:10:00Z")+
+ trackPoint("43.009", "5.0", "2026-01-01T10:12:00Z"),
+ )
+
+ assert.Equal(t, 2*time.Minute, ride.MovingTime(parsed))
+}
+
+func TestMovingTimeExcludesLunchBreak(t *testing.T) {
+ parsed := gpxWithPoints(t,
+ trackPoint("43.0", "5.0", "2026-01-01T10:00:00Z")+
+ trackPoint("43.0001", "5.0", "2026-01-01T10:30:00Z")+
+ trackPoint("43.009", "5.0", "2026-01-01T10:31:00Z"),
+ )
+
+ assert.Equal(t, time.Minute, ride.MovingTime(parsed))
+}
+
+func TestMovingTimeWithoutTimestamps(t *testing.T) {
+ data := parserTestGPXPrefix +
+ `<trkpt lat="43.0" lon="5.0"><ele>100</ele></trkpt>` +
+ `<trkpt lat="43.009" lon="5.0"><ele>100</ele></trkpt>` +
+ parserTestGPXSuffix
+
+ parsed, err := (ride.GPXRideParser{}).Parse(strings.NewReader(data))
+ require.NoError(t, err)
+
+ assert.Equal(t, time.Duration(0), ride.MovingTime(parsed))
+}
+
+func TestMovingTimeAlgorithmVersion(t *testing.T) {
+ assert.Equal(t, "v1", ride.MovingTimeAlgorithmVersion)
+} \ No newline at end of file