hevy-shared 1.0.1045 → 1.0.1047
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/built/API/types.d.ts +1 -0
- package/built/hevyTrainer.d.ts +43 -34
- package/built/hevyTrainer.js +65 -64
- package/built/index.d.ts +6 -112
- package/built/index.js +0 -1
- package/built/tests/hevyTrainer.test.js +75 -62
- package/built/tests/testUtils.js +0 -1
- package/built/workout/normalizedWorkout.d.ts +0 -1
- package/built/workout/postWorkoutRequest.d.ts +0 -1
- package/built/workout/userWorkout.d.ts +0 -1
- package/built/workout/workout.d.ts +0 -1
- package/package.json +1 -1
package/built/API/types.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface RequestConfig {
|
|
|
13
13
|
baseURL?: string;
|
|
14
14
|
params?: unknown;
|
|
15
15
|
signal?: AbortSignal;
|
|
16
|
+
timeout?: number;
|
|
16
17
|
}
|
|
17
18
|
export type HTTPMethod = keyof {
|
|
18
19
|
[K in keyof HTTPClient as HTTPClient[K] extends (...args: any) => Promise<HTTPResponse<never>> ? K : never]: never;
|
package/built/hevyTrainer.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { WeeklyTrainingFrequency, TrainingGoal, TrainingLevel, SimplifiedMuscleGroup, MuscleGroup, LibraryExercise, ExerciseCategory, GranularEquipment, HevyTrainerProgramEquipment, RestTimerLength, CardioPreference
|
|
1
|
+
import { WeeklyTrainingFrequency, TrainingGoal, TrainingLevel, SimplifiedMuscleGroup, MuscleGroup, LibraryExercise, ExerciseCategory, GranularEquipment, HevyTrainerProgramEquipment, RestTimerLength, CardioPreference } from '.';
|
|
2
2
|
export type HevyTrainerExerciseCategory = (typeof hevyTrainerExerciseCategories)[number];
|
|
3
|
-
export type
|
|
3
|
+
export type HevyTrainerRoutineName = (typeof routineNames)[number];
|
|
4
4
|
export declare const workoutDurationOptions: readonly [40, 60, 80];
|
|
5
5
|
export type WorkoutDurationMinutes = (typeof workoutDurationOptions)[number];
|
|
6
6
|
export declare const trainerGymTypes: readonly ["home_gym", "garage_gym", "commercial_gym", "full_gym"];
|
|
@@ -16,12 +16,12 @@ export declare const trainerEquipmentToGranularEquipments: (equipments: HevyTrai
|
|
|
16
16
|
export declare const granularEquipmentsToTrainerEquipments: (granularEquipments: GranularEquipment[]) => HevyTrainerProgramEquipment[];
|
|
17
17
|
export declare const hevyTrainerExerciseCategories: readonly ["compound", "isolation"];
|
|
18
18
|
export declare const defaultDurationPerFrequency: Record<WeeklyTrainingFrequency, WorkoutDurationMinutes>;
|
|
19
|
-
export declare const
|
|
19
|
+
export declare const routineNames: readonly ["full_body_1", "full_body_2_a", "full_body_2_b", "full_body_3_a", "full_body_3_b", "full_body_3_c", "upper_1_a", "lower_1_a", "upper_1_b", "lower_1_b", "push_1", "pull_1", "legs_1", "upper_2", "lower_2", "push_2_a", "pull_2_a", "legs_2_a", "push_2_b", "pull_2_b", "legs_2_b"];
|
|
20
20
|
export type exerciseId = string;
|
|
21
21
|
export interface ExerciseSelectionCriteria {
|
|
22
22
|
exerciseCategory: HevyTrainerExerciseCategory | 'all';
|
|
23
23
|
equipments: GranularEquipment[];
|
|
24
|
-
|
|
24
|
+
routineBarbellExerciseCount: number;
|
|
25
25
|
level: TrainingLevel;
|
|
26
26
|
goal: TrainingGoal;
|
|
27
27
|
muscleGroup: MuscleGroup;
|
|
@@ -29,7 +29,7 @@ export interface ExerciseSelectionCriteria {
|
|
|
29
29
|
}
|
|
30
30
|
export interface ExerciseSelectionContext {
|
|
31
31
|
programUsedExerciseIds?: Set<string>;
|
|
32
|
-
|
|
32
|
+
routineUsedExerciseIds?: Set<string>;
|
|
33
33
|
excludedExerciseIds?: Set<string>;
|
|
34
34
|
}
|
|
35
35
|
export interface ExerciseSelectionParams<T extends HevyTrainerLibraryExercise> {
|
|
@@ -69,7 +69,7 @@ export interface ProgramGenerationParams<T extends HevyTrainerLibraryExercise> {
|
|
|
69
69
|
}
|
|
70
70
|
export interface TemplateExerciseSelectionTraceRecord {
|
|
71
71
|
traceKind: 'template';
|
|
72
|
-
|
|
72
|
+
routine: HevyTrainerRoutineName;
|
|
73
73
|
prescriptionIndex: number;
|
|
74
74
|
prescription: ExercisePrescription;
|
|
75
75
|
resolvedMuscleGroup: MuscleGroup;
|
|
@@ -79,7 +79,7 @@ export interface TemplateExerciseSelectionTraceRecord {
|
|
|
79
79
|
}
|
|
80
80
|
export interface CardioExerciseSelectionTraceRecord {
|
|
81
81
|
traceKind: 'cardio';
|
|
82
|
-
|
|
82
|
+
routine: HevyTrainerRoutineName;
|
|
83
83
|
prescriptionIndex: number;
|
|
84
84
|
selectedExerciseId?: string;
|
|
85
85
|
equipments: GranularEquipment[];
|
|
@@ -92,19 +92,19 @@ export type TrainerProgramAttemptWithTraces = TrainerProgramAttempt & {
|
|
|
92
92
|
};
|
|
93
93
|
export type FrequencyString = 'one_day' | 'two_days' | 'three_days' | 'four_days' | 'five_days' | 'six_days';
|
|
94
94
|
export declare const frequencyMap: Record<WeeklyTrainingFrequency, FrequencyString>;
|
|
95
|
-
export declare const programSplits: Record<WeeklyTrainingFrequency,
|
|
95
|
+
export declare const programSplits: Record<WeeklyTrainingFrequency, HevyTrainerRoutineName[]>;
|
|
96
96
|
export type SetsPerGoal = {
|
|
97
97
|
[key in TrainingGoal]: number;
|
|
98
98
|
};
|
|
99
99
|
export type SetsPerFrequency = {
|
|
100
100
|
[key in FrequencyString]: SetsPerGoal;
|
|
101
101
|
};
|
|
102
|
-
export interface
|
|
102
|
+
export interface RepRange {
|
|
103
103
|
rep_range_start: number;
|
|
104
104
|
rep_range_end: number;
|
|
105
105
|
}
|
|
106
106
|
export type RepRangesPerCategory = {
|
|
107
|
-
[key in HevyTrainerExerciseCategory]:
|
|
107
|
+
[key in HevyTrainerExerciseCategory]: RepRange;
|
|
108
108
|
};
|
|
109
109
|
export type RepRanges = {
|
|
110
110
|
[key in TrainingGoal]: RepRangesPerCategory;
|
|
@@ -135,11 +135,10 @@ export type ExerciseReplacements = {
|
|
|
135
135
|
};
|
|
136
136
|
export interface WorkoutTemplate {
|
|
137
137
|
exercises: ExercisePrescription[];
|
|
138
|
-
/** @deprecated Because this is english-only and not translatable so we are not using it currently */
|
|
139
138
|
notes?: string;
|
|
140
139
|
}
|
|
141
140
|
export type Templates = {
|
|
142
|
-
[key in
|
|
141
|
+
[key in HevyTrainerRoutineName]: WorkoutTemplate;
|
|
143
142
|
};
|
|
144
143
|
export interface BackofficeTrainerPreset {
|
|
145
144
|
id?: number;
|
|
@@ -158,38 +157,39 @@ export interface TrainerAlgorithmSettings {
|
|
|
158
157
|
exercise_notes: ExerciseNotes;
|
|
159
158
|
exercise_replacements: ExerciseReplacements;
|
|
160
159
|
}
|
|
160
|
+
/** Resistance prescriptions from templates (sets, reps, rest, notes). */
|
|
161
161
|
export interface TrainerProgramResistanceExercise {
|
|
162
162
|
kind: 'resistance';
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
exerciseTemplate: HevyTrainerLibraryExercise;
|
|
164
|
+
muscleGroup: MuscleGroup | 'focus_muscle';
|
|
165
|
+
category: HevyTrainerExerciseCategory;
|
|
166
|
+
sets: number;
|
|
167
|
+
warmupSetCount?: number;
|
|
168
|
+
repRangeStart: number;
|
|
169
|
+
repRangeEnd: number;
|
|
170
|
+
restTimerSeconds: number;
|
|
171
|
+
notes?: string;
|
|
168
172
|
}
|
|
173
|
+
/** Optional cardio attachment — no prescription semantics beyond exercise choice. */
|
|
169
174
|
export interface TrainerProgramCardioExercise {
|
|
170
175
|
kind: 'cardio';
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
duration_seconds: number;
|
|
176
|
+
exerciseTemplate: HevyTrainerLibraryExercise;
|
|
177
|
+
durationSeconds: number;
|
|
174
178
|
}
|
|
175
|
-
export
|
|
176
|
-
kind: 'other';
|
|
177
|
-
exercise_template: HevyTrainerLibraryExercise;
|
|
178
|
-
set_count: number;
|
|
179
|
-
}
|
|
180
|
-
export type TrainerProgramExercise = TrainerProgramResistanceExercise | TrainerProgramCardioExercise | TrainerProgramOtherExercise;
|
|
179
|
+
export type TrainerProgramExercise = TrainerProgramResistanceExercise | TrainerProgramCardioExercise;
|
|
181
180
|
export declare const isTrainerProgramResistanceExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramResistanceExercise;
|
|
182
181
|
export declare const isTrainerProgramCardioExercise: (exercise: TrainerProgramExercise) => exercise is TrainerProgramCardioExercise;
|
|
183
|
-
export
|
|
184
|
-
|
|
185
|
-
name: TrainerWorkoutTemplateName;
|
|
182
|
+
export interface TrainerProgramRoutine {
|
|
183
|
+
name: HevyTrainerRoutineName;
|
|
186
184
|
exercises: TrainerProgramExercise[];
|
|
185
|
+
notes?: string;
|
|
187
186
|
}
|
|
188
|
-
export interface
|
|
189
|
-
|
|
187
|
+
export interface TrainerProgram {
|
|
188
|
+
name: WeeklyTrainingFrequency;
|
|
189
|
+
routines: TrainerProgramRoutine[];
|
|
190
190
|
}
|
|
191
191
|
export declare const getTrainerSetCount: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, frequency: WeeklyTrainingFrequency) => number;
|
|
192
|
-
export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) =>
|
|
192
|
+
export declare const getTrainerRepRange: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, exerciseCategory: HevyTrainerExerciseCategory) => RepRange;
|
|
193
193
|
export declare const getTrainerRestTimerSeconds: (trainerAlgorithmSettings: TrainerAlgorithmSettings, goal: TrainingGoal, length: RestTimerLength, exerciseCategory: HevyTrainerExerciseCategory) => number;
|
|
194
194
|
/**
|
|
195
195
|
* Normalizes the exercise category to a HevyTrainerExerciseCategory
|
|
@@ -243,14 +243,23 @@ export interface ExercisePrescriptionError {
|
|
|
243
243
|
focusMuscle?: SimplifiedMuscleGroup;
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
+
export interface TrainerProgramExerciseResult {
|
|
247
|
+
success: true;
|
|
248
|
+
exercise: TrainerProgramExercise;
|
|
249
|
+
}
|
|
250
|
+
export interface TrainerProgramExerciseError {
|
|
251
|
+
success: false;
|
|
252
|
+
error: ExercisePrescriptionError;
|
|
253
|
+
}
|
|
254
|
+
export type TrainerProgramExerciseAttempt = TrainerProgramExerciseResult | TrainerProgramExerciseError;
|
|
246
255
|
export interface TrainerProgramResult {
|
|
247
256
|
success: true;
|
|
248
|
-
program:
|
|
257
|
+
program: TrainerProgram;
|
|
249
258
|
}
|
|
250
259
|
export interface TrainerProgramError {
|
|
251
260
|
success: false;
|
|
252
261
|
errors: ExercisePrescriptionError[];
|
|
253
|
-
partialProgram:
|
|
262
|
+
partialProgram: TrainerProgram;
|
|
254
263
|
}
|
|
255
264
|
export type TrainerProgramAttempt = TrainerProgramResult | TrainerProgramError;
|
|
256
265
|
/**
|
package/built/hevyTrainer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = exports.getPrioritySortedExercises = exports.isEquipmentCompatible = exports.normalizeExerciseCategory = exports.getTrainerRestTimerSeconds = exports.getTrainerRepRange = exports.getTrainerSetCount = exports.
|
|
3
|
+
exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = exports.getPrioritySortedExercises = exports.isEquipmentCompatible = exports.normalizeExerciseCategory = exports.getTrainerRestTimerSeconds = exports.getTrainerRepRange = exports.getTrainerSetCount = exports.isTrainerProgramCardioExercise = exports.isTrainerProgramResistanceExercise = exports.programSplits = exports.frequencyMap = exports.routineNames = exports.defaultDurationPerFrequency = exports.hevyTrainerExerciseCategories = exports.granularEquipmentsToTrainerEquipments = exports.trainerEquipmentToGranularEquipments = exports.granularEquipmentDefaults = exports.trainerGymTypes = exports.workoutDurationOptions = void 0;
|
|
4
4
|
exports.pickTrainerExercise = pickTrainerExercise;
|
|
5
5
|
exports.generateProgram = generateProgram;
|
|
6
6
|
const _1 = require(".");
|
|
@@ -152,7 +152,7 @@ exports.defaultDurationPerFrequency = {
|
|
|
152
152
|
5: 40,
|
|
153
153
|
6: 40,
|
|
154
154
|
};
|
|
155
|
-
exports.
|
|
155
|
+
exports.routineNames = [
|
|
156
156
|
// Full body 1x
|
|
157
157
|
'full_body_1',
|
|
158
158
|
// Full body 2x
|
|
@@ -201,8 +201,6 @@ const isTrainerProgramResistanceExercise = (exercise) => exercise.kind === 'resi
|
|
|
201
201
|
exports.isTrainerProgramResistanceExercise = isTrainerProgramResistanceExercise;
|
|
202
202
|
const isTrainerProgramCardioExercise = (exercise) => exercise.kind === 'cardio';
|
|
203
203
|
exports.isTrainerProgramCardioExercise = isTrainerProgramCardioExercise;
|
|
204
|
-
const isTrainerProgramOtherExercise = (exercise) => exercise.kind === 'other';
|
|
205
|
-
exports.isTrainerProgramOtherExercise = isTrainerProgramOtherExercise;
|
|
206
204
|
const getTrainerSetCount = (trainerAlgorithmSettings, goal, frequency) => {
|
|
207
205
|
return trainerAlgorithmSettings.sets[exports.frequencyMap[frequency]][goal];
|
|
208
206
|
};
|
|
@@ -293,12 +291,12 @@ const MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK = 3;
|
|
|
293
291
|
* - Only enforce the cap when the user has at least one "barbell substitute" equipment available
|
|
294
292
|
* (otherwise we allow barbell exercises to avoid running out of viable options)
|
|
295
293
|
*/
|
|
296
|
-
const isBarbellExerciseAllowed = (exercise,
|
|
294
|
+
const isBarbellExerciseAllowed = (exercise, routineBarbellExerciseCount, userEquipments, frequency) => {
|
|
297
295
|
const isCandidateBarbell = exercise.equipment_category === 'barbell';
|
|
298
296
|
const isOncePerWeek = frequency === 1;
|
|
299
297
|
if (!isCandidateBarbell || !isOncePerWeek)
|
|
300
298
|
return true;
|
|
301
|
-
const isAtOrOverLimit =
|
|
299
|
+
const isAtOrOverLimit = routineBarbellExerciseCount >= MAX_BARBELL_EXERCISES_FOR_ONCE_PER_WEEK;
|
|
302
300
|
if (!isAtOrOverLimit)
|
|
303
301
|
return true;
|
|
304
302
|
const barbellSubstitutes = [
|
|
@@ -316,7 +314,7 @@ const isBarbellExerciseAllowed = (exercise, workoutBarbellExerciseCount, userEqu
|
|
|
316
314
|
const isExerciseUsed = (exercise, context) => {
|
|
317
315
|
var _a, _b, _c;
|
|
318
316
|
return (((_a = context.programUsedExerciseIds) === null || _a === void 0 ? void 0 : _a.has(exercise.id)) ||
|
|
319
|
-
((_b = context.
|
|
317
|
+
((_b = context.routineUsedExerciseIds) === null || _b === void 0 ? void 0 : _b.has(exercise.id)) ||
|
|
320
318
|
((_c = context.excludedExerciseIds) === null || _c === void 0 ? void 0 : _c.has(exercise.id)) ||
|
|
321
319
|
false);
|
|
322
320
|
};
|
|
@@ -395,16 +393,16 @@ const getPrioritySortedExercises = (exercisePriorities, exerciseStore) => {
|
|
|
395
393
|
return sortedExercises;
|
|
396
394
|
};
|
|
397
395
|
exports.getPrioritySortedExercises = getPrioritySortedExercises;
|
|
398
|
-
const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle,
|
|
396
|
+
const getMuscleGroup = ({ muscleGroupPrescription, programFocusMuscleExerciseCount, focusMuscle, routineName, }) => {
|
|
399
397
|
if (muscleGroupPrescription === 'focus_muscle') {
|
|
400
|
-
// If the user has selected a focus muscle and it is allowed for the
|
|
398
|
+
// If the user has selected a focus muscle and it is allowed for the routine,
|
|
401
399
|
// we return the focus muscle extra exercise
|
|
402
400
|
if (!!focusMuscle &&
|
|
403
|
-
isFocusMuscleExtraExerciseAllowed(
|
|
401
|
+
isFocusMuscleExtraExerciseAllowed(routineName, focusMuscle)) {
|
|
404
402
|
const n = _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle].length;
|
|
405
403
|
return _1.simplifiedMuscleGroupToMuscleGroups[focusMuscle][programFocusMuscleExerciseCount % n];
|
|
406
404
|
}
|
|
407
|
-
// If the user has not selected a focus muscle or it is not allowed for the
|
|
405
|
+
// If the user has not selected a focus muscle or it is not allowed for the routine
|
|
408
406
|
// we skip this extra exercise for the focus muscle in the program
|
|
409
407
|
return undefined;
|
|
410
408
|
}
|
|
@@ -422,7 +420,7 @@ const isExerciseMatch = (exercise, criteria) => {
|
|
|
422
420
|
const levelMatch = (_b = (_a = exercise.level) === null || _a === void 0 ? void 0 : _a.includes(criteria.level)) !== null && _b !== void 0 ? _b : false;
|
|
423
421
|
const goalMatch = (_d = (_c = exercise.goal) === null || _c === void 0 ? void 0 : _c.includes(criteria.goal)) !== null && _d !== void 0 ? _d : false;
|
|
424
422
|
const equipmentMatch = (0, exports.isEquipmentCompatible)(exercise, criteria.equipments);
|
|
425
|
-
const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.
|
|
423
|
+
const barbellLimitOk = isBarbellExerciseAllowed(exercise, criteria.routineBarbellExerciseCount, criteria.equipments, criteria.frequency);
|
|
426
424
|
return (categoryMatch && levelMatch && goalMatch && equipmentMatch && barbellLimitOk);
|
|
427
425
|
};
|
|
428
426
|
/**
|
|
@@ -467,8 +465,8 @@ const pickTrainerExerciseWithTrace = (params) => {
|
|
|
467
465
|
programUsedExerciseIds: context.programUsedExerciseIds,
|
|
468
466
|
excludedExerciseIds: context.excludedExerciseIds,
|
|
469
467
|
};
|
|
470
|
-
const
|
|
471
|
-
|
|
468
|
+
const routineContext = {
|
|
469
|
+
routineUsedExerciseIds: context.routineUsedExerciseIds,
|
|
472
470
|
excludedExerciseIds: context.excludedExerciseIds,
|
|
473
471
|
};
|
|
474
472
|
// Pass 1
|
|
@@ -482,10 +480,10 @@ const pickTrainerExerciseWithTrace = (params) => {
|
|
|
482
480
|
if (exercise)
|
|
483
481
|
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 1 }) };
|
|
484
482
|
// Pass 2
|
|
485
|
-
exercise = findMatchingExercise(exercises, criteria,
|
|
483
|
+
exercise = findMatchingExercise(exercises, criteria, routineContext);
|
|
486
484
|
trace.entries.push({
|
|
487
485
|
pass: 2,
|
|
488
|
-
label: 'exact match; not used in
|
|
486
|
+
label: 'exact match; not used in routine (reuse allowed in program)',
|
|
489
487
|
candidatePoolSize: exercises.length,
|
|
490
488
|
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
491
489
|
});
|
|
@@ -502,20 +500,20 @@ const pickTrainerExerciseWithTrace = (params) => {
|
|
|
502
500
|
if (exercise)
|
|
503
501
|
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 3 }) };
|
|
504
502
|
// Pass 4
|
|
505
|
-
exercise = findAlternativeIsolationExercise(exercises, criteria,
|
|
503
|
+
exercise = findAlternativeIsolationExercise(exercises, criteria, routineContext);
|
|
506
504
|
trace.entries.push({
|
|
507
505
|
pass: 4,
|
|
508
|
-
label: 'handpicked alternative isolation; not used in
|
|
506
|
+
label: 'handpicked alternative isolation; not used in routine (reuse allowed in program)',
|
|
509
507
|
candidatePoolSize: exercises.length,
|
|
510
508
|
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
511
509
|
});
|
|
512
510
|
if (exercise)
|
|
513
511
|
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 4 }) };
|
|
514
512
|
// Pass 5
|
|
515
|
-
exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }),
|
|
513
|
+
exercise = findMatchingExercise(exercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
|
|
516
514
|
trace.entries.push({
|
|
517
515
|
pass: 5,
|
|
518
|
-
label: 'match regardless of isolation or compound; not used in
|
|
516
|
+
label: 'match regardless of isolation or compound; not used in routine (reuse allowed in program)',
|
|
519
517
|
candidatePoolSize: exercises.length,
|
|
520
518
|
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
521
519
|
});
|
|
@@ -525,10 +523,10 @@ const pickTrainerExerciseWithTrace = (params) => {
|
|
|
525
523
|
const secondaryMuscleExercises = Object.values(sortedExercises)
|
|
526
524
|
.flat()
|
|
527
525
|
.filter((e) => e.other_muscles.includes(criteria.muscleGroup));
|
|
528
|
-
exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }),
|
|
526
|
+
exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
|
|
529
527
|
trace.entries.push({
|
|
530
528
|
pass: 6,
|
|
531
|
-
label: 'secondary muscle match; regardless of isolation or compound; not used in
|
|
529
|
+
label: 'secondary muscle match; regardless of isolation or compound; not used in routine (reuse allowed in program)',
|
|
532
530
|
candidatePoolSize: secondaryMuscleExercises.length,
|
|
533
531
|
selectedExerciseId: exercise === null || exercise === void 0 ? void 0 : exercise.id,
|
|
534
532
|
});
|
|
@@ -544,11 +542,11 @@ function pickTrainerExercise(params) {
|
|
|
544
542
|
}
|
|
545
543
|
const CARDIO_TRACE_INDEX_BEFORE_TEMPLATE = -1;
|
|
546
544
|
exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS = 600;
|
|
547
|
-
/** Inserts optional cardio before or after template exercises for one
|
|
548
|
-
const
|
|
545
|
+
/** Inserts optional cardio before or after template exercises for one routine. */
|
|
546
|
+
const attachOptionalCardioToRoutine = ({ cardioPreference, sortedExercises, criteria, debugExerciseSelectionTrace, exerciseSelectionTraces, routine, templatePrescriptionCount, routineExercises, }) => {
|
|
549
547
|
if (cardioPreference === 'no-cardio')
|
|
550
548
|
return;
|
|
551
|
-
// Cardio is reusable across
|
|
549
|
+
// Cardio is reusable across routines, so program/routine used-id contexts are
|
|
552
550
|
// intentionally omitted — only the global excluded set applies.
|
|
553
551
|
const selectionParams = {
|
|
554
552
|
sortedExercises,
|
|
@@ -559,7 +557,7 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
|
|
|
559
557
|
equipments: criteria.equipments,
|
|
560
558
|
muscleGroup: 'cardio',
|
|
561
559
|
exerciseCategory: 'all',
|
|
562
|
-
|
|
560
|
+
routineBarbellExerciseCount: 0,
|
|
563
561
|
},
|
|
564
562
|
context: { excludedExerciseIds: criteria.excludedExerciseIds },
|
|
565
563
|
};
|
|
@@ -569,18 +567,17 @@ const attachOptionalCardioToWorkoutTemplate = ({ cardioPreference, sortedExercis
|
|
|
569
567
|
return;
|
|
570
568
|
const slot = {
|
|
571
569
|
kind: 'cardio',
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
set_count: 1,
|
|
570
|
+
exerciseTemplate: cardioExercise,
|
|
571
|
+
durationSeconds: exports.DEFAULT_TRAINER_CARDIO_ATTACHMENT_DURATION_SECONDS,
|
|
575
572
|
};
|
|
576
573
|
if (cardioPreference === 'workout-start')
|
|
577
|
-
|
|
574
|
+
routineExercises.unshift(slot);
|
|
578
575
|
else
|
|
579
|
-
|
|
576
|
+
routineExercises.push(slot);
|
|
580
577
|
if (debugExerciseSelectionTrace) {
|
|
581
578
|
exerciseSelectionTraces.push({
|
|
582
579
|
traceKind: 'cardio',
|
|
583
|
-
|
|
580
|
+
routine,
|
|
584
581
|
prescriptionIndex: cardioPreference === 'workout-start'
|
|
585
582
|
? CARDIO_TRACE_INDEX_BEFORE_TEMPLATE
|
|
586
583
|
: templatePrescriptionCount,
|
|
@@ -595,27 +592,28 @@ function generateProgram(params) {
|
|
|
595
592
|
var _a, _b;
|
|
596
593
|
const { trainerAlgorithmSettings, frequency, goal, level, equipments, workoutDurationMinutes, restTimerLength, exerciseStore, focusMuscle, excludedExerciseIds, debugExerciseSelectionTrace, cardioPreference, } = params;
|
|
597
594
|
const exerciseSelectionTraces = [];
|
|
598
|
-
const
|
|
595
|
+
const routines = exports.programSplits[frequency];
|
|
599
596
|
const program = {
|
|
600
|
-
|
|
597
|
+
name: frequency,
|
|
598
|
+
routines: [],
|
|
601
599
|
};
|
|
602
600
|
// TODO: Rename sortedExercises to sortedExercisesByMuscleGroup or something similar
|
|
603
601
|
const sortedExercises = (0, exports.getPrioritySortedExercises)(trainerAlgorithmSettings.exercise_priorities, exerciseStore);
|
|
604
602
|
const programUsedExerciseIds = new Set();
|
|
605
603
|
let programFocusMuscleExerciseCount = 0;
|
|
606
604
|
const allErrors = [];
|
|
607
|
-
for (const
|
|
608
|
-
const
|
|
609
|
-
let
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
for (let prescriptionIndex = 0; prescriptionIndex <
|
|
613
|
-
const exercisePrescription =
|
|
605
|
+
for (const routine of routines) {
|
|
606
|
+
const routineTemplate = trainerAlgorithmSettings.templates[routine];
|
|
607
|
+
let routineBarbellExerciseCount = 0;
|
|
608
|
+
const routineUsedExerciseIds = new Set();
|
|
609
|
+
const routineExercises = [];
|
|
610
|
+
for (let prescriptionIndex = 0; prescriptionIndex < routineTemplate.exercises.length; prescriptionIndex++) {
|
|
611
|
+
const exercisePrescription = routineTemplate.exercises[prescriptionIndex];
|
|
614
612
|
const muscleGroup = getMuscleGroup({
|
|
615
613
|
muscleGroupPrescription: exercisePrescription.muscle_group,
|
|
616
614
|
programFocusMuscleExerciseCount,
|
|
617
615
|
focusMuscle,
|
|
618
|
-
|
|
616
|
+
routineName: routine,
|
|
619
617
|
});
|
|
620
618
|
// If the muscle group is not found, skip the exercise
|
|
621
619
|
if (!muscleGroup) {
|
|
@@ -634,7 +632,7 @@ function generateProgram(params) {
|
|
|
634
632
|
exerciseCategory: exercisePrescription.category,
|
|
635
633
|
equipments,
|
|
636
634
|
muscleGroup,
|
|
637
|
-
|
|
635
|
+
routineBarbellExerciseCount,
|
|
638
636
|
level,
|
|
639
637
|
goal,
|
|
640
638
|
};
|
|
@@ -645,7 +643,7 @@ function generateProgram(params) {
|
|
|
645
643
|
criteria,
|
|
646
644
|
context: {
|
|
647
645
|
programUsedExerciseIds,
|
|
648
|
-
|
|
646
|
+
routineUsedExerciseIds,
|
|
649
647
|
excludedExerciseIds,
|
|
650
648
|
},
|
|
651
649
|
withTrace: true,
|
|
@@ -653,7 +651,7 @@ function generateProgram(params) {
|
|
|
653
651
|
exercise = selection.exercise;
|
|
654
652
|
exerciseSelectionTraces.push({
|
|
655
653
|
traceKind: 'template',
|
|
656
|
-
|
|
654
|
+
routine,
|
|
657
655
|
prescriptionIndex,
|
|
658
656
|
prescription: exercisePrescription,
|
|
659
657
|
resolvedMuscleGroup: muscleGroup,
|
|
@@ -668,29 +666,30 @@ function generateProgram(params) {
|
|
|
668
666
|
criteria,
|
|
669
667
|
context: {
|
|
670
668
|
programUsedExerciseIds,
|
|
671
|
-
|
|
669
|
+
routineUsedExerciseIds,
|
|
672
670
|
excludedExerciseIds,
|
|
673
671
|
},
|
|
674
672
|
});
|
|
675
673
|
}
|
|
676
674
|
if (!!exercise) {
|
|
677
675
|
programUsedExerciseIds.add(exercise.id);
|
|
678
|
-
|
|
676
|
+
routineUsedExerciseIds.add(exercise.id);
|
|
679
677
|
if (exercise.equipment_category === 'barbell')
|
|
680
|
-
|
|
678
|
+
routineBarbellExerciseCount++;
|
|
681
679
|
if (exercisePrescription.muscle_group === 'focus_muscle')
|
|
682
680
|
programFocusMuscleExerciseCount++;
|
|
683
681
|
const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings, goal, exercisePrescription.category);
|
|
684
|
-
|
|
682
|
+
routineExercises.push({
|
|
685
683
|
kind: 'resistance',
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
684
|
+
exerciseTemplate: exercise,
|
|
685
|
+
muscleGroup: exercisePrescription.muscle_group,
|
|
686
|
+
category: exercisePrescription.category,
|
|
687
|
+
sets: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
|
|
688
|
+
repRangeStart: repRange.rep_range_start,
|
|
689
|
+
repRangeEnd: repRange.rep_range_end,
|
|
690
|
+
restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
|
|
691
|
+
warmupSetCount: exercisePrescription.warmup_set_count,
|
|
692
|
+
notes: (_b = trainerAlgorithmSettings.exercise_notes[exercise.id]) !== null && _b !== void 0 ? _b : '',
|
|
694
693
|
});
|
|
695
694
|
}
|
|
696
695
|
else {
|
|
@@ -708,21 +707,23 @@ function generateProgram(params) {
|
|
|
708
707
|
});
|
|
709
708
|
}
|
|
710
709
|
}
|
|
711
|
-
|
|
710
|
+
attachOptionalCardioToRoutine({
|
|
712
711
|
cardioPreference,
|
|
713
712
|
sortedExercises,
|
|
714
713
|
criteria: { equipments, level, goal, frequency, excludedExerciseIds },
|
|
715
714
|
debugExerciseSelectionTrace,
|
|
716
715
|
exerciseSelectionTraces,
|
|
717
|
-
|
|
718
|
-
templatePrescriptionCount:
|
|
719
|
-
|
|
716
|
+
routine,
|
|
717
|
+
templatePrescriptionCount: routineTemplate.exercises.length,
|
|
718
|
+
routineExercises,
|
|
720
719
|
});
|
|
721
|
-
program.
|
|
722
|
-
name:
|
|
723
|
-
|
|
720
|
+
program.routines.push({
|
|
721
|
+
name: routine,
|
|
722
|
+
notes: routineTemplate.notes,
|
|
723
|
+
exercises: routineExercises,
|
|
724
724
|
});
|
|
725
725
|
}
|
|
726
|
+
// Return result based on whether there were errors
|
|
726
727
|
if (allErrors.length > 0) {
|
|
727
728
|
const result = {
|
|
728
729
|
success: false,
|
package/built/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { InstructionsLanguage } from './exerciseLocaleUtils';
|
|
2
|
-
import {
|
|
2
|
+
import { WorkoutDurationMinutes } from './hevyTrainer';
|
|
3
3
|
import { BugReportQuestionSchema, CreateBugReportRequestBodySchema, postEmailBackofficeSchema } from './schemas';
|
|
4
4
|
import { Language } from './translations';
|
|
5
5
|
import { Lookup } from './typeUtils';
|
|
@@ -302,7 +302,8 @@ export interface BackofficeExistingUserResponse {
|
|
|
302
302
|
public_api_key: string | null;
|
|
303
303
|
limited_discovery: boolean;
|
|
304
304
|
coach_trial_expire_date?: string;
|
|
305
|
-
hevy_trainer_program:
|
|
305
|
+
hevy_trainer_program: HevyTrainerProgram | null;
|
|
306
|
+
email_consent: boolean;
|
|
306
307
|
}
|
|
307
308
|
export interface BackofficeDeletedUserResponse {
|
|
308
309
|
state: 'deleted-account';
|
|
@@ -747,10 +748,6 @@ export interface RepRange {
|
|
|
747
748
|
start: number | null;
|
|
748
749
|
end: number | null;
|
|
749
750
|
}
|
|
750
|
-
export interface StrictRepRange extends RepRange {
|
|
751
|
-
start: number;
|
|
752
|
-
end: number;
|
|
753
|
-
}
|
|
754
751
|
export interface WorkoutComment {
|
|
755
752
|
id: number;
|
|
756
753
|
username: string;
|
|
@@ -879,14 +876,12 @@ export interface Routine extends BaseRoutine {
|
|
|
879
876
|
username: string;
|
|
880
877
|
coach_id: null;
|
|
881
878
|
}
|
|
882
|
-
/** @deprecated Use TrainerWorkoutTemplate instead */
|
|
883
879
|
export interface HevyTrainerRoutine extends Routine {
|
|
884
880
|
hevy_trainer_program_id: string;
|
|
885
881
|
program_id: null;
|
|
886
882
|
coach_force_rpe_enabled: false;
|
|
887
883
|
folder_id: null;
|
|
888
884
|
}
|
|
889
|
-
/** @deprecated Use TrainerWorkoutTemplate instead */
|
|
890
885
|
export declare const isHevyTrainerRoutine: (routine: BaseRoutine) => routine is HevyTrainerRoutine;
|
|
891
886
|
export type CoachsShallowLibraryRoutine = Omit<BaseRoutine, 'exercises' | 'profile_pic' | 'hevy_trainer_program_id' | 'username'>;
|
|
892
887
|
export interface CoachesRoutine extends BaseRoutine {
|
|
@@ -1055,105 +1050,6 @@ export interface GetTeamInviteResponse {
|
|
|
1055
1050
|
export interface OutstandingInvitesForCoachTeamResponse {
|
|
1056
1051
|
invites: CoachTeamInvite[];
|
|
1057
1052
|
}
|
|
1058
|
-
export interface TrainerWorkoutTemplateResistanceExercise {
|
|
1059
|
-
kind: 'resistance';
|
|
1060
|
-
id: string;
|
|
1061
|
-
exercise_template_id: string;
|
|
1062
|
-
index: number;
|
|
1063
|
-
set_count: number;
|
|
1064
|
-
warmup_set_count: number;
|
|
1065
|
-
rep_range: StrictRepRange;
|
|
1066
|
-
rest_seconds: number;
|
|
1067
|
-
}
|
|
1068
|
-
export interface TrainerWorkoutTemplateCardioExercise {
|
|
1069
|
-
kind: 'cardio';
|
|
1070
|
-
id: string;
|
|
1071
|
-
exercise_template_id: string;
|
|
1072
|
-
index: number;
|
|
1073
|
-
set_count: 1;
|
|
1074
|
-
duration_seconds: number;
|
|
1075
|
-
}
|
|
1076
|
-
export interface TrainerWorkoutTemplateOtherExercise {
|
|
1077
|
-
kind: 'other';
|
|
1078
|
-
id: string;
|
|
1079
|
-
exercise_template_id: string;
|
|
1080
|
-
index: number;
|
|
1081
|
-
set_count: number;
|
|
1082
|
-
}
|
|
1083
|
-
export type TrainerWorkoutTemplateExercise = TrainerWorkoutTemplateResistanceExercise | TrainerWorkoutTemplateCardioExercise | TrainerWorkoutTemplateOtherExercise;
|
|
1084
|
-
export interface TrainerWorkoutTemplate {
|
|
1085
|
-
id: string;
|
|
1086
|
-
hevy_trainer_program_id: string;
|
|
1087
|
-
index: number;
|
|
1088
|
-
name: TrainerWorkoutTemplateName;
|
|
1089
|
-
exercises: TrainerWorkoutTemplateExercise[];
|
|
1090
|
-
}
|
|
1091
|
-
export interface TrainerProgramV3 {
|
|
1092
|
-
id: string;
|
|
1093
|
-
schema_version: 'v3';
|
|
1094
|
-
created_at: string;
|
|
1095
|
-
updated_at: string;
|
|
1096
|
-
level: TrainingLevel;
|
|
1097
|
-
goal: TrainingGoal;
|
|
1098
|
-
equipments: GranularEquipment[];
|
|
1099
|
-
weekly_frequency: WeeklyTrainingFrequency;
|
|
1100
|
-
templates: TrainerWorkoutTemplate[];
|
|
1101
|
-
focus_muscle?: SimplifiedMuscleGroup;
|
|
1102
|
-
next_workout_index: number;
|
|
1103
|
-
workout_duration_minutes: WorkoutDurationMinutes;
|
|
1104
|
-
rest_timer_length: RestTimerLength;
|
|
1105
|
-
cardio_preference: CardioPreference;
|
|
1106
|
-
}
|
|
1107
|
-
export type TrainerWorkoutTemplateExerciseInput = Omit<TrainerWorkoutTemplateResistanceExercise, 'id'> | Omit<TrainerWorkoutTemplateCardioExercise, 'id'> | Omit<TrainerWorkoutTemplateOtherExercise, 'id'>;
|
|
1108
|
-
export interface PostTrainerWorkoutTemplate {
|
|
1109
|
-
name: TrainerWorkoutTemplateName;
|
|
1110
|
-
index: number;
|
|
1111
|
-
exercises: TrainerWorkoutTemplateExerciseInput[];
|
|
1112
|
-
}
|
|
1113
|
-
export interface UpdateTrainerWorkoutTemplate extends PostTrainerWorkoutTemplate {
|
|
1114
|
-
id: string;
|
|
1115
|
-
}
|
|
1116
|
-
export interface PostTrainerProgramV3RequestBody {
|
|
1117
|
-
program: {
|
|
1118
|
-
version: 3;
|
|
1119
|
-
title: string;
|
|
1120
|
-
level: TrainingLevel;
|
|
1121
|
-
goal: TrainingGoal;
|
|
1122
|
-
equipments: GranularEquipment[];
|
|
1123
|
-
weekly_frequency: WeeklyTrainingFrequency;
|
|
1124
|
-
focus_muscle?: SimplifiedMuscleGroup;
|
|
1125
|
-
next_workout_index?: number;
|
|
1126
|
-
workout_duration_minutes: WorkoutDurationMinutes;
|
|
1127
|
-
rest_timer_length: RestTimerLength;
|
|
1128
|
-
cardio_preference: CardioPreference;
|
|
1129
|
-
templates: PostTrainerWorkoutTemplate[];
|
|
1130
|
-
};
|
|
1131
|
-
/**
|
|
1132
|
-
* Whether to delete (archive) the caller's currently active Hevy Trainer
|
|
1133
|
-
* program before creating the new one. Creating a program implicitly
|
|
1134
|
-
* replaces any active program, so this must be set explicitly:
|
|
1135
|
-
* - `false` — fail with `ActiveProgramExists` (409) if one already exists.
|
|
1136
|
-
* - `true` — archive the existing active program, then create the new one.
|
|
1137
|
-
*/
|
|
1138
|
-
deleteActiveProgram: boolean;
|
|
1139
|
-
}
|
|
1140
|
-
export interface UpdateTrainerProgramV3RequestBody {
|
|
1141
|
-
program: {
|
|
1142
|
-
version: 3;
|
|
1143
|
-
programId: string;
|
|
1144
|
-
level: TrainingLevel;
|
|
1145
|
-
goal: TrainingGoal;
|
|
1146
|
-
equipments: GranularEquipment[];
|
|
1147
|
-
weekly_frequency: WeeklyTrainingFrequency;
|
|
1148
|
-
focus_muscle?: SimplifiedMuscleGroup;
|
|
1149
|
-
next_workout_index?: number;
|
|
1150
|
-
workout_duration_minutes: WorkoutDurationMinutes;
|
|
1151
|
-
rest_timer_length: RestTimerLength;
|
|
1152
|
-
cardio_preference: CardioPreference;
|
|
1153
|
-
templates: UpdateTrainerWorkoutTemplate[];
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1156
|
-
/** @deprecated Use TrainerProgramV3 instead */
|
|
1157
1053
|
export interface HevyTrainerProgram {
|
|
1158
1054
|
id: string;
|
|
1159
1055
|
created_at: string;
|
|
@@ -1170,7 +1066,6 @@ export interface HevyTrainerProgram {
|
|
|
1170
1066
|
rest_timer_length: RestTimerLength;
|
|
1171
1067
|
cardio_preference: CardioPreference;
|
|
1172
1068
|
}
|
|
1173
|
-
/** @deprecated Use PostTrainerProgramV3RequestBody instead */
|
|
1174
1069
|
export interface PostHevyTrainerProgramRequestBody {
|
|
1175
1070
|
program: {
|
|
1176
1071
|
version: 1;
|
|
@@ -1187,7 +1082,6 @@ export interface PostHevyTrainerProgramRequestBody {
|
|
|
1187
1082
|
cardio_preference: CardioPreference;
|
|
1188
1083
|
};
|
|
1189
1084
|
}
|
|
1190
|
-
/** @deprecated Use UpdateTrainerProgramV3RequestBody instead */
|
|
1191
1085
|
export interface UpdateHevyTrainerProgramRequestBody {
|
|
1192
1086
|
program: {
|
|
1193
1087
|
version: 1;
|
|
@@ -1210,7 +1104,7 @@ export interface UpdateHevyTrainerProgramRequestBody {
|
|
|
1210
1104
|
}[];
|
|
1211
1105
|
};
|
|
1212
1106
|
}
|
|
1213
|
-
/** @deprecated Use
|
|
1107
|
+
/** @deprecated Use HevyTrainerProgram instead */
|
|
1214
1108
|
export interface HevyTrainerProgramOld {
|
|
1215
1109
|
id: string;
|
|
1216
1110
|
created_at: string;
|
|
@@ -1225,7 +1119,7 @@ export interface HevyTrainerProgramOld {
|
|
|
1225
1119
|
next_workout_index: number;
|
|
1226
1120
|
workout_duration_minutes?: WorkoutDurationMinutes;
|
|
1227
1121
|
}
|
|
1228
|
-
/** @deprecated Use
|
|
1122
|
+
/** @deprecated Use PostHevyTrainerProgramRequestBody instead */
|
|
1229
1123
|
export interface PostHevyTrainerProgramOldRequestBody {
|
|
1230
1124
|
program: {
|
|
1231
1125
|
title: string;
|
|
@@ -1239,7 +1133,7 @@ export interface PostHevyTrainerProgramOldRequestBody {
|
|
|
1239
1133
|
workout_duration_minutes?: WorkoutDurationMinutes;
|
|
1240
1134
|
};
|
|
1241
1135
|
}
|
|
1242
|
-
/** @deprecated Use
|
|
1136
|
+
/** @deprecated Use UpdateHevyTrainerProgramRequestBody instead */
|
|
1243
1137
|
export interface UpdateHevyTrainerProgramOldRequestBody {
|
|
1244
1138
|
program: {
|
|
1245
1139
|
programId: string;
|
package/built/index.js
CHANGED
|
@@ -321,7 +321,6 @@ const isWorkoutBiometrics = (x) => {
|
|
|
321
321
|
return caloriesAreValid && heartSamplesAreValid;
|
|
322
322
|
};
|
|
323
323
|
exports.isWorkoutBiometrics = isWorkoutBiometrics;
|
|
324
|
-
/** @deprecated Use TrainerWorkoutTemplate instead */
|
|
325
324
|
const isHevyTrainerRoutine = (routine) => routine.hevy_trainer_program_id !== null;
|
|
326
325
|
exports.isHevyTrainerRoutine = isHevyTrainerRoutine;
|
|
327
326
|
exports.measurementsList = [
|
|
@@ -236,7 +236,7 @@ const makeSettings = (overrides = {}) => {
|
|
|
236
236
|
};
|
|
237
237
|
return acc;
|
|
238
238
|
}, {});
|
|
239
|
-
const templates = hevyTrainer_1.
|
|
239
|
+
const templates = hevyTrainer_1.routineNames.reduce((acc, name) => {
|
|
240
240
|
acc[name] = { exercises: [] };
|
|
241
241
|
return acc;
|
|
242
242
|
}, {});
|
|
@@ -372,7 +372,7 @@ describe('getPrioritySortedExercises', () => {
|
|
|
372
372
|
expect(result.quadriceps.map((e) => e.id)).toEqual(['leg-ex']);
|
|
373
373
|
});
|
|
374
374
|
});
|
|
375
|
-
const baseCriteria = (overrides = {}) => (Object.assign({ exerciseCategory: 'compound', equipments: [],
|
|
375
|
+
const baseCriteria = (overrides = {}) => (Object.assign({ exerciseCategory: 'compound', equipments: [], routineBarbellExerciseCount: 0, level: 'beginner', goal: 'strength', muscleGroup: 'chest', frequency: 3 }, overrides));
|
|
376
376
|
/** Produces a `sortedExercises` record where only `chest` is populated. */
|
|
377
377
|
const chestOnlySorted = (exercises) => {
|
|
378
378
|
const empty = __1.muscleGroups.reduce((acc, mg) => {
|
|
@@ -394,7 +394,7 @@ describe('pickTrainerExercise', () => {
|
|
|
394
394
|
});
|
|
395
395
|
expect(result === null || result === void 0 ? void 0 : result.id).toBe('fresh');
|
|
396
396
|
});
|
|
397
|
-
it('pass 2: falls back to exercises not used in the current
|
|
397
|
+
it('pass 2: falls back to exercises not used in the current routine when all are used in the program', () => {
|
|
398
398
|
const onlyExercise = makeExercise({ id: 'only' });
|
|
399
399
|
const result = (0, hevyTrainer_1.pickTrainerExercise)({
|
|
400
400
|
sortedExercises: chestOnlySorted([onlyExercise]),
|
|
@@ -402,8 +402,8 @@ describe('pickTrainerExercise', () => {
|
|
|
402
402
|
context: {
|
|
403
403
|
// Marked as used in the whole program already.
|
|
404
404
|
programUsedExerciseIds: new Set(['only']),
|
|
405
|
-
// But not used in this
|
|
406
|
-
|
|
405
|
+
// But not used in this routine, so pass 2 should succeed.
|
|
406
|
+
routineUsedExerciseIds: new Set(),
|
|
407
407
|
},
|
|
408
408
|
});
|
|
409
409
|
expect(result === null || result === void 0 ? void 0 : result.id).toBe('only');
|
|
@@ -506,7 +506,7 @@ describe('pickTrainerExercise', () => {
|
|
|
506
506
|
sortedExercises: chestOnlySorted([ex]),
|
|
507
507
|
criteria: baseCriteria({
|
|
508
508
|
frequency: 3,
|
|
509
|
-
|
|
509
|
+
routineBarbellExerciseCount: 10,
|
|
510
510
|
equipments: ['dumbbell'],
|
|
511
511
|
}),
|
|
512
512
|
context: {},
|
|
@@ -519,7 +519,7 @@ describe('pickTrainerExercise', () => {
|
|
|
519
519
|
sortedExercises: chestOnlySorted([ex]),
|
|
520
520
|
criteria: baseCriteria({
|
|
521
521
|
frequency: 1,
|
|
522
|
-
|
|
522
|
+
routineBarbellExerciseCount: 2,
|
|
523
523
|
equipments: ['dumbbell'],
|
|
524
524
|
}),
|
|
525
525
|
context: {},
|
|
@@ -532,7 +532,7 @@ describe('pickTrainerExercise', () => {
|
|
|
532
532
|
sortedExercises: chestOnlySorted([ex]),
|
|
533
533
|
criteria: baseCriteria({
|
|
534
534
|
frequency: 1,
|
|
535
|
-
|
|
535
|
+
routineBarbellExerciseCount: 3,
|
|
536
536
|
// Dumbbell is listed as a barbell substitute, so the cap applies.
|
|
537
537
|
equipments: ['dumbbell'],
|
|
538
538
|
}),
|
|
@@ -546,7 +546,7 @@ describe('pickTrainerExercise', () => {
|
|
|
546
546
|
sortedExercises: chestOnlySorted([ex]),
|
|
547
547
|
criteria: baseCriteria({
|
|
548
548
|
frequency: 1,
|
|
549
|
-
|
|
549
|
+
routineBarbellExerciseCount: 5,
|
|
550
550
|
// No substitutes → avoid dead-ends by allowing the barbell.
|
|
551
551
|
equipments: [],
|
|
552
552
|
}),
|
|
@@ -587,7 +587,7 @@ describe('pickTrainerExercise', () => {
|
|
|
587
587
|
describe('generateProgram', () => {
|
|
588
588
|
const buildSettingsForChestProgram = () => {
|
|
589
589
|
const settings = makeSettings();
|
|
590
|
-
// Put a single chest compound prescription into the 1-day full body
|
|
590
|
+
// Put a single chest compound prescription into the 1-day full body routine.
|
|
591
591
|
settings.templates.full_body_1 = {
|
|
592
592
|
exercises: [
|
|
593
593
|
{ muscle_group: 'chest', category: 'compound', warmup_set_count: 2 },
|
|
@@ -596,7 +596,7 @@ describe('generateProgram', () => {
|
|
|
596
596
|
};
|
|
597
597
|
return settings;
|
|
598
598
|
};
|
|
599
|
-
it('produces one
|
|
599
|
+
it('produces one routine per programSplit and populates exercise fields from the settings', () => {
|
|
600
600
|
const settings = buildSettingsForChestProgram();
|
|
601
601
|
const chestExercise = makeExercise({ id: 'bench', muscle_group: 'chest' });
|
|
602
602
|
const result = (0, hevyTrainer_1.generateProgram)({
|
|
@@ -613,19 +613,27 @@ describe('generateProgram', () => {
|
|
|
613
613
|
expect(result.success).toBe(true);
|
|
614
614
|
if (!result.success)
|
|
615
615
|
return;
|
|
616
|
-
expect(result.program.
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
expect(
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
616
|
+
expect(result.program.name).toBe(1);
|
|
617
|
+
expect(result.program.routines).toHaveLength(hevyTrainer_1.programSplits[1].length);
|
|
618
|
+
const [routine] = result.program.routines;
|
|
619
|
+
expect(routine.name).toBe('full_body_1');
|
|
620
|
+
expect(routine.notes).toBe('Hit the chest hard.');
|
|
621
|
+
expect(routine.exercises).toHaveLength(1);
|
|
622
|
+
const [exercise] = routine.exercises;
|
|
623
623
|
expect(exercise.kind).toBe('resistance');
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
expect(exercise.
|
|
627
|
-
expect(exercise.
|
|
628
|
-
expect(exercise.
|
|
624
|
+
if (exercise.kind !== 'resistance')
|
|
625
|
+
return;
|
|
626
|
+
expect(exercise.exerciseTemplate.id).toBe('bench');
|
|
627
|
+
expect(exercise.category).toBe('compound');
|
|
628
|
+
expect(exercise.muscleGroup).toBe('chest');
|
|
629
|
+
expect(exercise.warmupSetCount).toBe(2);
|
|
630
|
+
// strength + one_day -> 1 (see `makeSettings`)
|
|
631
|
+
expect(exercise.sets).toBe(1);
|
|
632
|
+
// strength + compound -> 1..5
|
|
633
|
+
expect(exercise.repRangeStart).toBe(1);
|
|
634
|
+
expect(exercise.repRangeEnd).toBe(5);
|
|
635
|
+
// strength + medium + compound -> 60
|
|
636
|
+
expect(exercise.restTimerSeconds).toBe(60);
|
|
629
637
|
});
|
|
630
638
|
it('returns a partial program and errors when no matching exercise can be found', () => {
|
|
631
639
|
const settings = buildSettingsForChestProgram();
|
|
@@ -648,14 +656,14 @@ describe('generateProgram', () => {
|
|
|
648
656
|
type: 'exercise_not_found',
|
|
649
657
|
muscleGroup: 'chest',
|
|
650
658
|
});
|
|
651
|
-
// The
|
|
659
|
+
// The routine structure still exists in the partial program, just without
|
|
652
660
|
// any resolved exercises.
|
|
653
|
-
expect(result.partialProgram.
|
|
654
|
-
expect(result.partialProgram.
|
|
661
|
+
expect(result.partialProgram.routines).toHaveLength(1);
|
|
662
|
+
expect(result.partialProgram.routines[0].exercises).toEqual([]);
|
|
655
663
|
});
|
|
656
664
|
describe('min_workout_duration_limit filtering', () => {
|
|
657
665
|
/**
|
|
658
|
-
* Runs a 1-day
|
|
666
|
+
* Runs a 1-day program containing a single chest compound prescription
|
|
659
667
|
* with the provided `min_workout_duration_limit` against the provided
|
|
660
668
|
* `workoutDurationMinutes`, and returns whether the prescription was
|
|
661
669
|
* placed.
|
|
@@ -682,8 +690,9 @@ describe('generateProgram', () => {
|
|
|
682
690
|
exerciseStore: [makeExercise({ id: 'bench' })],
|
|
683
691
|
cardioPreference: 'no-cardio',
|
|
684
692
|
});
|
|
685
|
-
const
|
|
686
|
-
|
|
693
|
+
const routine = (result.success ? result.program : result.partialProgram)
|
|
694
|
+
.routines[0];
|
|
695
|
+
return routine.exercises.length === 1;
|
|
687
696
|
};
|
|
688
697
|
it('skips prescriptions whose limit is strictly greater than the selected duration', () => {
|
|
689
698
|
expect(runDurationProbe({ limit: 80, workoutDurationMinutes: 40 })).toBe(false);
|
|
@@ -755,11 +764,11 @@ describe('generateProgram', () => {
|
|
|
755
764
|
return;
|
|
756
765
|
// Only the 40- and 60-minute prescriptions survive; the 80-minute one
|
|
757
766
|
// is filtered out. Order is preserved.
|
|
758
|
-
const placed = result.program.
|
|
767
|
+
const placed = result.program.routines[0].exercises;
|
|
759
768
|
expect(placed.every((e) => e.kind === 'resistance')).toBe(true);
|
|
760
769
|
expect(placed
|
|
761
770
|
.filter(hevyTrainer_1.isTrainerProgramResistanceExercise)
|
|
762
|
-
.map((e) => e.
|
|
771
|
+
.map((e) => e.warmupSetCount)).toEqual([1, 2]);
|
|
763
772
|
});
|
|
764
773
|
});
|
|
765
774
|
it('skips focus_muscle prescriptions when no focusMuscle is provided', () => {
|
|
@@ -783,7 +792,7 @@ describe('generateProgram', () => {
|
|
|
783
792
|
expect(result.success).toBe(true);
|
|
784
793
|
if (!result.success)
|
|
785
794
|
return;
|
|
786
|
-
expect(result.program.
|
|
795
|
+
expect(result.program.routines[0].exercises).toEqual([]);
|
|
787
796
|
});
|
|
788
797
|
it('routes focus_muscle prescriptions to the requested SimplifiedMuscleGroup', () => {
|
|
789
798
|
const settings = makeSettings();
|
|
@@ -810,11 +819,12 @@ describe('generateProgram', () => {
|
|
|
810
819
|
expect(result.success).toBe(true);
|
|
811
820
|
if (!result.success)
|
|
812
821
|
return;
|
|
813
|
-
const [exercise] = result.program.
|
|
822
|
+
const [exercise] = result.program.routines[0].exercises;
|
|
814
823
|
expect(exercise.kind).toBe('resistance');
|
|
815
824
|
if (exercise.kind !== 'resistance')
|
|
816
825
|
return;
|
|
817
|
-
expect(exercise.
|
|
826
|
+
expect(exercise.exerciseTemplate.id).toBe('biceps-curl');
|
|
827
|
+
expect(exercise.muscleGroup).toBe('focus_muscle');
|
|
818
828
|
});
|
|
819
829
|
it('excludes exercises listed in `excludedExerciseIds`', () => {
|
|
820
830
|
const settings = buildSettingsForChestProgram();
|
|
@@ -835,7 +845,7 @@ describe('generateProgram', () => {
|
|
|
835
845
|
expect(result.success).toBe(true);
|
|
836
846
|
if (!result.success)
|
|
837
847
|
return;
|
|
838
|
-
expect(result.program.
|
|
848
|
+
expect(result.program.routines[0].exercises[0].exerciseTemplate.id).toBe('alt');
|
|
839
849
|
});
|
|
840
850
|
it('attaches exercise selection traces when debugExerciseSelectionTrace is true', () => {
|
|
841
851
|
const settings = buildSettingsForChestProgram();
|
|
@@ -856,7 +866,7 @@ describe('generateProgram', () => {
|
|
|
856
866
|
expect(result.exerciseSelectionTraces).toHaveLength(1);
|
|
857
867
|
const [trace] = result.exerciseSelectionTraces;
|
|
858
868
|
expect(trace.traceKind).toBe('template');
|
|
859
|
-
expect(trace.
|
|
869
|
+
expect(trace.routine).toBe('full_body_1');
|
|
860
870
|
expect(trace.selectedExerciseId).toBe('bench');
|
|
861
871
|
expect(trace.trace.selectedPass).toBe(1);
|
|
862
872
|
});
|
|
@@ -928,7 +938,7 @@ describe('generateProgram', () => {
|
|
|
928
938
|
expect(cardioTrace.equipments).toEqual([]);
|
|
929
939
|
expect(cardioTrace.level).toBe('beginner');
|
|
930
940
|
});
|
|
931
|
-
it('uses the same prioritized cardio exercise on every
|
|
941
|
+
it('uses the same prioritized cardio exercise on every routine (not blocked by program reuse)', () => {
|
|
932
942
|
const settings = makeSettings();
|
|
933
943
|
settings.templates.full_body_2_a = {
|
|
934
944
|
exercises: [{ muscle_group: 'chest', category: 'compound' }],
|
|
@@ -961,13 +971,13 @@ describe('generateProgram', () => {
|
|
|
961
971
|
expect(result.success).toBe(true);
|
|
962
972
|
if (!result.success)
|
|
963
973
|
return;
|
|
964
|
-
expect(result.program.
|
|
974
|
+
expect(result.program.routines[0].exercises[0]).toMatchObject({
|
|
965
975
|
kind: 'cardio',
|
|
966
|
-
|
|
976
|
+
exerciseTemplate: { id: 'run' },
|
|
967
977
|
});
|
|
968
|
-
expect(result.program.
|
|
978
|
+
expect(result.program.routines[1].exercises[0]).toMatchObject({
|
|
969
979
|
kind: 'cardio',
|
|
970
|
-
|
|
980
|
+
exerciseTemplate: { id: 'run' },
|
|
971
981
|
});
|
|
972
982
|
});
|
|
973
983
|
it('skips cardio exercises that need equipment the user does not have', () => {
|
|
@@ -1006,9 +1016,9 @@ describe('generateProgram', () => {
|
|
|
1006
1016
|
expect(result.success).toBe(true);
|
|
1007
1017
|
if (!result.success)
|
|
1008
1018
|
return;
|
|
1009
|
-
expect(result.program.
|
|
1019
|
+
expect(result.program.routines[0].exercises[0]).toMatchObject({
|
|
1010
1020
|
kind: 'cardio',
|
|
1011
|
-
|
|
1021
|
+
exerciseTemplate: { id: 'body-run' },
|
|
1012
1022
|
});
|
|
1013
1023
|
});
|
|
1014
1024
|
it('skips globally excluded cardio and uses the next priority exercise', () => {
|
|
@@ -1047,12 +1057,12 @@ describe('generateProgram', () => {
|
|
|
1047
1057
|
expect(result.success).toBe(true);
|
|
1048
1058
|
if (!result.success)
|
|
1049
1059
|
return;
|
|
1050
|
-
expect(result.program.
|
|
1060
|
+
expect(result.program.routines[0].exercises[0]).toMatchObject({
|
|
1051
1061
|
kind: 'cardio',
|
|
1052
|
-
|
|
1062
|
+
exerciseTemplate: { id: 'run-b' },
|
|
1053
1063
|
});
|
|
1054
1064
|
});
|
|
1055
|
-
it('keeps global trace order aligned with
|
|
1065
|
+
it('keeps global trace order aligned with routine generation (no cross-routine unshift)', () => {
|
|
1056
1066
|
const settings = makeSettings();
|
|
1057
1067
|
settings.templates.full_body_2_a = {
|
|
1058
1068
|
exercises: [{ muscle_group: 'chest', category: 'compound' }],
|
|
@@ -1086,15 +1096,15 @@ describe('generateProgram', () => {
|
|
|
1086
1096
|
expect(result.success).toBe(true);
|
|
1087
1097
|
if (!result.success)
|
|
1088
1098
|
return;
|
|
1089
|
-
const idxA = result.exerciseSelectionTraces.findIndex((t) => t.
|
|
1090
|
-
const idxB = result.exerciseSelectionTraces.findIndex((t) => t.
|
|
1099
|
+
const idxA = result.exerciseSelectionTraces.findIndex((t) => t.routine === 'full_body_2_a');
|
|
1100
|
+
const idxB = result.exerciseSelectionTraces.findIndex((t) => t.routine === 'full_body_2_b');
|
|
1091
1101
|
expect(idxA).toBeGreaterThanOrEqual(0);
|
|
1092
1102
|
expect(idxB).toBeGreaterThan(idxA);
|
|
1093
1103
|
});
|
|
1094
1104
|
describe('barbell cap at frequency === 1 (end-to-end)', () => {
|
|
1095
1105
|
it('drops the 4th+ barbell prescription when the user has a substitute', () => {
|
|
1096
1106
|
const settings = makeSettings();
|
|
1097
|
-
// 4 identical barbell compound prescriptions in the 1-day
|
|
1107
|
+
// 4 identical barbell compound prescriptions in the 1-day routine.
|
|
1098
1108
|
settings.templates.full_body_1 = {
|
|
1099
1109
|
exercises: Array.from({ length: 4 }, () => ({
|
|
1100
1110
|
muscle_group: 'chest',
|
|
@@ -1123,7 +1133,7 @@ describe('generateProgram', () => {
|
|
|
1123
1133
|
expect(result.success).toBe(false);
|
|
1124
1134
|
if (result.success)
|
|
1125
1135
|
return;
|
|
1126
|
-
expect(result.partialProgram.
|
|
1136
|
+
expect(result.partialProgram.routines[0].exercises).toHaveLength(3);
|
|
1127
1137
|
expect(result.errors).toHaveLength(1);
|
|
1128
1138
|
});
|
|
1129
1139
|
it('allows 4+ barbell prescriptions at frequency 1 when no substitute is present', () => {
|
|
@@ -1154,19 +1164,19 @@ describe('generateProgram', () => {
|
|
|
1154
1164
|
expect(result.success).toBe(true);
|
|
1155
1165
|
if (!result.success)
|
|
1156
1166
|
return;
|
|
1157
|
-
expect(result.program.
|
|
1167
|
+
expect(result.program.routines[0].exercises).toHaveLength(4);
|
|
1158
1168
|
});
|
|
1159
1169
|
});
|
|
1160
1170
|
describe('focus_muscle cycling', () => {
|
|
1161
1171
|
it('cycles through simplifiedMuscleGroupToMuscleGroups as focus_muscle prescriptions are placed', () => {
|
|
1162
1172
|
// Use a 2-day split so we can place 4 focus_muscle exercises across two
|
|
1163
|
-
//
|
|
1173
|
+
// routines and confirm the counter survives between routines.
|
|
1164
1174
|
const settings = makeSettings();
|
|
1165
1175
|
const focusPrescription = {
|
|
1166
1176
|
muscle_group: 'focus_muscle',
|
|
1167
1177
|
category: 'isolation',
|
|
1168
1178
|
};
|
|
1169
|
-
// full_body
|
|
1179
|
+
// full_body routines allow any focus muscle. Place 2 per routine so the
|
|
1170
1180
|
// combined count across the program is 4.
|
|
1171
1181
|
settings.templates.full_body_2_a = {
|
|
1172
1182
|
exercises: [focusPrescription, focusPrescription],
|
|
@@ -1236,7 +1246,7 @@ describe('generateProgram', () => {
|
|
|
1236
1246
|
settings.templates.upper_2 = { exercises: [focusPrescription] };
|
|
1237
1247
|
settings.templates.lower_2 = { exercises: [focusPrescription] };
|
|
1238
1248
|
// `legs` cycles through ['quadriceps', 'hamstrings', 'calves', 'glutes',
|
|
1239
|
-
// 'abductors', 'adductors']. The first two allowed
|
|
1249
|
+
// 'abductors', 'adductors']. The first two allowed routines (legs_1 and
|
|
1240
1250
|
// lower_2) should land on quadriceps and hamstrings respectively.
|
|
1241
1251
|
const exercises = [
|
|
1242
1252
|
makeExercise({
|
|
@@ -1269,7 +1279,10 @@ describe('generateProgram', () => {
|
|
|
1269
1279
|
// Only legs_1 and lower_2 run the focus_muscle prescription. Both
|
|
1270
1280
|
// succeed and should have selected quadriceps then hamstrings in order.
|
|
1271
1281
|
expect(result.exerciseSelectionTraces).toHaveLength(2);
|
|
1272
|
-
expect(result.exerciseSelectionTraces.map((t) => t.
|
|
1282
|
+
expect(result.exerciseSelectionTraces.map((t) => t.routine)).toEqual([
|
|
1283
|
+
'legs_1',
|
|
1284
|
+
'lower_2',
|
|
1285
|
+
]);
|
|
1273
1286
|
expect(result.exerciseSelectionTraces
|
|
1274
1287
|
.filter((t) => t.traceKind === 'template')
|
|
1275
1288
|
.map((t) => t.resolvedMuscleGroup)).toEqual(['quadriceps', 'hamstrings']);
|
|
@@ -1277,14 +1290,14 @@ describe('generateProgram', () => {
|
|
|
1277
1290
|
});
|
|
1278
1291
|
describe('isFocusMuscleExtraExerciseAllowed template compatibility', () => {
|
|
1279
1292
|
/**
|
|
1280
|
-
* Runs a single-
|
|
1293
|
+
* Runs a single-routine program that places one focus_muscle
|
|
1281
1294
|
* prescription into the requested template and returns whether the
|
|
1282
1295
|
* prescription produced an exercise or was skipped.
|
|
1283
1296
|
*/
|
|
1284
|
-
const runFocusMuscleProbe = (
|
|
1297
|
+
const runFocusMuscleProbe = (routineName, focusMuscle, frequency, candidateExercise) => {
|
|
1285
1298
|
var _a;
|
|
1286
1299
|
const settings = makeSettings();
|
|
1287
|
-
settings.templates[
|
|
1300
|
+
settings.templates[routineName] = {
|
|
1288
1301
|
exercises: [{ muscle_group: 'focus_muscle', category: 'isolation' }],
|
|
1289
1302
|
};
|
|
1290
1303
|
const result = (0, hevyTrainer_1.generateProgram)({
|
|
@@ -1300,13 +1313,13 @@ describe('generateProgram', () => {
|
|
|
1300
1313
|
debugExerciseSelectionTrace: true,
|
|
1301
1314
|
cardioPreference: 'no-cardio',
|
|
1302
1315
|
});
|
|
1303
|
-
// Find the
|
|
1304
|
-
const
|
|
1316
|
+
// Find the routine we targeted (it may not be first in the split).
|
|
1317
|
+
const routine = (result.success ? result.program : result.partialProgram).routines.find((r) => r.name === routineName);
|
|
1305
1318
|
return {
|
|
1306
|
-
placed: ((_a =
|
|
1319
|
+
placed: ((_a = routine === null || routine === void 0 ? void 0 : routine.exercises.length) !== null && _a !== void 0 ? _a : 0) > 0,
|
|
1307
1320
|
};
|
|
1308
1321
|
};
|
|
1309
|
-
it('allows every focus muscle on full_body
|
|
1322
|
+
it('allows every focus muscle on full_body routines', () => {
|
|
1310
1323
|
const muscles = ['chest', 'shoulders', 'arms', 'legs', 'back'];
|
|
1311
1324
|
muscles.forEach((focusMuscle) => {
|
|
1312
1325
|
// Use a muscle that's in the simplified group: biceps for arms, chest
|
package/built/tests/testUtils.js
CHANGED
|
@@ -39,7 +39,6 @@ export interface PostWorkoutRequestWorkout {
|
|
|
39
39
|
biometrics?: WorkoutBiometrics;
|
|
40
40
|
is_biometrics_public: boolean;
|
|
41
41
|
trainer_program_id: string | undefined;
|
|
42
|
-
trainer_workout_template_id: string | undefined;
|
|
43
42
|
gym_id: string | undefined;
|
|
44
43
|
is_home_gym: boolean;
|
|
45
44
|
}
|
|
@@ -46,7 +46,6 @@ export interface UserWorkout {
|
|
|
46
46
|
biometrics?: WorkoutBiometrics;
|
|
47
47
|
is_biometrics_public: boolean;
|
|
48
48
|
trainer_program_id: string | undefined;
|
|
49
|
-
trainer_workout_template_id: string | undefined;
|
|
50
49
|
gym: UserWorkoutGym | undefined;
|
|
51
50
|
}
|
|
52
51
|
export interface UserWorkoutExercise {
|