@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,236 @@
1
+ import { DateService } from '@aneuhold/core-ts-lib';
2
+ import type { UUID } from 'crypto';
3
+ import {
4
+ ExerciseRepRange,
5
+ type WorkoutExercise
6
+ } from '../../../documents/workout/WorkoutExercise.js';
7
+ import type {
8
+ CalibrationExercisePair,
9
+ WorkoutExerciseCalibration
10
+ } from '../../../documents/workout/WorkoutExerciseCalibration.js';
11
+ import WorkoutExerciseService from '../Exercise/WorkoutExerciseService.js';
12
+ import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
13
+ import WorkoutSessionService from '../Session/WorkoutSessionService.js';
14
+ import WorkoutVolumePlanningService from '../util/VolumePlanning/WorkoutVolumePlanningService.js';
15
+
16
+ /**
17
+ * A service for handling operations related to {@link WorkoutMicrocycle}s.
18
+ */
19
+ export default class WorkoutMicrocycleService {
20
+ /**
21
+ * Generates sessions for a specific microcycle.
22
+ */
23
+ static generateSessionsForMicrocycle({
24
+ context,
25
+ microcycleIndex,
26
+ targetRir,
27
+ isDeloadMicrocycle
28
+ }: {
29
+ context: WorkoutMesocyclePlanContext;
30
+ microcycleIndex: number;
31
+ targetRir: number;
32
+ isDeloadMicrocycle: boolean;
33
+ }): void {
34
+ const mesocycle = context.mesocycle;
35
+ const microcycle = context.microcyclesInOrder[microcycleIndex];
36
+
37
+ if (!context.plannedSessionExercisePairs) {
38
+ throw new Error(
39
+ 'WorkoutMesocyclePlanContext.plannedSessionExercisePairs is not initialized. This should be set during mesocycle planning.'
40
+ );
41
+ }
42
+ if (!context.muscleGroupToExercisePairsMap) {
43
+ throw new Error(
44
+ 'WorkoutMesocyclePlanContext.muscleGroupToExercisePairsMap is not initialized. This should be derived when the planned session exercise pairs are set.'
45
+ );
46
+ }
47
+
48
+ const sessionsToExerciseSessionsArray = context.plannedSessionExercisePairs;
49
+
50
+ const setPlan = WorkoutVolumePlanningService.calculateSetPlanForMicrocycle(
51
+ context,
52
+ microcycleIndex,
53
+ isDeloadMicrocycle
54
+ );
55
+
56
+ let currentSessionDate = new Date(microcycle.startDate);
57
+ let sessionIndex = 0;
58
+
59
+ for (let day = 0; day < mesocycle.plannedMicrocycleLengthInDays; day++) {
60
+ // Skip rest days
61
+ if (mesocycle.plannedMicrocycleRestDays.includes(day)) {
62
+ currentSessionDate = DateService.addDays(currentSessionDate, 1);
63
+ continue;
64
+ }
65
+
66
+ // Stop if we've created all planned sessions
67
+ if (sessionIndex >= mesocycle.plannedSessionCountPerMicrocycle) {
68
+ break;
69
+ }
70
+
71
+ const sessionExerciseList = sessionsToExerciseSessionsArray[sessionIndex] || [];
72
+
73
+ WorkoutSessionService.generateSession({
74
+ context,
75
+ microcycleIndex,
76
+ sessionIndex,
77
+ sessionStartDate: currentSessionDate,
78
+ sessionExerciseList,
79
+ targetRir,
80
+ isDeloadMicrocycle,
81
+ setPlan
82
+ });
83
+
84
+ sessionIndex++;
85
+ currentSessionDate = DateService.addDays(currentSessionDate, 1);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Distributes exercises across sessions within a microcycle, by putting them into a consecutive
91
+ * array of arrays structure. The embedded array is the list of exercises for that session.
92
+ *
93
+ * @param sessionCount The number of sessions to distribute exercises across.
94
+ * @param calibrationMap The map of calibration documents.
95
+ * @param exerciseMap The map of exercise documents.
96
+ */
97
+ static distributeExercisesAcrossSessions(
98
+ sessionCount: number,
99
+ calibrationMap: Map<UUID, WorkoutExerciseCalibration>,
100
+ exerciseMap: Map<UUID, WorkoutExercise>
101
+ ): CalibrationExercisePair[][] {
102
+ // Build calibration-exercise pairs from the provided maps
103
+ const validExercises: CalibrationExercisePair[] = [];
104
+ for (const calibration of calibrationMap.values()) {
105
+ const exercise = exerciseMap.get(calibration.workoutExerciseId);
106
+ if (!exercise) {
107
+ throw new Error(
108
+ `Exercise ${calibration.workoutExerciseId} not found for calibration ${calibration._id}`
109
+ );
110
+ }
111
+ validExercises.push({ calibration, exercise });
112
+ }
113
+
114
+ // Group exercises by their primary muscle group (use first one as main identifier, this might
115
+ // need to be revisited later for multi-group exercises)
116
+ const muscleGroupsMap = new Map<UUID, CalibrationExercisePair[]>();
117
+ for (const exercisePair of validExercises) {
118
+ const muscleGroupId = exercisePair.exercise.primaryMuscleGroups[0];
119
+ const existingGroup = muscleGroupsMap.get(muscleGroupId);
120
+ if (existingGroup) {
121
+ existingGroup.push(exercisePair);
122
+ } else {
123
+ muscleGroupsMap.set(muscleGroupId, [exercisePair]);
124
+ }
125
+ }
126
+
127
+ // 1. Calculate max fatigue for each muscle group to sort them initially
128
+ const muscleGroupFatigueScores = new Map<UUID, number>();
129
+ for (const [muscleGroupId, muscleGroupExercises] of muscleGroupsMap.entries()) {
130
+ const fatigueScores = muscleGroupExercises.map((pair) =>
131
+ WorkoutExerciseService.getFatigueScore(pair.exercise)
132
+ );
133
+ const maxFatigue = Math.max(...fatigueScores);
134
+ muscleGroupFatigueScores.set(muscleGroupId, maxFatigue);
135
+ }
136
+
137
+ // 2. Sort muscle groups by their max fatigue (Desc) so hardest exercises for a group get assigned first
138
+ const sortedMuscleGroupIds = [...muscleGroupsMap.keys()].sort((a, b) => {
139
+ const fatigueA = muscleGroupFatigueScores.get(a) || 0;
140
+ const fatigueB = muscleGroupFatigueScores.get(b) || 0;
141
+ return fatigueB - fatigueA;
142
+ });
143
+
144
+ // Prepare sessions array (so we can push exercises into each empty array)
145
+ const sessions: CalibrationExercisePair[][] = Array.from({ length: sessionCount }, () => []);
146
+ const usedStarterGroups = new Set<UUID>();
147
+
148
+ // 3. Assign Headliners (Priority 1 & 2)
149
+ // Iterate sessions and pick a starter exercise
150
+ for (let sessionIndex = 0; sessionIndex < sessionCount; sessionIndex++) {
151
+ let candidateMuscleGroupId: UUID | undefined;
152
+
153
+ // Try to find a group that hasn't started a session yet (Priority 1)
154
+ for (const groupId of sortedMuscleGroupIds) {
155
+ if (!usedStarterGroups.has(groupId)) {
156
+ candidateMuscleGroupId = groupId;
157
+ break;
158
+ }
159
+ }
160
+
161
+ // If no unused group available/has exercises, pick any group with highest fatigue available (Priority 2 fallback)
162
+ if (!candidateMuscleGroupId) {
163
+ let maxFatigue = -1;
164
+ for (const groupId of sortedMuscleGroupIds) {
165
+ const exercisePairs = muscleGroupsMap.get(groupId);
166
+ if (exercisePairs && exercisePairs.length > 0) {
167
+ const fatigueScoresAmongExercises = exercisePairs.map((pair) =>
168
+ WorkoutExerciseService.getFatigueScore(pair.exercise)
169
+ );
170
+ const groupMax = Math.max(...fatigueScoresAmongExercises);
171
+ if (groupMax > maxFatigue) {
172
+ maxFatigue = groupMax;
173
+ candidateMuscleGroupId = groupId;
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ if (candidateMuscleGroupId) {
180
+ const group = muscleGroupsMap.get(candidateMuscleGroupId);
181
+ if (group && group.length > 0) {
182
+ // Pick highest fatigue exercise in this group to be the headliner
183
+ group.sort(
184
+ (a, b) =>
185
+ WorkoutExerciseService.getFatigueScore(b.exercise) -
186
+ WorkoutExerciseService.getFatigueScore(a.exercise)
187
+ );
188
+ const headliner = group.shift();
189
+ if (headliner) {
190
+ sessions[sessionIndex].push(headliner);
191
+ usedStarterGroups.add(candidateMuscleGroupId);
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ // 4. Distribute Remainder (Priority 2 continued)
198
+ const remainingExercises: CalibrationExercisePair[] = [];
199
+ for (const group of muscleGroupsMap.values()) {
200
+ remainingExercises.push(...group);
201
+ }
202
+
203
+ // Sort remaining by fatigue (High -> Low)
204
+ remainingExercises.sort(
205
+ (a, b) =>
206
+ WorkoutExerciseService.getFatigueScore(b.exercise) -
207
+ WorkoutExerciseService.getFatigueScore(a.exercise)
208
+ );
209
+
210
+ // Distribute round-robin
211
+ let currentSessionIndex = 0;
212
+ for (const pair of remainingExercises) {
213
+ sessions[currentSessionIndex].push(pair);
214
+ currentSessionIndex = (currentSessionIndex + 1) % sessionCount;
215
+ }
216
+
217
+ // 5. Sort within session (Priority 3)
218
+ // Headliner stays first (Index 0). Rest sorted by Rep Range (Heavy -> Med -> Light)
219
+ for (const session of sessions) {
220
+ if (session.length <= 1) continue;
221
+ const [headliner, ...rest] = session;
222
+ rest.sort((a, b) => {
223
+ const order = {
224
+ [ExerciseRepRange.Heavy]: 0,
225
+ [ExerciseRepRange.Medium]: 1,
226
+ [ExerciseRepRange.Light]: 2
227
+ };
228
+ return order[a.exercise.repRange] - order[b.exercise.repRange];
229
+ });
230
+ session.length = 0;
231
+ session.push(headliner, ...rest);
232
+ }
233
+
234
+ return sessions;
235
+ }
236
+ }
@@ -0,0 +1,49 @@
1
+ import type { UUID } from 'crypto';
2
+ import type { CalibrationExercisePair } from '../../../documents/workout/WorkoutExerciseCalibration.js';
3
+ import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
4
+ import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
5
+ /**
6
+ * A service for handling operations related to {@link WorkoutSession}s.
7
+ *
8
+ * SCOPE: Session-level operations (calculating totals, managing session generation)
9
+ *
10
+ * RESPONSIBILITIES:
11
+ * - Calculate session-level RSM/Fatigue totals
12
+ * - Generate workout sessions with exercises and sets
13
+ *
14
+ * RELATED SERVICES:
15
+ * - {@link WorkoutVolumePlanningService} - Provides set count plans for session generation
16
+ * - {@link WorkoutSetService} - Generates individual sets within sessions
17
+ * - {@link WorkoutSFRService} - Underlying calculations for RSM/Fatigue
18
+ */
19
+ export default class WorkoutSessionService {
20
+ /**
21
+ * Calculates the total Raw Stimulus Magnitude for a session.
22
+ */
23
+ static getRsmTotal(session: WorkoutSession): number | null;
24
+ /**
25
+ * Calculates the total fatigue score for a session.
26
+ */
27
+ static getFatigueTotal(session: WorkoutSession): number | null;
28
+ /**
29
+ * Calculates the Stimulus to Fatigue Ratio (SFR) for a session.
30
+ */
31
+ static getSFR(session: WorkoutSession): number | null;
32
+ /**
33
+ * Generates a session and its associated exercises and sets.
34
+ */
35
+ static generateSession({ context, microcycleIndex, sessionIndex, sessionStartDate, sessionExerciseList, targetRir, isDeloadMicrocycle, setPlan }: {
36
+ context: WorkoutMesocyclePlanContext;
37
+ microcycleIndex: number;
38
+ sessionIndex: number;
39
+ sessionStartDate: Date;
40
+ sessionExerciseList: CalibrationExercisePair[];
41
+ targetRir: number;
42
+ isDeloadMicrocycle: boolean;
43
+ setPlan: {
44
+ exerciseIdToSetCount: Map<UUID, number>;
45
+ recoveryExerciseIds: Set<UUID>;
46
+ };
47
+ }): void;
48
+ }
49
+ //# sourceMappingURL=WorkoutSessionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutSessionService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Session/WorkoutSessionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0DAA0D,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AAGnF,OAAO,KAAK,2BAA2B,MAAM,6CAA6C,CAAC;AAI3F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,OAAO,OAAO,qBAAqB;IACxC;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI;IAI1D;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI;IAI9D;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI;IAIrD;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,EACrB,OAAO,EACP,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,OAAO,EACR,EAAE;QACD,OAAO,EAAE,2BAA2B,CAAC;QACrC,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,IAAI,CAAC;QACvB,mBAAmB,EAAE,uBAAuB,EAAE,CAAC;QAC/C,SAAS,EAAE,MAAM,CAAC;QAClB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,OAAO,EAAE;YAAE,oBAAoB,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAAC,mBAAmB,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;SAAE,CAAC;KACtF,GAAG,IAAI;CAqET"}
@@ -0,0 +1,95 @@
1
+ import { WorkoutSessionSchema } from '../../../documents/workout/WorkoutSession.js';
2
+ import { WorkoutSessionExerciseSchema } from '../../../documents/workout/WorkoutSessionExercise.js';
3
+ import WorkoutSetService from '../Set/WorkoutSetService.js';
4
+ import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
5
+ /**
6
+ * A service for handling operations related to {@link WorkoutSession}s.
7
+ *
8
+ * SCOPE: Session-level operations (calculating totals, managing session generation)
9
+ *
10
+ * RESPONSIBILITIES:
11
+ * - Calculate session-level RSM/Fatigue totals
12
+ * - Generate workout sessions with exercises and sets
13
+ *
14
+ * RELATED SERVICES:
15
+ * - {@link WorkoutVolumePlanningService} - Provides set count plans for session generation
16
+ * - {@link WorkoutSetService} - Generates individual sets within sessions
17
+ * - {@link WorkoutSFRService} - Underlying calculations for RSM/Fatigue
18
+ */
19
+ export default class WorkoutSessionService {
20
+ /**
21
+ * Calculates the total Raw Stimulus Magnitude for a session.
22
+ */
23
+ static getRsmTotal(session) {
24
+ return WorkoutSFRService.getRsmTotal(session.rsm);
25
+ }
26
+ /**
27
+ * Calculates the total fatigue score for a session.
28
+ */
29
+ static getFatigueTotal(session) {
30
+ return WorkoutSFRService.getFatigueTotal(session.fatigue);
31
+ }
32
+ /**
33
+ * Calculates the Stimulus to Fatigue Ratio (SFR) for a session.
34
+ */
35
+ static getSFR(session) {
36
+ return WorkoutSFRService.getSFR(session.rsm, session.fatigue);
37
+ }
38
+ /**
39
+ * Generates a session and its associated exercises and sets.
40
+ */
41
+ static generateSession({ context, microcycleIndex, sessionIndex, sessionStartDate, sessionExerciseList, targetRir, isDeloadMicrocycle, setPlan }) {
42
+ const mesocycle = context.mesocycle;
43
+ const microcycle = context.microcyclesInOrder[microcycleIndex];
44
+ const resolvedSetPlan = setPlan;
45
+ // Create session
46
+ const session = WorkoutSessionSchema.parse({
47
+ userId: mesocycle.userId,
48
+ workoutMicrocycleId: microcycle._id,
49
+ title: `Microcycle ${microcycleIndex + 1} - Session ${sessionIndex + 1}`,
50
+ startTime: sessionStartDate
51
+ });
52
+ // Create session exercise groupings and associated sets
53
+ for (let exerciseIndex = 0; exerciseIndex < sessionExerciseList.length; exerciseIndex++) {
54
+ const { calibration, exercise } = sessionExerciseList[exerciseIndex];
55
+ const setCountFromPlan = resolvedSetPlan.exerciseIdToSetCount.get(exercise._id);
56
+ if (setCountFromPlan == null) {
57
+ throw new Error(`No set plan found for exercise ${exercise._id}, ${exercise.exerciseName} in microcycle ${microcycleIndex}`);
58
+ }
59
+ // Get equipment for weight calculations
60
+ const equipment = context.equipmentMap.get(exercise.workoutEquipmentTypeId);
61
+ if (!equipment) {
62
+ throw new Error(`Equipment type not found for exercise ${exercise._id}, ${exercise.exerciseName}`);
63
+ }
64
+ if (!equipment.weightOptions || equipment.weightOptions.length === 0) {
65
+ throw new Error(`No weight options defined for equipment type ${equipment._id}, ${equipment.title}`);
66
+ }
67
+ const sessionExercise = WorkoutSessionExerciseSchema.parse({
68
+ userId: mesocycle.userId,
69
+ workoutSessionId: session._id,
70
+ workoutExerciseId: exercise._id,
71
+ isRecoveryExercise: resolvedSetPlan.recoveryExerciseIds.has(exercise._id)
72
+ });
73
+ WorkoutSetService.generateSetsForSessionExercise({
74
+ context,
75
+ exercise,
76
+ calibration,
77
+ session,
78
+ sessionExercise,
79
+ microcycleIndex,
80
+ sessionIndex,
81
+ setCount: setCountFromPlan,
82
+ targetRir,
83
+ isDeloadMicrocycle
84
+ });
85
+ const setsForThisExercise = context.setsToCreate.slice(-setCountFromPlan);
86
+ sessionExercise.setOrder.push(...setsForThisExercise.map((s) => s._id));
87
+ session.sessionExerciseOrder.push(sessionExercise._id);
88
+ context.addSessionExercise(sessionExercise);
89
+ }
90
+ // Add session to microcycle's session order and context
91
+ microcycle.sessionOrder.push(session._id);
92
+ context.addSession(session);
93
+ }
94
+ }
95
+ //# sourceMappingURL=WorkoutSessionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutSessionService.js","sourceRoot":"","sources":["../../../../src/services/workout/Session/WorkoutSessionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import type { UUID } from 'crypto';
2
+ import type { CalibrationExercisePair } from '../../../documents/workout/WorkoutExerciseCalibration.js';
3
+ import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
4
+ import { WorkoutSessionSchema } from '../../../documents/workout/WorkoutSession.js';
5
+ import { WorkoutSessionExerciseSchema } from '../../../documents/workout/WorkoutSessionExercise.js';
6
+ import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
7
+ import WorkoutSetService from '../Set/WorkoutSetService.js';
8
+ import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
9
+
10
+ /**
11
+ * A service for handling operations related to {@link WorkoutSession}s.
12
+ *
13
+ * SCOPE: Session-level operations (calculating totals, managing session generation)
14
+ *
15
+ * RESPONSIBILITIES:
16
+ * - Calculate session-level RSM/Fatigue totals
17
+ * - Generate workout sessions with exercises and sets
18
+ *
19
+ * RELATED SERVICES:
20
+ * - {@link WorkoutVolumePlanningService} - Provides set count plans for session generation
21
+ * - {@link WorkoutSetService} - Generates individual sets within sessions
22
+ * - {@link WorkoutSFRService} - Underlying calculations for RSM/Fatigue
23
+ */
24
+ export default class WorkoutSessionService {
25
+ /**
26
+ * Calculates the total Raw Stimulus Magnitude for a session.
27
+ */
28
+ static getRsmTotal(session: WorkoutSession): number | null {
29
+ return WorkoutSFRService.getRsmTotal(session.rsm);
30
+ }
31
+
32
+ /**
33
+ * Calculates the total fatigue score for a session.
34
+ */
35
+ static getFatigueTotal(session: WorkoutSession): number | null {
36
+ return WorkoutSFRService.getFatigueTotal(session.fatigue);
37
+ }
38
+
39
+ /**
40
+ * Calculates the Stimulus to Fatigue Ratio (SFR) for a session.
41
+ */
42
+ static getSFR(session: WorkoutSession): number | null {
43
+ return WorkoutSFRService.getSFR(session.rsm, session.fatigue);
44
+ }
45
+
46
+ /**
47
+ * Generates a session and its associated exercises and sets.
48
+ */
49
+ static generateSession({
50
+ context,
51
+ microcycleIndex,
52
+ sessionIndex,
53
+ sessionStartDate,
54
+ sessionExerciseList,
55
+ targetRir,
56
+ isDeloadMicrocycle,
57
+ setPlan
58
+ }: {
59
+ context: WorkoutMesocyclePlanContext;
60
+ microcycleIndex: number;
61
+ sessionIndex: number;
62
+ sessionStartDate: Date;
63
+ sessionExerciseList: CalibrationExercisePair[];
64
+ targetRir: number;
65
+ isDeloadMicrocycle: boolean;
66
+ setPlan: { exerciseIdToSetCount: Map<UUID, number>; recoveryExerciseIds: Set<UUID> };
67
+ }): void {
68
+ const mesocycle = context.mesocycle;
69
+ const microcycle = context.microcyclesInOrder[microcycleIndex];
70
+
71
+ const resolvedSetPlan = setPlan;
72
+
73
+ // Create session
74
+ const session = WorkoutSessionSchema.parse({
75
+ userId: mesocycle.userId,
76
+ workoutMicrocycleId: microcycle._id,
77
+ title: `Microcycle ${microcycleIndex + 1} - Session ${sessionIndex + 1}`,
78
+ startTime: sessionStartDate
79
+ });
80
+
81
+ // Create session exercise groupings and associated sets
82
+ for (let exerciseIndex = 0; exerciseIndex < sessionExerciseList.length; exerciseIndex++) {
83
+ const { calibration, exercise } = sessionExerciseList[exerciseIndex];
84
+
85
+ const setCountFromPlan = resolvedSetPlan.exerciseIdToSetCount.get(exercise._id);
86
+ if (setCountFromPlan == null) {
87
+ throw new Error(
88
+ `No set plan found for exercise ${exercise._id}, ${exercise.exerciseName} in microcycle ${microcycleIndex}`
89
+ );
90
+ }
91
+
92
+ // Get equipment for weight calculations
93
+ const equipment = context.equipmentMap.get(exercise.workoutEquipmentTypeId);
94
+ if (!equipment) {
95
+ throw new Error(
96
+ `Equipment type not found for exercise ${exercise._id}, ${exercise.exerciseName}`
97
+ );
98
+ }
99
+ if (!equipment.weightOptions || equipment.weightOptions.length === 0) {
100
+ throw new Error(
101
+ `No weight options defined for equipment type ${equipment._id}, ${equipment.title}`
102
+ );
103
+ }
104
+
105
+ const sessionExercise = WorkoutSessionExerciseSchema.parse({
106
+ userId: mesocycle.userId,
107
+ workoutSessionId: session._id,
108
+ workoutExerciseId: exercise._id,
109
+ isRecoveryExercise: resolvedSetPlan.recoveryExerciseIds.has(exercise._id)
110
+ });
111
+
112
+ WorkoutSetService.generateSetsForSessionExercise({
113
+ context,
114
+ exercise,
115
+ calibration,
116
+ session,
117
+ sessionExercise,
118
+ microcycleIndex,
119
+ sessionIndex,
120
+ setCount: setCountFromPlan,
121
+ targetRir,
122
+ isDeloadMicrocycle
123
+ });
124
+
125
+ const setsForThisExercise = context.setsToCreate.slice(-setCountFromPlan);
126
+
127
+ sessionExercise.setOrder.push(...setsForThisExercise.map((s) => s._id));
128
+ session.sessionExerciseOrder.push(sessionExercise._id);
129
+ context.addSessionExercise(sessionExercise);
130
+ }
131
+
132
+ // Add session to microcycle's session order and context
133
+ microcycle.sessionOrder.push(session._id);
134
+ context.addSession(session);
135
+ }
136
+ }
@@ -0,0 +1,45 @@
1
+ import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
2
+ /**
3
+ * A service for handling operations related to {@link WorkoutSessionExercise}s.
4
+ */
5
+ export default class WorkoutSessionExerciseService {
6
+ /**
7
+ * Calculates the total Raw Stimulus Magnitude for a specific exercise within a session.
8
+ *
9
+ * @param sessionExercise The workout session exercise.
10
+ */
11
+ static getRsmTotal(sessionExercise: WorkoutSessionExercise): number | null;
12
+ /**
13
+ * Calculates the total fatigue score for a specific exercise within a session.
14
+ *
15
+ * @param sessionExercise The workout session exercise.
16
+ */
17
+ static getFatigueTotal(sessionExercise: WorkoutSessionExercise): number | null;
18
+ /**
19
+ * Calculates the Stimulus to Fatigue Ratio (SFR) for a specific exercise.
20
+ *
21
+ * @param sessionExercise The workout session exercise.
22
+ */
23
+ static getSFR(sessionExercise: WorkoutSessionExercise): number | null;
24
+ /**
25
+ * Uses the soreness/performance table from the workout model notes to recommend whether to add
26
+ * sets next microcycle or employ recovery sessions.
27
+ *
28
+ * Interpretation:
29
+ * - Returns `-1` when recovery sessions should be employed.
30
+ * - Returns `0` when no sets should be added.
31
+ * - Returns a non-negative integer when sets should be added.
32
+ * - Returns `null` when insufficient data is available.
33
+ *
34
+ * The table is:
35
+ *
36
+ * | Soreness Score ↓ \ Performance Score → | 0 | 1 | 2 | 3 |
37
+ |---|---|---|---|---|
38
+ | **0** | Add 1–3 sets | Add 0–2 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
39
+ | **1** | Add 1–2 sets | Add 0–1 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
40
+ | **2** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
41
+ | **3** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
42
+ */
43
+ static getRecommendedSetAdditionsOrRecovery(workoutSessionExercise: WorkoutSessionExercise): number | null;
44
+ }
45
+ //# sourceMappingURL=WorkoutSessionExerciseService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutSessionExerciseService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/SessionExercise/WorkoutSessionExerciseService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sDAAsD,CAAC;AAGnG;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,6BAA6B;IAChD;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,eAAe,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI;IAI1E;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,eAAe,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI;IAI9E;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI;IAIrE;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,oCAAoC,CACzC,sBAAsB,EAAE,sBAAsB,GAC7C,MAAM,GAAG,IAAI;CAqBjB"}
@@ -0,0 +1,69 @@
1
+ import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
2
+ /**
3
+ * A service for handling operations related to {@link WorkoutSessionExercise}s.
4
+ */
5
+ export default class WorkoutSessionExerciseService {
6
+ /**
7
+ * Calculates the total Raw Stimulus Magnitude for a specific exercise within a session.
8
+ *
9
+ * @param sessionExercise The workout session exercise.
10
+ */
11
+ static getRsmTotal(sessionExercise) {
12
+ return WorkoutSFRService.getRsmTotal(sessionExercise.rsm);
13
+ }
14
+ /**
15
+ * Calculates the total fatigue score for a specific exercise within a session.
16
+ *
17
+ * @param sessionExercise The workout session exercise.
18
+ */
19
+ static getFatigueTotal(sessionExercise) {
20
+ return WorkoutSFRService.getFatigueTotal(sessionExercise.fatigue);
21
+ }
22
+ /**
23
+ * Calculates the Stimulus to Fatigue Ratio (SFR) for a specific exercise.
24
+ *
25
+ * @param sessionExercise The workout session exercise.
26
+ */
27
+ static getSFR(sessionExercise) {
28
+ return WorkoutSFRService.getSFR(sessionExercise.rsm, sessionExercise.fatigue);
29
+ }
30
+ /**
31
+ * Uses the soreness/performance table from the workout model notes to recommend whether to add
32
+ * sets next microcycle or employ recovery sessions.
33
+ *
34
+ * Interpretation:
35
+ * - Returns `-1` when recovery sessions should be employed.
36
+ * - Returns `0` when no sets should be added.
37
+ * - Returns a non-negative integer when sets should be added.
38
+ * - Returns `null` when insufficient data is available.
39
+ *
40
+ * The table is:
41
+ *
42
+ * | Soreness Score ↓ \ Performance Score → | 0 | 1 | 2 | 3 |
43
+ |---|---|---|---|---|
44
+ | **0** | Add 1–3 sets | Add 0–2 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
45
+ | **1** | Add 1–2 sets | Add 0–1 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
46
+ | **2** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
47
+ | **3** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
48
+ */
49
+ static getRecommendedSetAdditionsOrRecovery(workoutSessionExercise) {
50
+ const { performanceScore, sorenessScore } = workoutSessionExercise;
51
+ if (sorenessScore == null || performanceScore == null) {
52
+ return null;
53
+ }
54
+ // Table mapping (sorenessScore rows, performanceScore columns).
55
+ // Values are representative set additions (midpoint of table ranges), or -1 for recovery.
56
+ const table = [
57
+ // Soreness 0: [Add 1-3, Add 0-2, Do not add, Recovery]
58
+ [2, 1, 0, -1],
59
+ // Soreness 1: [Add 1-2, Add 0-1, Do not add, Recovery]
60
+ [1, 0, 0, -1],
61
+ // Soreness 2: [Do not add, Do not add, Do not add, Recovery]
62
+ [0, 0, 0, -1],
63
+ // Soreness 3: [Do not add, Do not add, Do not add, Recovery]
64
+ [0, 0, 0, -1]
65
+ ];
66
+ return table[sorenessScore]?.[performanceScore] ?? null;
67
+ }
68
+ }
69
+ //# sourceMappingURL=WorkoutSessionExerciseService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkoutSessionExerciseService.js","sourceRoot":"","sources":["../../../../src/services/workout/SessionExercise/WorkoutSessionExerciseService.ts"],"names":[],"mappings":"AACA,OAAO,iBAAiB,MAAM,kCAAkC,CAAC;AAEjE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,6BAA6B;IAChD;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,eAAuC;QACxD,OAAO,iBAAiB,CAAC,WAAW,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,eAAuC;QAC5D,OAAO,iBAAiB,CAAC,eAAe,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IACpE,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,MAAM,CAAC,eAAuC;QACnD,OAAO,iBAAiB,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;IAChF,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,oCAAoC,CACzC,sBAA8C;QAE9C,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,sBAAsB,CAAC;QACnE,IAAI,aAAa,IAAI,IAAI,IAAI,gBAAgB,IAAI,IAAI,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,gEAAgE;QAChE,0FAA0F;QAC1F,MAAM,KAAK,GAAe;YACxB,uDAAuD;YACvD,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACb,uDAAuD;YACvD,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACb,6DAA6D;YAC7D,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACb,6DAA6D;YAC7D,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;SACd,CAAC;QAEF,OAAO,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC;IAC1D,CAAC;CACF"}