diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-25 08:46:58 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-25 08:46:58 +0200 |
| commit | 077aa1700fbcaf3f499c2178e0e58e89ae9dcaf8 (patch) | |
| tree | 020d0d8b34ab7b47cf551b50034c1e086a247af3 /rideanalysis | |
| parent | 90614736f2a0c556f31eca5361b3c032f5f309f6 (diff) | |
Chart power vs time for official climb attempts
Snapshot average power per attempt in a nullable average_power_w column (backfilled from stored rides), plot each powered attempt on the climb page as duration vs average watts with ride hover and click-through, and show avg power in the attempts table.
Diffstat (limited to 'rideanalysis')
| -rw-r--r-- | rideanalysis/attempts.go | 3 | ||||
| -rw-r--r-- | rideanalysis/attempts_test.go | 14 |
2 files changed, 16 insertions, 1 deletions
diff --git a/rideanalysis/attempts.go b/rideanalysis/attempts.go index faf02e9..0a89895 100644 --- a/rideanalysis/attempts.go +++ b/rideanalysis/attempts.go @@ -9,6 +9,7 @@ import ( func Attempts(rideID int64, analysis Result, parsed ride.Ride) []official_climb.Attempt { occurrences := make(map[int64]int) attempts := make([]official_climb.Attempt, 0) + powers, _ := parsed.Powers() for _, analyzed := range analysis.Climbs { if analyzed.OfficialClimbID == 0 { continue @@ -23,7 +24,7 @@ func Attempts(rideID int64, analysis Result, parsed ride.Ride) []official_climb. Cotacol: analyzed.Cotacol, StartIndex: analyzed.Interval.Start, EndIndex: analyzed.Interval.End, - }, parsed.Timestamps())) + }, parsed.Timestamps(), powers)) } return attempts } diff --git a/rideanalysis/attempts_test.go b/rideanalysis/attempts_test.go index 6fab8e1..b5b1271 100644 --- a/rideanalysis/attempts_test.go +++ b/rideanalysis/attempts_test.go @@ -36,6 +36,20 @@ func TestAttemptsSnapshotsOfficialClimbs(t *testing.T) { 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) { |
