package climb import ( "fmt" "math" ) const ( ClimbDistanceMinimum = 500 ClimbSlopeMinimum = 0.035 detectionMinimumScore = 35 ) type Category int const ( CategoryNo Category = iota CategoryFour CategoryThree CategoryTwo CategoryOne CategoryHC ) type categoryInfo struct { label string minScore float64 } var categories = map[Category]categoryInfo{ 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}, } func (c Category) String() string { return categories[c].label } func CategoryOf(cotacol float64) Category { best := CategoryNo bestMin := math.Inf(-1) found := false for category, info := range categories { if cotacol >= info.minScore && (!found || info.minScore > bestMin) { best = category bestMin = info.minScore found = true } } if !found { // NaN reaches no floor; preserve the original switch default (HC). return CategoryHC } return best } type Climb struct { Route } func (climb Climb) String() string { cotacol := climb.DifficultyScore() return fmt.Sprintf("%.1fkm at %.1f%% (%d pts - %s)", climb.Distances[len(climb.Distances)-1]/1000, Slope(climb.Distances, climb.Elevations)*100, int(cotacol), CategoryOf(cotacol)) } func (climb Climb) DifficultyScore() float64 { return Cotacol(climb.Distances, climb.Elevations) } func (climb Climb) Top() (int, float64) { top := 0 for i := 1; i < len(climb.Elevations); i++ { if climb.Elevations[i] > climb.Elevations[top] { top = i } } return top, climb.Elevations[top] } func Slope(distances, elevations []float64) float64 { end := len(distances) - 1 return (elevations[end] - elevations[0]) / (distances[end] - distances[0]) }