@aneuhold/core-ts-db-lib 4.1.12 → 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 (57) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/lib/browser.d.ts +11 -8
  3. package/lib/browser.d.ts.map +1 -1
  4. package/lib/browser.js +6 -4
  5. package/lib/browser.js.map +1 -1
  6. package/lib/browser.ts +23 -9
  7. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts +14 -0
  8. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts.map +1 -1
  9. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.ts +18 -0
  10. package/lib/documents/workout/WorkoutSet.d.ts +8 -0
  11. package/lib/documents/workout/WorkoutSet.d.ts.map +1 -1
  12. package/lib/documents/workout/WorkoutSet.ts +9 -0
  13. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +40 -0
  14. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -1
  15. package/lib/services/workout/Exercise/WorkoutExerciseService.js +134 -1
  16. package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -1
  17. package/lib/services/workout/Exercise/WorkoutExerciseService.ts +204 -1
  18. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +15 -0
  19. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -1
  20. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +18 -1
  21. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -1
  22. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +19 -1
  23. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +26 -4
  24. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -1
  25. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +51 -4
  26. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -1
  27. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +58 -3
  28. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +45 -1
  29. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -1
  30. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +201 -11
  31. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -1
  32. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +285 -9
  33. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts +33 -0
  34. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts.map +1 -0
  35. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js +24 -0
  36. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js.map +1 -0
  37. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.ts +36 -0
  38. package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -1
  39. package/lib/services/workout/Session/WorkoutSessionService.js +1 -11
  40. package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -1
  41. package/lib/services/workout/Session/WorkoutSessionService.ts +1 -17
  42. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +14 -2
  43. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -1
  44. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +17 -3
  45. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -1
  46. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +28 -3
  47. package/lib/services/workout/Set/WorkoutSetService.d.ts +17 -5
  48. package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -1
  49. package/lib/services/workout/Set/WorkoutSetService.js +83 -16
  50. package/lib/services/workout/Set/WorkoutSetService.js.map +1 -1
  51. package/lib/services/workout/Set/WorkoutSetService.ts +107 -24
  52. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +72 -2
  53. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -1
  54. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +202 -15
  55. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -1
  56. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +268 -18
  57. package/package.json +1 -1
@@ -5,8 +5,10 @@ import {
5
5
  ExerciseRepRange
6
6
  } from '../../../documents/workout/WorkoutExercise.js';
7
7
  import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
8
+ import type { CompletedWorkoutSet, WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
8
9
  import WorkoutEquipmentTypeService from '../EquipmentType/WorkoutEquipmentTypeService.js';
9
10
  import WorkoutExerciseCalibrationService from '../ExerciseCalibration/WorkoutExerciseCalibrationService.js';
11
+ import WorkoutSessionExerciseService from '../SessionExercise/WorkoutSessionExerciseService.js';
10
12
  import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
11
13
 
12
14
  /**
@@ -50,6 +52,10 @@ export default class WorkoutExerciseService {
50
52
  * This method applies either rep-based or load-based progression depending on the exercise's
51
53
  * preferred progression type, then rounds the weight to available equipment options.
52
54
  *
55
+ * When a `previousFirstSet` is provided, autoregulation adjusts progression based on the
56
+ * surplus between planned and actual performance. Without it, the calibration-based formula
57
+ * is used as a baseline.
58
+ *
53
59
  * Rep progression: The weight is calculated based on reps at microcycle 0, and reps increase
54
60
  * by 2 per microcycle to reach max reps at the final accumulation microcycle (ideally),
55
61
  * or drop back down and increase weight by 2%.
@@ -63,8 +69,16 @@ export default class WorkoutExerciseService {
63
69
  equipment: WorkoutEquipmentType;
64
70
  microcycleIndex: number;
65
71
  firstMicrocycleRir: number;
72
+ previousFirstSet?: WorkoutSet;
66
73
  }): { targetWeight: number; targetReps: number } {
67
- const { exercise, calibration, equipment, microcycleIndex, firstMicrocycleRir } = params;
74
+ const {
75
+ exercise,
76
+ calibration,
77
+ equipment,
78
+ microcycleIndex,
79
+ firstMicrocycleRir,
80
+ previousFirstSet
81
+ } = params;
68
82
 
69
83
  // Validate equipment has weight options
70
84
  if (!equipment.weightOptions || equipment.weightOptions.length === 0) {
@@ -77,6 +91,195 @@ export default class WorkoutExerciseService {
77
91
  const repRange = this.getRepRangeValues(exercise.repRange);
78
92
  const repRangeMidpoint = Math.floor((repRange.min + repRange.max) / 2);
79
93
 
94
+ // If we have a previous set with complete data, use autoregulation
95
+ if (previousFirstSet && this.hasCompleteAutoRegulationData(previousFirstSet)) {
96
+ return this.calculateAutoRegulatedTargets({
97
+ exercise,
98
+ equipment,
99
+ previousFirstSet,
100
+ repRange
101
+ });
102
+ }
103
+
104
+ // Calibration-based formula (no previous set available)
105
+ return this.calculateCalibrationBasedTargets({
106
+ exercise,
107
+ calibration,
108
+ equipment,
109
+ microcycleIndex,
110
+ firstMicrocycleRir,
111
+ repRange,
112
+ repRangeMidpoint
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Calculates targets using auto-regulation based on actual performance from a previous set.
118
+ */
119
+ private static calculateAutoRegulatedTargets(params: {
120
+ exercise: WorkoutExercise;
121
+ equipment: WorkoutEquipmentType;
122
+ previousFirstSet: CompletedWorkoutSet;
123
+ repRange: { min: number; max: number };
124
+ }): { targetWeight: number; targetReps: number } {
125
+ const { exercise, equipment, previousFirstSet, repRange } = params;
126
+
127
+ const surplus = WorkoutSessionExerciseService.calculateSetSurplus(
128
+ previousFirstSet.actualReps,
129
+ previousFirstSet.plannedReps,
130
+ previousFirstSet.rir,
131
+ previousFirstSet.plannedRir
132
+ );
133
+
134
+ if (exercise.preferredProgressionType === ExerciseProgressionType.Rep) {
135
+ return this.calculateAutoRegulatedRepTargets(previousFirstSet, surplus, repRange, equipment);
136
+ }
137
+
138
+ return this.calculateAutoRegulatedLoadTargets(previousFirstSet, surplus, repRange, equipment);
139
+ }
140
+
141
+ /**
142
+ * Auto-regulated rep progression. Weight stays the same, reps adjust based on surplus.
143
+ *
144
+ * | Surplus | Action |
145
+ * |---:|---|
146
+ * | >= 3 | Accelerate: actualReps + 2 (progress from actual, not planned) |
147
+ * | 0 to 2 | Normal: plannedReps + 2 |
148
+ * | -1 to -2 | Hold: plannedReps (don't add reps) |
149
+ * | <= -3 | Regress: actualReps (use actual as new baseline) |
150
+ */
151
+ private static calculateAutoRegulatedRepTargets(
152
+ previousSet: CompletedWorkoutSet,
153
+ surplus: number,
154
+ repRange: { min: number; max: number },
155
+ equipment: WorkoutEquipmentType
156
+ ): { targetWeight: number; targetReps: number } {
157
+ let targetReps: number;
158
+ let targetWeight = previousSet.plannedWeight;
159
+
160
+ if (surplus >= 3) {
161
+ targetReps = previousSet.actualReps + 2;
162
+ } else if (surplus >= 0) {
163
+ targetReps = previousSet.plannedReps + 2;
164
+ } else if (surplus >= -2) {
165
+ targetReps = previousSet.plannedReps;
166
+ } else {
167
+ targetReps = previousSet.actualReps;
168
+ }
169
+
170
+ // Clamp to rep range floor (never target below min, even if actual was 0)
171
+ targetReps = Math.max(targetReps, repRange.min);
172
+
173
+ // Handle rep range ceiling: if target exceeds max, reset and bump weight
174
+ if (targetReps > repRange.max) {
175
+ targetReps = repRange.max;
176
+ const nextWeight = this.findNextTwoPercentWeight(targetWeight, equipment);
177
+ if (nextWeight !== null) {
178
+ targetWeight = nextWeight;
179
+ }
180
+ }
181
+
182
+ // Round weight to equipment
183
+ const roundedWeight = WorkoutEquipmentTypeService.findNearestWeight(
184
+ equipment,
185
+ targetWeight,
186
+ 'prefer-down'
187
+ );
188
+ if (roundedWeight !== null) {
189
+ targetWeight = roundedWeight;
190
+ }
191
+
192
+ return { targetWeight, targetReps };
193
+ }
194
+
195
+ /**
196
+ * Auto-regulated load progression. Reps stay at rep range max, weight adjusts based on surplus.
197
+ *
198
+ * | Surplus | Action |
199
+ * |---:|---|
200
+ * | >= 2 | Accelerate: increase weight by ~4% |
201
+ * | 0 to 1 | Normal: increase weight by 2% |
202
+ * | -1 to -2 | Hold weight (no increase) |
203
+ * | <= -3 | Reduce weight by minimum equipment increment |
204
+ */
205
+ private static calculateAutoRegulatedLoadTargets(
206
+ previousSet: CompletedWorkoutSet,
207
+ surplus: number,
208
+ repRange: { min: number; max: number },
209
+ equipment: WorkoutEquipmentType
210
+ ): { targetWeight: number; targetReps: number } {
211
+ const targetReps = repRange.max;
212
+ let targetWeight = previousSet.plannedWeight;
213
+
214
+ if (surplus >= 2) {
215
+ // Accelerate: increase by ~4%
216
+ const fourPercentIncrease = targetWeight * 1.04;
217
+ const nextWeight = WorkoutEquipmentTypeService.findNearestWeight(
218
+ equipment,
219
+ fourPercentIncrease,
220
+ 'up'
221
+ );
222
+ if (nextWeight !== null) {
223
+ targetWeight = nextWeight;
224
+ }
225
+ } else if (surplus >= 0) {
226
+ // Normal: increase by 2%
227
+ const nextWeight = this.findNextTwoPercentWeight(targetWeight, equipment);
228
+ if (nextWeight !== null) {
229
+ targetWeight = nextWeight;
230
+ }
231
+ } else if (surplus >= -2) {
232
+ // Hold weight - no change
233
+ } else {
234
+ // Reduce by minimum equipment increment
235
+ const reducedWeight = WorkoutEquipmentTypeService.findNearestWeight(
236
+ equipment,
237
+ targetWeight - 0.01,
238
+ 'down'
239
+ );
240
+ if (reducedWeight !== null) {
241
+ targetWeight = reducedWeight;
242
+ }
243
+ }
244
+
245
+ return { targetWeight, targetReps };
246
+ }
247
+
248
+ /**
249
+ * Checks whether a set has all the data needed for autoregulation calculations.
250
+ */
251
+ private static hasCompleteAutoRegulationData(set: WorkoutSet): set is CompletedWorkoutSet {
252
+ return (
253
+ set.actualReps != null &&
254
+ set.plannedReps != null &&
255
+ set.rir != null &&
256
+ set.plannedRir != null &&
257
+ set.plannedWeight != null
258
+ );
259
+ }
260
+
261
+ /**
262
+ * Calculates targets using the calibration-based formula (original behavior).
263
+ */
264
+ private static calculateCalibrationBasedTargets(params: {
265
+ exercise: WorkoutExercise;
266
+ calibration: WorkoutExerciseCalibration;
267
+ equipment: WorkoutEquipmentType;
268
+ microcycleIndex: number;
269
+ firstMicrocycleRir: number;
270
+ repRange: { min: number; max: number };
271
+ repRangeMidpoint: number;
272
+ }): { targetWeight: number; targetReps: number } {
273
+ const {
274
+ exercise,
275
+ calibration,
276
+ equipment,
277
+ microcycleIndex,
278
+ firstMicrocycleRir,
279
+ repRange,
280
+ repRangeMidpoint
281
+ } = params;
282
+
80
283
  // For rep progression, calculate weight based on reps at microcycle 0
81
284
  // For load progression, use max reps
82
285
  let baseRepsForWeight: number;
@@ -45,6 +45,21 @@ export default class WorkoutExerciseCalibrationService {
45
45
  * @param targetReps The target number of reps.
46
46
  */
47
47
  static getTargetWeight(calibration: WorkoutExerciseCalibration, targetReps: number): number;
48
+ /**
49
+ * Calculates the target weight from a raw 1RM value and a target rep count.
50
+ *
51
+ * This applies the same targetPercentage formula as {@link getTargetWeight}
52
+ * but accepts a pre-computed 1RM instead of a calibration document. Useful
53
+ * when the effective 1RM is derived from multiple sources (calibrations and
54
+ * actual sets).
55
+ *
56
+ * Returns the calculated weight without rounding. Consumer can use
57
+ * WorkoutEquipmentTypeService.findNearestWeight() to round if needed.
58
+ *
59
+ * @param effective1RM The effective 1 Rep Max value.
60
+ * @param targetReps The target number of reps.
61
+ */
62
+ static getTargetWeightFrom1RM(effective1RM: number, targetReps: number): number;
48
63
  /**
49
64
  * Generates auto-calibrations from exercise CTOs whose best set 1RM exceeds
50
65
  * their best calibration 1RM.
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseCalibrationService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AAM3G;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iCAAiC;IACpD;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,0BAA0B,GAAG,MAAM;IAI9D;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAItD;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;IAM7D;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAM3F;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,wBAAwB,CAC7B,YAAY,EAAE,kBAAkB,EAAE,EAClC,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,IAAI,GACjB,0BAA0B,EAAE;IA4B/B;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;CAGnC"}
1
+ {"version":3,"file":"WorkoutExerciseCalibrationService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AAM3G;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iCAAiC;IACpD;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,0BAA0B,GAAG,MAAM;IAI9D;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAItD;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;IAM7D;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAK3F;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,sBAAsB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAK/E;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,wBAAwB,CAC7B,YAAY,EAAE,kBAAkB,EAAE,EAClC,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,IAAI,GACjB,0BAA0B,EAAE;IA4B/B;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,CAAC,mBAAmB;CAGnC"}
@@ -48,8 +48,25 @@ export default class WorkoutExerciseCalibrationService {
48
48
  */
49
49
  static getTargetWeight(calibration, targetReps) {
50
50
  const oneRepMax = this.get1RM(calibration);
51
+ return this.getTargetWeightFrom1RM(oneRepMax, targetReps);
52
+ }
53
+ /**
54
+ * Calculates the target weight from a raw 1RM value and a target rep count.
55
+ *
56
+ * This applies the same targetPercentage formula as {@link getTargetWeight}
57
+ * but accepts a pre-computed 1RM instead of a calibration document. Useful
58
+ * when the effective 1RM is derived from multiple sources (calibrations and
59
+ * actual sets).
60
+ *
61
+ * Returns the calculated weight without rounding. Consumer can use
62
+ * WorkoutEquipmentTypeService.findNearestWeight() to round if needed.
63
+ *
64
+ * @param effective1RM The effective 1 Rep Max value.
65
+ * @param targetReps The target number of reps.
66
+ */
67
+ static getTargetWeightFrom1RM(effective1RM, targetReps) {
51
68
  const targetPercentage = this.getTargetPercentage(targetReps);
52
- return (targetPercentage / 100) * oneRepMax;
69
+ return (targetPercentage / 100) * effective1RM;
53
70
  }
54
71
  /**
55
72
  * Generates auto-calibrations from exercise CTOs whose best set 1RM exceeds
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseCalibrationService.js","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gCAAgC,EAAE,MAAM,0DAA0D,CAAC;AAE5G,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iCAAiC;IACpD;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,WAAuC;QACnD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,MAAc,EAAE,IAAY;QAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,gBAAgB,GAAG,MAAM,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CAAC,WAAmB,EAAE,SAAiB;QAC3D,OAAO;YACL,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,gBAAgB,CAAC,EAAE,EAAE,WAAW,CAAC;SAC9F,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,WAAuC,EAAE,UAAkB;QAChF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC3C,MAAM,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,CAAC,gBAAgB,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,wBAAwB,CAC7B,YAAkC,EAClC,MAAY,EACZ,YAAkB;QAElB,MAAM,eAAe,GAAiC,EAAE,CAAC;QAEzD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC;YACzC,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC;gBAAE,SAAS;YAEvF,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAElE,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC;gBACpB,eAAe,CAAC,IAAI,CAClB,gCAAgC,CAAC,KAAK,CAAC;oBACrC,MAAM;oBACN,iBAAiB,EAAE,GAAG,CAAC,GAAG;oBAC1B,MAAM,EAAE,OAAO,CAAC,YAAY;oBAC5B,IAAI,EAAE,OAAO,CAAC,UAAU;oBACxB,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;oBAC9C,YAAY;oBACZ,sBAAsB,EAAE,OAAO,CAAC,GAAG;iBACpC,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAED;;;;;;;;;OASG;IACK,MAAM,CAAC,mBAAmB,CAAC,UAAkB;QACnD,OAAO,EAAE,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;IACrC,CAAC;CACF"}
1
+ {"version":3,"file":"WorkoutExerciseCalibrationService.js","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gCAAgC,EAAE,MAAM,0DAA0D,CAAC;AAE5G,mFAAmF;AACnF,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iCAAiC;IACpD;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,WAAuC;QACnD,OAAO,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CAAC,MAAc,EAAE,IAAY;QAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,gBAAgB,GAAG,MAAM,CAAC;IACrD,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,eAAe,CAAC,WAAmB,EAAE,SAAiB;QAC3D,OAAO;YACL,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,gBAAgB,CAAC,EAAE,EAAE,WAAW,CAAC;SAC9F,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,WAAuC,EAAE,UAAkB;QAChF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,sBAAsB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC5D,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,sBAAsB,CAAC,YAAoB,EAAE,UAAkB;QACpE,MAAM,gBAAgB,GAAG,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,CAAC,gBAAgB,GAAG,GAAG,CAAC,GAAG,YAAY,CAAC;IACjD,CAAC;IAED;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,wBAAwB,CAC7B,YAAkC,EAClC,MAAY,EACZ,YAAkB;QAElB,MAAM,eAAe,GAAiC,EAAE,CAAC;QAEzD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC;YACzC,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC;gBAAE,SAAS;YAEvF,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAElE,IAAI,MAAM,GAAG,MAAM,EAAE,CAAC;gBACpB,eAAe,CAAC,IAAI,CAClB,gCAAgC,CAAC,KAAK,CAAC;oBACrC,MAAM;oBACN,iBAAiB,EAAE,GAAG,CAAC,GAAG;oBAC1B,MAAM,EAAE,OAAO,CAAC,YAAY;oBAC5B,IAAI,EAAE,OAAO,CAAC,UAAU;oBACxB,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;oBAC9C,YAAY;oBACZ,sBAAsB,EAAE,OAAO,CAAC,GAAG;iBACpC,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAED;;;;;;;;;OASG;IACK,MAAM,CAAC,mBAAmB,CAAC,UAAkB;QACnD,OAAO,EAAE,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;IACrC,CAAC;CACF"}
@@ -56,8 +56,26 @@ export default class WorkoutExerciseCalibrationService {
56
56
  */
57
57
  static getTargetWeight(calibration: WorkoutExerciseCalibration, targetReps: number): number {
58
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 {
59
77
  const targetPercentage = this.getTargetPercentage(targetReps);
60
- return (targetPercentage / 100) * oneRepMax;
78
+ return (targetPercentage / 100) * effective1RM;
61
79
  }
62
80
 
63
81
  /**
@@ -1,5 +1,6 @@
1
1
  import type { UUID } from 'crypto';
2
2
  import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
3
+ import type { WorkoutMuscleGroupVolumeCTO } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
3
4
  import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
4
5
  import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
5
6
  import type { WorkoutMesocycle } from '../../../documents/workout/WorkoutMesocycle.js';
@@ -7,6 +8,7 @@ import type { WorkoutMicrocycle } from '../../../documents/workout/WorkoutMicroc
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,14 +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;
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;
28
40
  readonly exerciseMap: Map<UUID, WorkoutExercise>;
29
41
  readonly equipmentMap: Map<UUID, WorkoutEquipmentType>;
30
42
  readonly sessionMap: Map<UUID, WorkoutSession>;
31
43
  readonly sessionExerciseMap: Map<UUID, WorkoutSessionExercise>;
44
+ readonly setMap: Map<UUID, WorkoutSet>;
32
45
  readonly microcyclesToCreate: WorkoutMicrocycle[];
33
46
  /**
34
47
  * All microcycles for this mesocycle in chronological order.
@@ -53,11 +66,16 @@ export default class WorkoutMesocyclePlanContext {
53
66
  * ended up in.
54
67
  */
55
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>;
56
74
  exerciseIdToSessionIndex: Map<UUID, number> | undefined;
57
75
  /**
58
76
  * Creates a new workout mesocycle planning context.
59
77
  */
60
- constructor(mesocycle: WorkoutMesocycle, exerciseCTOs: WorkoutExerciseCTO[], existingMicrocycles?: WorkoutMicrocycle[], existingSessions?: WorkoutSession[], existingSessionExercises?: WorkoutSessionExercise[], existingSets?: WorkoutSet[]);
78
+ constructor(mesocycle: WorkoutMesocycle, exerciseCTOs: WorkoutExerciseCTO[], volumeCTOs?: WorkoutMuscleGroupVolumeCTO[], existingMicrocycles?: WorkoutMicrocycle[], existingSessions?: WorkoutSession[], existingSessionExercises?: WorkoutSessionExercise[], existingSets?: WorkoutSet[]);
61
79
  /**
62
80
  * Adds a microcycle to the list of microcycles to create and the overall chronological list.
63
81
  */
@@ -70,6 +88,10 @@ export default class WorkoutMesocyclePlanContext {
70
88
  * Adds a session exercise to the context and updates internal maps.
71
89
  */
72
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;
73
95
  /**
74
96
  * Stores the planned session -> exercises structure for the mesocycle and derives
75
97
  * the muscle-group-wide ordering used for set progression.
@@ -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,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AACtF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oDAAoD,CAAC;AAC/F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+CAA+C,CAAC;AACrF,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;IA2CrC,SAAS,EAAE,gBAAgB;IAE3B,mBAAmB,EAAE,iBAAiB,EAAE;IACxC,gBAAgB,EAAE,cAAc,EAAE;IAClC,wBAAwB,EAAE,sBAAsB,EAAE;IAClD,YAAY,EAAE,UAAU,EAAE;IA/CnC;;;OAGG;IACH,SAAgB,oBAAoB,KAAK;IAEzC,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,0BAA0B,EAAE,kBAAkB,EAAE,EAAE,GAAG,SAAS,CAAC;IACtE;;;;;;OAMG;IACI,4BAA4B,EAAE,GAAG,CAAC,IAAI,EAAE,kBAAkB,EAAE,CAAC,GAAG,SAAS,CAAC;IAC1E,wBAAwB,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAE/D;;OAEG;gBAEM,SAAS,EAAE,gBAAgB,EAClC,YAAY,EAAE,kBAAkB,EAAE,EAC3B,mBAAmB,GAAE,iBAAiB,EAAO,EAC7C,gBAAgB,GAAE,cAAc,EAAO,EACvC,wBAAwB,GAAE,sBAAsB,EAAO,EACvD,YAAY,GAAE,UAAU,EAAO;IAmBxC;;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,6BAA6B,CAAC,0BAA0B,EAAE,kBAAkB,EAAE,EAAE,GAAG,IAAI;IAK9F;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;CA0B5B"}
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,14 +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;
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;
19
31
  exerciseMap;
20
32
  equipmentMap;
21
33
  sessionMap;
22
34
  sessionExerciseMap;
35
+ setMap;
23
36
  microcyclesToCreate = [];
24
37
  /**
25
38
  * All microcycles for this mesocycle in chronological order.
@@ -44,22 +57,47 @@ export default class WorkoutMesocyclePlanContext {
44
57
  * ended up in.
45
58
  */
46
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;
47
65
  exerciseIdToSessionIndex;
48
66
  /**
49
67
  * Creates a new workout mesocycle planning context.
50
68
  */
51
- constructor(mesocycle, exerciseCTOs, existingMicrocycles = [], existingSessions = [], existingSessionExercises = [], existingSets = []) {
69
+ constructor(mesocycle, exerciseCTOs, volumeCTOs = [], existingMicrocycles = [], existingSessions = [], existingSessionExercises = [], existingSets = []) {
52
70
  this.mesocycle = mesocycle;
53
71
  this.existingMicrocycles = existingMicrocycles;
54
72
  this.existingSessions = existingSessions;
55
73
  this.existingSessionExercises = existingSessionExercises;
56
74
  this.existingSets = existingSets;
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)]));
57
94
  // Derive exercise map from CTOs
58
95
  this.exerciseMap = new Map(exerciseCTOs.map((cto) => [cto._id, cto]));
59
96
  // Derive equipment map from CTOs
60
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,6 +124,15 @@ 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.
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutMesocyclePlanContext.js","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkoutMesocyclePlanContext.js","sourceRoot":"","sources":["../../../../src/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,gDAAgD,CAAC;AAM3E,OAAO,4BAA4B,MAAM,wDAAwD,CAAC;AAElG;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,OAAO,2BAA2B;IA6DrC;IAGA;IACA;IACA;IACA;IAlET;;;OAGG;IACa,kBAAkB,CAAS;IAE3C;;;OAGG;IACa,mBAAmB,CAAS;IAE5C;;;OAGG;IACa,UAAU,CAAU;IAEpB,WAAW,CAA6B;IACxC,YAAY,CAAkC;IAC9C,UAAU,CAA4B;IACtC,kBAAkB,CAAoC;IACtD,MAAM,CAAwB;IAE9B,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,0BAA0B,CAAqC;IACtE;;;;;;OAMG;IACI,4BAA4B,CAA8C;IACjF;;;OAGG;IACI,8BAA8B,CAA2C;IACzE,wBAAwB,CAAgC;IAE/D;;OAEG;IACH,YACS,SAA2B,EAClC,YAAkC,EAClC,aAA4C,EAAE,EACvC,sBAA2C,EAAE,EAC7C,mBAAqC,EAAE,EACvC,2BAAqD,EAAE,EACvD,eAA6B,EAAE;QAN/B,cAAS,GAAT,SAAS,CAAkB;QAG3B,wBAAmB,GAAnB,mBAAmB,CAA0B;QAC7C,qBAAgB,GAAhB,gBAAgB,CAAuB;QACvC,6BAAwB,GAAxB,wBAAwB,CAA+B;QACvD,iBAAY,GAAZ,YAAY,CAAmB;QAEtC,8CAA8C;QAC9C,MAAM,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;QAChC,IAAI,SAAS,KAAK,SAAS,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QAC1B,CAAC;aAAM,IAAI,SAAS,KAAK,SAAS,CAAC,eAAe,EAAE,CAAC;YACnD,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,kBAAkB,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QAC1B,CAAC;QAED,uDAAuD;QACvD,IAAI,CAAC,8BAA8B,GAAG,IAAI,GAAG,CAC3C,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,4BAA4B,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,CAC9F,CAAC;QAEF,gCAAgC;QAChC,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QAEtE,iCAAiC;QACjC,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CACzB,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CACtE,CAAC;QAEF,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;QACnF,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAE3D,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;;OAEG;IACI,OAAO,CAAC,IAAkB;QAC/B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,6BAA6B,CAAC,0BAAkD;QACrF,IAAI,CAAC,0BAA0B,GAAG,0BAA0B,CAAC;QAC7D,IAAI,CAAC,mBAAmB,CAAC,0BAA0B,CAAC,CAAC;IACvD,CAAC;IAED;;;;;;;;OAQG;IACK,mBAAmB,CAAC,cAAsC;QAChE,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA8B,CAAC;QACnE,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAgB,CAAC;QAEzD,KAAK,IAAI,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC;YAChF,MAAM,WAAW,GAAG,cAAc,CAAC,YAAY,CAAC,CAAC;YACjD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;gBAC9B,wBAAwB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;gBAEpD,MAAM,oBAAoB,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;gBACxD,IAAI,CAAC,oBAAoB,EAAE,CAAC;oBAC1B,MAAM,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,YAAY,8BAA8B,CAAC,CAAC;gBAC1F,CAAC;gBAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;gBAChE,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACrB,CAAC;qBAAM,CAAC;oBACN,oBAAoB,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxD,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,4BAA4B,GAAG,oBAAoB,CAAC;QACzD,IAAI,CAAC,wBAAwB,GAAG,wBAAwB,CAAC;IAC3D,CAAC;CACF"}
@@ -1,12 +1,16 @@
1
1
  import type { UUID } from 'crypto';
2
2
  import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
3
+ import type { WorkoutMuscleGroupVolumeCTO } from '../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
3
4
  import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
4
5
  import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
5
6
  import type { WorkoutMesocycle } from '../../../documents/workout/WorkoutMesocycle.js';
7
+ import { CycleType } from '../../../documents/workout/WorkoutMesocycle.js';
6
8
  import type { WorkoutMicrocycle } from '../../../documents/workout/WorkoutMicrocycle.js';
7
9
  import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
8
10
  import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
9
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';
10
14
 
11
15
  /**
12
16
  * Central shared context for generating or updating a {@link WorkoutMesocycle}.
@@ -17,15 +21,28 @@ import type { WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
17
21
  */
18
22
  export default class WorkoutMesocyclePlanContext {
19
23
  /**
20
- * A constant for now that defines what the target RIR is for the first microcycle of every mesocycle,
21
- * 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.
22
26
  */
23
- 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;
24
40
 
25
41
  public readonly exerciseMap: Map<UUID, WorkoutExercise>;
26
42
  public readonly equipmentMap: Map<UUID, WorkoutEquipmentType>;
27
43
  public readonly sessionMap: Map<UUID, WorkoutSession>;
28
44
  public readonly sessionExerciseMap: Map<UUID, WorkoutSessionExercise>;
45
+ public readonly setMap: Map<UUID, WorkoutSet>;
29
46
 
30
47
  public readonly microcyclesToCreate: WorkoutMicrocycle[] = [];
31
48
  /**
@@ -52,6 +69,11 @@ export default class WorkoutMesocyclePlanContext {
52
69
  * ended up in.
53
70
  */
54
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>;
55
77
  public exerciseIdToSessionIndex: Map<UUID, number> | undefined;
56
78
 
57
79
  /**
@@ -60,11 +82,33 @@ export default class WorkoutMesocyclePlanContext {
60
82
  constructor(
61
83
  public mesocycle: WorkoutMesocycle,
62
84
  exerciseCTOs: WorkoutExerciseCTO[],
85
+ volumeCTOs: WorkoutMuscleGroupVolumeCTO[] = [],
63
86
  public existingMicrocycles: WorkoutMicrocycle[] = [],
64
87
  public existingSessions: WorkoutSession[] = [],
65
88
  public existingSessionExercises: WorkoutSessionExercise[] = [],
66
89
  public existingSets: WorkoutSet[] = []
67
90
  ) {
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
+
68
112
  // Derive exercise map from CTOs
69
113
  this.exerciseMap = new Map(exerciseCTOs.map((cto) => [cto._id, cto]));
70
114
 
@@ -75,6 +119,7 @@ export default class WorkoutMesocyclePlanContext {
75
119
 
76
120
  this.sessionMap = new Map(existingSessions.map((s) => [s._id, s]));
77
121
  this.sessionExerciseMap = new Map(existingSessionExercises.map((s) => [s._id, s]));
122
+ this.setMap = new Map(existingSets.map((s) => [s._id, s]));
78
123
 
79
124
  const existingMicrocyclesForMesocycle = existingMicrocycles
80
125
  .filter((m) => m.workoutMesocycleId === mesocycle._id)
@@ -106,6 +151,16 @@ export default class WorkoutMesocyclePlanContext {
106
151
  this.sessionExerciseMap.set(sessionExercise._id, sessionExercise);
107
152
  }
108
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
+
109
164
  /**
110
165
  * Stores the planned session -> exercises structure for the mesocycle and derives
111
166
  * the muscle-group-wide ordering used for set progression.