feat: extend equipment and workout models with service tracking

This commit is contained in:
Blake Ridgway
2026-02-12 10:09:50 -06:00
parent eb9ac1b67a
commit 178ffb3425
37 changed files with 4005 additions and 40 deletions

View File

@@ -0,0 +1,94 @@
package activity
import (
"bytes"
"fmt"
"github.com/muktihari/fit/decoder"
"github.com/muktihari/fit/profile/basetype"
"github.com/muktihari/fit/profile/mesgdef"
"github.com/muktihari/fit/profile/typedef"
)
// ParseFIT decodes a FIT activity file and extracts session-level metrics.
func ParseFIT(data []byte) (*ParsedActivity, error) {
dec := decoder.New(bytes.NewReader(data))
if !dec.Next() {
return nil, fmt.Errorf("empty or invalid FIT file")
}
fit, err := dec.Decode()
if err != nil {
return nil, fmt.Errorf("failed to decode FIT file: %w", err)
}
result := &ParsedActivity{}
foundSession := false
for i := range fit.Messages {
if fit.Messages[i].Num != typedef.MesgNumSession {
continue
}
foundSession = true
session := mesgdef.NewSession(&fit.Messages[i])
// total_elapsed_time: FIT stores as uint32 with scale 1000 (ms -> seconds)
if session.TotalElapsedTime != basetype.Uint32Invalid {
result.Duration = int(session.TotalElapsedTime / 1000)
}
// total_distance: FIT stores as uint32 with scale 100 (centimeters -> meters)
// Convert to km for our model
if session.TotalDistance != basetype.Uint32Invalid {
result.Distance = float64(session.TotalDistance) / 100.0 / 1000.0
}
// Power (no scale)
if session.AvgPower != basetype.Uint16Invalid {
result.AvgPower = int(session.AvgPower)
}
if session.MaxPower != basetype.Uint16Invalid {
result.MaxPower = int(session.MaxPower)
}
// Heart rate (no scale)
if session.AvgHeartRate != basetype.Uint8Invalid {
result.AvgHR = int(session.AvgHeartRate)
}
if session.MaxHeartRate != basetype.Uint8Invalid {
result.MaxHR = int(session.MaxHeartRate)
}
// Calories (no scale)
if session.TotalCalories != basetype.Uint16Invalid {
result.CaloriesBurned = int(session.TotalCalories)
}
// Elevation gain (no scale, meters)
if session.TotalAscent != basetype.Uint16Invalid {
result.ElevGain = int(session.TotalAscent)
}
// Cadence (no scale)
if session.AvgCadence != basetype.Uint8Invalid {
result.AvgCadence = int(session.AvgCadence)
}
// Start time
if session.StartTime.IsZero() {
result.StartTime = session.Timestamp
} else {
result.StartTime = session.StartTime
}
break // use first session
}
if !foundSession {
return nil, fmt.Errorf("no session data found in FIT file")
}
return result, nil
}