hevy-shared 1.0.960 → 1.0.962
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -0
- package/.eslintrc +21 -0
- package/.github/workflows/ci.yml +15 -0
- package/.github/workflows/npm-publish.yml +59 -0
- package/.github/workflows/pr-auto-assign.yml +15 -0
- package/.prettierrc.js +5 -0
- package/README.md +2 -17
- package/built/chat.d.ts +23 -25
- package/built/coachPlans.d.ts +1 -2
- package/built/coachPlans.js +2 -2
- package/built/filterExercises.d.ts +3 -19
- package/built/filterExercises.js +60 -72
- package/built/index.d.ts +304 -1140
- package/built/index.js +75 -269
- package/built/setIndicatorUtils.d.ts +3 -4
- package/built/setIndicatorUtils.js +1 -15
- package/built/tests/utils.test.js +0 -748
- package/built/tests/workoutVolume.test.js +49 -165
- package/built/units.d.ts +7 -14
- package/built/units.js +14 -24
- package/built/utils.d.ts +5 -192
- package/built/utils.js +85 -598
- package/built/websocket.d.ts +2 -14
- package/built/workoutVolume.d.ts +5 -24
- package/built/workoutVolume.js +34 -25
- package/jest.config.js +4 -0
- package/package.json +10 -32
- package/src/chat.ts +130 -0
- package/src/coachPlans.ts +57 -0
- package/src/constants.ts +14 -0
- package/src/filterExercises.ts +222 -0
- package/src/index.ts +1576 -0
- package/src/setIndicatorUtils.ts +137 -0
- package/src/tests/utils.test.ts +156 -0
- package/src/tests/workoutVolume.test.ts +93 -0
- package/src/units.ts +41 -0
- package/src/utils.ts +516 -0
- package/src/websocket.ts +36 -0
- package/src/workoutVolume.ts +175 -0
- package/tsconfig.json +70 -0
- package/built/API/APIClient.d.ts +0 -157
- package/built/API/APIClient.js +0 -381
- package/built/API/index.d.ts +0 -2
- package/built/API/index.js +0 -18
- package/built/API/types.d.ts +0 -38
- package/built/API/types.js +0 -18
- package/built/adjustEventTokens.d.ts +0 -16
- package/built/adjustEventTokens.js +0 -18
- package/built/adminPermissions.d.ts +0 -4
- package/built/adminPermissions.js +0 -22
- package/built/async.d.ts +0 -50
- package/built/async.js +0 -170
- package/built/cue.d.ts +0 -12
- package/built/cue.js +0 -22
- package/built/exerciseLocaleUtils.d.ts +0 -17
- package/built/exerciseLocaleUtils.js +0 -62
- package/built/hevyTrainer.d.ts +0 -250
- package/built/hevyTrainer.js +0 -676
- package/built/muscleHeatmaps.d.ts +0 -31
- package/built/muscleHeatmaps.js +0 -68
- package/built/muscleSplits.d.ts +0 -36
- package/built/muscleSplits.js +0 -100
- package/built/normalizedWorkoutUtils.d.ts +0 -88
- package/built/normalizedWorkoutUtils.js +0 -112
- package/built/notifications.d.ts +0 -215
- package/built/notifications.js +0 -9
- package/built/routineUtils.d.ts +0 -14
- package/built/routineUtils.js +0 -186
- package/built/schemas.d.ts +0 -6
- package/built/schemas.js +0 -12
- package/built/tests/async.test.d.ts +0 -1
- package/built/tests/async.test.js +0 -49
- package/built/tests/hevyTrainer.test.d.ts +0 -1
- package/built/tests/hevyTrainer.test.js +0 -1199
- package/built/tests/muscleSplit.test.d.ts +0 -1
- package/built/tests/muscleSplit.test.js +0 -153
- package/built/tests/routineUtils.test.d.ts +0 -1
- package/built/tests/routineUtils.test.js +0 -745
- package/built/tests/testUtils.d.ts +0 -85
- package/built/tests/testUtils.js +0 -319
- package/built/translations/index.d.ts +0 -2
- package/built/translations/index.js +0 -18
- package/built/translations/translationUtils.d.ts +0 -2
- package/built/translations/translationUtils.js +0 -61
- package/built/translations/types.d.ts +0 -8
- package/built/translations/types.js +0 -20
- package/built/typeUtils.d.ts +0 -70
- package/built/typeUtils.js +0 -55
package/built/hevyTrainer.js
DELETED
|
@@ -1,676 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getPrioritySortedExercises = exports.isEquipmentCompatible = exports.normalizeExerciseCategory = exports.getTrainerRestTimerSeconds = exports.getTrainerRepRange = exports.getTrainerSetCount = exports.programSplits = exports.frequencyMap = exports.routineNames = exports.defaultDurationPerFrequency = exports.hevyTrainerExerciseCategories = exports.granularEquipmentsToTrainerEquipments = exports.trainerEquipmentToGranularEquipments = exports.granularEquipmentDefaults = exports.trainerGymTypes = exports.workoutDurationOptions = void 0;
|
|
4
|
-
exports.pickExerciseForPrescription = pickExerciseForPrescription;
|
|
5
|
-
exports.generateProgram = generateProgram;
|
|
6
|
-
const _1 = require(".");
|
|
7
|
-
exports.workoutDurationOptions = [40, 60, 80];
|
|
8
|
-
exports.trainerGymTypes = [
|
|
9
|
-
'home_gym',
|
|
10
|
-
'garage_gym',
|
|
11
|
-
'commercial_gym',
|
|
12
|
-
'full_gym',
|
|
13
|
-
];
|
|
14
|
-
exports.granularEquipmentDefaults = {
|
|
15
|
-
home_gym: ['dumbbell', 'pullup_bar', 'adjustable_bench'],
|
|
16
|
-
garage_gym: [
|
|
17
|
-
'dumbbell',
|
|
18
|
-
'kettlebell',
|
|
19
|
-
'barbell',
|
|
20
|
-
'plate',
|
|
21
|
-
'pullup_bar',
|
|
22
|
-
'dip_bar',
|
|
23
|
-
'squat_rack',
|
|
24
|
-
'adjustable_bench',
|
|
25
|
-
'spinning',
|
|
26
|
-
],
|
|
27
|
-
commercial_gym: [
|
|
28
|
-
'dumbbell',
|
|
29
|
-
'barbell',
|
|
30
|
-
'plate',
|
|
31
|
-
'ez_bar',
|
|
32
|
-
'pullup_bar',
|
|
33
|
-
'dip_bar',
|
|
34
|
-
'squat_rack',
|
|
35
|
-
'adjustable_bench',
|
|
36
|
-
'single_cable_machine',
|
|
37
|
-
'lat_pulldown_cable',
|
|
38
|
-
'leg_press_machine',
|
|
39
|
-
'smith_machine',
|
|
40
|
-
'stack_machines',
|
|
41
|
-
'treadmill',
|
|
42
|
-
'elliptical_trainer',
|
|
43
|
-
'spinning',
|
|
44
|
-
],
|
|
45
|
-
// All equipment is available in a full gym
|
|
46
|
-
full_gym: [
|
|
47
|
-
'barbell',
|
|
48
|
-
'dumbbell',
|
|
49
|
-
'kettlebell',
|
|
50
|
-
'plate',
|
|
51
|
-
'medicine_ball',
|
|
52
|
-
'ez_bar',
|
|
53
|
-
'landmine',
|
|
54
|
-
'trap_bar',
|
|
55
|
-
'pullup_bar',
|
|
56
|
-
'dip_bar',
|
|
57
|
-
'squat_rack',
|
|
58
|
-
'flat_bench',
|
|
59
|
-
'adjustable_bench',
|
|
60
|
-
'dual_cable_machine',
|
|
61
|
-
'single_cable_machine',
|
|
62
|
-
'lat_pulldown_cable',
|
|
63
|
-
'leg_press_machine',
|
|
64
|
-
'smith_machine',
|
|
65
|
-
't_bar',
|
|
66
|
-
'plate_machines',
|
|
67
|
-
'stack_machines',
|
|
68
|
-
'treadmill',
|
|
69
|
-
'elliptical_trainer',
|
|
70
|
-
'rowing_machine',
|
|
71
|
-
'spinning',
|
|
72
|
-
'stair_machine',
|
|
73
|
-
'air_bike',
|
|
74
|
-
'suspension_band',
|
|
75
|
-
'resistance_band',
|
|
76
|
-
'battle_rope',
|
|
77
|
-
'rings',
|
|
78
|
-
'jump_rope',
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
const trainerEquipmentToGranularEquipmentsMap = {
|
|
82
|
-
barbell: [
|
|
83
|
-
'adjustable_bench',
|
|
84
|
-
'barbell',
|
|
85
|
-
'ez_bar',
|
|
86
|
-
'flat_bench',
|
|
87
|
-
'landmine',
|
|
88
|
-
'plate',
|
|
89
|
-
'squat_rack',
|
|
90
|
-
't_bar',
|
|
91
|
-
],
|
|
92
|
-
dumbbell: ['adjustable_bench', 'dumbbell', 'flat_bench'],
|
|
93
|
-
machine: [
|
|
94
|
-
'adjustable_bench',
|
|
95
|
-
'dip_bar',
|
|
96
|
-
'dual_cable_machine',
|
|
97
|
-
'flat_bench',
|
|
98
|
-
'lat_pulldown_cable',
|
|
99
|
-
'leg_press_machine',
|
|
100
|
-
'plate',
|
|
101
|
-
'plate_machines',
|
|
102
|
-
'pullup_bar',
|
|
103
|
-
'single_cable_machine',
|
|
104
|
-
'smith_machine',
|
|
105
|
-
'stack_machines',
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
const trainerEquipmentToGranularEquipments = (equipments) => {
|
|
109
|
-
const granularEquipments = [];
|
|
110
|
-
equipments.forEach((equipment) => {
|
|
111
|
-
granularEquipments.push(...trainerEquipmentToGranularEquipmentsMap[equipment]);
|
|
112
|
-
});
|
|
113
|
-
return Array.from(new Set(granularEquipments));
|
|
114
|
-
};
|
|
115
|
-
exports.trainerEquipmentToGranularEquipments = trainerEquipmentToGranularEquipments;
|
|
116
|
-
const MACHINE_SPECIFIC_GRANULAR_EQUIPMENTS = [
|
|
117
|
-
'smith_machine',
|
|
118
|
-
'leg_press_machine',
|
|
119
|
-
'dual_cable_machine',
|
|
120
|
-
'single_cable_machine',
|
|
121
|
-
'lat_pulldown_cable',
|
|
122
|
-
'plate_machines',
|
|
123
|
-
'stack_machines',
|
|
124
|
-
'dip_bar',
|
|
125
|
-
'pullup_bar',
|
|
126
|
-
];
|
|
127
|
-
/**
|
|
128
|
-
* Converts granular equipment values back to the old HevyTrainerProgramEquipment
|
|
129
|
-
* types for backwards compatibility with old mobile clients.
|
|
130
|
-
*/
|
|
131
|
-
const granularEquipmentsToTrainerEquipments = (granularEquipments) => {
|
|
132
|
-
const result = [];
|
|
133
|
-
if (granularEquipments.includes('barbell')) {
|
|
134
|
-
result.push('barbell');
|
|
135
|
-
}
|
|
136
|
-
if (granularEquipments.includes('dumbbell')) {
|
|
137
|
-
result.push('dumbbell');
|
|
138
|
-
}
|
|
139
|
-
if (granularEquipments.some((eq) => MACHINE_SPECIFIC_GRANULAR_EQUIPMENTS.includes(eq))) {
|
|
140
|
-
result.push('machine');
|
|
141
|
-
}
|
|
142
|
-
return result;
|
|
143
|
-
};
|
|
144
|
-
exports.granularEquipmentsToTrainerEquipments = granularEquipmentsToTrainerEquipments;
|
|
145
|
-
exports.hevyTrainerExerciseCategories = ['compound', 'isolation'];
|
|
146
|
-
// These are the values that Philip decided for the default duration per frequency
|
|
147
|
-
exports.defaultDurationPerFrequency = {
|
|
148
|
-
1: 80,
|
|
149
|
-
2: 60,
|
|
150
|
-
3: 60,
|
|
151
|
-
4: 60,
|
|
152
|
-
5: 40,
|
|
153
|
-
6: 40,
|
|
154
|
-
};
|
|
155
|
-
exports.routineNames = [
|
|
156
|
-
// Full body 1x
|
|
157
|
-
'full_body_1',
|
|
158
|
-
// Full body 2x
|
|
159
|
-
'full_body_2_a',
|
|
160
|
-
'full_body_2_b',
|
|
161
|
-
// Full body 3x
|
|
162
|
-
'full_body_3_a',
|
|
163
|
-
'full_body_3_b',
|
|
164
|
-
'full_body_3_c',
|
|
165
|
-
// Upper body x4 per week
|
|
166
|
-
'upper_1_a',
|
|
167
|
-
'lower_1_a',
|
|
168
|
-
'upper_1_b',
|
|
169
|
-
'lower_1_b',
|
|
170
|
-
// Push Pull Legs Upper Lower 5x per week
|
|
171
|
-
'push_1',
|
|
172
|
-
'pull_1',
|
|
173
|
-
'legs_1',
|
|
174
|
-
'upper_2',
|
|
175
|
-
'lower_2',
|
|
176
|
-
// Push Pull Legs x6 per week
|
|
177
|
-
'push_2_a',
|
|
178
|
-
'pull_2_a',
|
|
179
|
-
'legs_2_a',
|
|
180
|
-
'push_2_b',
|
|
181
|
-
'pull_2_b',
|
|
182
|
-
'legs_2_b',
|
|
183
|
-
];
|
|
184
|
-
exports.frequencyMap = {
|
|
185
|
-
1: 'one_day',
|
|
186
|
-
2: 'two_days',
|
|
187
|
-
3: 'three_days',
|
|
188
|
-
4: 'four_days',
|
|
189
|
-
5: 'five_days',
|
|
190
|
-
6: 'six_days',
|
|
191
|
-
};
|
|
192
|
-
exports.programSplits = {
|
|
193
|
-
1: ['full_body_1'],
|
|
194
|
-
2: ['full_body_2_a', 'full_body_2_b'],
|
|
195
|
-
3: ['full_body_3_a', 'full_body_3_b', 'full_body_3_c'],
|
|
196
|
-
4: ['upper_1_a', 'lower_1_a', 'upper_1_b', 'lower_1_b'],
|
|
197
|
-
5: ['push_1', 'pull_1', 'legs_1', 'upper_2', 'lower_2'],
|
|
198
|
-
6: ['push_2_a', 'pull_2_a', 'legs_2_a', 'push_2_b', 'pull_2_b', 'legs_2_b'],
|
|
199
|
-
};
|
|
200
|
-
const getTrainerSetCount = (trainerAlgorithmSettings, goal, frequency) => {
|
|
201
|
-
return trainerAlgorithmSettings.sets[exports.frequencyMap[frequency]][goal];
|
|
202
|
-
};
|
|
203
|
-
exports.getTrainerSetCount = getTrainerSetCount;
|
|
204
|
-
const getTrainerRepRange = (trainerAlgorithmSettings, goal, exerciseCategory) => {
|
|
205
|
-
return trainerAlgorithmSettings.rep_ranges[goal][exerciseCategory];
|
|
206
|
-
};
|
|
207
|
-
exports.getTrainerRepRange = getTrainerRepRange;
|
|
208
|
-
const getTrainerRestTimerSeconds = (trainerAlgorithmSettings, goal, length, exerciseCategory) => {
|
|
209
|
-
return trainerAlgorithmSettings.rest_timers[goal][length][exerciseCategory];
|
|
210
|
-
};
|
|
211
|
-
exports.getTrainerRestTimerSeconds = getTrainerRestTimerSeconds;
|
|
212
|
-
/**
|
|
213
|
-
* Normalizes the exercise category to a HevyTrainerExerciseCategory
|
|
214
|
-
* - Treat custom exercises and exercises with no category as `compound`
|
|
215
|
-
* - Treat `assistance-compound` as `compound`
|
|
216
|
-
* - Treat `isolation` as `isolation`
|
|
217
|
-
* @param exercise - The exercise to normalize the category for
|
|
218
|
-
* @returns The normalized exercise category
|
|
219
|
-
*/
|
|
220
|
-
const normalizeExerciseCategory = (exercise) => {
|
|
221
|
-
return exercise.is_custom
|
|
222
|
-
? 'compound'
|
|
223
|
-
: exercise.category === 'isolation'
|
|
224
|
-
? 'isolation'
|
|
225
|
-
: 'compound';
|
|
226
|
-
};
|
|
227
|
-
exports.normalizeExerciseCategory = normalizeExerciseCategory;
|
|
228
|
-
/**
|
|
229
|
-
* Returns true if the focus muscle group extra exercise is allowed for a given template
|
|
230
|
-
*/
|
|
231
|
-
const isFocusMuscleExtraExerciseAllowed = (templateName, focusMuscle) => {
|
|
232
|
-
const isFullBodyTemplate = templateName.startsWith('full_body');
|
|
233
|
-
// Full body templates allow all muscle groups
|
|
234
|
-
if (isFullBodyTemplate) {
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
const isLegsTemplate = templateName.startsWith('legs');
|
|
238
|
-
const isUpperTemplate = templateName.startsWith('upper');
|
|
239
|
-
const isLowerTemplate = templateName.startsWith('lower');
|
|
240
|
-
const isPushTemplate = templateName.startsWith('push');
|
|
241
|
-
const isPullTemplate = templateName.startsWith('pull');
|
|
242
|
-
switch (focusMuscle) {
|
|
243
|
-
case 'core':
|
|
244
|
-
// Core exercises are allowed for all templates
|
|
245
|
-
return true;
|
|
246
|
-
case 'chest':
|
|
247
|
-
case 'shoulders':
|
|
248
|
-
return isUpperTemplate || isPushTemplate;
|
|
249
|
-
case 'arms':
|
|
250
|
-
return isUpperTemplate || isPullTemplate || isPushTemplate;
|
|
251
|
-
case 'legs':
|
|
252
|
-
return isLowerTemplate || isLegsTemplate;
|
|
253
|
-
case 'back':
|
|
254
|
-
return isUpperTemplate || isPullTemplate;
|
|
255
|
-
default:
|
|
256
|
-
(0, _1.exhaustiveTypeCheck)(focusMuscle);
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
/**
|
|
261
|
-
* Checks if an exercise is compatible with the user's selected granular equipments
|
|
262
|
-
*
|
|
263
|
-
* - If the exercise has no granular equipments, it is compatible no matter what the user has selected
|
|
264
|
-
* - Otherwise, the exercise is only allowed if the user has all the granular equipments
|
|
265
|
-
*/
|
|
266
|
-
const isEquipmentCompatible = (exercise, userSelectedGranularEquipments) => {
|
|
267
|
-
if (!exercise.granular_equipments ||
|
|
268
|
-
exercise.granular_equipments.length === 0) {
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
const allowedEquipments = [...userSelectedGranularEquipments];
|
|
272
|
-
// Special case: adjustable bench can substitute for flat bench
|
|
273
|
-
if (userSelectedGranularEquipments.includes('adjustable_bench')) {
|
|
274
|
-
allowedEquipments.push('flat_bench');
|
|
275
|
-
}
|
|
276
|
-
// Special case: dual cable machine can substitute for single cable machine
|
|
277
|
-
if (userSelectedGranularEquipments.includes('dual_cable_machine')) {
|
|
278
|
-
allowedEquipments.push('single_cable_machine');
|
|
279
|
-
}
|
|
280
|
-
return exercise.granular_equipments.every((granularEquipment) => allowedEquipments.includes(granularEquipment));
|
|
281
|
-
};
|
|
282
|
-
exports.isEquipmentCompatible = isEquipmentCompatible;
|
|
283
|
-
const MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK = 3;
|
|
284
|
-
/**
|
|
285
|
-
* - If frequency is 1 and the candidate exercise is a barbell exercise
|
|
286
|
-
* - Then allow at most `MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK` barbell exercises
|
|
287
|
-
* - Only enforce the cap when the user has at least one "barbell substitute" equipment available
|
|
288
|
-
* (otherwise we allow barbell exercises to avoid running out of viable options)
|
|
289
|
-
*/
|
|
290
|
-
const isBarbellExerciseAllowed = (exercise, routineBarbellExerciseCount, userEquipments, frequency) => {
|
|
291
|
-
const isCandidateBarbell = exercise.equipment_category === 'barbell';
|
|
292
|
-
const isOncePerWeek = frequency === 1;
|
|
293
|
-
if (!isCandidateBarbell || !isOncePerWeek)
|
|
294
|
-
return true;
|
|
295
|
-
const isAtOrOverLimit = routineBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
|
|
296
|
-
if (!isAtOrOverLimit)
|
|
297
|
-
return true;
|
|
298
|
-
const barbellSubstitutes = [
|
|
299
|
-
'dumbbell',
|
|
300
|
-
'dual_cable_machine',
|
|
301
|
-
'single_cable_machine',
|
|
302
|
-
'smith_machine',
|
|
303
|
-
'plate_machines',
|
|
304
|
-
'stack_machines',
|
|
305
|
-
];
|
|
306
|
-
const hasAnySubstitute = barbellSubstitutes.some((substitute) => userEquipments.includes(substitute));
|
|
307
|
-
// If the user has substitutes, enforce the cap; otherwise allow barbell to avoid dead-ends.
|
|
308
|
-
return !hasAnySubstitute;
|
|
309
|
-
};
|
|
310
|
-
const isExerciseUsed = (exercise, context) => {
|
|
311
|
-
var _a, _b, _c;
|
|
312
|
-
return (((_a = context.programUsedExerciseIds) === null || _a === void 0 ? void 0 : _a.has(exercise.id)) ||
|
|
313
|
-
((_b = context.routineUsedExerciseIds) === null || _b === void 0 ? void 0 : _b.has(exercise.id)) ||
|
|
314
|
-
((_c = context.excludedExerciseIds) === null || _c === void 0 ? void 0 : _c.has(exercise.id)) ||
|
|
315
|
-
false);
|
|
316
|
-
};
|
|
317
|
-
const isolationExerciseAlternatives = {
|
|
318
|
-
/**
|
|
319
|
-
* - B74A95BB (Kneeling Push Up)
|
|
320
|
-
* - 392887AA (Push Up)
|
|
321
|
-
* - 39C99849 (Incline Push Ups)
|
|
322
|
-
* - 982734D4 (Plate Press)
|
|
323
|
-
*/
|
|
324
|
-
chest: ['B74A95BB', '392887AA', '39C99849', '982734D4'],
|
|
325
|
-
/**
|
|
326
|
-
* - 6575F52D (Diamond Push Up)
|
|
327
|
-
* - CD6DC8E5 (Bench Dip)
|
|
328
|
-
*/
|
|
329
|
-
triceps: ['6575F52D', 'CD6DC8E5'],
|
|
330
|
-
/**
|
|
331
|
-
* - 32HKJ34K (Walking Lunge)
|
|
332
|
-
* - A733CC5B (Walking Lunge (Dumbbell))
|
|
333
|
-
* - 9694DA61 (Squat (Bodyweight))
|
|
334
|
-
*/
|
|
335
|
-
quadriceps: ['32HKJ34K', 'A733CC5B', '9694DA61'],
|
|
336
|
-
/**
|
|
337
|
-
* - F6948F17 (Frog Pumps (Dumbbell))
|
|
338
|
-
* - 487B3755 (Single Leg Hip Thrust)
|
|
339
|
-
* - CDA23948 (Glute Bridge)
|
|
340
|
-
*/
|
|
341
|
-
hamstrings: ['F6948F17', '487B3755', 'CDA23948'],
|
|
342
|
-
glutes: [],
|
|
343
|
-
lats: [],
|
|
344
|
-
lower_back: [],
|
|
345
|
-
upper_back: [],
|
|
346
|
-
shoulders: [],
|
|
347
|
-
biceps: [],
|
|
348
|
-
adductors: [],
|
|
349
|
-
abductors: [],
|
|
350
|
-
calves: [],
|
|
351
|
-
forearms: [],
|
|
352
|
-
abdominals: [],
|
|
353
|
-
other: [],
|
|
354
|
-
cardio: [],
|
|
355
|
-
traps: [],
|
|
356
|
-
neck: [],
|
|
357
|
-
full_body: [],
|
|
358
|
-
};
|
|
359
|
-
/**
|
|
360
|
-
* Sorts exercises by priority for each muscle group based on the provided priorities
|
|
361
|
-
* and adds any remaining exercises from the store that weren't in the priorities.
|
|
362
|
-
*
|
|
363
|
-
* @param exercisePriorities - Object mapping muscle groups to arrays of exercise IDs in priority order
|
|
364
|
-
* @param exerciseStore - Array of all available exercises
|
|
365
|
-
* @returns Object mapping muscle groups to arrays of exercises sorted by priority
|
|
366
|
-
*/
|
|
367
|
-
const getPrioritySortedExercises = (exercisePriorities, exerciseStore) => {
|
|
368
|
-
// Have a map of muscle group to exercises sorted by exercise priority
|
|
369
|
-
const sortedExercises = Object.entries(exercisePriorities).reduce((acc, [muscleGroup, exercises]) => {
|
|
370
|
-
const foundExercises = exercises
|
|
371
|
-
.map((exercise) => exerciseStore.find((e) => e.id === exercise))
|
|
372
|
-
.filter((exercise) => exercise !== undefined);
|
|
373
|
-
// Debug: Log missing exercise IDs
|
|
374
|
-
const missingIds = exercises.filter((id) => !exerciseStore.some((e) => e.id === id));
|
|
375
|
-
if (missingIds.length > 0) {
|
|
376
|
-
console.log(`Missing the following exercises with IDs in the store for muscle group ${muscleGroup}:`, missingIds);
|
|
377
|
-
}
|
|
378
|
-
acc[muscleGroup] = foundExercises;
|
|
379
|
-
return acc;
|
|
380
|
-
}, {});
|
|
381
|
-
// Then add remaining exercises to the map
|
|
382
|
-
Object.keys(sortedExercises).forEach((muscleGroup) => {
|
|
383
|
-
const remainingExercises = exerciseStore
|
|
384
|
-
.filter((e) => e.muscle_group === muscleGroup &&
|
|
385
|
-
!sortedExercises[muscleGroup].includes(e))
|
|
386
|
-
.sort((a, b) => b.priority - a.priority);
|
|
387
|
-
sortedExercises[muscleGroup].push(...remainingExercises);
|
|
388
|
-
});
|
|
389
|
-
return sortedExercises;
|
|
390
|
-
};
|
|
391
|
-
exports.getPrioritySortedExercises = getPrioritySortedExercises;
|
|
392
|
-
const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, routineName, }) => {
|
|
393
|
-
if (muscleGroupPrescription === 'focus_muscle') {
|
|
394
|
-
// If the user has selected a focus muscle and it is allowed for the routine,
|
|
395
|
-
// we return the focus muscle extra exercise
|
|
396
|
-
if (!!focusMuscle &&
|
|
397
|
-
isFocusMuscleExtraExerciseAllowed(routineName, focusMuscle)) {
|
|
398
|
-
const n = _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle].length;
|
|
399
|
-
return _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle][programFocusMuscleExerciseCount % n];
|
|
400
|
-
}
|
|
401
|
-
// If the user has not selected a focus muscle or it is not allowed for the routine
|
|
402
|
-
// we skip this extra exercise for the focus muscle in the program
|
|
403
|
-
return undefined;
|
|
404
|
-
}
|
|
405
|
-
return muscleGroupPrescription;
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* Checks if an exercise matches the given criteria for selection
|
|
409
|
-
*/
|
|
410
|
-
const isExerciseMatch = (exercise, criteria) => {
|
|
411
|
-
var _a, _b, _c, _d;
|
|
412
|
-
const categoryMatch = criteria.exerciseCategory === 'all' ||
|
|
413
|
-
exercise.category === criteria.exerciseCategory ||
|
|
414
|
-
(exercise.category === 'assistance-compound' &&
|
|
415
|
-
criteria.exerciseCategory === 'compound');
|
|
416
|
-
const levelMatch = (_b = (_a = exercise.level) === null || _a === void 0 ? void 0 : _a.includes(criteria.level)) !== null && _b !== void 0 ? _b : false;
|
|
417
|
-
const goalMatch = (_d = (_c = exercise.goal) === null || _c === void 0 ? void 0 : _c.includes(criteria.goal)) !== null && _d !== void 0 ? _d : false;
|
|
418
|
-
const equipmentMatch = (0, exports.isEquipmentCompatible)(exercise, criteria.equipments);
|
|
419
|
-
const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.routineBarbellExerciseCount, criteria.equipments, criteria.frequency);
|
|
420
|
-
return (categoryMatch && levelMatch && goalMatch && equipmentMatch && barbellLimitOk);
|
|
421
|
-
};
|
|
422
|
-
/**
|
|
423
|
-
* Checks if an exercise is a valid alternative isolation exercise
|
|
424
|
-
*/
|
|
425
|
-
const isAlternativeIsolationExerciseMatch = (exercise, criteria) => {
|
|
426
|
-
var _a, _b;
|
|
427
|
-
const isIsolationCategory = criteria.exerciseCategory === 'isolation';
|
|
428
|
-
const isAlternativeExercise = isolationExerciseAlternatives[criteria.muscleGroup].includes(exercise.id);
|
|
429
|
-
const equipmentMatch = (0, exports.isEquipmentCompatible)(exercise, criteria.equipments);
|
|
430
|
-
const levelMatch = (_b = (_a = exercise.level) === null || _a === void 0 ? void 0 : _a.includes(criteria.level)) !== null && _b !== void 0 ? _b : false;
|
|
431
|
-
return (isIsolationCategory && isAlternativeExercise && equipmentMatch && levelMatch);
|
|
432
|
-
};
|
|
433
|
-
/**
|
|
434
|
-
* Finds an exercise that matches the criteria and is not already used
|
|
435
|
-
*/
|
|
436
|
-
const findMatchingExercise = (exercises, criteria, context) => {
|
|
437
|
-
return exercises.find((exercise) => {
|
|
438
|
-
const matchesCriteria = isExerciseMatch(exercise, criteria);
|
|
439
|
-
const isNotUsed = !isExerciseUsed(exercise, context);
|
|
440
|
-
return matchesCriteria && isNotUsed;
|
|
441
|
-
});
|
|
442
|
-
};
|
|
443
|
-
/**
|
|
444
|
-
* Finds an alternative isolation exercise that matches the criteria
|
|
445
|
-
*/
|
|
446
|
-
const findAlternativeIsolationExercise = (exercises, criteria, context) => {
|
|
447
|
-
return exercises.find((exercise) => {
|
|
448
|
-
const matchesCriteria = isAlternativeIsolationExerciseMatch(exercise, criteria);
|
|
449
|
-
const isNotUsed = !isExerciseUsed(exercise, context);
|
|
450
|
-
return matchesCriteria && isNotUsed;
|
|
451
|
-
});
|
|
452
|
-
};
|
|
453
|
-
const pickExerciseForPrescriptionWithTrace = (params) => {
|
|
454
|
-
const { sortedExercises, handPickedExercises, criteria, context } = params;
|
|
455
|
-
const trace = { entries: [] };
|
|
456
|
-
const exercises = handPickedExercises
|
|
457
|
-
? [...handPickedExercises, ...sortedExercises[criteria.muscleGroup]]
|
|
458
|
-
: sortedExercises[criteria.muscleGroup];
|
|
459
|
-
const programContext = {
|
|
460
|
-
programUsedExerciseIds: context.programUsedExerciseIds,
|
|
461
|
-
excludedExerciseIds: context.excludedExerciseIds,
|
|
462
|
-
};
|
|
463
|
-
const routineContext = {
|
|
464
|
-
routineUsedExerciseIds: context.routineUsedExerciseIds,
|
|
465
|
-
excludedExerciseIds: context.excludedExerciseIds,
|
|
466
|
-
};
|
|
467
|
-
// Pass 1
|
|
468
|
-
let exercise = findMatchingExercise(exercises, criteria, programContext);
|
|
469
|
-
trace.entries.push({
|
|
470
|
-
pass: 1,
|
|
471
|
-
label: 'exact match; not used in program',
|
|
472
|
-
candidatePoolSize: exercises.length,
|
|
473
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
474
|
-
});
|
|
475
|
-
if (exercise)
|
|
476
|
-
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 1 }) };
|
|
477
|
-
// Pass 2
|
|
478
|
-
exercise = findMatchingExercise(exercises, criteria, routineContext);
|
|
479
|
-
trace.entries.push({
|
|
480
|
-
pass: 2,
|
|
481
|
-
label: 'exact match; not used in routine (reuse allowed in program)',
|
|
482
|
-
candidatePoolSize: exercises.length,
|
|
483
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
484
|
-
});
|
|
485
|
-
if (exercise)
|
|
486
|
-
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 2 }) };
|
|
487
|
-
// Pass 3
|
|
488
|
-
exercise = findAlternativeIsolationExercise(exercises, criteria, programContext);
|
|
489
|
-
trace.entries.push({
|
|
490
|
-
pass: 3,
|
|
491
|
-
label: 'handpicked alternative isolation; not used in program',
|
|
492
|
-
candidatePoolSize: exercises.length,
|
|
493
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
494
|
-
});
|
|
495
|
-
if (exercise)
|
|
496
|
-
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 3 }) };
|
|
497
|
-
// Pass 4
|
|
498
|
-
exercise = findAlternativeIsolationExercise(exercises, criteria, routineContext);
|
|
499
|
-
trace.entries.push({
|
|
500
|
-
pass: 4,
|
|
501
|
-
label: 'handpicked alternative isolation; not used in routine (reuse allowed in program)',
|
|
502
|
-
candidatePoolSize: exercises.length,
|
|
503
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
504
|
-
});
|
|
505
|
-
if (exercise)
|
|
506
|
-
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 4 }) };
|
|
507
|
-
// Pass 5
|
|
508
|
-
exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
|
|
509
|
-
trace.entries.push({
|
|
510
|
-
pass: 5,
|
|
511
|
-
label: 'match regardless of isolation or compound; not used in routine (reuse allowed in program)',
|
|
512
|
-
candidatePoolSize: exercises.length,
|
|
513
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
514
|
-
});
|
|
515
|
-
if (exercise)
|
|
516
|
-
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 5 }) };
|
|
517
|
-
// Pass 6
|
|
518
|
-
const secondaryMuscleExercises = Object.values(sortedExercises)
|
|
519
|
-
.flat()
|
|
520
|
-
.filter((e) => e.other_muscles.includes(criteria.muscleGroup));
|
|
521
|
-
exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
|
|
522
|
-
trace.entries.push({
|
|
523
|
-
pass: 6,
|
|
524
|
-
label: 'secondary muscle match; regardless of isolation or compound; not used in routine (reuse allowed in program)',
|
|
525
|
-
candidatePoolSize: secondaryMuscleExercises.length,
|
|
526
|
-
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
527
|
-
});
|
|
528
|
-
return exercise
|
|
529
|
-
? { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 6 }) }
|
|
530
|
-
: { exercise: undefined, trace };
|
|
531
|
-
};
|
|
532
|
-
function pickExerciseForPrescription(params) {
|
|
533
|
-
if (params.withTrace) {
|
|
534
|
-
return pickExerciseForPrescriptionWithTrace(params);
|
|
535
|
-
}
|
|
536
|
-
return pickExerciseForPrescriptionWithTrace(params).exercise;
|
|
537
|
-
}
|
|
538
|
-
function generateProgram(params) {
|
|
539
|
-
var _a, _b;
|
|
540
|
-
const { trainerAlgorithmSettings, frequency, goal, level, equipments, workoutDurationMinutes, restTimerLength, exerciseStore, focusMuscle, excludedExerciseIds, debugExerciseSelectionTrace, } = params;
|
|
541
|
-
const exerciseSelectionTraces = [];
|
|
542
|
-
const routines = exports.programSplits[frequency];
|
|
543
|
-
const program = {
|
|
544
|
-
name: frequency,
|
|
545
|
-
routines: [],
|
|
546
|
-
};
|
|
547
|
-
const sortedExercises = (0, exports.getPrioritySortedExercises)(trainerAlgorithmSettings.exercise_priorities, exerciseStore);
|
|
548
|
-
const programUsedExerciseIds = new Set();
|
|
549
|
-
let programFocusMuscleExerciseCount = 0;
|
|
550
|
-
const allErrors = [];
|
|
551
|
-
for (const routine of routines) {
|
|
552
|
-
const routineTemplate = trainerAlgorithmSettings.templates[routine];
|
|
553
|
-
let routineBarbellExerciseCount = 0;
|
|
554
|
-
const routineUsedExerciseIds = new Set();
|
|
555
|
-
const routineExercises = [];
|
|
556
|
-
for (let prescriptionIndex = 0; prescriptionIndex < routineTemplate.exercises.length; prescriptionIndex++) {
|
|
557
|
-
const exercisePrescription = routineTemplate.exercises[prescriptionIndex];
|
|
558
|
-
const muscleGroup = getMuscleGroup({
|
|
559
|
-
muscleGroupPrescription: exercisePrescription.muscle_group,
|
|
560
|
-
programFocusMuscleExerciseCount,
|
|
561
|
-
focusMuscle,
|
|
562
|
-
routineName: routine,
|
|
563
|
-
});
|
|
564
|
-
// If the muscle group is not found, skip the exercise
|
|
565
|
-
if (!muscleGroup) {
|
|
566
|
-
continue;
|
|
567
|
-
}
|
|
568
|
-
// If the exercise does not meet the min workout duration limit, skip the exercise
|
|
569
|
-
const execiseDoesNotMeetMinWorkoutDurationLimit = exercisePrescription.min_workout_duration_limit
|
|
570
|
-
? exercisePrescription.min_workout_duration_limit >
|
|
571
|
-
workoutDurationMinutes
|
|
572
|
-
: false;
|
|
573
|
-
if (execiseDoesNotMeetMinWorkoutDurationLimit) {
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const criteria = {
|
|
577
|
-
frequency,
|
|
578
|
-
exerciseCategory: exercisePrescription.category,
|
|
579
|
-
equipments,
|
|
580
|
-
muscleGroup,
|
|
581
|
-
routineBarbellExerciseCount,
|
|
582
|
-
level,
|
|
583
|
-
goal,
|
|
584
|
-
};
|
|
585
|
-
let exercise;
|
|
586
|
-
if (debugExerciseSelectionTrace) {
|
|
587
|
-
const selection = pickExerciseForPrescription({
|
|
588
|
-
sortedExercises,
|
|
589
|
-
criteria,
|
|
590
|
-
context: {
|
|
591
|
-
programUsedExerciseIds,
|
|
592
|
-
routineUsedExerciseIds,
|
|
593
|
-
excludedExerciseIds,
|
|
594
|
-
},
|
|
595
|
-
withTrace: true,
|
|
596
|
-
});
|
|
597
|
-
exercise = selection.exercise;
|
|
598
|
-
exerciseSelectionTraces.push({
|
|
599
|
-
routine,
|
|
600
|
-
prescriptionIndex,
|
|
601
|
-
prescription: exercisePrescription,
|
|
602
|
-
resolvedMuscleGroup: muscleGroup,
|
|
603
|
-
criteria,
|
|
604
|
-
selectedExerciseId: (_a = selection.exercise) === null || _a === void 0 ? void 0 : _a.id,
|
|
605
|
-
trace: selection.trace,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
else {
|
|
609
|
-
exercise = pickExerciseForPrescription({
|
|
610
|
-
sortedExercises,
|
|
611
|
-
criteria,
|
|
612
|
-
context: {
|
|
613
|
-
programUsedExerciseIds,
|
|
614
|
-
routineUsedExerciseIds,
|
|
615
|
-
excludedExerciseIds,
|
|
616
|
-
},
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
if (!!exercise) {
|
|
620
|
-
programUsedExerciseIds.add(exercise.id);
|
|
621
|
-
routineUsedExerciseIds.add(exercise.id);
|
|
622
|
-
if (exercise.equipment_category === 'barbell')
|
|
623
|
-
routineBarbellExerciseCount++;
|
|
624
|
-
if (exercisePrescription.muscle_group === 'focus_muscle')
|
|
625
|
-
programFocusMuscleExerciseCount++;
|
|
626
|
-
const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings, goal, exercisePrescription.category);
|
|
627
|
-
routineExercises.push({
|
|
628
|
-
exerciseTemplate: exercise,
|
|
629
|
-
muscleGroup: exercisePrescription.muscle_group,
|
|
630
|
-
category: exercisePrescription.category,
|
|
631
|
-
sets: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
|
|
632
|
-
repRangeStart: repRange.rep_range_start,
|
|
633
|
-
repRangeEnd: repRange.rep_range_end,
|
|
634
|
-
restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
|
|
635
|
-
warmupSetCount: exercisePrescription.warmup_set_count,
|
|
636
|
-
notes: (_b = trainerAlgorithmSettings.exercise_notes[exercise.id]) !== null && _b !== void 0 ? _b : '',
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
// Collect error instead of throwing
|
|
641
|
-
allErrors.push({
|
|
642
|
-
type: 'exercise_not_found',
|
|
643
|
-
prescription: exercisePrescription,
|
|
644
|
-
muscleGroup,
|
|
645
|
-
context: {
|
|
646
|
-
goal,
|
|
647
|
-
level,
|
|
648
|
-
equipments,
|
|
649
|
-
focusMuscle,
|
|
650
|
-
},
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
program.routines.push({
|
|
655
|
-
name: routine,
|
|
656
|
-
notes: routineTemplate.notes,
|
|
657
|
-
exercises: routineExercises,
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
// Return result based on whether there were errors
|
|
661
|
-
if (allErrors.length > 0) {
|
|
662
|
-
const result = {
|
|
663
|
-
success: false,
|
|
664
|
-
errors: allErrors,
|
|
665
|
-
partialProgram: program,
|
|
666
|
-
};
|
|
667
|
-
return debugExerciseSelectionTrace
|
|
668
|
-
? Object.assign(Object.assign({}, result), { exerciseSelectionTraces }) : result;
|
|
669
|
-
}
|
|
670
|
-
const result = {
|
|
671
|
-
success: true,
|
|
672
|
-
program,
|
|
673
|
-
};
|
|
674
|
-
return debugExerciseSelectionTrace
|
|
675
|
-
? Object.assign(Object.assign({}, result), { exerciseSelectionTraces }) : result;
|
|
676
|
-
}
|