aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ride/climb.go38
-rw-r--r--ride/difficulty_test.go27
2 files changed, 49 insertions, 16 deletions
diff --git a/ride/climb.go b/ride/climb.go
index 8852cbf..29a4dcf 100644
--- a/ride/climb.go
+++ b/ride/climb.go
@@ -28,20 +28,19 @@ const (
type categoryInfo struct {
label string
- maxScore float64
+ minScore float64
}
-// categories describes each Category: its display label and the exclusive
-// Cotacol score ceiling it applies to. A score at or above CategoryOne's
-// ceiling is Hors Catégorie.
+// categories describes each Category: its display label and the inclusive
+// Cotacol score floor at which it starts applying. A score at or above
+// CategoryOne's floor is Hors Catégorie; CategoryNo has no floor.
var categories = map[Category]categoryInfo{
- CategoryNo: {label: "NO", maxScore: 35},
- CategoryFour: {label: "Cat 4", maxScore: 80},
- CategoryThree: {label: "Cat 3", maxScore: 180},
- CategoryTwo: {label: "Cat 2", maxScore: 250},
- CategoryOne: {label: "Cat 1", maxScore: 600},
- // Hors Catégorie has no ceiling.
- CategoryHC: {label: "HC", maxScore: math.Inf(+1)},
+ CategoryNo: {label: "NO", minScore: math.Inf(-1)},
+ CategoryFour: {label: "Cat 4", minScore: 35},
+ CategoryThree: {label: "Cat 3", minScore: 80},
+ CategoryTwo: {label: "Cat 2", minScore: 180},
+ CategoryOne: {label: "Cat 1", minScore: 250},
+ CategoryHC: {label: "HC", minScore: 600},
}
// climbDetectionMinimumScore is the climb-detection score below which a
@@ -144,16 +143,23 @@ func (c Category) String() string {
return categories[c].label
}
-// CategoryOf returns the difficulty class of a Cotacol score.
+// CategoryOf returns the difficulty class of a Cotacol score: the category
+// with the highest floor the score reaches.
func CategoryOf(cotacol float64) Category {
- best := CategoryHC
- bestMax := math.Inf(+1)
+ best := CategoryNo
+ bestMin := math.Inf(-1)
+ found := false
for category, info := range categories {
- if cotacol < info.maxScore && info.maxScore < bestMax {
+ if cotacol >= info.minScore && (!found || info.minScore > bestMin) {
best = category
- bestMax = info.maxScore
+ bestMin = info.minScore
+ found = true
}
}
+ if !found {
+ // NaN reaches no floor; preserve the original switch default (HC).
+ return CategoryHC
+ }
return best
}
diff --git a/ride/difficulty_test.go b/ride/difficulty_test.go
index 142ac0a..5773376 100644
--- a/ride/difficulty_test.go
+++ b/ride/difficulty_test.go
@@ -1,6 +1,7 @@
package ride_test
import (
+ "math"
"testing"
"github.com/martinlehoux/biking_home/ride"
@@ -48,6 +49,32 @@ func TestCotacolClimbNotAtStart(t *testing.T) {
assert.InDelta(t, 98.0, climbs[0].DifficultyScore(), 1e-9)
}
+func TestCategoryOfBoundaries(t *testing.T) {
+ cases := []struct {
+ score float64
+ want ride.Category
+ }{
+ {-5, ride.CategoryNo},
+ {30, ride.CategoryNo},
+ {35, ride.CategoryFour}, // 35 is the first floor, not below it
+ {79, ride.CategoryFour},
+ {80, ride.CategoryThree},
+ {179, ride.CategoryThree},
+ {180, ride.CategoryTwo},
+ {249, ride.CategoryTwo},
+ {250, ride.CategoryOne},
+ {599, ride.CategoryOne},
+ {600, ride.CategoryHC},
+ {700, ride.CategoryHC},
+ {math.NaN(), ride.CategoryHC}, // reaches no floor, like the original switch default
+ }
+ for _, c := range cases {
+ assert.Equal(t, c.want, ride.CategoryOf(c.score), "score %v", c.score)
+ }
+ assert.Equal(t, "Cat 1", ride.CategoryOne.String())
+ assert.Equal(t, "NO", ride.CategoryNo.String())
+}
+
func TestCotacolHautacamClimbfinderReference(t *testing.T) {
r, err := ride.ParseFile(parser, "../examples/2022-07-21.Pogacar.gpx")
assert.NoError(t, err)