aboutsummaryrefslogtreecommitdiff
path: root/web/official-climbs.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-09-24 08:37:33 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-09-24 08:37:33 +0200
commit90614736f2a0c556f31eca5361b3c032f5f309f6 (patch)
tree81fda954dc2bff8ae8b0023b502343c455995272 /web/official-climbs.go
parente3d0beee5be3dd2854b0f4fc10905a4fcdc08f47 (diff)
Match official climbs against their recorded sample
An official climb matched on endpoint coordinates alone. On a loop ridden the other way the route meets the climb's top before its bottom, so pairing each start with the first end inside its lap window credited a 5.9 km passage as the 850 m Chemin de la Tour Pey Blanc, and a 49 km one on another ride. Climbs now keep the sample of the segment they were created from: the recorded length and elevation gain of the selected route points, captured by the ride detail and preview handlers and stored NOT NULL. Matching takes the whole route and drops any candidate outside ±50% of that sample on either axis, and attempts are refreshed by upsert and prune so re-analyzing a ride keeps the recorded-at stamp of the passages that survive. The backfill leaves the web package: backfill.Run recomputes ride values then official climb attempts as a single `go run . backfill` command (climbs-backfill is gone), rideanalysis owns the attempt snapshot, and the mountain-pass loading margin is shared. Applied to the stored rides it stamps 78 samples, drops the four bogus attempts and leaves ride 800 with its three Pont Rout passages.
Diffstat (limited to 'web/official-climbs.go')
-rw-r--r--web/official-climbs.go43
1 files changed, 3 insertions, 40 deletions
diff --git a/web/official-climbs.go b/web/official-climbs.go
index ce9d7cc..b312bb5 100644
--- a/web/official-climbs.go
+++ b/web/official-climbs.go
@@ -9,51 +9,14 @@ import (
"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.
+// analysisFor runs the same pipeline as the ride profile, so recorded attempts and displayed climbs 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.
+// recordOfficialClimbAttempts refreshes the ride's official-climb attempts; failures are logged, never fatal.
func (s *Server) recordOfficialClimbAttempts(rideID int64, parsed ride.Ride, analysis rideanalysis.Result) {
- attempts := attemptsFromAnalysis(rideID, analysis, parsed)
+ attempts := rideanalysis.Attempts(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)
}