aboutsummaryrefslogtreecommitdiff
path: root/climb/difficulty_test.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-08-27 11:21:17 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-08-27 11:21:17 +0200
commit825cf388b55a6c8441e02149749bb6e2968477c0 (patch)
treee3394cef97aa43df9775a1465d3c99f386ce3a9b /climb/difficulty_test.go
parent366e7b0d671395c2ad32f85ee7706a592db69d15 (diff)
Extract climb detection into a dedicated climb package
Move climb detection, slope, and Cotacol scoring out of ride into a new climb package that operates on a geometry-only Route (distances, elevations, coords). Ride embeds climb.Route and keeps its timing and telemetry columns. - climb.Detect returns []DetectedClimb (Climb + ride-absolute Start). - Climb owns its sliced geometry; indices are local, with Start reported at the detection boundary for absolute position. - Detected bounds use core.Interval; freeRegions claim suppression fixed. - ride no longer builds slices: ClimbFromDist/ClimbFromIndexes/climbFrom and ride.Climb/Interval/Range are removed; rideanalysis, mountain_pass MatchClimb, and web views are rewired to climb types. - New internal/routetest builds climb.Route from the ride DSL; ridetest reuses it and adds timestamps and telemetry. - Tests moved to climb (synthetic detection and difficulty) and kept in ride for GPX-example integration; ride.comparison detection coverage retained. - mountain_pass.nearestCrossing now reuses geo.Nearest.
Diffstat (limited to 'climb/difficulty_test.go')
-rw-r--r--climb/difficulty_test.go72
1 files changed, 72 insertions, 0 deletions
diff --git a/climb/difficulty_test.go b/climb/difficulty_test.go
new file mode 100644
index 0000000..92c0a74
--- /dev/null
+++ b/climb/difficulty_test.go
@@ -0,0 +1,72 @@
+package climb_test
+
+import (
+ "math"
+ "testing"
+
+ "github.com/martinlehoux/biking_home/climb"
+ "github.com/martinlehoux/biking_home/internal/routetest"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestCotacolConstantClimb(t *testing.T) {
+ route := routetest.MustRoute("2km at 7%")
+ assert.InDelta(t, 98.0, climb.Cotacol(route.Distances, route.Elevations), 1e-9)
+}
+
+func TestCotacolIgnoresDescent(t *testing.T) {
+ route := routetest.MustRoute("2km at 7% | 2km at -7%")
+ assert.InDelta(t, 98.0, climb.Cotacol(route.Distances, route.Elevations), 1e-9)
+}
+
+func TestCotacolFlatIsZero(t *testing.T) {
+ route := routetest.MustRoute("5km at 0%")
+ assert.Zero(t, climb.Cotacol(route.Distances, route.Elevations))
+}
+
+func TestCotacolSteepShortCountsMore(t *testing.T) {
+ steep := routetest.MustRoute("0.5km at 14%")
+ long := routetest.MustRoute("1km at 7%")
+ assert.InDelta(t, 98.0, climb.Cotacol(steep.Distances, steep.Elevations), 1e-9)
+ assert.InDelta(t, 49.0, climb.Cotacol(long.Distances, long.Elevations), 1e-9)
+}
+
+func TestCotacolPrecisionInvariant(t *testing.T) {
+ coarse := routetest.MustRoute("3km at 5%")
+ fine := routetest.MustRoute("3km at 5%", routetest.Sampling(50))
+ assert.InDelta(t, climb.Cotacol(coarse.Distances, coarse.Elevations), climb.Cotacol(fine.Distances, fine.Elevations), 1e-9)
+}
+
+func TestCotacolClimbNotAtStart(t *testing.T) {
+ route := routetest.MustRoute("1km at 0% | 2km at 7%")
+ detected := climb.Detect(route, nil, climb.ClimbSlopeMinimum)
+ assert.Len(t, detected, 1)
+ assert.InDelta(t, 98.0, detected[0].Climb.DifficultyScore(), 1e-9)
+}
+
+func TestCategoryOfBoundaries(t *testing.T) {
+ cases := []struct {
+ score float64
+ want climb.Category
+ }{
+ {-5, climb.CategoryNo},
+ {30, climb.CategoryNo},
+ {35, climb.CategoryFour}, // 35 is the first floor, not below it
+ {79, climb.CategoryFour},
+ {80, climb.CategoryThree},
+ {179, climb.CategoryThree},
+ {180, climb.CategoryTwo},
+ {249, climb.CategoryTwo},
+ {250, climb.CategoryOne},
+ {599, climb.CategoryOne},
+ {600, climb.CategoryHC},
+ {700, climb.CategoryHC},
+ {math.NaN(), climb.CategoryHC}, // reaches no floor, like the original switch default
+ }
+
+ for _, c := range cases {
+ assert.Equal(t, c.want, climb.CategoryOf(c.score), "score %v", c.score)
+ }
+ assert.Equal(t, "Cat 1", climb.CategoryOne.String())
+ assert.Equal(t, "NO", climb.CategoryNo.String())
+}