@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,6 +1,7 @@
1
1
  import { ExerciseProgressionType, ExerciseRepRange } from '../../../documents/workout/WorkoutExercise.js';
2
2
  import WorkoutEquipmentTypeService from '../EquipmentType/WorkoutEquipmentTypeService.js';
3
3
  import WorkoutExerciseCalibrationService from '../ExerciseCalibration/WorkoutExerciseCalibrationService.js';
4
+ import WorkoutSessionExerciseService from '../SessionExercise/WorkoutSessionExerciseService.js';
4
5
  import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
5
6
  /**
6
7
  * A service for handling operations related to {@link WorkoutExercise}s.
@@ -41,6 +42,10 @@ export default class WorkoutExerciseService {
41
42
  * This method applies either rep-based or load-based progression depending on the exercise's
42
43
  * preferred progression type, then rounds the weight to available equipment options.
43
44
  *
45
+ * When a `previousFirstSet` is provided, autoregulation adjusts progression based on the
46
+ * surplus between planned and actual performance. Without it, the calibration-based formula
47
+ * is used as a baseline.
48
+ *
44
49
  * Rep progression: The weight is calculated based on reps at microcycle 0, and reps increase
45
50
  * by 2 per microcycle to reach max reps at the final accumulation microcycle (ideally),
46
51
  * or drop back down and increase weight by 2%.
@@ -49,7 +54,7 @@ export default class WorkoutExerciseService {
49
54
  * If weight can't be increased, adds 2 reps instead.
50
55
  */
51
56
  static calculateTargetRepsAndWeightForFirstSet(params) {
52
- const { exercise, calibration, equipment, microcycleIndex, firstMicrocycleRir } = params;
57
+ const { exercise, calibration, equipment, microcycleIndex, firstMicrocycleRir, previousFirstSet } = params;
53
58
  // Validate equipment has weight options
54
59
  if (!equipment.weightOptions || equipment.weightOptions.length === 0) {
55
60
  throw new Error(`No weight options defined for equipment type ${equipment._id}, ${equipment.title}`);
@@ -57,6 +62,134 @@ export default class WorkoutExerciseService {
57
62
  // Get rep range for this exercise
58
63
  const repRange = this.getRepRangeValues(exercise.repRange);
59
64
  const repRangeMidpoint = Math.floor((repRange.min + repRange.max) / 2);
65
+ // If we have a previous set with complete data, use autoregulation
66
+ if (previousFirstSet && this.hasCompleteAutoRegulationData(previousFirstSet)) {
67
+ return this.calculateAutoRegulatedTargets({
68
+ exercise,
69
+ equipment,
70
+ previousFirstSet,
71
+ repRange
72
+ });
73
+ }
74
+ // Calibration-based formula (no previous set available)
75
+ return this.calculateCalibrationBasedTargets({
76
+ exercise,
77
+ calibration,
78
+ equipment,
79
+ microcycleIndex,
80
+ firstMicrocycleRir,
81
+ repRange,
82
+ repRangeMidpoint
83
+ });
84
+ }
85
+ /**
86
+ * Calculates targets using auto-regulation based on actual performance from a previous set.
87
+ */
88
+ static calculateAutoRegulatedTargets(params) {
89
+ const { exercise, equipment, previousFirstSet, repRange } = params;
90
+ const surplus = WorkoutSessionExerciseService.calculateSetSurplus(previousFirstSet.actualReps, previousFirstSet.plannedReps, previousFirstSet.rir, previousFirstSet.plannedRir);
91
+ if (exercise.preferredProgressionType === ExerciseProgressionType.Rep) {
92
+ return this.calculateAutoRegulatedRepTargets(previousFirstSet, surplus, repRange, equipment);
93
+ }
94
+ return this.calculateAutoRegulatedLoadTargets(previousFirstSet, surplus, repRange, equipment);
95
+ }
96
+ /**
97
+ * Auto-regulated rep progression. Weight stays the same, reps adjust based on surplus.
98
+ *
99
+ * | Surplus | Action |
100
+ * |---:|---|
101
+ * | >= 3 | Accelerate: actualReps + 2 (progress from actual, not planned) |
102
+ * | 0 to 2 | Normal: plannedReps + 2 |
103
+ * | -1 to -2 | Hold: plannedReps (don't add reps) |
104
+ * | <= -3 | Regress: actualReps (use actual as new baseline) |
105
+ */
106
+ static calculateAutoRegulatedRepTargets(previousSet, surplus, repRange, equipment) {
107
+ let targetReps;
108
+ let targetWeight = previousSet.plannedWeight;
109
+ if (surplus >= 3) {
110
+ targetReps = previousSet.actualReps + 2;
111
+ }
112
+ else if (surplus >= 0) {
113
+ targetReps = previousSet.plannedReps + 2;
114
+ }
115
+ else if (surplus >= -2) {
116
+ targetReps = previousSet.plannedReps;
117
+ }
118
+ else {
119
+ targetReps = previousSet.actualReps;
120
+ }
121
+ // Clamp to rep range floor (never target below min, even if actual was 0)
122
+ targetReps = Math.max(targetReps, repRange.min);
123
+ // Handle rep range ceiling: if target exceeds max, reset and bump weight
124
+ if (targetReps > repRange.max) {
125
+ targetReps = repRange.max;
126
+ const nextWeight = this.findNextTwoPercentWeight(targetWeight, equipment);
127
+ if (nextWeight !== null) {
128
+ targetWeight = nextWeight;
129
+ }
130
+ }
131
+ // Round weight to equipment
132
+ const roundedWeight = WorkoutEquipmentTypeService.findNearestWeight(equipment, targetWeight, 'prefer-down');
133
+ if (roundedWeight !== null) {
134
+ targetWeight = roundedWeight;
135
+ }
136
+ return { targetWeight, targetReps };
137
+ }
138
+ /**
139
+ * Auto-regulated load progression. Reps stay at rep range max, weight adjusts based on surplus.
140
+ *
141
+ * | Surplus | Action |
142
+ * |---:|---|
143
+ * | >= 2 | Accelerate: increase weight by ~4% |
144
+ * | 0 to 1 | Normal: increase weight by 2% |
145
+ * | -1 to -2 | Hold weight (no increase) |
146
+ * | <= -3 | Reduce weight by minimum equipment increment |
147
+ */
148
+ static calculateAutoRegulatedLoadTargets(previousSet, surplus, repRange, equipment) {
149
+ const targetReps = repRange.max;
150
+ let targetWeight = previousSet.plannedWeight;
151
+ if (surplus >= 2) {
152
+ // Accelerate: increase by ~4%
153
+ const fourPercentIncrease = targetWeight * 1.04;
154
+ const nextWeight = WorkoutEquipmentTypeService.findNearestWeight(equipment, fourPercentIncrease, 'up');
155
+ if (nextWeight !== null) {
156
+ targetWeight = nextWeight;
157
+ }
158
+ }
159
+ else if (surplus >= 0) {
160
+ // Normal: increase by 2%
161
+ const nextWeight = this.findNextTwoPercentWeight(targetWeight, equipment);
162
+ if (nextWeight !== null) {
163
+ targetWeight = nextWeight;
164
+ }
165
+ }
166
+ else if (surplus >= -2) {
167
+ // Hold weight - no change
168
+ }
169
+ else {
170
+ // Reduce by minimum equipment increment
171
+ const reducedWeight = WorkoutEquipmentTypeService.findNearestWeight(equipment, targetWeight - 0.01, 'down');
172
+ if (reducedWeight !== null) {
173
+ targetWeight = reducedWeight;
174
+ }
175
+ }
176
+ return { targetWeight, targetReps };
177
+ }
178
+ /**
179
+ * Checks whether a set has all the data needed for autoregulation calculations.
180
+ */
181
+ static hasCompleteAutoRegulationData(set) {
182
+ return (set.actualReps != null &&
183
+ set.plannedReps != null &&
184
+ set.rir != null &&
185
+ set.plannedRir != null &&
186
+ set.plannedWeight != null);
187
+ }
188
+ /**
189
+ * Calculates targets using the calibration-based formula (original behavior).
190
+ */
191
+ static calculateCalibrationBasedTargets(params) {
192
+ const { exercise, calibration, equipment, microcycleIndex, firstMicrocycleRir, repRange, repRangeMidpoint } = params;
60
193
  // For rep progression, calculate weight based on reps at microcycle 0
61
194
  // For load progression, use max reps
62
195
  let baseRepsForWeight;
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseService.js","sourceRoot":"","sources":["../../../../src/services/workout/Exercise/WorkoutExerciseService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"WorkoutExerciseService.js","sourceRoot":"","sources":["../../../../src/services/workout/Exercise/WorkoutExerciseService.ts"],"names":[],"mappings":"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"}
@@ -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;
@@ -1,3 +1,5 @@
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';
2
4
  /**
3
5
  * A service for handling operations related to {@link WorkoutExerciseCalibration}s.
@@ -18,6 +20,21 @@ export default class WorkoutExerciseCalibrationService {
18
20
  * @param reps The number of reps performed.
19
21
  */
20
22
  static get1RMRaw(weight: number, reps: number): number;
23
+ /**
24
+ * Returns the NASM 1RM formula as a MongoDB aggregation expression.
25
+ * This is the MongoDB-equivalent of {@link get1RMRaw} and must be kept
26
+ * in sync with it.
27
+ *
28
+ * @param weightField The MongoDB field reference for weight (e.g. `'$weight'`).
29
+ * @param repsField The MongoDB field reference for reps (e.g. `'$reps'`).
30
+ */
31
+ static get1RMMongoExpr(weightField: string, repsField: string): {
32
+ $add: (string | {
33
+ $divide: (number | {
34
+ $multiply: string[];
35
+ })[];
36
+ })[];
37
+ };
21
38
  /**
22
39
  * Calculates the target weight for a set based on target reps and 1RM.
23
40
  *
@@ -28,12 +45,41 @@ export default class WorkoutExerciseCalibrationService {
28
45
  * @param targetReps The target number of reps.
29
46
  */
30
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;
63
+ /**
64
+ * Generates auto-calibrations from exercise CTOs whose best set 1RM exceeds
65
+ * their best calibration 1RM.
66
+ *
67
+ * The CTO already provides `bestCalibration` and `bestSet` per exercise, so
68
+ * this method just compares those two pre-computed values and creates new
69
+ * calibrations where the set wins.
70
+ *
71
+ * @param exerciseCTOs The exercise CTOs to evaluate.
72
+ * @param userId The user ID for the new calibrations.
73
+ * @param dateRecorded The date to use as dateRecorded for new calibrations.
74
+ */
75
+ static generateAutoCalibrations(exerciseCTOs: WorkoutExerciseCTO[], userId: UUID, dateRecorded: Date): WorkoutExerciseCalibration[];
31
76
  /**
32
77
  * Calculates the target percentage of 1RM for a given target rep count.
33
78
  *
34
- * Uses the formula: targetPercentage = 30 + ((targetReps - 5) * 2.2)
79
+ * Uses the formula: targetPercentage = 85 - ((targetReps - 5) * 2.2)
35
80
  *
36
- * This ensures training stays within the 30%-85% 1RM range (30 reps to 5 reps).
81
+ * This ensures training stays within the 85%-30% 1RM range (5 reps to 30 reps).
82
+ * Higher rep counts produce lower percentages of 1RM.
37
83
  *
38
84
  * @param targetReps The target number of reps.
39
85
  */
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseCalibrationService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AAE3G;;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;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAM3F;;;;;;;;OAQG;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"}
@@ -1,3 +1,6 @@
1
+ import { WorkoutExerciseCalibrationSchema } from '../../../documents/workout/WorkoutExerciseCalibration.js';
2
+ /** Divisor used in the NASM 1RM formula: 1RM = (weight × reps / 30.48) + weight */
3
+ const NASM_1RM_DIVISOR = 30.48;
1
4
  /**
2
5
  * A service for handling operations related to {@link WorkoutExerciseCalibration}s.
3
6
  */
@@ -19,7 +22,20 @@ export default class WorkoutExerciseCalibrationService {
19
22
  * @param reps The number of reps performed.
20
23
  */
21
24
  static get1RMRaw(weight, reps) {
22
- return (weight * reps) / 30.48 + weight;
25
+ return (weight * reps) / NASM_1RM_DIVISOR + weight;
26
+ }
27
+ /**
28
+ * Returns the NASM 1RM formula as a MongoDB aggregation expression.
29
+ * This is the MongoDB-equivalent of {@link get1RMRaw} and must be kept
30
+ * in sync with it.
31
+ *
32
+ * @param weightField The MongoDB field reference for weight (e.g. `'$weight'`).
33
+ * @param repsField The MongoDB field reference for reps (e.g. `'$reps'`).
34
+ */
35
+ static get1RMMongoExpr(weightField, repsField) {
36
+ return {
37
+ $add: [{ $divide: [{ $multiply: [weightField, repsField] }, NASM_1RM_DIVISOR] }, weightField]
38
+ };
23
39
  }
24
40
  /**
25
41
  * Calculates the target weight for a set based on target reps and 1RM.
@@ -32,20 +48,72 @@ export default class WorkoutExerciseCalibrationService {
32
48
  */
33
49
  static getTargetWeight(calibration, targetReps) {
34
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) {
35
68
  const targetPercentage = this.getTargetPercentage(targetReps);
36
- return (targetPercentage / 100) * oneRepMax;
69
+ return (targetPercentage / 100) * effective1RM;
70
+ }
71
+ /**
72
+ * Generates auto-calibrations from exercise CTOs whose best set 1RM exceeds
73
+ * their best calibration 1RM.
74
+ *
75
+ * The CTO already provides `bestCalibration` and `bestSet` per exercise, so
76
+ * this method just compares those two pre-computed values and creates new
77
+ * calibrations where the set wins.
78
+ *
79
+ * @param exerciseCTOs The exercise CTOs to evaluate.
80
+ * @param userId The user ID for the new calibrations.
81
+ * @param dateRecorded The date to use as dateRecorded for new calibrations.
82
+ */
83
+ static generateAutoCalibrations(exerciseCTOs, userId, dateRecorded) {
84
+ const newCalibrations = [];
85
+ for (const cto of exerciseCTOs) {
86
+ const { bestSet, bestCalibration } = cto;
87
+ if (!bestSet?.actualWeight || !bestSet.actualReps || bestSet.actualReps <= 0)
88
+ continue;
89
+ const set1RM = this.get1RMRaw(bestSet.actualWeight, bestSet.actualReps);
90
+ const cal1RM = bestCalibration ? this.get1RM(bestCalibration) : 0;
91
+ if (set1RM > cal1RM) {
92
+ newCalibrations.push(WorkoutExerciseCalibrationSchema.parse({
93
+ userId,
94
+ workoutExerciseId: cto._id,
95
+ weight: bestSet.actualWeight,
96
+ reps: bestSet.actualReps,
97
+ exerciseProperties: bestSet.exerciseProperties,
98
+ dateRecorded,
99
+ associatedWorkoutSetId: bestSet._id
100
+ }));
101
+ }
102
+ }
103
+ return newCalibrations;
37
104
  }
38
105
  /**
39
106
  * Calculates the target percentage of 1RM for a given target rep count.
40
107
  *
41
- * Uses the formula: targetPercentage = 30 + ((targetReps - 5) * 2.2)
108
+ * Uses the formula: targetPercentage = 85 - ((targetReps - 5) * 2.2)
42
109
  *
43
- * This ensures training stays within the 30%-85% 1RM range (30 reps to 5 reps).
110
+ * This ensures training stays within the 85%-30% 1RM range (5 reps to 30 reps).
111
+ * Higher rep counts produce lower percentages of 1RM.
44
112
  *
45
113
  * @param targetReps The target number of reps.
46
114
  */
47
115
  static getTargetPercentage(targetReps) {
48
- return 30 + (targetReps - 5) * 2.2;
116
+ return 85 - (targetReps - 5) * 2.2;
49
117
  }
50
118
  }
51
119
  //# sourceMappingURL=WorkoutExerciseCalibrationService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkoutExerciseCalibrationService.js","sourceRoot":"","sources":["../../../../src/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts"],"names":[],"mappings":"AAEA;;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,KAAK,GAAG,MAAM,CAAC;IAC1C,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;;;;;;;;OAQG;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"}