aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-20 18:48:47 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-20 18:48:47 +0200
commit5242b55d878d56b71d443a440c7017015b9df2dd (patch)
treef98bf4568b65c178ed75c86f4d580700e1654c15
parent3c8dbdbbb13ca233bd03466de2f0ea4fc4be38d4 (diff)
chore(bench): exclude rides with low moving time ratio
-rw-r--r--cli/cli.go13
-rw-r--r--cli/cli_test.go14
-rw-r--r--ride/moving_time.go4
3 files changed, 24 insertions, 7 deletions
diff --git a/cli/cli.go b/cli/cli.go
index cac29ec..37f64bb 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -32,7 +32,10 @@ var (
backtest = flag.Bool("backtest", false, "run data benchmarks comparing computed values against stored reference values")
)
-const backtestMinimumDistanceM = 10_000
+const (
+ backtestMinimumDistanceM = 10_000
+ backtestMinimumMovingRatio = 0.8
+)
func Run(db *sql.DB, configPath string, appConfig config.Config) {
switch {
@@ -134,12 +137,16 @@ func runBacktest(db *sql.DB) error {
fmt.Fprintf(table, "p%g\t%.1f\n", p, distribution[p])
}
table.Flush()
- slog.Info("Moving time backtest", "rides", len(samples), "min_distance_m", backtestMinimumDistanceM, "mean_err_pct", math.Round(meanAbsErr(absErrors)*10)/10, "stderr_pct", math.Round(stdErrAbsErr(absErrors)*10)/10)
+ slog.Info("Moving time backtest", "rides", len(samples), "min_distance_m", backtestMinimumDistanceM, "min_moving_ratio", backtestMinimumMovingRatio, "mean_err_pct", math.Round(meanAbsErr(absErrors)*10)/10, "stderr_pct", math.Round(stdErrAbsErr(absErrors)*10)/10)
return nil
}
func isBacktestEligible(item rides.Ride) bool {
- return item.GPXPath != "" && item.DistanceM >= backtestMinimumDistanceM && item.MovingTimeS > 0
+ return item.GPXPath != "" &&
+ item.DistanceM >= backtestMinimumDistanceM &&
+ item.MovingTimeS > 0 &&
+ item.ElapsedTimeS > 0 &&
+ float64(item.MovingTimeS)/float64(item.ElapsedTimeS) >= backtestMinimumMovingRatio
}
func meanAbsErr(absErrors []float64) float64 {
diff --git a/cli/cli_test.go b/cli/cli_test.go
index 6a3e148..59dec79 100644
--- a/cli/cli_test.go
+++ b/cli/cli_test.go
@@ -8,13 +8,23 @@ import (
)
func TestBacktestEligibilityExcludesShortDistances(t *testing.T) {
- short := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000 - 1, MovingTimeS: 1}
- minimum := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000, MovingTimeS: 1}
+ short := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000 - 1, MovingTimeS: 8_000, ElapsedTimeS: 10_000}
+ minimum := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000, MovingTimeS: 8_000, ElapsedTimeS: 10_000}
assert.False(t, isBacktestEligible(short))
assert.True(t, isBacktestEligible(minimum))
}
+func TestBacktestEligibilityExcludesLowMovingRatio(t *testing.T) {
+ low := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000, MovingTimeS: 7_999, ElapsedTimeS: 10_000}
+ boundary := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000, MovingTimeS: 8_000, ElapsedTimeS: 10_000}
+ noElapsed := rides.Ride{GPXPath: "ride.gpx", DistanceM: 10_000, MovingTimeS: 1}
+
+ assert.False(t, isBacktestEligible(low))
+ assert.True(t, isBacktestEligible(boundary))
+ assert.False(t, isBacktestEligible(noElapsed))
+}
+
func TestBacktestStatsUseUnroundedSamples(t *testing.T) {
absErrors := []float64{0, 0.1, 0.1}
diff --git a/ride/moving_time.go b/ride/moving_time.go
index 7d7f5fe..84cc28e 100644
--- a/ride/moving_time.go
+++ b/ride/moving_time.go
@@ -16,8 +16,8 @@ const movingTimeMinPowerW = 20
// that can count as moving. Longer gaps are pauses (stops, lost signal).
const movingTimeMaxGap = 120 * time.Second
-// Bench (117 rides, `-backtest`, distance >= 10 km):
-// absMean 0.6%, stderr 0.2, p50 0.2, p75 0.4, p90 1.2, p95 2.2, p99 4.8.
+// Bench (104 rides, `-backtest`, distance >= 10 km, moving/elapsed ratio >= 0.8):
+// absMean 0.4%, stderr 0.1, p50 0.2, p75 0.4, p90 0.8, p95 1.4, p99 2.9.
func MovingTime(r Ride) time.Duration {
return movingTime(r, movingTimeMinSpeedMps, movingTimeMaxGap)
}