aboutsummaryrefslogtreecommitdiff
path: root/official_climb/aggregate_test.go
blob: 71494940007cb6014c910df24493e1e10c6fb96f (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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

package official_climb

import (
	"testing"
	"time"

	"github.com/martinlehoux/biking_home/climb"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMedianOddAndEven(t *testing.T) {
	assert.Equal(t, 3.0, Median([]float64{5, 1, 3}))
	assert.Equal(t, 2.5, Median([]float64{1, 2, 3, 4}))
	assert.Equal(t, 0.0, Median(nil))
}

func TestAggregateAttemptsUsesMedians(t *testing.T) {
	attempts := []Attempt{
		{DistanceM: 3000, ElevationGainM: 200, SlopePercent: 6.7, Cotacol: 80, StartedAt: time.Date(2026, 1, 1, 8, 0, 0, 0, time.UTC), EndedAt: time.Date(2026, 1, 1, 8, 20, 0, 0, time.UTC)},
		{DistanceM: 3200, ElevationGainM: 210, SlopePercent: 6.6, Cotacol: 84, StartedAt: time.Date(2026, 2, 1, 8, 0, 0, 0, time.UTC), EndedAt: time.Date(2026, 2, 1, 8, 15, 0, 0, time.UTC)},
		{DistanceM: 6000, ElevationGainM: 900, SlopePercent: 15, Cotacol: 400, StartedAt: time.Date(2026, 3, 1, 8, 0, 0, 0, time.UTC), EndedAt: time.Date(2026, 3, 1, 8, 40, 0, 0, time.UTC)},
	}
	aggregate := AggregateAttempts(attempts)
	assert.Equal(t, 3, aggregate.Attempts)
	assert.Equal(t, 3200.0, aggregate.DistanceM)
	assert.Equal(t, 210.0, aggregate.ElevationGainM)
	assert.Equal(t, 6.7, aggregate.SlopePercent)
	assert.Equal(t, 84.0, aggregate.Cotacol)
	require.True(t, aggregate.HasBest)
	assert.Equal(t, 15*time.Minute, aggregate.BestDuration)
}

func TestAggregateAttemptsIgnoresBrokenDurations(t *testing.T) {
	attempts := []Attempt{
		{DistanceM: 3000, StartedAt: time.Date(2026, 1, 1, 8, 0, 0, 0, time.UTC), EndedAt: time.Date(2026, 1, 1, 7, 0, 0, 0, time.UTC)},
		{DistanceM: 3200, StartedAt: time.Date(2026, 2, 1, 8, 0, 0, 0, time.UTC), EndedAt: time.Date(2026, 2, 1, 8, 15, 0, 0, time.UTC)},
	}
	aggregate := AggregateAttempts(attempts)
	require.True(t, aggregate.HasBest)
	assert.Equal(t, 15*time.Minute, aggregate.BestDuration)
}

func TestAggregateAttemptsEmpty(t *testing.T) {
	aggregate := AggregateAttempts(nil)
	assert.Equal(t, 0, aggregate.Attempts)
	assert.False(t, aggregate.HasBest)
	assert.Equal(t, climb.CategoryNo, aggregate.Category)
}

func TestNormalizeProfileLinearSegment(t *testing.T) {
	segment := climb.Climb{
		Distances:  []float64{0, 500, 1000},
		Elevations: []float64{100, 150, 200}}
	samples := NormalizeProfile(segment)
	require.Len(t, samples, profileSteps)
	assert.InDelta(t, 100, samples[0], 0.01)
	assert.InDelta(t, 200, samples[profileSteps-1], 0.01)
	assert.InDelta(t, 100+100*float64(profileSteps/2)/float64(profileSteps-1), samples[profileSteps/2], 0.01)
}

func TestMedianProfileRobustToOutlier(t *testing.T) {
	straight := make([]float64, profileSteps)
	for i := range straight {
		straight[i] = float64(i)
	}
	spiked := make([]float64, profileSteps)
	for i := range spiked {
		spiked[i] = float64(i) + 500
	}
	median := MedianProfile([][]float64{straight, spiked, straight})
	require.Len(t, median, profileSteps)
	assert.InDelta(t, 10, median[10], 0.01)
	assert.InDelta(t, 49, median[49], 0.01)
}