aboutsummaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/cli.go22
-rw-r--r--cli/cli_test.go28
2 files changed, 32 insertions, 18 deletions
diff --git a/cli/cli.go b/cli/cli.go
index bd54b35..e2138cd 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -28,11 +28,6 @@ var (
enablePprof = flag.Bool("pprof", false, "expose pprof endpoints at /debug/pprof")
)
-const (
- backtestMinimumDistanceM = 10_000
- backtestMinimumMovingRatio = 0.8
-)
-
func Run(db *sql.DB, configPath string, appConfig config.Config) {
switch {
case *fetchOSM:
@@ -127,16 +122,21 @@ 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, "min_moving_ratio", backtestMinimumMovingRatio, "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), "mean_err_pct", math.Round(meanAbsErr(absErrors)*10)/10, "stderr_pct", math.Round(stdErrAbsErr(absErrors)*10)/10)
return nil
}
func isBacktestEligible(item ride.StoredRide) bool {
- return item.GPXPath != "" &&
- item.DistanceM >= backtestMinimumDistanceM &&
- item.ExternalMovingTimeS > 0 &&
- item.ElapsedTimeS > 0 &&
- float64(item.ExternalMovingTimeS)/float64(item.ElapsedTimeS) >= backtestMinimumMovingRatio
+ if item.GPXPath == "" || item.ExternalMovingTimeS <= 0 || item.ElapsedTimeS <= 0 {
+ return false
+ }
+ if !item.IsCategorised() {
+ return false
+ }
+ if item.RideType == ride.RideTypeIndoor || item.RideType == ride.RideTypeTransport {
+ return false
+ }
+ return true
}
func meanAbsErr(absErrors []float64) float64 {
diff --git a/cli/cli_test.go b/cli/cli_test.go
index 3e2afe2..28260e0 100644
--- a/cli/cli_test.go
+++ b/cli/cli_test.go
@@ -8,23 +8,37 @@ import (
)
func TestBacktestEligibilityExcludesShortDistances(t *testing.T) {
- short := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000 - 1, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000}
- minimum := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000}
+ 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}
- assert.False(t, isBacktestEligible(short))
+ 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}
- boundary := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 8_000, ElapsedTimeS: 10_000}
- noElapsed := ride.StoredRide{GPXPath: "ride.gpx", DistanceM: 10_000, ExternalMovingTimeS: 1}
+ 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}
- assert.False(t, isBacktestEligible(low))
+ assert.True(t, isBacktestEligible(low))
assert.True(t, isBacktestEligible(boundary))
assert.False(t, isBacktestEligible(noElapsed))
}
+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}
+
+ assert.True(t, isBacktestEligible(base))
+ assert.False(t, isBacktestEligible(uncategorised))
+ assert.False(t, isBacktestEligible(indoor))
+ assert.False(t, isBacktestEligible(transport))
+ assert.True(t, isBacktestEligible(race))
+}
+
func TestBacktestStatsUseUnroundedSamples(t *testing.T) {
absErrors := []float64{0, 0.1, 0.1}