@aneuhold/core-ts-db-lib 4.1.11 → 4.1.13

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/lib/browser.d.ts +12 -3
  3. package/lib/browser.d.ts.map +1 -1
  4. package/lib/browser.js +6 -1
  5. package/lib/browser.js.map +1 -1
  6. package/lib/browser.ts +30 -8
  7. package/lib/ctos/workout/WorkoutExerciseCTO.d.ts +121 -0
  8. package/lib/ctos/workout/WorkoutExerciseCTO.d.ts.map +1 -0
  9. package/lib/ctos/workout/WorkoutExerciseCTO.js +58 -0
  10. package/lib/ctos/workout/WorkoutExerciseCTO.js.map +1 -0
  11. package/lib/ctos/workout/WorkoutExerciseCTO.ts +71 -0
  12. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts +50 -0
  13. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts.map +1 -0
  14. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.js +21 -0
  15. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.js.map +1 -0
  16. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.ts +47 -0
  17. package/lib/documents/workout/README.md +5 -5
  18. package/lib/documents/workout/WorkoutExerciseCalibration.d.ts +1 -8
  19. package/lib/documents/workout/WorkoutExerciseCalibration.d.ts.map +1 -1
  20. package/lib/documents/workout/WorkoutExerciseCalibration.js +12 -1
  21. package/lib/documents/workout/WorkoutExerciseCalibration.js.map +1 -1
  22. package/lib/documents/workout/WorkoutExerciseCalibration.ts +12 -10
  23. package/lib/documents/workout/WorkoutSessionExercise.js +4 -4
  24. package/lib/documents/workout/WorkoutSessionExercise.ts +4 -4
  25. package/lib/documents/workout/WorkoutSet.d.ts +8 -0
  26. package/lib/documents/workout/WorkoutSet.d.ts.map +1 -1
  27. package/lib/documents/workout/WorkoutSet.ts +9 -0
  28. package/lib/embedded-types/workout/MesocycleVolumeSummary.d.ts +27 -0
  29. package/lib/embedded-types/workout/MesocycleVolumeSummary.d.ts.map +1 -0
  30. package/lib/embedded-types/workout/MesocycleVolumeSummary.js +39 -0
  31. package/lib/embedded-types/workout/MesocycleVolumeSummary.js.map +1 -0
  32. package/lib/embedded-types/workout/MesocycleVolumeSummary.ts +55 -0
  33. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +40 -0
  34. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -1
  35. package/lib/services/workout/Exercise/WorkoutExerciseService.js +134 -1
  36. package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -1
  37. package/lib/services/workout/Exercise/WorkoutExerciseService.ts +204 -1
  38. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +48 -2
  39. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -1
  40. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +73 -5
  41. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -1
  42. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +88 -5
  43. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +33 -12
  44. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -1
  45. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +74 -28
  46. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -1
  47. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +87 -38
  48. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +48 -9
  49. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -1
  50. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +209 -15
  51. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -1
  52. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +299 -23
  53. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts +33 -0
  54. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts.map +1 -0
  55. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js +24 -0
  56. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js.map +1 -0
  57. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.ts +36 -0
  58. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts +3 -6
  59. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts.map +1 -1
  60. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js +22 -34
  61. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js.map +1 -1
  62. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.ts +30 -52
  63. package/lib/services/workout/Session/WorkoutSessionService.d.ts +7 -7
  64. package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -1
  65. package/lib/services/workout/Session/WorkoutSessionService.js +23 -32
  66. package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -1
  67. package/lib/services/workout/Session/WorkoutSessionService.ts +28 -42
  68. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +17 -5
  69. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -1
  70. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +21 -7
  71. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -1
  72. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +32 -7
  73. package/lib/services/workout/Set/WorkoutSetService.d.ts +17 -5
  74. package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -1
  75. package/lib/services/workout/Set/WorkoutSetService.js +83 -16
  76. package/lib/services/workout/Set/WorkoutSetService.js.map +1 -1
  77. package/lib/services/workout/Set/WorkoutSetService.ts +107 -24
  78. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +72 -2
  79. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -1
  80. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +222 -35
  81. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -1
  82. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +290 -40
  83. package/package.json +3 -3
@@ -1,4 +1,10 @@
1
+ import type { UUID } from 'crypto';
2
+ import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
1
3
  import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
4
+ import { WorkoutExerciseCalibrationSchema } from '../../../documents/workout/WorkoutExerciseCalibration.js';
5
+
6
+ /** Divisor used in the NASM 1RM formula: 1RM = (weight × reps / 30.48) + weight */
7
+ const NASM_1RM_DIVISOR = 30.48;
2
8
 
3
9
  /**
4
10
  * A service for handling operations related to {@link WorkoutExerciseCalibration}s.
@@ -22,7 +28,21 @@ export default class WorkoutExerciseCalibrationService {
22
28
  * @param reps The number of reps performed.
23
29
  */
24
30
  static get1RMRaw(weight: number, reps: number): number {
25
- return (weight * reps) / 30.48 + weight;
31
+ return (weight * reps) / NASM_1RM_DIVISOR + weight;
32
+ }
33
+
34
+ /**
35
+ * Returns the NASM 1RM formula as a MongoDB aggregation expression.
36
+ * This is the MongoDB-equivalent of {@link get1RMRaw} and must be kept
37
+ * in sync with it.
38
+ *
39
+ * @param weightField The MongoDB field reference for weight (e.g. `'$weight'`).
40
+ * @param repsField The MongoDB field reference for reps (e.g. `'$reps'`).
41
+ */
42
+ static get1RMMongoExpr(weightField: string, repsField: string) {
43
+ return {
44
+ $add: [{ $divide: [{ $multiply: [weightField, repsField] }, NASM_1RM_DIVISOR] }, weightField]
45
+ };
26
46
  }
27
47
 
28
48
  /**
@@ -36,20 +56,83 @@ export default class WorkoutExerciseCalibrationService {
36
56
  */
37
57
  static getTargetWeight(calibration: WorkoutExerciseCalibration, targetReps: number): number {
38
58
  const oneRepMax = this.get1RM(calibration);
59
+ return this.getTargetWeightFrom1RM(oneRepMax, targetReps);
60
+ }
61
+
62
+ /**
63
+ * Calculates the target weight from a raw 1RM value and a target rep count.
64
+ *
65
+ * This applies the same targetPercentage formula as {@link getTargetWeight}
66
+ * but accepts a pre-computed 1RM instead of a calibration document. Useful
67
+ * when the effective 1RM is derived from multiple sources (calibrations and
68
+ * actual sets).
69
+ *
70
+ * Returns the calculated weight without rounding. Consumer can use
71
+ * WorkoutEquipmentTypeService.findNearestWeight() to round if needed.
72
+ *
73
+ * @param effective1RM The effective 1 Rep Max value.
74
+ * @param targetReps The target number of reps.
75
+ */
76
+ static getTargetWeightFrom1RM(effective1RM: number, targetReps: number): number {
39
77
  const targetPercentage = this.getTargetPercentage(targetReps);
40
- return (targetPercentage / 100) * oneRepMax;
78
+ return (targetPercentage / 100) * effective1RM;
79
+ }
80
+
81
+ /**
82
+ * Generates auto-calibrations from exercise CTOs whose best set 1RM exceeds
83
+ * their best calibration 1RM.
84
+ *
85
+ * The CTO already provides `bestCalibration` and `bestSet` per exercise, so
86
+ * this method just compares those two pre-computed values and creates new
87
+ * calibrations where the set wins.
88
+ *
89
+ * @param exerciseCTOs The exercise CTOs to evaluate.
90
+ * @param userId The user ID for the new calibrations.
91
+ * @param dateRecorded The date to use as dateRecorded for new calibrations.
92
+ */
93
+ static generateAutoCalibrations(
94
+ exerciseCTOs: WorkoutExerciseCTO[],
95
+ userId: UUID,
96
+ dateRecorded: Date
97
+ ): WorkoutExerciseCalibration[] {
98
+ const newCalibrations: WorkoutExerciseCalibration[] = [];
99
+
100
+ for (const cto of exerciseCTOs) {
101
+ const { bestSet, bestCalibration } = cto;
102
+ if (!bestSet?.actualWeight || !bestSet.actualReps || bestSet.actualReps <= 0) continue;
103
+
104
+ const set1RM = this.get1RMRaw(bestSet.actualWeight, bestSet.actualReps);
105
+ const cal1RM = bestCalibration ? this.get1RM(bestCalibration) : 0;
106
+
107
+ if (set1RM > cal1RM) {
108
+ newCalibrations.push(
109
+ WorkoutExerciseCalibrationSchema.parse({
110
+ userId,
111
+ workoutExerciseId: cto._id,
112
+ weight: bestSet.actualWeight,
113
+ reps: bestSet.actualReps,
114
+ exerciseProperties: bestSet.exerciseProperties,
115
+ dateRecorded,
116
+ associatedWorkoutSetId: bestSet._id
117
+ })
118
+ );
119
+ }
120
+ }
121
+
122
+ return newCalibrations;
41
123
  }
42
124
 
43
125
  /**
44
126
  * Calculates the target percentage of 1RM for a given target rep count.
45
127
  *
46
- * Uses the formula: targetPercentage = 30 + ((targetReps - 5) * 2.2)
128
+ * Uses the formula: targetPercentage = 85 - ((targetReps - 5) * 2.2)
47
129
  *
48
- * This ensures training stays within the 30%-85% 1RM range (30 reps to 5 reps).
130
+ * This ensures training stays within the 85%-30% 1RM range (5 reps to 30 reps).
131
+ * Higher rep counts produce lower percentages of 1RM.
49
132
  *
50
133
  * @param targetReps The target number of reps.
51
134
  */
52
135
  private static getTargetPercentage(targetReps: number): number {
53
- return 30 + (targetReps - 5) * 2.2;
136
+ return 85 - (targetReps - 5) * 2.2;
54
137
  }
55
138
  }
@@ -1,12 +1,14 @@
1
1
  import type { UUID } from 'crypto';
2
+ import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
3
+ import type { WorkoutMuscleGroupVolumeCTO } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
2
4
  import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
3
5
  import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
4
- import type { CalibrationExercisePair, WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
5
6
  import type { WorkoutMesocycle } from '../../../documents/workout/WorkoutMesocycle.js';
6
7
  import type { WorkoutMicrocycle } from '../../../documents/workout/WorkoutMicrocycle.js';
7
8
  import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
8
9
  import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
9
10
  import type { WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
11
+ import type { WorkoutVolumeLandmarkEstimate } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
10
12
  /**
11
13
  * Central shared context for generating or updating a {@link WorkoutMesocycle}.
12
14
  *
@@ -21,15 +23,25 @@ export default class WorkoutMesocyclePlanContext {
21
23
  existingSessionExercises: WorkoutSessionExercise[];
22
24
  existingSets: WorkoutSet[];
23
25
  /**
24
- * A constant for now that defines what the target RIR is for the first microcycle of every mesocycle,
25
- * regardless of anything else.
26
+ * The target RIR for the first microcycle of the mesocycle. Varies by cycle
27
+ * type: MuscleGain starts at 4, Cut starts at 3, Resensitization stays at 3.
26
28
  */
27
- readonly FIRST_MICROCYCLE_RIR = 4;
28
- readonly calibrationMap: Map<UUID, WorkoutExerciseCalibration>;
29
+ readonly firstMicrocycleRir: number;
30
+ /**
31
+ * Number of microcycles between each baseline set addition.
32
+ * MuscleGain = 1 (every microcycle), Cut = 2 (every other), Resensitization = 0 (never).
33
+ */
34
+ readonly progressionInterval: number;
35
+ /**
36
+ * Whether this mesocycle type skips the deload microcycle. True for
37
+ * Resensitization cycles.
38
+ */
39
+ readonly skipDeload: boolean;
29
40
  readonly exerciseMap: Map<UUID, WorkoutExercise>;
30
41
  readonly equipmentMap: Map<UUID, WorkoutEquipmentType>;
31
42
  readonly sessionMap: Map<UUID, WorkoutSession>;
32
43
  readonly sessionExerciseMap: Map<UUID, WorkoutSessionExercise>;
44
+ readonly setMap: Map<UUID, WorkoutSet>;
33
45
  readonly microcyclesToCreate: WorkoutMicrocycle[];
34
46
  /**
35
47
  * All microcycles for this mesocycle in chronological order.
@@ -42,10 +54,10 @@ export default class WorkoutMesocyclePlanContext {
42
54
  readonly sessionExercisesToCreate: WorkoutSessionExercise[];
43
55
  readonly setsToCreate: WorkoutSet[];
44
56
  /**
45
- * The array of sessions corresponding to the array of calibration exercise pairs, in order,
57
+ * The array of sessions corresponding to the array of exercise CTOs, in order,
46
58
  * for that session.
47
59
  */
48
- plannedSessionExercisePairs: CalibrationExercisePair[][] | undefined;
60
+ plannedSessionExerciseCTOs: WorkoutExerciseCTO[][] | undefined;
49
61
  /**
50
62
  * Stores the mesocycle's muscle-group-wide exercise ordering.
51
63
  *
@@ -53,12 +65,17 @@ export default class WorkoutMesocyclePlanContext {
53
65
  * exercises that share a primary muscle group, regardless of which session they
54
66
  * ended up in.
55
67
  */
56
- muscleGroupToExercisePairsMap: Map<UUID, CalibrationExercisePair[]> | undefined;
68
+ muscleGroupToExerciseCTOsMap: Map<UUID, WorkoutExerciseCTO[]> | undefined;
69
+ /**
70
+ * Maps muscle group ID to its estimated volume landmarks (MEV, MRV, MAV).
71
+ * Populated from WorkoutMuscleGroupVolumeCTOs when provided.
72
+ */
73
+ muscleGroupToVolumeLandmarkMap: Map<UUID, WorkoutVolumeLandmarkEstimate>;
57
74
  exerciseIdToSessionIndex: Map<UUID, number> | undefined;
58
75
  /**
59
76
  * Creates a new workout mesocycle planning context.
60
77
  */
61
- constructor(mesocycle: WorkoutMesocycle, calibrations: WorkoutExerciseCalibration[], exercises: WorkoutExercise[], equipmentTypes: WorkoutEquipmentType[], existingMicrocycles?: WorkoutMicrocycle[], existingSessions?: WorkoutSession[], existingSessionExercises?: WorkoutSessionExercise[], existingSets?: WorkoutSet[]);
78
+ constructor(mesocycle: WorkoutMesocycle, exerciseCTOs: WorkoutExerciseCTO[], volumeCTOs?: WorkoutMuscleGroupVolumeCTO[], existingMicrocycles?: WorkoutMicrocycle[], existingSessions?: WorkoutSession[], existingSessionExercises?: WorkoutSessionExercise[], existingSets?: WorkoutSet[]);
62
79
  /**
63
80
  * Adds a microcycle to the list of microcycles to create and the overall chronological list.
64
81
  */
@@ -71,13 +88,17 @@ export default class WorkoutMesocyclePlanContext {
71
88
  * Adds a session exercise to the context and updates internal maps.
72
89
  */
73
90
  addSessionExercise(sessionExercise: WorkoutSessionExercise): void;
91
+ /**
92
+ * Adds sets to the context and updates the set map for O(1) lookup.
93
+ */
94
+ addSets(sets: WorkoutSet[]): void;
74
95
  /**
75
96
  * Stores the planned session -> exercises structure for the mesocycle and derives
76
97
  * the muscle-group-wide ordering used for set progression.
77
98
  */
78
- setPlannedSessionExercisePairs(plannedSessionExercisePairs: CalibrationExercisePair[][]): void;
99
+ setPlannedSessionExerciseCTOs(plannedSessionExerciseCTOs: WorkoutExerciseCTO[][]): void;
79
100
  /**
80
- * Builds and stores a map of primary muscle group -> ordered exercise pairs for the mesocycle plan.
101
+ * Builds and stores a map of primary muscle group -> ordered exercise CTOs for the mesocycle plan.
81
102
  *
82
103
  * Also builds a map of exercise ID -> session index for use during session generation.
83
104
  *
@@ -85,6 +106,6 @@ export default class WorkoutMesocyclePlanContext {
85
106
  * each session in order. This allows set progression to be distributed evenly across a muscle group
86
107
  * for the entire microcycle, even when exercises are split across sessions.
87
108
  */
88
- private buildSessionPairsMaps;
109
+ private buildSessionCTOMaps;
89
110
  }
90
111
  //# sourceMappingURL=WorkoutMesocyclePlanContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutMesocyclePlanContext.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAC;AAC/F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+CAA+C,CAAC;AACrF,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC3B,MAAM,0DAA0D,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gDAAgD,CAAC;AACvF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAC;AACzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sDAAsD,CAAC;AACnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,2BAA2B;IA4CrC,SAAS,EAAE,gBAAgB;IAI3B,mBAAmB,EAAE,iBAAiB,EAAE;IACxC,gBAAgB,EAAE,cAAc,EAAE;IAClC,wBAAwB,EAAE,sBAAsB,EAAE;IAClD,YAAY,EAAE,UAAU,EAAE;IAlDnC;;;OAGG;IACH,SAAgB,oBAAoB,KAAK;IAEzC,SAAgB,cAAc,EAAE,GAAG,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC;IACtE,SAAgB,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IACxD,SAAgB,YAAY,EAAE,GAAG,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAC9D,SAAgB,UAAU,EAAE,GAAG,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACtD,SAAgB,kBAAkB,EAAE,GAAG,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;IAEtE,SAAgB,mBAAmB,EAAE,iBAAiB,EAAE,CAAM;IAC9D;;;;;OAKG;IACH,SAAgB,kBAAkB,EAAE,iBAAiB,EAAE,CAAM;IAC7D,SAAgB,gBAAgB,EAAE,cAAc,EAAE,CAAM;IACxD,SAAgB,wBAAwB,EAAE,sBAAsB,EAAE,CAAM;IACxE,SAAgB,YAAY,EAAE,UAAU,EAAE,CAAM;IAEhD;;;OAGG;IACI,2BAA2B,EAAE,uBAAuB,EAAE,EAAE,GAAG,SAAS,CAAC;IAC5E;;;;;;OAMG;IACI,6BAA6B,EAAE,GAAG,CAAC,IAAI,EAAE,uBAAuB,EAAE,CAAC,GAAG,SAAS,CAAC;IAChF,wBAAwB,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAE/D;;OAEG;gBAEM,SAAS,EAAE,gBAAgB,EAClC,YAAY,EAAE,0BAA0B,EAAE,EAC1C,SAAS,EAAE,eAAe,EAAE,EAC5B,cAAc,EAAE,oBAAoB,EAAE,EAC/B,mBAAmB,GAAE,iBAAiB,EAAO,EAC7C,gBAAgB,GAAE,cAAc,EAAO,EACvC,wBAAwB,GAAE,sBAAsB,EAAO,EACvD,YAAY,GAAE,UAAU,EAAO;IAcxC;;OAEG;IACI,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAKvD;;OAEG;IACI,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAKhD;;OAEG;IACI,kBAAkB,CAAC,eAAe,EAAE,sBAAsB,GAAG,IAAI;IAKxE;;;OAGG;IACI,8BAA8B,CACnC,2BAA2B,EAAE,uBAAuB,EAAE,EAAE,GACvD,IAAI;IAKP;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;CA6B9B"}
1
+ {"version":3,"file":"WorkoutMesocyclePlanContext.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { CycleType } from '../../../documents/workout/WorkoutMesocycle.js';
2
+ import WorkoutVolumePlanningService from '../util/VolumePlanning/WorkoutVolumePlanningService.js';
1
3
  /**
2
4
  * Central shared context for generating or updating a {@link WorkoutMesocycle}.
3
5
  *
@@ -12,15 +14,25 @@ export default class WorkoutMesocyclePlanContext {
12
14
  existingSessionExercises;
13
15
  existingSets;
14
16
  /**
15
- * A constant for now that defines what the target RIR is for the first microcycle of every mesocycle,
16
- * regardless of anything else.
17
+ * The target RIR for the first microcycle of the mesocycle. Varies by cycle
18
+ * type: MuscleGain starts at 4, Cut starts at 3, Resensitization stays at 3.
17
19
  */
18
- FIRST_MICROCYCLE_RIR = 4;
19
- calibrationMap;
20
+ firstMicrocycleRir;
21
+ /**
22
+ * Number of microcycles between each baseline set addition.
23
+ * MuscleGain = 1 (every microcycle), Cut = 2 (every other), Resensitization = 0 (never).
24
+ */
25
+ progressionInterval;
26
+ /**
27
+ * Whether this mesocycle type skips the deload microcycle. True for
28
+ * Resensitization cycles.
29
+ */
30
+ skipDeload;
20
31
  exerciseMap;
21
32
  equipmentMap;
22
33
  sessionMap;
23
34
  sessionExerciseMap;
35
+ setMap;
24
36
  microcyclesToCreate = [];
25
37
  /**
26
38
  * All microcycles for this mesocycle in chronological order.
@@ -33,10 +45,10 @@ export default class WorkoutMesocyclePlanContext {
33
45
  sessionExercisesToCreate = [];
34
46
  setsToCreate = [];
35
47
  /**
36
- * The array of sessions corresponding to the array of calibration exercise pairs, in order,
48
+ * The array of sessions corresponding to the array of exercise CTOs, in order,
37
49
  * for that session.
38
50
  */
39
- plannedSessionExercisePairs;
51
+ plannedSessionExerciseCTOs;
40
52
  /**
41
53
  * Stores the mesocycle's muscle-group-wide exercise ordering.
42
54
  *
@@ -44,22 +56,48 @@ export default class WorkoutMesocyclePlanContext {
44
56
  * exercises that share a primary muscle group, regardless of which session they
45
57
  * ended up in.
46
58
  */
47
- muscleGroupToExercisePairsMap;
59
+ muscleGroupToExerciseCTOsMap;
60
+ /**
61
+ * Maps muscle group ID to its estimated volume landmarks (MEV, MRV, MAV).
62
+ * Populated from WorkoutMuscleGroupVolumeCTOs when provided.
63
+ */
64
+ muscleGroupToVolumeLandmarkMap;
48
65
  exerciseIdToSessionIndex;
49
66
  /**
50
67
  * Creates a new workout mesocycle planning context.
51
68
  */
52
- constructor(mesocycle, calibrations, exercises, equipmentTypes, existingMicrocycles = [], existingSessions = [], existingSessionExercises = [], existingSets = []) {
69
+ constructor(mesocycle, exerciseCTOs, volumeCTOs = [], existingMicrocycles = [], existingSessions = [], existingSessionExercises = [], existingSets = []) {
53
70
  this.mesocycle = mesocycle;
54
71
  this.existingMicrocycles = existingMicrocycles;
55
72
  this.existingSessions = existingSessions;
56
73
  this.existingSessionExercises = existingSessionExercises;
57
74
  this.existingSets = existingSets;
58
- this.calibrationMap = new Map(calibrations.map((c) => [c._id, c]));
59
- this.exerciseMap = new Map(exercises.map((e) => [e._id, e]));
60
- this.equipmentMap = new Map(equipmentTypes.map((et) => [et._id, et]));
75
+ // Set cycle-type-specific planning parameters
76
+ const { cycleType } = mesocycle;
77
+ if (cycleType === CycleType.Cut) {
78
+ this.firstMicrocycleRir = 3;
79
+ this.progressionInterval = 2;
80
+ this.skipDeload = false;
81
+ }
82
+ else if (cycleType === CycleType.Resensitization) {
83
+ this.firstMicrocycleRir = 3;
84
+ this.progressionInterval = 0;
85
+ this.skipDeload = true;
86
+ }
87
+ else {
88
+ this.firstMicrocycleRir = 4;
89
+ this.progressionInterval = 1;
90
+ this.skipDeload = false;
91
+ }
92
+ // Build volume landmark estimates from historical CTOs
93
+ this.muscleGroupToVolumeLandmarkMap = new Map(volumeCTOs.map((cto) => [cto._id, WorkoutVolumePlanningService.estimateVolumeLandmarks(cto)]));
94
+ // Derive exercise map from CTOs
95
+ this.exerciseMap = new Map(exerciseCTOs.map((cto) => [cto._id, cto]));
96
+ // Derive equipment map from CTOs
97
+ this.equipmentMap = new Map(exerciseCTOs.map((cto) => [cto.equipmentType._id, cto.equipmentType]));
61
98
  this.sessionMap = new Map(existingSessions.map((s) => [s._id, s]));
62
99
  this.sessionExerciseMap = new Map(existingSessionExercises.map((s) => [s._id, s]));
100
+ this.setMap = new Map(existingSets.map((s) => [s._id, s]));
63
101
  const existingMicrocyclesForMesocycle = existingMicrocycles
64
102
  .filter((m) => m.workoutMesocycleId === mesocycle._id)
65
103
  .sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
@@ -86,16 +124,25 @@ export default class WorkoutMesocyclePlanContext {
86
124
  this.sessionExercisesToCreate.push(sessionExercise);
87
125
  this.sessionExerciseMap.set(sessionExercise._id, sessionExercise);
88
126
  }
127
+ /**
128
+ * Adds sets to the context and updates the set map for O(1) lookup.
129
+ */
130
+ addSets(sets) {
131
+ this.setsToCreate.push(...sets);
132
+ for (const set of sets) {
133
+ this.setMap.set(set._id, set);
134
+ }
135
+ }
89
136
  /**
90
137
  * Stores the planned session -> exercises structure for the mesocycle and derives
91
138
  * the muscle-group-wide ordering used for set progression.
92
139
  */
93
- setPlannedSessionExercisePairs(plannedSessionExercisePairs) {
94
- this.plannedSessionExercisePairs = plannedSessionExercisePairs;
95
- this.buildSessionPairsMaps(plannedSessionExercisePairs);
140
+ setPlannedSessionExerciseCTOs(plannedSessionExerciseCTOs) {
141
+ this.plannedSessionExerciseCTOs = plannedSessionExerciseCTOs;
142
+ this.buildSessionCTOMaps(plannedSessionExerciseCTOs);
96
143
  }
97
144
  /**
98
- * Builds and stores a map of primary muscle group -> ordered exercise pairs for the mesocycle plan.
145
+ * Builds and stores a map of primary muscle group -> ordered exercise CTOs for the mesocycle plan.
99
146
  *
100
147
  * Also builds a map of exercise ID -> session index for use during session generation.
101
148
  *
@@ -103,28 +150,27 @@ export default class WorkoutMesocyclePlanContext {
103
150
  * each session in order. This allows set progression to be distributed evenly across a muscle group
104
151
  * for the entire microcycle, even when exercises are split across sessions.
105
152
  */
106
- buildSessionPairsMaps(sessionsToExercisePairs) {
107
- const muscleGroupToPairsMap = new Map();
153
+ buildSessionCTOMaps(sessionsToCTOs) {
154
+ const muscleGroupToCTOsMap = new Map();
108
155
  const exerciseIdToSessionIndex = new Map();
109
- for (let sessionIndex = 0; sessionIndex < sessionsToExercisePairs.length; sessionIndex++) {
110
- const sessionExercisePairs = sessionsToExercisePairs[sessionIndex];
111
- for (const exercisePair of sessionExercisePairs) {
112
- // Set the session index for this exercise as well
113
- exerciseIdToSessionIndex.set(exercisePair.exercise._id, sessionIndex);
114
- const primaryMuscleGroupId = exercisePair.exercise.primaryMuscleGroups[0];
156
+ for (let sessionIndex = 0; sessionIndex < sessionsToCTOs.length; sessionIndex++) {
157
+ const sessionCTOs = sessionsToCTOs[sessionIndex];
158
+ for (const cto of sessionCTOs) {
159
+ exerciseIdToSessionIndex.set(cto._id, sessionIndex);
160
+ const primaryMuscleGroupId = cto.primaryMuscleGroups[0];
115
161
  if (!primaryMuscleGroupId) {
116
- throw new Error(`Exercise ${exercisePair.exercise._id}, ${exercisePair.exercise.exerciseName} has no primary muscle group`);
162
+ throw new Error(`Exercise ${cto._id}, ${cto.exerciseName} has no primary muscle group`);
117
163
  }
118
- const existing = muscleGroupToPairsMap.get(primaryMuscleGroupId);
164
+ const existing = muscleGroupToCTOsMap.get(primaryMuscleGroupId);
119
165
  if (existing) {
120
- existing.push(exercisePair);
166
+ existing.push(cto);
121
167
  }
122
168
  else {
123
- muscleGroupToPairsMap.set(primaryMuscleGroupId, [exercisePair]);
169
+ muscleGroupToCTOsMap.set(primaryMuscleGroupId, [cto]);
124
170
  }
125
171
  }
126
172
  }
127
- this.muscleGroupToExercisePairsMap = muscleGroupToPairsMap;
173
+ this.muscleGroupToExerciseCTOsMap = muscleGroupToCTOsMap;
128
174
  this.exerciseIdToSessionIndex = exerciseIdToSessionIndex;
129
175
  }
130
176
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutMesocyclePlanContext.js","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"AAaA;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,2BAA2B;IA4CrC;IAIA;IACA;IACA;IACA;IAlDT;;;OAGG;IACa,oBAAoB,GAAG,CAAC,CAAC;IAEzB,cAAc,CAAwC;IACtD,WAAW,CAA6B;IACxC,YAAY,CAAkC;IAC9C,UAAU,CAA4B;IACtC,kBAAkB,CAAoC;IAEtD,mBAAmB,GAAwB,EAAE,CAAC;IAC9D;;;;;OAKG;IACa,kBAAkB,GAAwB,EAAE,CAAC;IAC7C,gBAAgB,GAAqB,EAAE,CAAC;IACxC,wBAAwB,GAA6B,EAAE,CAAC;IACxD,YAAY,GAAiB,EAAE,CAAC;IAEhD;;;OAGG;IACI,2BAA2B,CAA0C;IAC5E;;;;;;OAMG;IACI,6BAA6B,CAAmD;IAChF,wBAAwB,CAAgC;IAE/D;;OAEG;IACH,YACS,SAA2B,EAClC,YAA0C,EAC1C,SAA4B,EAC5B,cAAsC,EAC/B,sBAA2C,EAAE,EAC7C,mBAAqC,EAAE,EACvC,2BAAqD,EAAE,EACvD,eAA6B,EAAE;QAP/B,cAAS,GAAT,SAAS,CAAkB;QAI3B,wBAAmB,GAAnB,mBAAmB,CAA0B;QAC7C,qBAAgB,GAAhB,gBAAgB,CAAuB;QACvC,6BAAwB,GAAxB,wBAAwB,CAA+B;QACvD,iBAAY,GAAZ,YAAY,CAAmB;QAEtC,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,UAAU,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,kBAAkB,GAAG,IAAI,GAAG,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnF,MAAM,+BAA+B,GAAG,mBAAmB;aACxD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,KAAK,SAAS,CAAC,GAAG,CAAC;aACrD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,+BAA+B,CAAC,CAAC;IACnE,CAAC;IAED;;OAEG;IACI,aAAa,CAAC,QAA2B;QAC9C,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED;;OAEG;IACI,UAAU,CAAC,OAAuB;QACvC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,eAAuC;QAC/D,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACpD,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IACpE,CAAC;IAED;;;OAGG;IACI,8BAA8B,CACnC,2BAAwD;QAExD,IAAI,CAAC,2BAA2B,GAAG,2BAA2B,CAAC;QAC/D,IAAI,CAAC,qBAAqB,CAAC,2BAA2B,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;OAQG;IACK,qBAAqB,CAAC,uBAAoD;QAChF,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAmC,CAAC;QACzE,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAgB,CAAC;QAEzD,KAAK,IAAI,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC;YACzF,MAAM,oBAAoB,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;YACnE,KAAK,MAAM,YAAY,IAAI,oBAAoB,EAAE,CAAC;gBAChD,kDAAkD;gBAClD,wBAAwB,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAEtE,MAAM,oBAAoB,GAAG,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;gBAC1E,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBAC1B,MAAM,IAAI,KAAK,CACb,YAAY,YAAY,CAAC,QAAQ,CAAC,GAAG,KAAK,YAAY,CAAC,QAAQ,CAAC,YAAY,8BAA8B,CAC3G,CAAC;gBACJ,CAAC;gBAED,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;gBACjE,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,qBAAqB,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;gBAClE,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,6BAA6B,GAAG,qBAAqB,CAAC;QAC3D,IAAI,CAAC,wBAAwB,GAAG,wBAAwB,CAAC;IAC3D,CAAC;CACF"}
1
+ {"version":3,"file":"WorkoutMesocyclePlanContext.js","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,16 @@
1
1
  import type { UUID } from 'crypto';
2
+ import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
3
+ import type { WorkoutMuscleGroupVolumeCTO } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
2
4
  import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
3
5
  import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
4
- import type {
5
- CalibrationExercisePair,
6
- WorkoutExerciseCalibration
7
- } from '../../../documents/workout/WorkoutExerciseCalibration.js';
8
6
  import type { WorkoutMesocycle } from '../../../documents/workout/WorkoutMesocycle.js';
7
+ import { CycleType } from '../../../documents/workout/WorkoutMesocycle.js';
9
8
  import type { WorkoutMicrocycle } from '../../../documents/workout/WorkoutMicrocycle.js';
10
9
  import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
11
10
  import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
12
11
  import type { WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
12
+ import type { WorkoutVolumeLandmarkEstimate } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
13
+ import WorkoutVolumePlanningService from '../util/VolumePlanning/WorkoutVolumePlanningService.js';
13
14
 
14
15
  /**
15
16
  * Central shared context for generating or updating a {@link WorkoutMesocycle}.
@@ -20,16 +21,28 @@ import type { WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
20
21
  */
21
22
  export default class WorkoutMesocyclePlanContext {
22
23
  /**
23
- * A constant for now that defines what the target RIR is for the first microcycle of every mesocycle,
24
- * regardless of anything else.
24
+ * The target RIR for the first microcycle of the mesocycle. Varies by cycle
25
+ * type: MuscleGain starts at 4, Cut starts at 3, Resensitization stays at 3.
25
26
  */
26
- public readonly FIRST_MICROCYCLE_RIR = 4;
27
+ public readonly firstMicrocycleRir: number;
28
+
29
+ /**
30
+ * Number of microcycles between each baseline set addition.
31
+ * MuscleGain = 1 (every microcycle), Cut = 2 (every other), Resensitization = 0 (never).
32
+ */
33
+ public readonly progressionInterval: number;
34
+
35
+ /**
36
+ * Whether this mesocycle type skips the deload microcycle. True for
37
+ * Resensitization cycles.
38
+ */
39
+ public readonly skipDeload: boolean;
27
40
 
28
- public readonly calibrationMap: Map<UUID, WorkoutExerciseCalibration>;
29
41
  public readonly exerciseMap: Map<UUID, WorkoutExercise>;
30
42
  public readonly equipmentMap: Map<UUID, WorkoutEquipmentType>;
31
43
  public readonly sessionMap: Map<UUID, WorkoutSession>;
32
44
  public readonly sessionExerciseMap: Map<UUID, WorkoutSessionExercise>;
45
+ public readonly setMap: Map<UUID, WorkoutSet>;
33
46
 
34
47
  public readonly microcyclesToCreate: WorkoutMicrocycle[] = [];
35
48
  /**
@@ -44,10 +57,10 @@ export default class WorkoutMesocyclePlanContext {
44
57
  public readonly setsToCreate: WorkoutSet[] = [];
45
58
 
46
59
  /**
47
- * The array of sessions corresponding to the array of calibration exercise pairs, in order,
60
+ * The array of sessions corresponding to the array of exercise CTOs, in order,
48
61
  * for that session.
49
62
  */
50
- public plannedSessionExercisePairs: CalibrationExercisePair[][] | undefined;
63
+ public plannedSessionExerciseCTOs: WorkoutExerciseCTO[][] | undefined;
51
64
  /**
52
65
  * Stores the mesocycle's muscle-group-wide exercise ordering.
53
66
  *
@@ -55,7 +68,12 @@ export default class WorkoutMesocyclePlanContext {
55
68
  * exercises that share a primary muscle group, regardless of which session they
56
69
  * ended up in.
57
70
  */
58
- public muscleGroupToExercisePairsMap: Map<UUID, CalibrationExercisePair[]> | undefined;
71
+ public muscleGroupToExerciseCTOsMap: Map<UUID, WorkoutExerciseCTO[]> | undefined;
72
+ /**
73
+ * Maps muscle group ID to its estimated volume landmarks (MEV, MRV, MAV).
74
+ * Populated from WorkoutMuscleGroupVolumeCTOs when provided.
75
+ */
76
+ public muscleGroupToVolumeLandmarkMap: Map<UUID, WorkoutVolumeLandmarkEstimate>;
59
77
  public exerciseIdToSessionIndex: Map<UUID, number> | undefined;
60
78
 
61
79
  /**
@@ -63,19 +81,45 @@ export default class WorkoutMesocyclePlanContext {
63
81
  */
64
82
  constructor(
65
83
  public mesocycle: WorkoutMesocycle,
66
- calibrations: WorkoutExerciseCalibration[],
67
- exercises: WorkoutExercise[],
68
- equipmentTypes: WorkoutEquipmentType[],
84
+ exerciseCTOs: WorkoutExerciseCTO[],
85
+ volumeCTOs: WorkoutMuscleGroupVolumeCTO[] = [],
69
86
  public existingMicrocycles: WorkoutMicrocycle[] = [],
70
87
  public existingSessions: WorkoutSession[] = [],
71
88
  public existingSessionExercises: WorkoutSessionExercise[] = [],
72
89
  public existingSets: WorkoutSet[] = []
73
90
  ) {
74
- this.calibrationMap = new Map(calibrations.map((c) => [c._id, c]));
75
- this.exerciseMap = new Map(exercises.map((e) => [e._id, e]));
76
- this.equipmentMap = new Map(equipmentTypes.map((et) => [et._id, et]));
91
+ // Set cycle-type-specific planning parameters
92
+ const { cycleType } = mesocycle;
93
+ if (cycleType === CycleType.Cut) {
94
+ this.firstMicrocycleRir = 3;
95
+ this.progressionInterval = 2;
96
+ this.skipDeload = false;
97
+ } else if (cycleType === CycleType.Resensitization) {
98
+ this.firstMicrocycleRir = 3;
99
+ this.progressionInterval = 0;
100
+ this.skipDeload = true;
101
+ } else {
102
+ this.firstMicrocycleRir = 4;
103
+ this.progressionInterval = 1;
104
+ this.skipDeload = false;
105
+ }
106
+
107
+ // Build volume landmark estimates from historical CTOs
108
+ this.muscleGroupToVolumeLandmarkMap = new Map(
109
+ volumeCTOs.map((cto) => [cto._id, WorkoutVolumePlanningService.estimateVolumeLandmarks(cto)])
110
+ );
111
+
112
+ // Derive exercise map from CTOs
113
+ this.exerciseMap = new Map(exerciseCTOs.map((cto) => [cto._id, cto]));
114
+
115
+ // Derive equipment map from CTOs
116
+ this.equipmentMap = new Map(
117
+ exerciseCTOs.map((cto) => [cto.equipmentType._id, cto.equipmentType])
118
+ );
119
+
77
120
  this.sessionMap = new Map(existingSessions.map((s) => [s._id, s]));
78
121
  this.sessionExerciseMap = new Map(existingSessionExercises.map((s) => [s._id, s]));
122
+ this.setMap = new Map(existingSets.map((s) => [s._id, s]));
79
123
 
80
124
  const existingMicrocyclesForMesocycle = existingMicrocycles
81
125
  .filter((m) => m.workoutMesocycleId === mesocycle._id)
@@ -107,19 +151,27 @@ export default class WorkoutMesocyclePlanContext {
107
151
  this.sessionExerciseMap.set(sessionExercise._id, sessionExercise);
108
152
  }
109
153
 
154
+ /**
155
+ * Adds sets to the context and updates the set map for O(1) lookup.
156
+ */
157
+ public addSets(sets: WorkoutSet[]): void {
158
+ this.setsToCreate.push(...sets);
159
+ for (const set of sets) {
160
+ this.setMap.set(set._id, set);
161
+ }
162
+ }
163
+
110
164
  /**
111
165
  * Stores the planned session -> exercises structure for the mesocycle and derives
112
166
  * the muscle-group-wide ordering used for set progression.
113
167
  */
114
- public setPlannedSessionExercisePairs(
115
- plannedSessionExercisePairs: CalibrationExercisePair[][]
116
- ): void {
117
- this.plannedSessionExercisePairs = plannedSessionExercisePairs;
118
- this.buildSessionPairsMaps(plannedSessionExercisePairs);
168
+ public setPlannedSessionExerciseCTOs(plannedSessionExerciseCTOs: WorkoutExerciseCTO[][]): void {
169
+ this.plannedSessionExerciseCTOs = plannedSessionExerciseCTOs;
170
+ this.buildSessionCTOMaps(plannedSessionExerciseCTOs);
119
171
  }
120
172
 
121
173
  /**
122
- * Builds and stores a map of primary muscle group -> ordered exercise pairs for the mesocycle plan.
174
+ * Builds and stores a map of primary muscle group -> ordered exercise CTOs for the mesocycle plan.
123
175
  *
124
176
  * Also builds a map of exercise ID -> session index for use during session generation.
125
177
  *
@@ -127,33 +179,30 @@ export default class WorkoutMesocyclePlanContext {
127
179
  * each session in order. This allows set progression to be distributed evenly across a muscle group
128
180
  * for the entire microcycle, even when exercises are split across sessions.
129
181
  */
130
- private buildSessionPairsMaps(sessionsToExercisePairs: CalibrationExercisePair[][]): void {
131
- const muscleGroupToPairsMap = new Map<UUID, CalibrationExercisePair[]>();
182
+ private buildSessionCTOMaps(sessionsToCTOs: WorkoutExerciseCTO[][]): void {
183
+ const muscleGroupToCTOsMap = new Map<UUID, WorkoutExerciseCTO[]>();
132
184
  const exerciseIdToSessionIndex = new Map<UUID, number>();
133
185
 
134
- for (let sessionIndex = 0; sessionIndex < sessionsToExercisePairs.length; sessionIndex++) {
135
- const sessionExercisePairs = sessionsToExercisePairs[sessionIndex];
136
- for (const exercisePair of sessionExercisePairs) {
137
- // Set the session index for this exercise as well
138
- exerciseIdToSessionIndex.set(exercisePair.exercise._id, sessionIndex);
186
+ for (let sessionIndex = 0; sessionIndex < sessionsToCTOs.length; sessionIndex++) {
187
+ const sessionCTOs = sessionsToCTOs[sessionIndex];
188
+ for (const cto of sessionCTOs) {
189
+ exerciseIdToSessionIndex.set(cto._id, sessionIndex);
139
190
 
140
- const primaryMuscleGroupId = exercisePair.exercise.primaryMuscleGroups[0];
191
+ const primaryMuscleGroupId = cto.primaryMuscleGroups[0];
141
192
  if (!primaryMuscleGroupId) {
142
- throw new Error(
143
- `Exercise ${exercisePair.exercise._id}, ${exercisePair.exercise.exerciseName} has no primary muscle group`
144
- );
193
+ throw new Error(`Exercise ${cto._id}, ${cto.exerciseName} has no primary muscle group`);
145
194
  }
146
195
 
147
- const existing = muscleGroupToPairsMap.get(primaryMuscleGroupId);
196
+ const existing = muscleGroupToCTOsMap.get(primaryMuscleGroupId);
148
197
  if (existing) {
149
- existing.push(exercisePair);
198
+ existing.push(cto);
150
199
  } else {
151
- muscleGroupToPairsMap.set(primaryMuscleGroupId, [exercisePair]);
200
+ muscleGroupToCTOsMap.set(primaryMuscleGroupId, [cto]);
152
201
  }
153
202
  }
154
203
  }
155
204
 
156
- this.muscleGroupToExercisePairsMap = muscleGroupToPairsMap;
205
+ this.muscleGroupToExerciseCTOsMap = muscleGroupToCTOsMap;
157
206
  this.exerciseIdToSessionIndex = exerciseIdToSessionIndex;
158
207
  }
159
208
  }