hevy-shared 1.0.954 → 1.0.956
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/hevyTrainer.d.ts +49 -8
- package/built/hevyTrainer.js +130 -61
- package/built/index.d.ts +0 -3
- package/built/tests/hevyTrainer.test.js +1086 -5
- package/package.json +2 -3
- package/built/schemas.d.ts +0 -6
- package/built/schemas.js +0 -12
package/built/hevyTrainer.d.ts
CHANGED
|
@@ -38,18 +38,46 @@ export interface ExerciseSelectionParams<T extends HevyTrainerLibraryExercise> {
|
|
|
38
38
|
criteria: ExerciseSelectionCriteria;
|
|
39
39
|
context: ExerciseSelectionContext;
|
|
40
40
|
}
|
|
41
|
+
export interface ExerciseSelectionTraceEntry {
|
|
42
|
+
pass: 1 | 2 | 3 | 4 | 5 | 6;
|
|
43
|
+
label: string;
|
|
44
|
+
candidatePoolSize: number;
|
|
45
|
+
selectedExerciseId?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ExerciseSelectionTrace {
|
|
48
|
+
entries: ExerciseSelectionTraceEntry[];
|
|
49
|
+
selectedPass?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
50
|
+
}
|
|
41
51
|
export interface ProgramGenerationParams<T extends HevyTrainerLibraryExercise> {
|
|
42
52
|
trainerAlgorithmSettings: TrainerAlgorithmSettings;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
frequency: WeeklyTrainingFrequency;
|
|
54
|
+
goal: TrainingGoal;
|
|
55
|
+
level: TrainingLevel;
|
|
56
|
+
equipments: GranularEquipment[];
|
|
57
|
+
workoutDurationMinutes: WorkoutDurationMinutes;
|
|
58
|
+
restTimerLength: RestTimerLength;
|
|
49
59
|
exerciseStore: T[];
|
|
50
60
|
focusMuscle?: SimplifiedMuscleGroup;
|
|
51
61
|
excludedExerciseIds?: Set<string>;
|
|
62
|
+
/**
|
|
63
|
+
* When enabled, includes exercise selection trace breadcrumbs in the result
|
|
64
|
+
* for debugging. Intended for troubleshooting on backoffice; keep off in
|
|
65
|
+
* normal usage on the app.
|
|
66
|
+
*/
|
|
67
|
+
debugExerciseSelectionTrace?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface ProgramExerciseSelectionTraceRecord {
|
|
70
|
+
routine: HevyTrainerRoutineName;
|
|
71
|
+
prescriptionIndex: number;
|
|
72
|
+
prescription: ExercisePrescription;
|
|
73
|
+
resolvedMuscleGroup: MuscleGroup;
|
|
74
|
+
criteria: ExerciseSelectionCriteria;
|
|
75
|
+
selectedExerciseId?: string;
|
|
76
|
+
trace: ExerciseSelectionTrace;
|
|
52
77
|
}
|
|
78
|
+
export type TrainerProgramAttemptWithTraces = TrainerProgramAttempt & {
|
|
79
|
+
exerciseSelectionTraces: ProgramExerciseSelectionTraceRecord[];
|
|
80
|
+
};
|
|
53
81
|
export type FrequencyString = 'one_day' | 'two_days' | 'three_days' | 'four_days' | 'five_days' | 'six_days';
|
|
54
82
|
export declare const frequencyMap: Record<WeeklyTrainingFrequency, FrequencyString>;
|
|
55
83
|
export declare const programSplits: Record<WeeklyTrainingFrequency, HevyTrainerRoutineName[]>;
|
|
@@ -171,7 +199,14 @@ export declare const getPrioritySortedExercises: <T extends HevyTrainerLibraryEx
|
|
|
171
199
|
/**
|
|
172
200
|
* Selects the best exercise for a given prescription using a multi-pass strategy
|
|
173
201
|
*/
|
|
174
|
-
|
|
202
|
+
type PickExerciseResult<T extends HevyTrainerLibraryExercise> = {
|
|
203
|
+
exercise?: T;
|
|
204
|
+
trace: ExerciseSelectionTrace;
|
|
205
|
+
};
|
|
206
|
+
export declare function pickExerciseForPrescription<T extends HevyTrainerLibraryExercise>(params: ExerciseSelectionParams<T> & {
|
|
207
|
+
withTrace: true;
|
|
208
|
+
}): PickExerciseResult<T>;
|
|
209
|
+
export declare function pickExerciseForPrescription<T extends HevyTrainerLibraryExercise>(params: ExerciseSelectionParams<T>): T | undefined;
|
|
175
210
|
export type HevyTrainerLibraryExercise = Pick<LibraryExercise, 'id' | 'title' | 'priority' | 'muscle_group' | 'other_muscles' | 'exercise_type' | 'equipment_category' | 'category' | 'level' | 'goal' | 'granular_equipments'>;
|
|
176
211
|
export interface ExercisePrescriptionError {
|
|
177
212
|
type: 'exercise_not_found';
|
|
@@ -205,5 +240,11 @@ export interface TrainerProgramError {
|
|
|
205
240
|
export type TrainerProgramAttempt = TrainerProgramResult | TrainerProgramError;
|
|
206
241
|
/**
|
|
207
242
|
* Generates a complete training program based on the provided parameters
|
|
243
|
+
* - debugExerciseSelectionTrace: true -> includes exercise selection trace breadcrumbs in the result for debugging.
|
|
244
|
+
* - debugExerciseSelectionTrace: false -> no exercise selection trace breadcrumbs in the result.
|
|
208
245
|
*/
|
|
209
|
-
export declare
|
|
246
|
+
export declare function generateProgram<T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T> & {
|
|
247
|
+
debugExerciseSelectionTrace: true;
|
|
248
|
+
}): TrainerProgramAttemptWithTraces;
|
|
249
|
+
export declare function generateProgram<T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T>): TrainerProgramAttempt;
|
|
250
|
+
export {};
|
package/built/hevyTrainer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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;
|
|
4
6
|
const _1 = require(".");
|
|
5
7
|
exports.workoutDurationOptions = [40, 60, 80];
|
|
6
8
|
exports.trainerGymTypes = [
|
|
@@ -448,60 +450,98 @@ const findAlternativeIsolationExercise = (exercises, criteria, context) => {
|
|
|
448
450
|
return matchesCriteria && isNotUsed;
|
|
449
451
|
});
|
|
450
452
|
};
|
|
451
|
-
|
|
452
|
-
* Selects the best exercise for a given prescription using a multi-pass strategy
|
|
453
|
-
*/
|
|
454
|
-
const pickExerciseForPrescription = (params) => {
|
|
453
|
+
const pickExerciseForPrescriptionWithTrace = (params) => {
|
|
455
454
|
const { sortedExercises, handPickedExercises, criteria, context } = params;
|
|
455
|
+
const trace = { entries: [] };
|
|
456
456
|
const exercises = handPickedExercises
|
|
457
457
|
? [...handPickedExercises, ...sortedExercises[criteria.muscleGroup]]
|
|
458
458
|
: sortedExercises[criteria.muscleGroup];
|
|
459
|
-
// Pass 1: Find exact match not used in program
|
|
460
459
|
const programContext = {
|
|
461
460
|
programUsedExerciseIds: context.programUsedExerciseIds,
|
|
462
461
|
excludedExerciseIds: context.excludedExerciseIds,
|
|
463
462
|
};
|
|
464
|
-
let exercise = findMatchingExercise(exercises, criteria, programContext);
|
|
465
|
-
if (exercise)
|
|
466
|
-
return exercise;
|
|
467
|
-
// Pass 2: Find exact match not used in routine (allow reuse in program)
|
|
468
463
|
const routineContext = {
|
|
469
464
|
routineUsedExerciseIds: context.routineUsedExerciseIds,
|
|
470
465
|
excludedExerciseIds: context.excludedExerciseIds,
|
|
471
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
|
|
472
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
|
+
});
|
|
473
485
|
if (exercise)
|
|
474
|
-
return exercise;
|
|
475
|
-
// Pass 3
|
|
486
|
+
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 2 }) };
|
|
487
|
+
// Pass 3
|
|
476
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
|
+
});
|
|
477
495
|
if (exercise)
|
|
478
|
-
return exercise;
|
|
479
|
-
// Pass 4
|
|
496
|
+
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 3 }) };
|
|
497
|
+
// Pass 4
|
|
480
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
|
+
});
|
|
481
505
|
if (exercise)
|
|
482
|
-
return exercise;
|
|
483
|
-
// Pass 5
|
|
506
|
+
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 4 }) };
|
|
507
|
+
// Pass 5
|
|
484
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
|
+
});
|
|
485
515
|
if (exercise)
|
|
486
|
-
return exercise;
|
|
487
|
-
// Pass 6
|
|
488
|
-
// is in the exercise's other muscles, also ignores exercise category
|
|
516
|
+
return { exercise, trace: Object.assign(Object.assign({}, trace), { selectedPass: 5 }) };
|
|
517
|
+
// Pass 6
|
|
489
518
|
const secondaryMuscleExercises = Object.values(sortedExercises)
|
|
490
519
|
.flat()
|
|
491
520
|
.filter((e) => e.other_muscles.includes(criteria.muscleGroup));
|
|
492
521
|
exercise = findMatchingExercise(secondaryMuscleExercises, Object.assign(Object.assign({}, criteria), { exerciseCategory: 'all' }), routineContext);
|
|
493
|
-
|
|
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 };
|
|
494
531
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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];
|
|
503
543
|
const program = {
|
|
504
|
-
name:
|
|
544
|
+
name: frequency,
|
|
505
545
|
routines: [],
|
|
506
546
|
};
|
|
507
547
|
const sortedExercises = (0, exports.getPrioritySortedExercises)(trainerAlgorithmSettings.exercise_priorities, exerciseStore);
|
|
@@ -513,7 +553,8 @@ const generateProgram = (params) => {
|
|
|
513
553
|
let routineBarbellExerciseCount = 0;
|
|
514
554
|
const routineUsedExerciseIds = new Set();
|
|
515
555
|
const routineExercises = [];
|
|
516
|
-
for (
|
|
556
|
+
for (let prescriptionIndex = 0; prescriptionIndex < routineTemplate.exercises.length; prescriptionIndex++) {
|
|
557
|
+
const exercisePrescription = routineTemplate.exercises[prescriptionIndex];
|
|
517
558
|
const muscleGroup = getMuscleGroup({
|
|
518
559
|
muscleGroupPrescription: exercisePrescription.muscle_group,
|
|
519
560
|
programFocusMuscleExerciseCount,
|
|
@@ -525,31 +566,56 @@ const generateProgram = (params) => {
|
|
|
525
566
|
continue;
|
|
526
567
|
}
|
|
527
568
|
// If the exercise does not meet the min workout duration limit, skip the exercise
|
|
528
|
-
const execiseDoesNotMeetMinWorkoutDurationLimit =
|
|
529
|
-
exercisePrescription.min_workout_duration_limit
|
|
569
|
+
const execiseDoesNotMeetMinWorkoutDurationLimit = exercisePrescription.min_workout_duration_limit
|
|
530
570
|
? exercisePrescription.min_workout_duration_limit >
|
|
531
|
-
|
|
571
|
+
workoutDurationMinutes
|
|
532
572
|
: false;
|
|
533
573
|
if (execiseDoesNotMeetMinWorkoutDurationLimit) {
|
|
534
574
|
continue;
|
|
535
575
|
}
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
}
|
|
553
619
|
if (!!exercise) {
|
|
554
620
|
programUsedExerciseIds.add(exercise.id);
|
|
555
621
|
routineUsedExerciseIds.add(exercise.id);
|
|
@@ -557,17 +623,17 @@ const generateProgram = (params) => {
|
|
|
557
623
|
routineBarbellExerciseCount++;
|
|
558
624
|
if (exercisePrescription.muscle_group === 'focus_muscle')
|
|
559
625
|
programFocusMuscleExerciseCount++;
|
|
560
|
-
const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings,
|
|
626
|
+
const repRange = (0, exports.getTrainerRepRange)(trainerAlgorithmSettings, goal, exercisePrescription.category);
|
|
561
627
|
routineExercises.push({
|
|
562
628
|
exerciseTemplate: exercise,
|
|
563
629
|
muscleGroup: exercisePrescription.muscle_group,
|
|
564
630
|
category: exercisePrescription.category,
|
|
565
|
-
sets: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings,
|
|
631
|
+
sets: (0, exports.getTrainerSetCount)(trainerAlgorithmSettings, goal, frequency),
|
|
566
632
|
repRangeStart: repRange.rep_range_start,
|
|
567
633
|
repRangeEnd: repRange.rep_range_end,
|
|
568
|
-
restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings,
|
|
634
|
+
restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
|
|
569
635
|
warmupSetCount: exercisePrescription.warmup_set_count,
|
|
570
|
-
notes: (
|
|
636
|
+
notes: (_b = trainerAlgorithmSettings.exercise_notes[exercise.id]) !== null && _b !== void 0 ? _b : '',
|
|
571
637
|
});
|
|
572
638
|
}
|
|
573
639
|
else {
|
|
@@ -577,9 +643,9 @@ const generateProgram = (params) => {
|
|
|
577
643
|
prescription: exercisePrescription,
|
|
578
644
|
muscleGroup,
|
|
579
645
|
context: {
|
|
580
|
-
goal
|
|
581
|
-
level
|
|
582
|
-
equipments
|
|
646
|
+
goal,
|
|
647
|
+
level,
|
|
648
|
+
equipments,
|
|
583
649
|
focusMuscle,
|
|
584
650
|
},
|
|
585
651
|
});
|
|
@@ -593,15 +659,18 @@ const generateProgram = (params) => {
|
|
|
593
659
|
}
|
|
594
660
|
// Return result based on whether there were errors
|
|
595
661
|
if (allErrors.length > 0) {
|
|
596
|
-
|
|
662
|
+
const result = {
|
|
597
663
|
success: false,
|
|
598
664
|
errors: allErrors,
|
|
599
665
|
partialProgram: program,
|
|
600
666
|
};
|
|
667
|
+
return debugExerciseSelectionTrace
|
|
668
|
+
? Object.assign(Object.assign({}, result), { exerciseSelectionTraces }) : result;
|
|
601
669
|
}
|
|
602
|
-
|
|
670
|
+
const result = {
|
|
603
671
|
success: true,
|
|
604
672
|
program,
|
|
605
673
|
};
|
|
606
|
-
|
|
607
|
-
|
|
674
|
+
return debugExerciseSelectionTrace
|
|
675
|
+
? Object.assign(Object.assign({}, result), { exerciseSelectionTraces }) : result;
|
|
676
|
+
}
|
package/built/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { WorkoutDurationMinutes } from './hevyTrainer';
|
|
2
|
-
import { postEmailBackofficeSchema } from './schemas';
|
|
3
2
|
import { Language } from './translations';
|
|
4
3
|
import { Lookup } from './typeUtils';
|
|
5
|
-
import z from 'zod';
|
|
6
4
|
export * from './constants';
|
|
7
5
|
export * from './utils';
|
|
8
6
|
export * from './units';
|
|
@@ -1989,4 +1987,3 @@ export interface GetAccountsByEmailResponse {
|
|
|
1989
1987
|
wellhub_email: string | null;
|
|
1990
1988
|
}[];
|
|
1991
1989
|
}
|
|
1992
|
-
export type PostEmailBackofficeRequest = z.infer<typeof postEmailBackofficeSchema>;
|