package rideanalysis_test import ( "testing" "time" "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" ) var attemptRideStart = time.Date(2026, 9, 1, 8, 0, 0, 0, time.UTC) func attemptRideForClimb(t *testing.T) (ride.Ride, official_climb.OfficialClimb) { t.Helper() parsed := ridetest.MustRide("1km at 14.7% | 1km at -6.7%", ridetest.StartCoord(43.61, 5.42), ridetest.StartElevation(300), ridetest.Sampling(1000), ridetest.StartTime(attemptRideStart)) return parsed, official_climb.OfficialClimb{Name: "Col de Test", StartCoord: parsed.Coords[0], EndCoord: parsed.Coords[1], Sample: parsed.Sample(0, 1)} } func TestAttemptsSnapshotsOfficialClimbs(t *testing.T) { parsed, climb := attemptRideForClimb(t) analysis := rideanalysis.Analyze(parsed, nil, []official_climb.OfficialClimb{{ID: 42, Name: climb.Name, StartCoord: climb.StartCoord, EndCoord: climb.EndCoord, Sample: climb.Sample}}, official_climb.DefaultMatchPolicy()) attempts := rideanalysis.Attempts(7, analysis, parsed) require.Len(t, attempts, 1) assert.Equal(t, int64(7), attempts[0].RideID) assert.Equal(t, int64(42), attempts[0].ClimbID) assert.Equal(t, 0, attempts[0].Occurrence) assert.InDelta(t, 1000, attempts[0].DistanceM, 1) assert.InDelta(t, 147, attempts[0].ElevationGainM, 1) assert.InDelta(t, 14.7, attempts[0].SlopePercent, 0.1) assert.Positive(t, attempts[0].Cotacol) assert.Equal(t, "v1", attempts[0].CotacolAlgoVersion) assert.Equal(t, attemptRideStart, attempts[0].StartedAt.UTC()) assert.False(t, attempts[0].EndedAt.IsZero(), "attempt end must come from ride timestamps") assert.Nil(t, attempts[0].AveragePowerW, "rides without a power meter record no average power") } func TestAttemptsSnapshotsAveragePower(t *testing.T) { parsed := ridetest.MustRide("0.5km at 10% w=200 | 0.5km at 10% w=400 | 1km at -5% w=100", ridetest.StartCoord(43.61, 5.42), ridetest.StartElevation(300), ridetest.Sampling(500), ridetest.StartTime(attemptRideStart)) official := official_climb.OfficialClimb{ID: 42, Name: "Col de Test", StartCoord: parsed.Coords[0], EndCoord: parsed.Coords[2], Sample: parsed.Sample(0, 2)} analysis := rideanalysis.Analyze(parsed, nil, []official_climb.OfficialClimb{official}, official_climb.DefaultMatchPolicy()) attempts := rideanalysis.Attempts(7, analysis, parsed) require.Len(t, attempts, 1) assert.InDelta(t, 1000, attempts[0].DistanceM, 1) // Mean of the power samples inside the climb interval: 200, 200, 400 W. require.NotNil(t, attempts[0].AveragePowerW) assert.InDelta(t, 800.0/3.0, *attempts[0].AveragePowerW, 0.01) } func TestAttemptsSkipsUnmatchedClimbs(t *testing.T) { parsed, _ := attemptRideForClimb(t) analysis := rideanalysis.Analyze(parsed, nil, nil, official_climb.DefaultMatchPolicy()) assert.Empty(t, rideanalysis.Attempts(7, analysis, parsed)) }