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 }