diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-10-18 21:19:36 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-10-18 21:19:36 +0200 |
| commit | 8a0032d08862dcf28c653bee3fd21052e967e35d (patch) | |
| tree | 6cb0d8efe6f5c2bf9ce9eb3ef8306faecbe7cfeb /rideanalysis/profile_test.go | |
| parent | d575274d00c8da251df29d5d6a636044673944a3 (diff) | |
Cut climb profile sections in the backend
The chart steps are computed in Go and shipped as finished sections, next to
the profile statistics the climb package already owns: climb.ProfileSections
cuts a Route at its widest bends under a ProfilePolicy, and
rideanalysis.MergeClimbProfile takes over the merge that lived in
web/climbs.go, so web only loads the attempt rides and maps the DTOs.
The payload carries sections instead of raw elevations, and the browser keeps
what needs the canvas: slope bands, labels, distance ticks and colours.
official-climb-profile-logic.ts and its tests are gone, replaced by
climb/profile_test.go and a page test reading the embedded profile JSON.
Diffstat (limited to 'rideanalysis/profile_test.go')
| -rw-r--r-- | rideanalysis/profile_test.go | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/rideanalysis/profile_test.go b/rideanalysis/profile_test.go new file mode 100644 index 0000000..5fb0772 --- /dev/null +++ b/rideanalysis/profile_test.go @@ -0,0 +1,56 @@ +package rideanalysis_test + +import ( + "testing" + + "github.com/martinlehoux/biking_home/climb" + "github.com/martinlehoux/biking_home/internal/ridetest" + "github.com/martinlehoux/biking_home/official_climb" + "github.com/martinlehoux/biking_home/ride" + "github.com/martinlehoux/biking_home/rideanalysis" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMergeClimbProfileMediansRecordedPassages(t *testing.T) { + parsed, officialClimb := attemptRideForClimb(t) + officialClimb.ID = 42 + // Same trace 10 m higher: the merge must average the two passages, not pick one. + shifted := ridetest.MustRide("1km at 14.7% | 1km at -6.7%", ridetest.StartCoord(43.61, 5.42), ridetest.StartElevation(310), ridetest.Sampling(1000), ridetest.StartTime(attemptRideStart)) + analysis := rideanalysis.Analyze(parsed, nil, []official_climb.OfficialClimb{officialClimb}, official_climb.DefaultMatchPolicy()) + aggregate := official_climb.AggregateAttempts(rideanalysis.Attempts(1, analysis, parsed)) + + profile := rideanalysis.MergeClimbProfile(officialClimb, []ride.Ride{parsed, shifted}, aggregate, official_climb.DefaultMatchPolicy(), climb.DefaultProfilePolicy()) + + assert.InDelta(t, 1000, profile.DistanceM, 0.01) + require.NotEmpty(t, profile.Sections) + assert.InDelta(t, 305, profile.Sections[0].StartElevationM, 1) + lastSection := profile.Sections[len(profile.Sections)-1] + assert.InDelta(t, 452, lastSection.EndElevationM, 2) + assert.InDelta(t, 1000, lastSection.EndDistanceM, 0.01) +} + +func TestMergeClimbProfileRouteKeepsRecordedPoints(t *testing.T) { + parsed, officialClimb := attemptRideForClimb(t) + officialClimb.ID = 42 + analysis := rideanalysis.Analyze(parsed, nil, []official_climb.OfficialClimb{officialClimb}, official_climb.DefaultMatchPolicy()) + aggregate := official_climb.AggregateAttempts(rideanalysis.Attempts(1, analysis, parsed)) + + profile := rideanalysis.MergeClimbProfile(officialClimb, []ride.Ride{parsed}, aggregate, official_climb.DefaultMatchPolicy(), climb.DefaultProfilePolicy()) + + // Full recorded precision: every matched segment point is present, not a fixed resample. + require.Len(t, profile.Route, 2) + assert.InDelta(t, parsed.Coords[0].Lon, profile.Route[0].Lon, 1e-9) + assert.InDelta(t, parsed.Coords[0].Lat, profile.Route[0].Lat, 1e-9) + assert.InDelta(t, parsed.Coords[1].Lon, profile.Route[1].Lon, 1e-9) + assert.InDelta(t, parsed.Coords[1].Lat, profile.Route[1].Lat, 1e-9) +} + +func TestMergeClimbProfileWithoutRides(t *testing.T) { + _, officialClimb := attemptRideForClimb(t) + + profile := rideanalysis.MergeClimbProfile(officialClimb, nil, official_climb.Aggregate{DistanceM: 1000}, official_climb.DefaultMatchPolicy(), climb.DefaultProfilePolicy()) + + assert.Empty(t, profile.Sections) + assert.Nil(t, profile.Route) +} |
