Skip to content

Commit

Permalink
[453] strength - handle cases where some exercises are missed from th…
Browse files Browse the repository at this point in the history
…e class plan (#459)

* [453] strength - handle cases where some exercises are missed from the class plan

* [453] polish
  • Loading branch information
philosowaffle committed Apr 7, 2023
1 parent 487b288 commit abb1e57
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 60 deletions.
56 changes: 1 addition & 55 deletions src/Common/Dto/P2GWorkout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,60 +47,6 @@ public static WorkoutType GetWorkoutType(this Workout workout)
};
}

public static ICollection<P2GExercise> GetClassPlanExercises(Workout workout, RideSegments rideSegments)
{
var movements = new List<P2GExercise>();

var trackedRepData = workout?.Movement_Tracker_Data?.Completed_Movements_Summary_Data?.Repetition_Summary_Data;
if (trackedRepData is not null && trackedRepData.Count > 0)
{
foreach (var repData in trackedRepData)
{
var movement = new P2GExercise()
{
Id = repData.Movement_Id,
Name = repData.Movement_Name,
Type = repData.Is_Hold ? MovementTargetType.Time : MovementTargetType.Reps,
StartOffsetSeconds = repData.Offset,
DurationSeconds = repData.Length,
Reps = repData.Completed_Number,
Weight = new P2GWeight()
{
Unit = repData?.Weight?.FirstOrDefault()?.Weight_Data?.Weight_Unit,
Value = repData?.Weight?.FirstOrDefault().Weight_Data?.Weight_Value ?? 0
}
};
movements.Add(movement);
}

return movements;
}

var segments = rideSegments?.Segments?.Segment_List;
if (segments is null || segments.Count <= 0) return movements;

foreach (var segment in segments)
{
if (segment.SubSegments_V2 is null || segment.SubSegments_V2.Count <= 0) continue;
foreach (var subSegment in segment.SubSegments_V2)
{
var mov = subSegment.Movements?.FirstOrDefault();
if (mov is null) continue;
var movement = new P2GExercise()
{
Id = mov.Id,
Name = mov.Name,
Type = MovementTargetType.Time,
StartOffsetSeconds = subSegment.Offset.GetValueOrDefault(),
DurationSeconds = subSegment.Length.GetValueOrDefault(),
Reps = subSegment.Rounds
};
movements.Add(movement);
}
}

return movements;
}
}

public record P2GExercise
Expand All @@ -111,7 +57,7 @@ public record P2GExercise
public int DurationSeconds { get; init; }
public MovementTargetType Type { get; init; }
public int? Reps { get; init; }
public P2GWeight? Weight { get; init; }
public P2GWeight Weight { get; init; }
}

public record P2GWeight
Expand Down
5 changes: 2 additions & 3 deletions src/Common/Dto/Peloton/Segment.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;

namespace Common.Dto.Peloton;

Expand All @@ -23,5 +22,5 @@ public record Movement
{
public string Id { get; init; }
public string Name { get; init; }

public string Weight_Level { get; init; } // enum { heavy, medium, ??? }
}
2 changes: 2 additions & 0 deletions src/Common/Dto/Unit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public static WeightUnit GetWeightUnit(string unit)
{
case "lb": return WeightUnit.Pounds;
case "kg": return WeightUnit.Kilograms;
case "":
case null:
case "null": return WeightUnit.None;
default:
Log.Error("Found unknown weight unit {@Unit}", unit);
Expand Down
10 changes: 9 additions & 1 deletion src/Conversion/ExerciseMapping.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Dynastream.Fit;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Conversion;

Expand All @@ -11,6 +10,9 @@ public static class ExerciseMapping
// A
/* AMRAP */ "98cde50f696746ff98727d5362229cfb",

// D
/* Demo */ "e54412161b594e54a86d6ef23ea3d017",

// T
/* Transition */ "a76c5edc0641475189442ecad456057a",

Expand All @@ -35,19 +37,25 @@ public static class ExerciseMapping
/* Clean */ { "f2a28d3ebf3c4844a704d2b94e283099", new (ExerciseCategory.OlympicLift, OlympicLiftExerciseName.DumbbellClean) },
/* Concentrated Curl */ { "3695ef0ec2ce484faedc8ce2bfa2819d", new (ExerciseCategory.Curl, CurlExerciseName.SeatedDumbbellBicepsCurl) },
/* Crunch */ { "61ac0d64602c48fba25af7e5e5dc1f97", new (ExerciseCategory.Crunch, CrunchExerciseName.Crunch) },
/* Cursh Press */ { "4899db2664ce47da8ec14282282d3b0d", new (ExerciseCategory.BenchPress, BenchPressExerciseName.CloseGripBarbellBenchPress) },
/* Cross Body Curl */ { "a66b797fc2014b799cc0cb114d9c5079", new (ExerciseCategory.Curl, CurlExerciseName.CrossBodyDumbbellHammerCurl) },

// D
/* Dead Bug */ { "3001f790c7ca471e8ba6d1b57a3a842d", new (ExerciseCategory.HipStability, HipStabilityExerciseName.DeadBug) },
/* Deadlift */ { "cd6046306b2c4c4a8f40e169ec924eb9", new (ExerciseCategory.Deadlift, DeadliftExerciseName.DumbbellDeadlift) },
/* Dumbbell Pushup */ { "ce8c746fb5224e9dbc401fef0013a54f", new (ExerciseCategory.PushUp, PushUpExerciseName.PushUp) },
/* Dumbbell Single Leg Deadlift */ { "6dd608bcc9394b49a68a918359839202", new (ExerciseCategory.Deadlift, DeadliftExerciseName.SingleLegRomanianDeadliftWithDumbbell) },
/* Dumbbell Squat */ { "7d82b59462a54e61926077ded0becae5", new (ExerciseCategory.Squat, SquatExerciseName.DumbbellSquat) },
/* Dumbbell Sumo Deadlisft */ { "cd25b61809884d60adb1d97cd646f4fd", new (ExerciseCategory.Deadlift, DeadliftExerciseName.SumoDeadlift) },
/* Dumbbell Swing */ { "4460e019d86c4e4ebe7284bb16d128d2", new (ExerciseCategory.HipSwing, HipSwingExerciseName.SingleArmDumbbellSwing) },
/* Dumbbell Thruster */ { "5ab0baeebee94d3995cb7f2b0332f430", new (ExerciseCategory.Squat, SquatExerciseName.Thrusters) },

// F
/* Flutter Kick */ { "6091566fa0674afd96a22fcec3ab18ce", new (ExerciseCategory.Crunch, CrunchExerciseName.FlutterKicks) },
/* Forearm Side Plank */ { "1c0403c4d7264d83b1c75d18c8cdac4f", new (ExerciseCategory.Plank, PlankExerciseName.SidePlank) },
/* ForeArm Plank */ { "feb44f24e2b8487b870a35f4501069be", new (ExerciseCategory.Plank, PlankExerciseName.Plank) },
/* Front to Back Lunge */ { "ed18d837c14746c5af38d4fa03b56918", new (ExerciseCategory.Lunge, LungeExerciseName.DumbbellLunge) },
/* Front Lunge */ { "8ef53816dc414bed800e8bf0cee3c484", new (ExerciseCategory.Lunge, LungeExerciseName.DumbbellLunge) },
/* Front Raise */ { "a9cefac3b8234bc0bc0ee8deb62d67d3", new (ExerciseCategory.LateralRaise, LateralRaiseExerciseName.FrontRaise) },

// G
Expand Down
79 changes: 79 additions & 0 deletions src/Peloton/P2GWorkoutExerciseMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Common.Dto.Peloton;
using Common.Dto;
using System.Collections.Generic;
using System.Linq;

namespace Peloton;

public static class P2GWorkoutExerciseMapper
{
public static ICollection<P2GExercise> GetWorkoutExercises(Workout workout, RideSegments rideSegments)
{
var exercises = GetExercisesTrackedByMovementTracker(workout);

var segments = rideSegments?.Segments?.Segment_List;
if (segments is null || segments.Count <= 0) return exercises;

foreach (var segment in segments)
{
if (segment?.SubSegments_V2 is null || segment.SubSegments_V2.Count <= 0) continue;

foreach (var subSegment in segment.SubSegments_V2)
{
if (subSegment?.Movements is null || subSegment.Movements.Count <= 0) continue;

foreach (var movement in subSegment.Movements)
{
if (movement is null) continue;

if (exercises.Any(e => e.StartOffsetSeconds == subSegment.Offset && e.Id == movement.Id))
continue; // we likely already have this movement accounted for from the TrackedMovements

var exercise = new P2GExercise()
{
Id = movement.Id,
Name = movement.Name,
Type = MovementTargetType.Time,
StartOffsetSeconds = subSegment.Offset.GetValueOrDefault(),
DurationSeconds = subSegment.Length.GetValueOrDefault() / subSegment.Movements.Count, // if this is a group of movements, divide up the time per movement
Reps = subSegment.Rounds
};
exercises.Add(exercise);
}
}
}

var sorted = exercises.OrderBy(e => e.StartOffsetSeconds).ToList();
return sorted;
}

public static ICollection<P2GExercise> GetExercisesTrackedByMovementTracker(Workout workout)
{
var movements = new List<P2GExercise>();

var trackedRepData = workout?.Movement_Tracker_Data?.Completed_Movements_Summary_Data?.Repetition_Summary_Data;
if (trackedRepData is not null && trackedRepData.Count > 0)
{
foreach (var repData in trackedRepData)
{
var movement = new P2GExercise()
{
Id = repData.Movement_Id,
Name = repData.Movement_Name,
Type = repData.Is_Hold ? MovementTargetType.Time : MovementTargetType.Reps,
StartOffsetSeconds = repData.Offset,
DurationSeconds = repData.Length,
Reps = repData.Completed_Number,
Weight = new P2GWeight()
{
Unit = repData?.Weight?.FirstOrDefault()?.Weight_Data?.Weight_Unit,
Value = repData?.Weight?.FirstOrDefault()?.Weight_Data?.Weight_Value ?? 0
}
};
movements.Add(movement);
}
}

return movements;
}
}
2 changes: 1 addition & 1 deletion src/Peloton/PelotonService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public async Task<P2GWorkout> GetWorkoutDetailsAsync(string workoutId)
&& classId != "00000000000000000000000000000000")
{
var workoutSegments = await _pelotonApi.GetClassSegmentsAsync(classId);
p2gWorkoutData.Exercises = Common.Dto.Extensions.GetClassPlanExercises(p2gWorkoutData.Workout, workoutSegments);
p2gWorkoutData.Exercises = P2GWorkoutExerciseMapper.GetWorkoutExercises(p2gWorkoutData.Workout, workoutSegments);
}

return p2gWorkoutData;
Expand Down
74 changes: 74 additions & 0 deletions src/UnitTests/Peloton/P2GWorkoutExerciseMapperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;
using Peloton;
using System.Collections.Generic;
using Common.Dto.Peloton;
using FluentAssertions;

namespace UnitTests.Peloton;

public class P2GWorkoutExerciseMapperTests
{
public static Workout[] Workouts =
{
null,
new Workout(),
new Workout() { Movement_Tracker_Data = new MovementTrackerData() },
new Workout() { Movement_Tracker_Data = new MovementTrackerData() { Completed_Movements_Summary_Data = new CompletedMovementsSummaryData() } },
new Workout() { Movement_Tracker_Data = new MovementTrackerData() { Completed_Movements_Summary_Data = new CompletedMovementsSummaryData() { Repetition_Summary_Data = new List<RepetitionSummaryData>() } } },
};

[Test, TestCaseSource(nameof(Workouts))]
public void GetExercisesTrackedByMovementTracker_WhenNoData(Workout workout)
{
var exercises = P2GWorkoutExerciseMapper.GetExercisesTrackedByMovementTracker(workout);
exercises.Should().NotBeNull();
exercises.Should().BeEmpty();
}

[Test]
public void GetExercisesTrackedByMovementTracker_WhenNoWeight()
{
var workout = new Workout()
{
Movement_Tracker_Data = new MovementTrackerData()
{
Completed_Movements_Summary_Data = new CompletedMovementsSummaryData()
{
Repetition_Summary_Data = new List<RepetitionSummaryData>()
{
new RepetitionSummaryData()
{
Weight = null
},
new RepetitionSummaryData()
{
Weight = new List<Weight>()
},
new RepetitionSummaryData()
{
Weight = new List<Weight>() { new Weight() }
},
new RepetitionSummaryData()
{
Weight = new List<Weight>() { new Weight() { Weight_Data = null } }
},
new RepetitionSummaryData()
{
Weight = new List<Weight>() { new Weight() { Weight_Data = new WeightData()
{
Weight_Unit = null,
} } }
},
}
}
}
};

var exercises = P2GWorkoutExerciseMapper.GetExercisesTrackedByMovementTracker(workout);
exercises.Should().NotBeNull();
exercises.Should().HaveCount(5);
exercises.Should().OnlyContain(e => e.Weight != null);
exercises.Should().OnlyContain(e => e.Weight.Unit == null);
exercises.Should().OnlyContain(e => e.Weight.Value == 0);
}
}
2 changes: 2 additions & 0 deletions vNextReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
## Features

- [#447] Map Exercises for Strength and Core classes when Class Plan information is available
- [#453] Map Exercises from Movement Tracker + untracked movements in Class Plan
- Added support for mapping several more Exercises from Peloton to Garmin

## Fixes

- [#457] P2G fails with Ride Id is null (Just Run/Just Ride workouts)
- [#463] Null reference exception when parsing some strength exercises (fixed in #453)

## Housekeeping

Expand Down

0 comments on commit abb1e57

Please sign in to comment.