hevy-shared 1.0.955 → 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.
@@ -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
- selectedDays: WeeklyTrainingFrequency;
44
- selectedGoal: TrainingGoal;
45
- selectedLevel: TrainingLevel;
46
- selectedEquipments: GranularEquipment[];
47
- selectedWorkoutDurationMinutes?: WorkoutDurationMinutes;
48
- selectedRestTimerLength: RestTimerLength;
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
- export declare const pickExerciseForPrescription: <T extends HevyTrainerLibraryExercise>(params: ExerciseSelectionParams<T>) => T | undefined;
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 const generateProgram: <T extends HevyTrainerLibraryExercise>(params: ProgramGenerationParams<T>) => TrainerProgramAttempt;
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 {};
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateProgram = exports.pickExerciseForPrescription = 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;
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: Find alternative isolation exercise not used in program
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: Find alternative isolation exercise not used in routine
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: Find exercise regardless of exercise category (isolation or compound)
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: Find exercises where the prescription muscle group
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
- return exercise;
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
- exports.pickExerciseForPrescription = pickExerciseForPrescription;
496
- /**
497
- * Generates a complete training program based on the provided parameters
498
- */
499
- const generateProgram = (params) => {
500
- var _a;
501
- const { trainerAlgorithmSettings, selectedDays, selectedGoal, selectedLevel, selectedEquipments, selectedWorkoutDurationMinutes, selectedRestTimerLength, exerciseStore, focusMuscle, excludedExerciseIds, } = params;
502
- const routines = exports.programSplits[selectedDays];
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: selectedDays,
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 (const exercisePrescription of routineTemplate.exercises) {
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 = selectedWorkoutDurationMinutes &&
529
- exercisePrescription.min_workout_duration_limit
569
+ const execiseDoesNotMeetMinWorkoutDurationLimit = exercisePrescription.min_workout_duration_limit
530
570
  ? exercisePrescription.min_workout_duration_limit >
531
- selectedWorkoutDurationMinutes
571
+ workoutDurationMinutes
532
572
  : false;
533
573
  if (execiseDoesNotMeetMinWorkoutDurationLimit) {
534
574
  continue;
535
575
  }
536
- const exercise = (0, exports.pickExerciseForPrescription)({
537
- sortedExercises,
538
- criteria: {
539
- frequency: selectedDays,
540
- exerciseCategory: exercisePrescription.category,
541
- equipments: selectedEquipments,
542
- muscleGroup,
543
- routineBarbellExerciseCount,
544
- level: selectedLevel,
545
- goal: selectedGoal,
546
- },
547
- context: {
548
- programUsedExerciseIds,
549
- routineUsedExerciseIds,
550
- excludedExerciseIds,
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, selectedGoal, exercisePrescription.category);
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, selectedGoal, selectedDays),
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, selectedGoal, selectedRestTimerLength, exercisePrescription.category),
634
+ restTimerSeconds: (0, exports.getTrainerRestTimerSeconds)(trainerAlgorithmSettings, goal, restTimerLength, exercisePrescription.category),
569
635
  warmupSetCount: exercisePrescription.warmup_set_count,
570
- notes: (_a = trainerAlgorithmSettings.exercise_notes[exercise.id]) !== null && _a !== void 0 ? _a : '',
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: selectedGoal,
581
- level: selectedLevel,
582
- equipments: selectedEquipments,
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
- return {
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
- return {
670
+ const result = {
603
671
  success: true,
604
672
  program,
605
673
  };
606
- };
607
- exports.generateProgram = generateProgram;
674
+ return debugExerciseSelectionTrace
675
+ ? Object.assign(Object.assign({}, result), { exerciseSelectionTraces }) : result;
676
+ }
package/built/index.d.ts CHANGED
@@ -1,9 +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
- export * from './schemas';
7
4
  export * from './constants';
8
5
  export * from './utils';
9
6
  export * from './units';
@@ -1990,4 +1987,3 @@ export interface GetAccountsByEmailResponse {
1990
1987
  wellhub_email: string | null;
1991
1988
  }[];
1992
1989
  }
1993
- export type PostEmailBackofficeRequest = z.infer<typeof postEmailBackofficeSchema>;
package/built/index.js CHANGED
@@ -17,7 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.isPublicWorkout = exports.isSetType = exports.isRPE = exports.validRpeValues = exports.isSetPersonalRecordType = exports.supportedInstructionsLanguages = exports.weeklyTrainingFrequencies = exports.isGranularEquipment = exports.granularEquipments = exports.hevyTrainerProgramEquipments = exports.restTimerLengths = exports.exerciseCategories = exports.trainingLevels = exports.trainingGoals = exports.isCustomExerciseType = exports.customExericseTypes = exports.isExerciseType = exports.exerciseTypes = exports.isExerciseRepType = exports.exerciseRepTypes = exports.isEquipmentFilter = exports.equipmentFilters = exports.isEquipment = exports.equipments = exports.simplifiedMuscleGroupToMuscleGroups = exports.isMuscleGroupFilter = exports.muscleGroupFilters = exports.isMuscleGroup = exports.muscleGroups = exports.isSimplifiedMuscleGroup = exports.simplifiedMuscleGroups = exports.miscellaneousMuscles = exports.chestMuscles = exports.backMuscles = exports.legMuscles = exports.armMuscles = exports.shoulderMuscles = exports.coreMuscles = exports.DefaultClientConfiguration = exports.parseClientAuthTokenResponse = exports.isCoachRole = exports.isErrorResponse = exports.isLivePRVolumeOption = exports.isTimerVolumeOption = exports.isWeekday = exports.orderedWeekdays = exports.isBodyMeasurementUnit = exports.isDistanceUnitShort = exports.isDistanceUnit = exports.isWeightUnit = void 0;
18
18
  exports.isOAuthScope = exports.supportedScopes = exports.isSuggestedUserSource = exports.isValidUserWorkoutMetricsType = exports.isBodyMeasurementKey = exports.measurementsList = exports.isHevyTrainerRoutine = exports.isWorkoutBiometrics = exports.isHeartRateSamples = void 0;
19
19
  const typeUtils_1 = require("./typeUtils");
20
- __exportStar(require("./schemas"), exports);
21
20
  __exportStar(require("./constants"), exports);
22
21
  __exportStar(require("./utils"), exports);
23
22
  __exportStar(require("./units"), exports);