@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.
- package/CHANGELOG.md +29 -1
- package/lib/browser.d.ts +36 -4
- package/lib/browser.d.ts.map +1 -1
- package/lib/browser.js +23 -2
- package/lib/browser.js.map +1 -1
- package/lib/browser.ts +126 -4
- package/lib/documents/BaseDocument.d.ts +8 -0
- package/lib/documents/BaseDocument.d.ts.map +1 -1
- package/lib/documents/BaseDocument.js +10 -0
- package/lib/documents/BaseDocument.js.map +1 -1
- package/lib/documents/BaseDocument.ts +18 -0
- package/lib/documents/common/User.d.ts +1 -0
- package/lib/documents/common/User.d.ts.map +1 -1
- package/lib/documents/common/User.js +4 -2
- package/lib/documents/common/User.js.map +1 -1
- package/lib/documents/common/User.ts +4 -2
- package/lib/documents/dashboard/NonogramKatanaItem.d.ts +1 -1
- package/lib/documents/dashboard/NonogramKatanaItem.js +1 -1
- package/lib/documents/dashboard/NonogramKatanaItem.js.map +1 -1
- package/lib/documents/dashboard/NonogramKatanaItem.ts +1 -1
- package/lib/documents/dashboard/NonogramKatanaUpgrade.d.ts +1 -1
- package/lib/documents/dashboard/NonogramKatanaUpgrade.js +1 -1
- package/lib/documents/dashboard/NonogramKatanaUpgrade.js.map +1 -1
- package/lib/documents/dashboard/NonogramKatanaUpgrade.ts +1 -1
- package/lib/documents/dashboard/Task.d.ts +4 -4
- package/lib/documents/dashboard/UserConfig.d.ts +4 -4
- package/lib/documents/workout/README.md +557 -0
- package/lib/documents/workout/WorkoutEquipmentType.d.ts +22 -0
- package/lib/documents/workout/WorkoutEquipmentType.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutEquipmentType.js +31 -0
- package/lib/documents/workout/WorkoutEquipmentType.js.map +1 -0
- package/lib/documents/workout/WorkoutEquipmentType.ts +40 -0
- package/lib/documents/workout/WorkoutExercise.d.ts +82 -0
- package/lib/documents/workout/WorkoutExercise.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutExercise.js +124 -0
- package/lib/documents/workout/WorkoutExercise.js.map +1 -0
- package/lib/documents/workout/WorkoutExercise.ts +143 -0
- package/lib/documents/workout/WorkoutExerciseCalibration.d.ts +43 -0
- package/lib/documents/workout/WorkoutExerciseCalibration.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutExerciseCalibration.js +45 -0
- package/lib/documents/workout/WorkoutExerciseCalibration.js.map +1 -0
- package/lib/documents/workout/WorkoutExerciseCalibration.ts +74 -0
- package/lib/documents/workout/WorkoutMesocycle.d.ts +49 -0
- package/lib/documents/workout/WorkoutMesocycle.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutMesocycle.js +84 -0
- package/lib/documents/workout/WorkoutMesocycle.js.map +1 -0
- package/lib/documents/workout/WorkoutMesocycle.ts +102 -0
- package/lib/documents/workout/WorkoutMicrocycle.d.ts +27 -0
- package/lib/documents/workout/WorkoutMicrocycle.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutMicrocycle.js +42 -0
- package/lib/documents/workout/WorkoutMicrocycle.js.map +1 -0
- package/lib/documents/workout/WorkoutMicrocycle.ts +55 -0
- package/lib/documents/workout/WorkoutMuscleGroup.d.ts +22 -0
- package/lib/documents/workout/WorkoutMuscleGroup.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutMuscleGroup.js +25 -0
- package/lib/documents/workout/WorkoutMuscleGroup.js.map +1 -0
- package/lib/documents/workout/WorkoutMuscleGroup.ts +34 -0
- package/lib/documents/workout/WorkoutSession.d.ts +39 -0
- package/lib/documents/workout/WorkoutSession.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutSession.js +66 -0
- package/lib/documents/workout/WorkoutSession.js.map +1 -0
- package/lib/documents/workout/WorkoutSession.ts +79 -0
- package/lib/documents/workout/WorkoutSessionExercise.d.ts +39 -0
- package/lib/documents/workout/WorkoutSessionExercise.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutSessionExercise.js +66 -0
- package/lib/documents/workout/WorkoutSessionExercise.js.map +1 -0
- package/lib/documents/workout/WorkoutSessionExercise.ts +79 -0
- package/lib/documents/workout/WorkoutSet.d.ts +41 -0
- package/lib/documents/workout/WorkoutSet.d.ts.map +1 -0
- package/lib/documents/workout/WorkoutSet.js +69 -0
- package/lib/documents/workout/WorkoutSet.js.map +1 -0
- package/lib/documents/workout/WorkoutSet.ts +90 -0
- package/lib/embedded-types/dashboard/task/FilterSettings.d.ts +6 -6
- package/lib/embedded-types/dashboard/task/FilterSettings.d.ts.map +1 -1
- package/lib/embedded-types/dashboard/task/FilterSettings.js +4 -2
- package/lib/embedded-types/dashboard/task/FilterSettings.js.map +1 -1
- package/lib/embedded-types/dashboard/task/FilterSettings.ts +5 -3
- package/lib/embedded-types/dashboard/task/SortSettings.d.ts +6 -6
- package/lib/embedded-types/dashboard/task/SortSettings.js +1 -1
- package/lib/embedded-types/dashboard/task/SortSettings.js.map +1 -1
- package/lib/embedded-types/dashboard/task/SortSettings.ts +1 -1
- package/lib/embedded-types/workout/Fatigue.d.ts +16 -0
- package/lib/embedded-types/workout/Fatigue.d.ts.map +1 -0
- package/lib/embedded-types/workout/Fatigue.js +34 -0
- package/lib/embedded-types/workout/Fatigue.js.map +1 -0
- package/lib/embedded-types/workout/Fatigue.ts +41 -0
- package/lib/embedded-types/workout/Rsm.d.ts +17 -0
- package/lib/embedded-types/workout/Rsm.d.ts.map +1 -0
- package/lib/embedded-types/workout/Rsm.js +34 -0
- package/lib/embedded-types/workout/Rsm.js.map +1 -0
- package/lib/embedded-types/workout/Rsm.ts +42 -0
- package/lib/services/DocumentService.d.ts +19 -0
- package/lib/services/DocumentService.d.ts.map +1 -1
- package/lib/services/DocumentService.js.map +1 -1
- package/lib/services/DocumentService.ts +20 -0
- package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.d.ts +35 -0
- package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.d.ts.map +1 -0
- package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.js +74 -0
- package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.js.map +1 -0
- package/lib/services/workout/EquipmentType/WorkoutEquipmentTypeService.ts +90 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +55 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.js +133 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.ts +173 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +35 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +42 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +45 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +90 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +131 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +159 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +52 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +180 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +274 -0
- package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts +28 -0
- package/lib/services/workout/Microcycle/WorkoutMicrocycleService.d.ts.map +1 -0
- package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js +172 -0
- package/lib/services/workout/Microcycle/WorkoutMicrocycleService.js.map +1 -0
- package/lib/services/workout/Microcycle/WorkoutMicrocycleService.ts +236 -0
- package/lib/services/workout/Session/WorkoutSessionService.d.ts +49 -0
- package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -0
- package/lib/services/workout/Session/WorkoutSessionService.js +95 -0
- package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -0
- package/lib/services/workout/Session/WorkoutSessionService.ts +136 -0
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +45 -0
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -0
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +69 -0
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -0
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +77 -0
- package/lib/services/workout/Set/WorkoutSetService.d.ts +36 -0
- package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -0
- package/lib/services/workout/Set/WorkoutSetService.js +90 -0
- package/lib/services/workout/Set/WorkoutSetService.js.map +1 -0
- package/lib/services/workout/Set/WorkoutSetService.ts +153 -0
- package/lib/services/workout/util/SFR/WorkoutSFRService.d.ts +29 -0
- package/lib/services/workout/util/SFR/WorkoutSFRService.d.ts.map +1 -0
- package/lib/services/workout/util/SFR/WorkoutSFRService.js +50 -0
- package/lib/services/workout/util/SFR/WorkoutSFRService.js.map +1 -0
- package/lib/services/workout/util/SFR/WorkoutSFRService.ts +61 -0
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +48 -0
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -0
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +261 -0
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -0
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +339 -0
- 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":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EACL,gBAAgB,EAEjB,MAAM,+CAA+C,CAAC;AAKvD,OAAO,sBAAsB,MAAM,uCAAuC,CAAC;AAE3E,OAAO,qBAAqB,MAAM,qCAAqC,CAAC;AACxE,OAAO,4BAA4B,MAAM,wDAAwD,CAAC;AAElG;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,wBAAwB;IAC3C;;OAEG;IACH,MAAM,CAAC,6BAA6B,CAAC,EACnC,OAAO,EACP,eAAe,EACf,SAAS,EACT,kBAAkB,EAMnB;QACC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,MAAM,UAAU,GAAG,OAAO,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAE/D,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,2HAA2H,CAC5H,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,6BAA6B,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CACb,uJAAuJ,CACxJ,CAAC;QACJ,CAAC;QAED,MAAM,+BAA+B,GAAG,OAAO,CAAC,2BAA2B,CAAC;QAE5E,MAAM,OAAO,GAAG,4BAA4B,CAAC,6BAA6B,CACxE,OAAO,EACP,eAAe,EACf,kBAAkB,CACnB,CAAC;QAEF,IAAI,kBAAkB,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QACxD,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC,6BAA6B,EAAE,GAAG,EAAE,EAAE,CAAC;YACvE,iBAAiB;YACjB,IAAI,SAAS,CAAC,yBAAyB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtD,kBAAkB,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;gBAChE,SAAS;YACX,CAAC;YAED,6CAA6C;YAC7C,IAAI,YAAY,IAAI,SAAS,CAAC,gCAAgC,EAAE,CAAC;gBAC/D,MAAM;YACR,CAAC;YAED,MAAM,mBAAmB,GAAG,+BAA+B,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YAEhF,qBAAqB,CAAC,eAAe,CAAC;gBACpC,OAAO;gBACP,eAAe;gBACf,YAAY;gBACZ,gBAAgB,EAAE,kBAAkB;gBACpC,mBAAmB;gBACnB,SAAS;gBACT,kBAAkB;gBAClB,OAAO;aACR,CAAC,CAAC;YAEH,YAAY,EAAE,CAAC;YACf,kBAAkB,GAAG,WAAW,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,iCAAiC,CACtC,YAAoB,EACpB,cAAqD,EACrD,WAAuC;QAEvC,0DAA0D;QAC1D,MAAM,cAAc,GAA8B,EAAE,CAAC;QACrD,KAAK,MAAM,WAAW,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;YAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CACb,YAAY,WAAW,CAAC,iBAAiB,8BAA8B,WAAW,CAAC,GAAG,EAAE,CACzF,CAAC;YACJ,CAAC;YACD,cAAc,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,8FAA8F;QAC9F,wDAAwD;QACxD,MAAM,eAAe,GAAG,IAAI,GAAG,EAAmC,CAAC;QACnE,KAAK,MAAM,YAAY,IAAI,cAAc,EAAE,CAAC;YAC1C,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;YACnE,MAAM,aAAa,GAAG,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YACzD,IAAI,aAAa,EAAE,CAAC;gBAClB,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,wEAAwE;QACxE,MAAM,wBAAwB,GAAG,IAAI,GAAG,EAAgB,CAAC;QACzD,KAAK,MAAM,CAAC,aAAa,EAAE,oBAAoB,CAAC,IAAI,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9E,MAAM,aAAa,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACtD,sBAAsB,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CACtD,CAAC;YACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC;YAC9C,wBAAwB,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QAC1D,CAAC;QAED,wGAAwG;QACxG,MAAM,oBAAoB,GAAG,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrE,MAAM,QAAQ,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,QAAQ,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtD,OAAO,QAAQ,GAAG,QAAQ,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,MAAM,QAAQ,GAAgC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7F,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAQ,CAAC;QAE1C,wCAAwC;QACxC,+CAA+C;QAC/C,KAAK,IAAI,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,YAAY,EAAE,YAAY,EAAE,EAAE,CAAC;YACvE,IAAI,sBAAwC,CAAC;YAE7C,qEAAqE;YACrE,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;gBAC3C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpC,sBAAsB,GAAG,OAAO,CAAC;oBACjC,MAAM;gBACR,CAAC;YACH,CAAC;YAED,kHAAkH;YAClH,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBAC5B,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;gBACpB,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;oBAC3C,MAAM,aAAa,GAAG,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACnD,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC9C,MAAM,2BAA2B,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC7D,sBAAsB,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CACtD,CAAC;wBACF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,2BAA2B,CAAC,CAAC;wBAC1D,IAAI,QAAQ,GAAG,UAAU,EAAE,CAAC;4BAC1B,UAAU,GAAG,QAAQ,CAAC;4BACtB,sBAAsB,GAAG,OAAO,CAAC;wBACnC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,sBAAsB,EAAE,CAAC;gBAC3B,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;gBAC1D,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,kEAAkE;oBAClE,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC;wBAClD,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CACrD,CAAC;oBACF,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;oBAChC,IAAI,SAAS,EAAE,CAAC;wBACd,QAAQ,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;wBACvC,iBAAiB,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;oBAChD,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,iDAAiD;QACjD,MAAM,kBAAkB,GAA8B,EAAE,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,kBAAkB,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QACpC,CAAC;QAED,0CAA0C;QAC1C,kBAAkB,CAAC,IAAI,CACrB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC;YAClD,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,CACrD,CAAC;QAEF,yBAAyB;QACzB,IAAI,mBAAmB,GAAG,CAAC,CAAC;QAC5B,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;YACtC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,mBAAmB,GAAG,CAAC,mBAAmB,GAAG,CAAC,CAAC,GAAG,YAAY,CAAC;QACjE,CAAC;QAED,sCAAsC;QACtC,oFAAoF;QACpF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;gBAAE,SAAS;YAClC,MAAM,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;YACrC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBACjB,MAAM,KAAK,GAAG;oBACZ,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC5B,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;iBAC5B,CAAC;gBACF,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACjE,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC;QACnC,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|