mirror of
https://github.com/sstent/aicyclingcoach-go.git
synced 2026-04-05 20:42:47 +00:00
sync
This commit is contained in:
95
fitness-tui/internal/analysis/downsample.go
Normal file
95
fitness-tui/internal/analysis/downsample.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DownsampledPoint represents a single data point in a downsampled metric series
|
||||
type DownsampledPoint struct {
|
||||
TimeOffset int `json:"time_offset"` // Seconds from activity start
|
||||
Value float64 `json:"value"` // Average value during this segment
|
||||
Min float64 `json:"min,omitempty"`
|
||||
Max float64 `json:"max,omitempty"`
|
||||
}
|
||||
|
||||
// DownsampleMetric downsamples a metric array to the specified number of points
|
||||
func DownsampleMetric(data []float64, duration time.Duration, targetPoints int) []DownsampledPoint {
|
||||
if len(data) == 0 || targetPoints <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
totalSeconds := int(duration.Seconds())
|
||||
if totalSeconds <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Calculate segment duration in seconds (floating point for accuracy)
|
||||
segmentDuration := float64(totalSeconds) / float64(targetPoints)
|
||||
if segmentDuration < 1 {
|
||||
segmentDuration = 1
|
||||
}
|
||||
|
||||
// Preallocate segments array
|
||||
segments := make([]struct {
|
||||
sum float64
|
||||
count int
|
||||
min float64
|
||||
max float64
|
||||
}, targetPoints)
|
||||
|
||||
// Initialize min/max with zero values - we'll update them when we see data
|
||||
|
||||
// Assign data points to segments
|
||||
for i, value := range data {
|
||||
// Calculate time offset for this data point
|
||||
timeOffset := float64(i) * float64(totalSeconds) / float64(len(data))
|
||||
segmentIndex := int(timeOffset / segmentDuration)
|
||||
|
||||
// Clamp to valid segment range
|
||||
if segmentIndex >= targetPoints {
|
||||
segmentIndex = targetPoints - 1
|
||||
}
|
||||
|
||||
seg := &segments[segmentIndex]
|
||||
seg.sum += value
|
||||
seg.count++
|
||||
|
||||
// Initialize min/max on first value in segment
|
||||
if seg.count == 1 {
|
||||
seg.min = value
|
||||
seg.max = value
|
||||
} else {
|
||||
if value < seg.min {
|
||||
seg.min = value
|
||||
}
|
||||
if value > seg.max {
|
||||
seg.max = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build results array
|
||||
results := make([]DownsampledPoint, targetPoints)
|
||||
for j := 0; j < targetPoints; j++ {
|
||||
seg := &segments[j]
|
||||
timeOffset := int(float64(j) * segmentDuration)
|
||||
|
||||
if seg.count == 0 {
|
||||
// For empty segments, omit min/max
|
||||
results[j] = DownsampledPoint{
|
||||
TimeOffset: timeOffset,
|
||||
Value: 0,
|
||||
}
|
||||
} else {
|
||||
avg := seg.sum / float64(seg.count)
|
||||
results[j] = DownsampledPoint{
|
||||
TimeOffset: timeOffset,
|
||||
Value: avg,
|
||||
Min: seg.min,
|
||||
Max: seg.max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user