aboutsummaryrefslogtreecommitdiff
path: root/web/official-climbs.go
blob: ce9d7cc359f8233e20493db97dbe831f3b7cd5e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

package web

import (
	"log/slog"

	"github.com/martinlehoux/biking_home/mountain_pass"
	"github.com/martinlehoux/biking_home/official_climb"
	"github.com/martinlehoux/biking_home/ride"
	"github.com/martinlehoux/biking_home/rideanalysis"
)

// analysisFor runs the ride analysis pipeline the same way the ride profile
// does, so recorded attempts and displayed climbs always agree.
func analysisFor(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) rideanalysis.Result {
	return rideanalysis.Analyze(parsed, passes, officialClimbs, matchPolicy)
}

// attemptsFromAnalysis extracts one attempt per official-climb occurrence.
// Occurrences are numbered per climb in route order (0, 1, … for laps).
func attemptsFromAnalysis(rideID int64, analysis rideanalysis.Result, parsed ride.Ride) []official_climb.Attempt {
	occurrences := make(map[int64]int)
	attempts := make([]official_climb.Attempt, 0)
	for _, analyzed := range analysis.Climbs {
		if analyzed.OfficialClimbID == 0 {
			continue
		}
		occurrence := occurrences[analyzed.OfficialClimbID]
		occurrences[analyzed.OfficialClimbID] = occurrence + 1
		attempts = append(attempts, official_climb.AttemptFromStats(rideID, occurrence, official_climb.AttemptStats{
			OfficialClimbID: analyzed.OfficialClimbID,
			DistanceM:       analyzed.DistanceM,
			ElevationGainM:  elevationGain(analyzed),
			SlopePercent:    analyzed.SlopePercent,
			Cotacol:         analyzed.Cotacol,
			StartIndex:      analyzed.Interval.Start,
			EndIndex:        analyzed.Interval.End,
		}, parsed.Timestamps()))
	}
	return attempts
}

func elevationGain(analyzed rideanalysis.AnalyzedClimb) float64 {
	gain := 0.0
	for i := 1; i < len(analyzed.Segment.Elevations); i++ {
		if diff := analyzed.Segment.Elevations[i] - analyzed.Segment.Elevations[i-1]; diff > 0 {
			gain += diff
		}
	}
	return gain
}

// recordOfficialClimbAttempts persists the ride's official-climb attempts,
// replacing any previously recorded ones. Failures are logged, never fatal:
// attempts are an optimization over on-the-fly matching.
func (s *Server) recordOfficialClimbAttempts(rideID int64, parsed ride.Ride, analysis rideanalysis.Result) {
	attempts := attemptsFromAnalysis(rideID, analysis, parsed)
	if err := official_climb.ReplaceRideAttempts(s.db, rideID, attempts); err != nil {
		slog.Warn("Failed to record official climb attempts", "ride_id", rideID, "error", err)
	}
}