@aneuhold/core-ts-db-lib 4.0.3 → 4.1.0

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 (151) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/lib/browser.d.ts +36 -4
  3. package/lib/browser.d.ts.map +1 -1
  4. package/lib/browser.js +23 -2
  5. package/lib/browser.js.map +1 -1
  6. package/lib/browser.ts +126 -4
  7. package/lib/documents/BaseDocument.d.ts +8 -0
  8. package/lib/documents/BaseDocument.d.ts.map +1 -1
  9. package/lib/documents/BaseDocument.js +10 -0
  10. package/lib/documents/BaseDocument.js.map +1 -1
  11. package/lib/documents/BaseDocument.ts +18 -0
  12. package/lib/documents/common/User.d.ts +1 -0
  13. package/lib/documents/common/User.d.ts.map +1 -1
  14. package/lib/documents/common/User.js +4 -2
  15. package/lib/documents/common/User.js.map +1 -1
  16. package/lib/documents/common/User.ts +4 -2
  17. package/lib/documents/dashboard/NonogramKatanaItem.d.ts +1 -1
  18. package/lib/documents/dashboard/NonogramKatanaItem.js +1 -1
  19. package/lib/documents/dashboard/NonogramKatanaItem.js.map +1 -1
  20. package/lib/documents/dashboard/NonogramKatanaItem.ts +1 -1
  21. package/lib/documents/dashboard/NonogramKatanaUpgrade.d.ts +1 -1
  22. package/lib/documents/dashboard/NonogramKatanaUpgrade.js +1 -1
  23. package/lib/documents/dashboard/NonogramKatanaUpgrade.js.map +1 -1
  24. package/lib/documents/dashboard/NonogramKatanaUpgrade.ts +1 -1
  25. package/lib/documents/dashboard/Task.d.ts +4 -4
  26. package/lib/documents/dashboard/UserConfig.d.ts +4 -4
  27. package/lib/documents/workout/README.md +557 -0
  28. package/lib/documents/workout/WorkoutEquipmentType.d.ts +22 -0
  29. package/lib/documents/workout/WorkoutEquipmentType.d.ts.map +1 -0
  30. package/lib/documents/workout/WorkoutEquipmentType.js +31 -0
  31. package/lib/documents/workout/WorkoutEquipmentType.js.map +1 -0
  32. package/lib/documents/workout/WorkoutEquipmentType.ts +40 -0
  33. package/lib/documents/workout/WorkoutExercise.d.ts +82 -0
  34. package/lib/documents/workout/WorkoutExercise.d.ts.map +1 -0
  35. package/lib/documents/workout/WorkoutExercise.js +124 -0
  36. package/lib/documents/workout/WorkoutExercise.js.map +1 -0
  37. package/lib/documents/workout/WorkoutExercise.ts +143 -0
  38. package/lib/documents/workout/WorkoutExerciseCalibration.d.ts +43 -0
  39. package/lib/documents/workout/WorkoutExerciseCalibration.d.ts.map +1 -0
  40. package/lib/documents/workout/WorkoutExerciseCalibration.js +45 -0
  41. package/lib/documents/workout/WorkoutExerciseCalibration.js.map +1 -0
  42. package/lib/documents/workout/WorkoutExerciseCalibration.ts +74 -0
  43. package/lib/documents/workout/WorkoutMesocycle.d.ts +49 -0
  44. package/lib/documents/workout/WorkoutMesocycle.d.ts.map +1 -0
  45. package/lib/documents/workout/WorkoutMesocycle.js +84 -0
  46. package/lib/documents/workout/WorkoutMesocycle.js.map +1 -0
  47. package/lib/documents/workout/WorkoutMesocycle.ts +102 -0
  48. package/lib/documents/workout/WorkoutMicrocycle.d.ts +27 -0
  49. package/lib/documents/workout/WorkoutMicrocycle.d.ts.map +1 -0
  50. package/lib/documents/workout/WorkoutMicrocycle.js +42 -0
  51. package/lib/documents/workout/WorkoutMicrocycle.js.map +1 -0
  52. package/lib/documents/workout/WorkoutMicrocycle.ts +55 -0
  53. package/lib/documents/workout/WorkoutMuscleGroup.d.ts +22 -0
  54. package/lib/documents/workout/WorkoutMuscleGroup.d.ts.map +1 -0
  55. package/lib/documents/workout/WorkoutMuscleGroup.js +25 -0
  56. package/lib/documents/workout/WorkoutMuscleGroup.js.map +1 -0
  57. package/lib/documents/workout/WorkoutMuscleGroup.ts +34 -0
  58. package/lib/documents/workout/WorkoutSession.d.ts +39 -0
  59. package/lib/documents/workout/WorkoutSession.d.ts.map +1 -0
  60. package/lib/documents/workout/WorkoutSession.js +66 -0
  61. package/lib/documents/workout/WorkoutSession.js.map +1 -0
  62. package/lib/documents/workout/WorkoutSession.ts +79 -0
  63. package/lib/documents/workout/WorkoutSessionExercise.d.ts +39 -0
  64. package/lib/documents/workout/WorkoutSessionExercise.d.ts.map +1 -0
  65. package/lib/documents/workout/WorkoutSessionExercise.js +66 -0
  66. package/lib/documents/workout/WorkoutSessionExercise.js.map +1 -0
  67. package/lib/documents/workout/WorkoutSessionExercise.ts +79 -0
  68. package/lib/documents/workout/WorkoutSet.d.ts +41 -0
  69. package/lib/documents/workout/WorkoutSet.d.ts.map +1 -0
  70. package/lib/documents/workout/WorkoutSet.js +69 -0
  71. package/lib/documents/workout/WorkoutSet.js.map +1 -0
  72. package/lib/documents/workout/WorkoutSet.ts +90 -0
  73. package/lib/embedded-types/dashboard/task/FilterSettings.d.ts +6 -6
  74. package/lib/embedded-types/dashboard/task/FilterSettings.d.ts.map +1 -1
  75. package/lib/embedded-types/dashboard/task/FilterSettings.js +4 -2
  76. package/lib/embedded-types/dashboard/task/FilterSettings.js.map +1 -1
  77. package/lib/embedded-types/dashboard/task/FilterSettings.ts +5 -3
  78. package/lib/embedded-types/dashboard/task/SortSettings.d.ts +6 -6
  79. package/lib/embedded-types/dashboard/task/SortSettings.js +1 -1
  80. package/lib/embedded-types/dashboard/task/SortSettings.js.map +1 -1
  81. package/lib/embedded-types/dashboard/task/SortSettings.ts +1 -1
  82. package/lib/embedded-types/workout/Fatigue.d.ts +16 -0
  83. package/lib/embedded-types/workout/Fatigue.d.ts.map +1 -0
  84. package/lib/embedded-types/workout/Fatigue.js +34 -0
  85. package/lib/embedded-types/workout/Fatigue.js.map +1 -0
  86. package/lib/embedded-types/workout/Fatigue.ts +41 -0
  87. package/lib/embedded-types/workout/Rsm.d.ts +17 -0
  88. package/lib/embedded-types/workout/Rsm.d.ts.map +1 -0
  89. package/lib/embedded-types/workout/Rsm.js +34 -0
  90. package/lib/embedded-types/workout/Rsm.js.map +1 -0
  91. package/lib/embedded-types/workout/Rsm.ts +42 -0
  92. package/lib/services/DocumentService.d.ts +19 -0
  93. package/lib/services/DocumentService.d.ts.map +1 -1
  94. package/lib/services/DocumentService.js.map +1 -1
  95. package/lib/services/DocumentService.ts +20 -0
  96. package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.d.ts +35 -0
  97. package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.d.ts.map +1 -0
  98. package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.js +74 -0
  99. package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.js.map +1 -0
  100. package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.ts +90 -0
  101. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +55 -0
  102. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -0
  103. package/lib/services/workout/Exercise/WorkoutExerciseService.js +133 -0
  104. package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -0
  105. package/lib/services/workout/Exercise/WorkoutExerciseService.ts +173 -0
  106. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +35 -0
  107. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -0
  108. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +42 -0
  109. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -0
  110. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +45 -0
  111. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +90 -0
  112. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -0
  113. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +131 -0
  114. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -0
  115. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +159 -0
  116. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +52 -0
  117. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -0
  118. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +180 -0
  119. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -0
  120. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +274 -0
  121. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts +28 -0
  122. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts.map +1 -0
  123. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js +172 -0
  124. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js.map +1 -0
  125. package/lib/services/workout/Microcycle/WorkoutMicrocycleService.ts +236 -0
  126. package/lib/services/workout/Session/WorkoutSessionService.d.ts +49 -0
  127. package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -0
  128. package/lib/services/workout/Session/WorkoutSessionService.js +95 -0
  129. package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -0
  130. package/lib/services/workout/Session/WorkoutSessionService.ts +136 -0
  131. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +45 -0
  132. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -0
  133. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +69 -0
  134. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -0
  135. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +77 -0
  136. package/lib/services/workout/Set/WorkoutSetService.d.ts +36 -0
  137. package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -0
  138. package/lib/services/workout/Set/WorkoutSetService.js +90 -0
  139. package/lib/services/workout/Set/WorkoutSetService.js.map +1 -0
  140. package/lib/services/workout/Set/WorkoutSetService.ts +153 -0
  141. package/lib/services/workout/util/SFR/WorkoutSFRService.d.ts +29 -0
  142. package/lib/services/workout/util/SFR/WorkoutSFRService.d.ts.map +1 -0
  143. package/lib/services/workout/util/SFR/WorkoutSFRService.js +50 -0
  144. package/lib/services/workout/util/SFR/WorkoutSFRService.js.map +1 -0
  145. package/lib/services/workout/util/SFR/WorkoutSFRService.ts +61 -0
  146. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +48 -0
  147. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -0
  148. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +261 -0
  149. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -0
  150. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +339 -0
  151. package/package.json +5 -4
@@ -0,0 +1,274 @@
1
+ import { DateService } from '@aneuhold/core-ts-lib';
2
+ import type { UUID } from 'crypto';
3
+ import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
4
+ import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
5
+ import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
6
+ import { CycleType, type WorkoutMesocycle } from '../../../documents/workout/WorkoutMesocycle.js';
7
+ import type { WorkoutMicrocycle } from '../../../documents/workout/WorkoutMicrocycle.js';
8
+ import { WorkoutMicrocycleSchema } from '../../../documents/workout/WorkoutMicrocycle.js';
9
+ import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
10
+ import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
11
+ import type { WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
12
+ import type { DocumentOperations } from '../../DocumentService.js';
13
+ import WorkoutMicrocycleService from '../Microcycle/WorkoutMicrocycleService.js';
14
+ import WorkoutMesocyclePlanContext from './WorkoutMesocyclePlanContext.js';
15
+
16
+ /**
17
+ * A service for handling operations related to {@link WorkoutMesocycle}s.
18
+ */
19
+ export default class WorkoutMesocycleService {
20
+ /**
21
+ * Generates or updates the workout plan for a mesocycle.
22
+ *
23
+ * This method supports incremental generation by only creating new microcycles that don't
24
+ * yet exist. It expects that the mesocycle's core parameters (planned session count,
25
+ * microcycle count, microcycle length) and exercise ordering have not changed since
26
+ * initial creation. If these parameters have changed, it is the responsibility of a
27
+ * higher-level service to convert the mesocycle to free-form mode before calling this method.
28
+ *
29
+ * The method will clean up any incomplete microcycles (where the last session is not
30
+ * complete) and regenerate from that point, unless the microcycle has already started
31
+ * (first session complete), in which case it will throw an error.
32
+ *
33
+ * @param mesocycle The mesocycle configuration.
34
+ * @param calibrations The calibration documents referenced by the mesocycle.
35
+ * @param exercises The exercise definitions for the calibrations.
36
+ * @param equipmentTypes The equipment types for weight increment calculations.
37
+ * @param existingMicrocycles Existing microcycle documents for this mesocycle.
38
+ * @param existingSessions Existing session documents.
39
+ * @param existingSessionExercises Existing session exercise documents.
40
+ * @param existingSets Existing set documents.
41
+ */
42
+ static generateOrUpdateMesocycle(
43
+ mesocycle: WorkoutMesocycle,
44
+ calibrations: WorkoutExerciseCalibration[],
45
+ exercises: WorkoutExercise[],
46
+ equipmentTypes: WorkoutEquipmentType[],
47
+ existingMicrocycles: WorkoutMicrocycle[] = [],
48
+ existingSessions: WorkoutSession[] = [],
49
+ existingSessionExercises: WorkoutSessionExercise[] = [],
50
+ existingSets: WorkoutSet[] = []
51
+ ): {
52
+ mesocycleUpdate?: Partial<WorkoutMesocycle>;
53
+ microcycles?: DocumentOperations<WorkoutMicrocycle>;
54
+ sessions?: DocumentOperations<WorkoutSession>;
55
+ sessionExercises?: DocumentOperations<WorkoutSessionExercise>;
56
+ sets?: DocumentOperations<WorkoutSet>;
57
+ } {
58
+ // Free-form mesocycles are intentionally not auto-planned. The user can still log workouts,
59
+ // but we avoid generating microcycles/sessions/sets because recommendations wouldn't be able
60
+ // to be done / make any sense.
61
+ if (mesocycle.cycleType === CycleType.FreeForm) {
62
+ return {};
63
+ }
64
+
65
+ // Clean up incomplete microcycles before creating context
66
+ const cleanupResult = this.cleanUpIncompleteMicrocycles(
67
+ mesocycle,
68
+ existingMicrocycles,
69
+ existingSessions,
70
+ existingSessionExercises,
71
+ existingSets
72
+ );
73
+
74
+ // Filter out documents that will be deleted
75
+ const cleanMicrocycles = existingMicrocycles.filter(
76
+ (m) => !cleanupResult.microcyclesToDelete.includes(m._id)
77
+ );
78
+ const cleanSessions = existingSessions.filter(
79
+ (s) => !cleanupResult.sessionsToDelete.includes(s._id)
80
+ );
81
+ const cleanSessionExercises = existingSessionExercises.filter(
82
+ (se) => !cleanupResult.sessionExercisesToDelete.includes(se._id)
83
+ );
84
+ const cleanSets = existingSets.filter((s) => !cleanupResult.setsToDelete.includes(s._id));
85
+
86
+ // Create planning context with clean data
87
+ const context = new WorkoutMesocyclePlanContext(
88
+ mesocycle,
89
+ calibrations,
90
+ exercises,
91
+ equipmentTypes,
92
+ cleanMicrocycles,
93
+ cleanSessions,
94
+ cleanSessionExercises,
95
+ cleanSets
96
+ );
97
+
98
+ // Distribute exercises across sessions once for the entire mesocycle plan.
99
+ // This session layout is expected to be stable across microcycles.
100
+ context.setPlannedSessionExercisePairs(
101
+ WorkoutMicrocycleService.distributeExercisesAcrossSessions(
102
+ mesocycle.plannedSessionCountPerMicrocycle,
103
+ context.calibrationMap,
104
+ context.exerciseMap
105
+ )
106
+ );
107
+
108
+ // Determine number of microcycles (default to 6 if not specified: 5 accumulation + 1 deload)
109
+ const totalMicrocycles = mesocycle.plannedMicrocycleCount ?? 6;
110
+ const deloadMicrocycleIndex = totalMicrocycles - 1;
111
+
112
+ // Determine starting point for generation
113
+ const startMicrocycleIndex = context.microcyclesInOrder.length;
114
+ let currentDate: Date;
115
+
116
+ if (startMicrocycleIndex === 0) {
117
+ // No existing microcycles, start from current date
118
+ currentDate = new Date();
119
+ } else {
120
+ // Continue from where the last existing microcycle ended
121
+ const lastExistingMicrocycle = context.microcyclesInOrder[startMicrocycleIndex - 1];
122
+ currentDate = new Date(lastExistingMicrocycle.endDate);
123
+ }
124
+
125
+ // Generate remaining microcycles
126
+ for (
127
+ let microcycleIndex = startMicrocycleIndex;
128
+ microcycleIndex < totalMicrocycles;
129
+ microcycleIndex++
130
+ ) {
131
+ const isDeloadMicrocycle = microcycleIndex === deloadMicrocycleIndex;
132
+
133
+ // Calculate RIR for this microcycle (4 -> 3 -> 2 -> 1 -> 0, capped at microcycle 5)
134
+ const rirForMicrocycle = Math.min(microcycleIndex, 4);
135
+ const targetRir = 4 - rirForMicrocycle;
136
+
137
+ // Create microcycle
138
+ const microcycle = WorkoutMicrocycleSchema.parse({
139
+ userId: mesocycle.userId,
140
+ workoutMesocycleId: mesocycle._id,
141
+ startDate: new Date(currentDate),
142
+ endDate: DateService.addDays(currentDate, mesocycle.plannedMicrocycleLengthInDays)
143
+ });
144
+ context.addMicrocycle(microcycle);
145
+
146
+ WorkoutMicrocycleService.generateSessionsForMicrocycle({
147
+ context,
148
+ microcycleIndex,
149
+ targetRir,
150
+ isDeloadMicrocycle
151
+ });
152
+
153
+ // Move to next microcycle
154
+ currentDate = new Date(microcycle.endDate);
155
+ }
156
+
157
+ return {
158
+ mesocycleUpdate: undefined,
159
+ microcycles: {
160
+ create: context.microcyclesToCreate,
161
+ update: [],
162
+ delete: cleanupResult.microcyclesToDelete
163
+ },
164
+ sessions: {
165
+ create: context.sessionsToCreate,
166
+ update: [],
167
+ delete: cleanupResult.sessionsToDelete
168
+ },
169
+ sessionExercises: {
170
+ create: context.sessionExercisesToCreate,
171
+ update: [],
172
+ delete: cleanupResult.sessionExercisesToDelete
173
+ },
174
+ sets: { create: context.setsToCreate, update: [], delete: cleanupResult.setsToDelete }
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Cleans up incomplete microcycles and their associated documents.
180
+ *
181
+ * Finds the first microcycle where the last session is not complete, validates that it
182
+ * hasn't started (first session incomplete), and returns IDs of all documents that should
183
+ * be deleted (microcycles from that point forward and all their associated data).
184
+ */
185
+ private static cleanUpIncompleteMicrocycles(
186
+ mesocycle: WorkoutMesocycle,
187
+ existingMicrocycles: WorkoutMicrocycle[],
188
+ existingSessions: WorkoutSession[],
189
+ existingSessionExercises: WorkoutSessionExercise[],
190
+ existingSets: WorkoutSet[]
191
+ ): {
192
+ microcyclesToDelete: UUID[];
193
+ sessionsToDelete: UUID[];
194
+ sessionExercisesToDelete: UUID[];
195
+ setsToDelete: UUID[];
196
+ } {
197
+ const microcyclesToDelete: UUID[] = [];
198
+ const sessionsToDelete: UUID[] = [];
199
+ const sessionExercisesToDelete: UUID[] = [];
200
+ const setsToDelete: UUID[] = [];
201
+
202
+ // Sort microcycles for this mesocycle
203
+ const microcyclesForMesocycle = existingMicrocycles.sort(
204
+ (a, b) => a.startDate.getTime() - b.startDate.getTime()
205
+ );
206
+
207
+ // Find first incomplete microcycle
208
+ let firstIncompleteMicrocycleIndex = -1;
209
+ for (let i = 0; i < microcyclesForMesocycle.length; i++) {
210
+ const microcycle = microcyclesForMesocycle[i];
211
+ if (microcycle.sessionOrder.length === 0) {
212
+ // Microcycle has no sessions, it's incomplete
213
+ firstIncompleteMicrocycleIndex = i;
214
+ break;
215
+ }
216
+
217
+ // Check if last session is complete
218
+ const lastSessionId = microcycle.sessionOrder[microcycle.sessionOrder.length - 1];
219
+ const lastSession = existingSessions.find((s) => s._id === lastSessionId);
220
+ if (!lastSession?.complete) {
221
+ firstIncompleteMicrocycleIndex = i;
222
+ break;
223
+ }
224
+ }
225
+
226
+ // If all microcycles are complete, nothing to clean up
227
+ if (firstIncompleteMicrocycleIndex === -1) {
228
+ return { microcyclesToDelete, sessionsToDelete, sessionExercisesToDelete, setsToDelete };
229
+ }
230
+
231
+ const firstIncompleteMicrocycle = microcyclesForMesocycle[firstIncompleteMicrocycleIndex];
232
+
233
+ // Check if the incomplete microcycle has started (first session complete)
234
+ if (firstIncompleteMicrocycle.sessionOrder.length > 0) {
235
+ const firstSessionId = firstIncompleteMicrocycle.sessionOrder[0];
236
+ const firstSession = existingSessions.find((s) => s._id === firstSessionId);
237
+ if (firstSession?.complete) {
238
+ throw new Error(
239
+ `Cannot generate new microcycles for mesocycle ${mesocycle._id}: ` +
240
+ `Microcycle at index ${firstIncompleteMicrocycleIndex} has started but is not complete. ` +
241
+ `All sessions in the current microcycle must be completed before generating new microcycles.`
242
+ );
243
+ }
244
+ }
245
+
246
+ // Collect IDs of incomplete microcycles (from firstIncompleteMicrocycleIndex onward)
247
+ const incompleteMicrocycles = microcyclesForMesocycle.slice(firstIncompleteMicrocycleIndex);
248
+ microcyclesToDelete.push(...incompleteMicrocycles.map((m) => m._id));
249
+
250
+ // Collect all sessions, session exercises, and sets associated with incomplete microcycles
251
+ const incompleteMicrocycleIds = new Set(incompleteMicrocycles.map((m) => m._id));
252
+ for (const session of existingSessions) {
253
+ if (session.workoutMicrocycleId && incompleteMicrocycleIds.has(session.workoutMicrocycleId)) {
254
+ sessionsToDelete.push(session._id);
255
+ }
256
+ }
257
+
258
+ const sessionIdsToDelete = new Set(sessionsToDelete);
259
+ for (const sessionExercise of existingSessionExercises) {
260
+ if (sessionIdsToDelete.has(sessionExercise.workoutSessionId)) {
261
+ sessionExercisesToDelete.push(sessionExercise._id);
262
+ }
263
+ }
264
+
265
+ const sessionExerciseIdsToDelete = new Set(sessionExercisesToDelete);
266
+ for (const set of existingSets) {
267
+ if (sessionExerciseIdsToDelete.has(set.workoutSessionExerciseId)) {
268
+ setsToDelete.push(set._id);
269
+ }
270
+ }
271
+
272
+ return { microcyclesToDelete, sessionsToDelete, sessionExercisesToDelete, setsToDelete };
273
+ }
274
+ }
@@ -0,0 +1,28 @@
1
+ import type { UUID } from 'crypto';
2
+ import { type WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
3
+ import type { CalibrationExercisePair, WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
4
+ import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
5
+ /**
6
+ * A service for handling operations related to {@link WorkoutMicrocycle}s.
7
+ */
8
+ export default class WorkoutMicrocycleService {
9
+ /**
10
+ * Generates sessions for a specific microcycle.
11
+ */
12
+ static generateSessionsForMicrocycle({ context, microcycleIndex, targetRir, isDeloadMicrocycle }: {
13
+ context: WorkoutMesocyclePlanContext;
14
+ microcycleIndex: number;
15
+ targetRir: number;
16
+ isDeloadMicrocycle: boolean;
17
+ }): void;
18
+ /**
19
+ * Distributes exercises across sessions within a microcycle, by putting them into a consecutive
20
+ * array of arrays structure. The embedded array is the list of exercises for that session.
21
+ *
22
+ * @param sessionCount The number of sessions to distribute exercises across.
23
+ * @param calibrationMap The map of calibration documents.
24
+ * @param exerciseMap The map of exercise documents.
25
+ */
26
+ static distributeExercisesAcrossSessions(sessionCount: number, calibrationMap: Map<UUID, WorkoutExerciseCalibration>, exerciseMap: Map<UUID, WorkoutExercise>): CalibrationExercisePair[][];
27
+ }
28
+ //# sourceMappingURL=WorkoutMicrocycleService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutMicrocycleService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Microcycle/WorkoutMicrocycleService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EACV,uBAAuB,EACvB,0BAA0B,EAC3B,MAAM,0DAA0D,CAAC;AAElE,OAAO,KAAK,2BAA2B,MAAM,6CAA6C,CAAC;AAI3F;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,wBAAwB;IAC3C;;OAEG;IACH,MAAM,CAAC,6BAA6B,CAAC,EACnC,OAAO,EACP,eAAe,EACf,SAAS,EACT,kBAAkB,EACnB,EAAE;QACD,OAAO,EAAE,2BAA2B,CAAC;QACrC,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,IAAI;IAwDR;;;;;;;OAOG;IACH,MAAM,CAAC,iCAAiC,CACtC,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,GAAG,CAAC,IAAI,EAAE,0BAA0B,CAAC,EACrD,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,eAAe,CAAC,GACtC,uBAAuB,EAAE,EAAE;CAuI/B"}
@@ -0,0 +1,172 @@
1
+ import { DateService } from '@aneuhold/core-ts-lib';
2
+ import { ExerciseRepRange } from '../../../documents/workout/WorkoutExercise.js';
3
+ import WorkoutExerciseService from '../Exercise/WorkoutExerciseService.js';
4
+ import WorkoutSessionService from '../Session/WorkoutSessionService.js';
5
+ import WorkoutVolumePlanningService from '../util/VolumePlanning/WorkoutVolumePlanningService.js';
6
+ /**
7
+ * A service for handling operations related to {@link WorkoutMicrocycle}s.
8
+ */
9
+ export default class WorkoutMicrocycleService {
10
+ /**
11
+ * Generates sessions for a specific microcycle.
12
+ */
13
+ static generateSessionsForMicrocycle({ context, microcycleIndex, targetRir, isDeloadMicrocycle }) {
14
+ const mesocycle = context.mesocycle;
15
+ const microcycle = context.microcyclesInOrder[microcycleIndex];
16
+ if (!context.plannedSessionExercisePairs) {
17
+ throw new Error('WorkoutMesocyclePlanContext.plannedSessionExercisePairs is not initialized. This should be set during mesocycle planning.');
18
+ }
19
+ if (!context.muscleGroupToExercisePairsMap) {
20
+ throw new Error('WorkoutMesocyclePlanContext.muscleGroupToExercisePairsMap is not initialized. This should be derived when the planned session exercise pairs are set.');
21
+ }
22
+ const sessionsToExerciseSessionsArray = context.plannedSessionExercisePairs;
23
+ const setPlan = WorkoutVolumePlanningService.calculateSetPlanForMicrocycle(context, microcycleIndex, isDeloadMicrocycle);
24
+ let currentSessionDate = new Date(microcycle.startDate);
25
+ let sessionIndex = 0;
26
+ for (let day = 0; day < mesocycle.plannedMicrocycleLengthInDays; day++) {
27
+ // Skip rest days
28
+ if (mesocycle.plannedMicrocycleRestDays.includes(day)) {
29
+ currentSessionDate = DateService.addDays(currentSessionDate, 1);
30
+ continue;
31
+ }
32
+ // Stop if we've created all planned sessions
33
+ if (sessionIndex >= mesocycle.plannedSessionCountPerMicrocycle) {
34
+ break;
35
+ }
36
+ const sessionExerciseList = sessionsToExerciseSessionsArray[sessionIndex] || [];
37
+ WorkoutSessionService.generateSession({
38
+ context,
39
+ microcycleIndex,
40
+ sessionIndex,
41
+ sessionStartDate: currentSessionDate,
42
+ sessionExerciseList,
43
+ targetRir,
44
+ isDeloadMicrocycle,
45
+ setPlan
46
+ });
47
+ sessionIndex++;
48
+ currentSessionDate = DateService.addDays(currentSessionDate, 1);
49
+ }
50
+ }
51
+ /**
52
+ * Distributes exercises across sessions within a microcycle, by putting them into a consecutive
53
+ * array of arrays structure. The embedded array is the list of exercises for that session.
54
+ *
55
+ * @param sessionCount The number of sessions to distribute exercises across.
56
+ * @param calibrationMap The map of calibration documents.
57
+ * @param exerciseMap The map of exercise documents.
58
+ */
59
+ static distributeExercisesAcrossSessions(sessionCount, calibrationMap, exerciseMap) {
60
+ // Build calibration-exercise pairs from the provided maps
61
+ const validExercises = [];
62
+ for (const calibration of calibrationMap.values()) {
63
+ const exercise = exerciseMap.get(calibration.workoutExerciseId);
64
+ if (!exercise) {
65
+ throw new Error(`Exercise ${calibration.workoutExerciseId} not found for calibration ${calibration._id}`);
66
+ }
67
+ validExercises.push({ calibration, exercise });
68
+ }
69
+ // Group exercises by their primary muscle group (use first one as main identifier, this might
70
+ // need to be revisited later for multi-group exercises)
71
+ const muscleGroupsMap = new Map();
72
+ for (const exercisePair of validExercises) {
73
+ const muscleGroupId = exercisePair.exercise.primaryMuscleGroups[0];
74
+ const existingGroup = muscleGroupsMap.get(muscleGroupId);
75
+ if (existingGroup) {
76
+ existingGroup.push(exercisePair);
77
+ }
78
+ else {
79
+ muscleGroupsMap.set(muscleGroupId, [exercisePair]);
80
+ }
81
+ }
82
+ // 1. Calculate max fatigue for each muscle group to sort them initially
83
+ const muscleGroupFatigueScores = new Map();
84
+ for (const [muscleGroupId, muscleGroupExercises] of muscleGroupsMap.entries()) {
85
+ const fatigueScores = muscleGroupExercises.map((pair) => WorkoutExerciseService.getFatigueScore(pair.exercise));
86
+ const maxFatigue = Math.max(...fatigueScores);
87
+ muscleGroupFatigueScores.set(muscleGroupId, maxFatigue);
88
+ }
89
+ // 2. Sort muscle groups by their max fatigue (Desc) so hardest exercises for a group get assigned first
90
+ const sortedMuscleGroupIds = [...muscleGroupsMap.keys()].sort((a, b) => {
91
+ const fatigueA = muscleGroupFatigueScores.get(a) || 0;
92
+ const fatigueB = muscleGroupFatigueScores.get(b) || 0;
93
+ return fatigueB - fatigueA;
94
+ });
95
+ // Prepare sessions array (so we can push exercises into each empty array)
96
+ const sessions = Array.from({ length: sessionCount }, () => []);
97
+ const usedStarterGroups = new Set();
98
+ // 3. Assign Headliners (Priority 1 & 2)
99
+ // Iterate sessions and pick a starter exercise
100
+ for (let sessionIndex = 0; sessionIndex < sessionCount; sessionIndex++) {
101
+ let candidateMuscleGroupId;
102
+ // Try to find a group that hasn't started a session yet (Priority 1)
103
+ for (const groupId of sortedMuscleGroupIds) {
104
+ if (!usedStarterGroups.has(groupId)) {
105
+ candidateMuscleGroupId = groupId;
106
+ break;
107
+ }
108
+ }
109
+ // If no unused group available/has exercises, pick any group with highest fatigue available (Priority 2 fallback)
110
+ if (!candidateMuscleGroupId) {
111
+ let maxFatigue = -1;
112
+ for (const groupId of sortedMuscleGroupIds) {
113
+ const exercisePairs = muscleGroupsMap.get(groupId);
114
+ if (exercisePairs && exercisePairs.length > 0) {
115
+ const fatigueScoresAmongExercises = exercisePairs.map((pair) => WorkoutExerciseService.getFatigueScore(pair.exercise));
116
+ const groupMax = Math.max(...fatigueScoresAmongExercises);
117
+ if (groupMax > maxFatigue) {
118
+ maxFatigue = groupMax;
119
+ candidateMuscleGroupId = groupId;
120
+ }
121
+ }
122
+ }
123
+ }
124
+ if (candidateMuscleGroupId) {
125
+ const group = muscleGroupsMap.get(candidateMuscleGroupId);
126
+ if (group && group.length > 0) {
127
+ // Pick highest fatigue exercise in this group to be the headliner
128
+ group.sort((a, b) => WorkoutExerciseService.getFatigueScore(b.exercise) -
129
+ WorkoutExerciseService.getFatigueScore(a.exercise));
130
+ const headliner = group.shift();
131
+ if (headliner) {
132
+ sessions[sessionIndex].push(headliner);
133
+ usedStarterGroups.add(candidateMuscleGroupId);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ // 4. Distribute Remainder (Priority 2 continued)
139
+ const remainingExercises = [];
140
+ for (const group of muscleGroupsMap.values()) {
141
+ remainingExercises.push(...group);
142
+ }
143
+ // Sort remaining by fatigue (High -> Low)
144
+ remainingExercises.sort((a, b) => WorkoutExerciseService.getFatigueScore(b.exercise) -
145
+ WorkoutExerciseService.getFatigueScore(a.exercise));
146
+ // Distribute round-robin
147
+ let currentSessionIndex = 0;
148
+ for (const pair of remainingExercises) {
149
+ sessions[currentSessionIndex].push(pair);
150
+ currentSessionIndex = (currentSessionIndex + 1) % sessionCount;
151
+ }
152
+ // 5. Sort within session (Priority 3)
153
+ // Headliner stays first (Index 0). Rest sorted by Rep Range (Heavy -> Med -> Light)
154
+ for (const session of sessions) {
155
+ if (session.length <= 1)
156
+ continue;
157
+ const [headliner, ...rest] = session;
158
+ rest.sort((a, b) => {
159
+ const order = {
160
+ [ExerciseRepRange.Heavy]: 0,
161
+ [ExerciseRepRange.Medium]: 1,
162
+ [ExerciseRepRange.Light]: 2
163
+ };
164
+ return order[a.exercise.repRange] - order[b.exercise.repRange];
165
+ });
166
+ session.length = 0;
167
+ session.push(headliner, ...rest);
168
+ }
169
+ return sessions;
170
+ }
171
+ }
172
+ //# sourceMappingURL=WorkoutMicrocycleService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutMicrocycleService.js","sourceRoot":"","sources":["../../../../src/services/workout/Microcycle/WorkoutMicrocycleService.ts"],"names":[],"mappings":"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"}