diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-20 15:47:52 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-20 15:47:52 +0200 |
| commit | 57e48b23987630936c84300d87ea5be682cb64bd (patch) | |
| tree | 96e840749bb658c2162788cedca627165747a254 /ride/moving_time_test.go | |
| parent | 992c36c6c7ce7e1e732866b3cd4fb8269a905875 (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.go | 72 |
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 |
