@aneuhold/core-ts-db-lib 4.1.12 → 4.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/lib/browser.d.ts +11 -8
- package/lib/browser.d.ts.map +1 -1
- package/lib/browser.js +6 -4
- package/lib/browser.js.map +1 -1
- package/lib/browser.ts +23 -9
- package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts +14 -0
- package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts.map +1 -1
- package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.ts +18 -0
- package/lib/documents/workout/WorkoutSet.d.ts +8 -0
- package/lib/documents/workout/WorkoutSet.d.ts.map +1 -1
- package/lib/documents/workout/WorkoutSet.ts +9 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +40 -0
- package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -1
- package/lib/services/workout/Exercise/WorkoutExerciseService.js +134 -1
- package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -1
- package/lib/services/workout/Exercise/WorkoutExerciseService.ts +204 -1
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +15 -0
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -1
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +18 -1
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -1
- package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +19 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +26 -4
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +51 -4
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +58 -3
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +45 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +201 -11
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +285 -9
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts +33 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js +24 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js.map +1 -0
- package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.ts +36 -0
- package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -1
- package/lib/services/workout/Session/WorkoutSessionService.js +1 -11
- package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -1
- package/lib/services/workout/Session/WorkoutSessionService.ts +1 -17
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +14 -2
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -1
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +17 -3
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -1
- package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +28 -3
- package/lib/services/workout/Set/WorkoutSetService.d.ts +17 -5
- package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -1
- package/lib/services/workout/Set/WorkoutSetService.js +83 -16
- package/lib/services/workout/Set/WorkoutSetService.js.map +1 -1
- package/lib/services/workout/Set/WorkoutSetService.ts +107 -24
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +72 -2
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -1
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +202 -15
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -1
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +268 -18
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkoutSessionExerciseService.js","sourceRoot":"","sources":["../../../../src/services/workout/SessionExercise/WorkoutSessionExerciseService.ts"],"names":[],"mappings":"AAEA,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;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,mBAAmB,CAAC,IAAkB;QAC3C,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IACE,GAAG,CAAC,WAAW,IAAI,IAAI;gBACvB,GAAG,CAAC,UAAU,IAAI,IAAI;gBACtB,GAAG,CAAC,UAAU,IAAI,IAAI;gBACtB,GAAG,CAAC,GAAG,IAAI,IAAI,EACf,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,SAAS;YACX,CAAC;YAED,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"WorkoutSessionExerciseService.js","sourceRoot":"","sources":["../../../../src/services/workout/SessionExercise/WorkoutSessionExerciseService.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,8 +36,8 @@ export default class WorkoutSessionExerciseService {
|
|
|
36
36
|
/**
|
|
37
37
|
* Calculates the performance score (0-3) for an exercise based on its sets.
|
|
38
38
|
*
|
|
39
|
-
* For each set with complete data, a surplus is computed
|
|
40
|
-
*
|
|
39
|
+
* For each set with complete data, a surplus is computed via
|
|
40
|
+
* {@link calculateSetSurplus}. The per-set score is:
|
|
41
41
|
* - 0: surplus >= 2 (exceeded expectations)
|
|
42
42
|
* - 1: surplus 0-1 (on target)
|
|
43
43
|
* - 2: surplus < 0 but hit target reps (declining)
|
|
@@ -64,7 +64,12 @@ export default class WorkoutSessionExerciseService {
|
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const surplus =
|
|
67
|
+
const surplus = this.calculateSetSurplus(
|
|
68
|
+
set.actualReps,
|
|
69
|
+
set.plannedReps,
|
|
70
|
+
set.rir,
|
|
71
|
+
set.plannedRir
|
|
72
|
+
);
|
|
68
73
|
|
|
69
74
|
if (surplus >= 2) {
|
|
70
75
|
setScores.push(0);
|
|
@@ -83,6 +88,26 @@ export default class WorkoutSessionExerciseService {
|
|
|
83
88
|
return Math.round(average);
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Calculates the surplus for a single set: how much the user exceeded or
|
|
93
|
+
* fell short of the plan. Positive means exceeded, negative means fell short.
|
|
94
|
+
*
|
|
95
|
+
* Formula: `(actualReps - plannedReps) + (rir - plannedRir)`
|
|
96
|
+
*
|
|
97
|
+
* @param actualReps The actual reps performed.
|
|
98
|
+
* @param plannedReps The planned reps.
|
|
99
|
+
* @param rir The actual RIR (reps in reserve).
|
|
100
|
+
* @param plannedRir The planned RIR.
|
|
101
|
+
*/
|
|
102
|
+
static calculateSetSurplus(
|
|
103
|
+
actualReps: number,
|
|
104
|
+
plannedReps: number,
|
|
105
|
+
rir: number,
|
|
106
|
+
plannedRir: number
|
|
107
|
+
): number {
|
|
108
|
+
return actualReps - plannedReps + (rir - plannedRir);
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
/**
|
|
87
112
|
* Uses the soreness/performance table from the workout model notes to recommend whether to add
|
|
88
113
|
* sets next microcycle or employ recovery sessions.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
|
|
1
|
+
import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
|
|
3
2
|
import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
|
|
4
3
|
import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
|
|
5
4
|
import { type WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
|
|
@@ -12,11 +11,11 @@ export default class WorkoutSetService {
|
|
|
12
11
|
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
13
12
|
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
14
13
|
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
14
|
+
* - Using previous performance data to adjust progression via autoregulation.
|
|
15
15
|
*/
|
|
16
|
-
static generateSetsForSessionExercise({ context,
|
|
16
|
+
static generateSetsForSessionExercise({ context, exerciseCTO, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }: {
|
|
17
17
|
context: WorkoutMesocyclePlanContext;
|
|
18
|
-
|
|
19
|
-
calibration: WorkoutExerciseCalibration;
|
|
18
|
+
exerciseCTO: WorkoutExerciseCTO;
|
|
20
19
|
session: WorkoutSession;
|
|
21
20
|
sessionExercise: WorkoutSessionExercise;
|
|
22
21
|
microcycleIndex: number;
|
|
@@ -31,6 +30,19 @@ export default class WorkoutSetService {
|
|
|
31
30
|
* and either rir is recorded or no plannedRir was expected (deload sets).
|
|
32
31
|
*/
|
|
33
32
|
static isCompleted(set: WorkoutSet): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Finds the previous microcycle's first set for an exercise to use for autoregulation.
|
|
35
|
+
*
|
|
36
|
+
* Uses the mesocycle's fixed exercise-to-session mapping to go directly to the
|
|
37
|
+
* correct session and exercise position rather than iterating all sessions.
|
|
38
|
+
*
|
|
39
|
+
* Returns undefined if the previous microcycle doesn't exist or has no sessions
|
|
40
|
+
* (the context may not have full history). Throws if the structure is present but
|
|
41
|
+
* inconsistent with the mesocycle plan.
|
|
42
|
+
*
|
|
43
|
+
* @throws {Error} If the session/exercise structure doesn't match the plan.
|
|
44
|
+
*/
|
|
45
|
+
private static findPreviousFirstSet;
|
|
34
46
|
/**
|
|
35
47
|
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
36
48
|
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkoutSetService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"WorkoutSetService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6CAA6C,CAAC;AAGtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sDAAsD,CAAC;AACnG,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG7F,OAAO,KAAK,2BAA2B,MAAM,6CAA6C,CAAC;AAE3F,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC;;;;;;;;OAQG;IACH,MAAM,CAAC,8BAA8B,CAAC,EACpC,OAAO,EACP,WAAW,EACX,OAAO,EACP,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,kBAAkB,EACnB,EAAE;QACD,OAAO,EAAE,2BAA2B,CAAC;QACrC,WAAW,EAAE,kBAAkB,CAAC;QAChC,OAAO,EAAE,cAAc,CAAC;QACxB,eAAe,EAAE,sBAAsB,CAAC;QACxC,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,IAAI;IA6DR;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IAQ5C;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAmEnC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAuDxC"}
|
|
@@ -9,44 +9,49 @@ export default class WorkoutSetService {
|
|
|
9
9
|
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
10
10
|
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
11
11
|
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
12
|
+
* - Using previous performance data to adjust progression via autoregulation.
|
|
12
13
|
*/
|
|
13
|
-
static generateSetsForSessionExercise({ context,
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
16
|
-
throw new Error(`
|
|
14
|
+
static generateSetsForSessionExercise({ context, exerciseCTO, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }) {
|
|
15
|
+
const { equipmentType, bestCalibration } = exerciseCTO;
|
|
16
|
+
if (!bestCalibration) {
|
|
17
|
+
throw new Error(`No calibration found for exercise ${exerciseCTO._id}, ${exerciseCTO.exerciseName}`);
|
|
17
18
|
}
|
|
18
19
|
const sets = [];
|
|
20
|
+
// For the first microcycle, use the CTO's previous performance data.
|
|
21
|
+
// For subsequent microcycles, look up the previous microcycle's first set from the context.
|
|
22
|
+
const previousFirstSet = microcycleIndex === 0
|
|
23
|
+
? (exerciseCTO.lastFirstSet ?? undefined)
|
|
24
|
+
: this.findPreviousFirstSet(context, exerciseCTO._id, microcycleIndex);
|
|
19
25
|
// Calculate progressed targets for the first set.
|
|
20
26
|
// For deload microcycles, use the previous microcycle's index so we base
|
|
21
27
|
// the deload on the last accumulation weight rather than progressing further.
|
|
22
|
-
const {
|
|
23
|
-
exercise,
|
|
24
|
-
calibration,
|
|
25
|
-
equipment,
|
|
28
|
+
const { targetReps: firstSetReps, targetWeight: firstSetWeight } = WorkoutExerciseService.calculateTargetRepsAndWeightForFirstSet({
|
|
29
|
+
exercise: exerciseCTO,
|
|
30
|
+
calibration: bestCalibration,
|
|
31
|
+
equipment: equipmentType,
|
|
26
32
|
microcycleIndex: isDeloadMicrocycle ? microcycleIndex - 1 : microcycleIndex,
|
|
27
|
-
firstMicrocycleRir: context.
|
|
33
|
+
firstMicrocycleRir: context.firstMicrocycleRir,
|
|
34
|
+
previousFirstSet
|
|
28
35
|
});
|
|
29
36
|
for (let setIndex = 0; setIndex < setCount; setIndex++) {
|
|
30
|
-
const { plannedReps, plannedWeight } = this.generateSetRepsAndWeight(
|
|
31
|
-
// If there is a previous set, base off that, otherwise use first set targets
|
|
32
|
-
sets[setIndex - 1]?.plannedReps || firstSetReps, sets[setIndex - 1]?.plannedWeight || firstSetWeight, setIndex, exercise.repRange, equipment, {
|
|
37
|
+
const { plannedReps, plannedWeight } = this.generateSetRepsAndWeight(sets[setIndex - 1]?.plannedReps || firstSetReps, sets[setIndex - 1]?.plannedWeight || firstSetWeight, setIndex, exerciseCTO.repRange, equipmentType, {
|
|
33
38
|
isDeloadMicrocycle,
|
|
34
39
|
sessionIndex,
|
|
35
40
|
plannedSessionCountPerMicrocycle: context.mesocycle.plannedSessionCountPerMicrocycle
|
|
36
41
|
});
|
|
37
42
|
const workoutSet = WorkoutSetSchema.parse({
|
|
38
|
-
userId:
|
|
39
|
-
workoutExerciseId:
|
|
43
|
+
userId: exerciseCTO.userId,
|
|
44
|
+
workoutExerciseId: exerciseCTO._id,
|
|
40
45
|
workoutSessionId: session._id,
|
|
41
46
|
workoutSessionExerciseId: sessionExercise._id,
|
|
42
47
|
plannedReps,
|
|
43
48
|
plannedWeight,
|
|
44
49
|
plannedRir: targetRir,
|
|
45
|
-
exerciseProperties:
|
|
50
|
+
exerciseProperties: bestCalibration.exerciseProperties
|
|
46
51
|
});
|
|
47
52
|
sets.push(workoutSet);
|
|
48
53
|
}
|
|
49
|
-
context.
|
|
54
|
+
context.addSets(sets);
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* Returns true if the set has been logged (has actual performance data).
|
|
@@ -58,6 +63,68 @@ export default class WorkoutSetService {
|
|
|
58
63
|
set.actualWeight != null &&
|
|
59
64
|
(set.rir != null || set.plannedRir == null));
|
|
60
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Finds the previous microcycle's first set for an exercise to use for autoregulation.
|
|
68
|
+
*
|
|
69
|
+
* Uses the mesocycle's fixed exercise-to-session mapping to go directly to the
|
|
70
|
+
* correct session and exercise position rather than iterating all sessions.
|
|
71
|
+
*
|
|
72
|
+
* Returns undefined if the previous microcycle doesn't exist or has no sessions
|
|
73
|
+
* (the context may not have full history). Throws if the structure is present but
|
|
74
|
+
* inconsistent with the mesocycle plan.
|
|
75
|
+
*
|
|
76
|
+
* @throws {Error} If the session/exercise structure doesn't match the plan.
|
|
77
|
+
*/
|
|
78
|
+
static findPreviousFirstSet(context, exerciseId, microcycleIndex) {
|
|
79
|
+
if (microcycleIndex <= 0) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const previousMicrocycle = context.microcyclesInOrder[microcycleIndex - 1];
|
|
83
|
+
if (!previousMicrocycle || previousMicrocycle.sessionOrder.length === 0) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
// Exercise-to-session mapping is fixed for the mesocycle — look up directly
|
|
87
|
+
const sessionIndex = context.exerciseIdToSessionIndex?.get(exerciseId);
|
|
88
|
+
if (sessionIndex == null) {
|
|
89
|
+
throw new Error(`Exercise ${exerciseId} has no session mapping in the mesocycle plan`);
|
|
90
|
+
}
|
|
91
|
+
const sessionId = previousMicrocycle.sessionOrder[sessionIndex];
|
|
92
|
+
if (!sessionId) {
|
|
93
|
+
// The previous microcycle may have fewer sessions than the plan (e.g. pruned
|
|
94
|
+
// during early deload). Treat as missing history rather than a structural error.
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const session = context.sessionMap.get(sessionId);
|
|
98
|
+
if (!session) {
|
|
99
|
+
throw new Error(`Session ${sessionId} not found in context`);
|
|
100
|
+
}
|
|
101
|
+
// Exercise order within a session is consistent — find the index from the plan
|
|
102
|
+
const plannedCTOs = context.plannedSessionExerciseCTOs?.[sessionIndex];
|
|
103
|
+
if (!plannedCTOs) {
|
|
104
|
+
throw new Error(`No planned CTOs for session index ${sessionIndex}`);
|
|
105
|
+
}
|
|
106
|
+
const exerciseIndex = plannedCTOs.findIndex((cto) => cto._id === exerciseId);
|
|
107
|
+
if (exerciseIndex === -1) {
|
|
108
|
+
throw new Error(`Exercise ${exerciseId} not found in planned CTOs for session ${sessionIndex}`);
|
|
109
|
+
}
|
|
110
|
+
const seId = session.sessionExerciseOrder[exerciseIndex];
|
|
111
|
+
if (!seId) {
|
|
112
|
+
throw new Error(`No session exercise at index ${exerciseIndex} in session ${sessionId}`);
|
|
113
|
+
}
|
|
114
|
+
const sessionExercise = context.sessionExerciseMap.get(seId);
|
|
115
|
+
if (!sessionExercise) {
|
|
116
|
+
throw new Error(`Session exercise ${seId} not found in context`);
|
|
117
|
+
}
|
|
118
|
+
const firstSetId = sessionExercise.setOrder[0];
|
|
119
|
+
if (!firstSetId) {
|
|
120
|
+
throw new Error(`Session exercise ${seId} for exercise ${exerciseId} has no sets`);
|
|
121
|
+
}
|
|
122
|
+
const set = context.setMap.get(firstSetId);
|
|
123
|
+
if (!set) {
|
|
124
|
+
throw new Error(`Set ${firstSetId} not found in context`);
|
|
125
|
+
}
|
|
126
|
+
return set;
|
|
127
|
+
}
|
|
61
128
|
/**
|
|
62
129
|
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
63
130
|
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkoutSetService.js","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WorkoutSetService.js","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
import type { UUID } from 'crypto';
|
|
2
|
+
import type { WorkoutExerciseCTO } from '../../../ctos/workout/WorkoutExerciseCTO.js';
|
|
1
3
|
import type { WorkoutEquipmentType } from '../../../documents/workout/WorkoutEquipmentType.js';
|
|
2
|
-
import type {
|
|
3
|
-
ExerciseRepRange,
|
|
4
|
-
WorkoutExercise
|
|
5
|
-
} from '../../../documents/workout/WorkoutExercise.js';
|
|
6
|
-
import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
|
|
4
|
+
import type { ExerciseRepRange } from '../../../documents/workout/WorkoutExercise.js';
|
|
7
5
|
import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
|
|
8
6
|
import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
|
|
9
7
|
import { WorkoutSetSchema, type WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
|
|
@@ -19,11 +17,11 @@ export default class WorkoutSetService {
|
|
|
19
17
|
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
20
18
|
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
21
19
|
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
20
|
+
* - Using previous performance data to adjust progression via autoregulation.
|
|
22
21
|
*/
|
|
23
22
|
static generateSetsForSessionExercise({
|
|
24
23
|
context,
|
|
25
|
-
|
|
26
|
-
calibration,
|
|
24
|
+
exerciseCTO,
|
|
27
25
|
session,
|
|
28
26
|
sessionExercise,
|
|
29
27
|
microcycleIndex,
|
|
@@ -33,8 +31,7 @@ export default class WorkoutSetService {
|
|
|
33
31
|
isDeloadMicrocycle
|
|
34
32
|
}: {
|
|
35
33
|
context: WorkoutMesocyclePlanContext;
|
|
36
|
-
|
|
37
|
-
calibration: WorkoutExerciseCalibration;
|
|
34
|
+
exerciseCTO: WorkoutExerciseCTO;
|
|
38
35
|
session: WorkoutSession;
|
|
39
36
|
sessionExercise: WorkoutSessionExercise;
|
|
40
37
|
microcycleIndex: number;
|
|
@@ -43,35 +40,42 @@ export default class WorkoutSetService {
|
|
|
43
40
|
targetRir: number | null;
|
|
44
41
|
isDeloadMicrocycle: boolean;
|
|
45
42
|
}): void {
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
43
|
+
const { equipmentType, bestCalibration } = exerciseCTO;
|
|
44
|
+
if (!bestCalibration) {
|
|
48
45
|
throw new Error(
|
|
49
|
-
`
|
|
46
|
+
`No calibration found for exercise ${exerciseCTO._id}, ${exerciseCTO.exerciseName}`
|
|
50
47
|
);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
const sets: WorkoutSet[] = [];
|
|
54
51
|
|
|
52
|
+
// For the first microcycle, use the CTO's previous performance data.
|
|
53
|
+
// For subsequent microcycles, look up the previous microcycle's first set from the context.
|
|
54
|
+
const previousFirstSet =
|
|
55
|
+
microcycleIndex === 0
|
|
56
|
+
? (exerciseCTO.lastFirstSet ?? undefined)
|
|
57
|
+
: this.findPreviousFirstSet(context, exerciseCTO._id, microcycleIndex);
|
|
58
|
+
|
|
55
59
|
// Calculate progressed targets for the first set.
|
|
56
60
|
// For deload microcycles, use the previous microcycle's index so we base
|
|
57
61
|
// the deload on the last accumulation weight rather than progressing further.
|
|
58
|
-
const {
|
|
62
|
+
const { targetReps: firstSetReps, targetWeight: firstSetWeight } =
|
|
59
63
|
WorkoutExerciseService.calculateTargetRepsAndWeightForFirstSet({
|
|
60
|
-
exercise,
|
|
61
|
-
calibration,
|
|
62
|
-
equipment,
|
|
64
|
+
exercise: exerciseCTO,
|
|
65
|
+
calibration: bestCalibration,
|
|
66
|
+
equipment: equipmentType,
|
|
63
67
|
microcycleIndex: isDeloadMicrocycle ? microcycleIndex - 1 : microcycleIndex,
|
|
64
|
-
firstMicrocycleRir: context.
|
|
68
|
+
firstMicrocycleRir: context.firstMicrocycleRir,
|
|
69
|
+
previousFirstSet
|
|
65
70
|
});
|
|
66
71
|
|
|
67
72
|
for (let setIndex = 0; setIndex < setCount; setIndex++) {
|
|
68
73
|
const { plannedReps, plannedWeight } = this.generateSetRepsAndWeight(
|
|
69
|
-
// If there is a previous set, base off that, otherwise use first set targets
|
|
70
74
|
sets[setIndex - 1]?.plannedReps || firstSetReps,
|
|
71
75
|
sets[setIndex - 1]?.plannedWeight || firstSetWeight,
|
|
72
76
|
setIndex,
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
exerciseCTO.repRange,
|
|
78
|
+
equipmentType,
|
|
75
79
|
{
|
|
76
80
|
isDeloadMicrocycle,
|
|
77
81
|
sessionIndex,
|
|
@@ -80,20 +84,20 @@ export default class WorkoutSetService {
|
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
const workoutSet = WorkoutSetSchema.parse({
|
|
83
|
-
userId:
|
|
84
|
-
workoutExerciseId:
|
|
87
|
+
userId: exerciseCTO.userId,
|
|
88
|
+
workoutExerciseId: exerciseCTO._id,
|
|
85
89
|
workoutSessionId: session._id,
|
|
86
90
|
workoutSessionExerciseId: sessionExercise._id,
|
|
87
91
|
plannedReps,
|
|
88
92
|
plannedWeight,
|
|
89
93
|
plannedRir: targetRir,
|
|
90
|
-
exerciseProperties:
|
|
94
|
+
exerciseProperties: bestCalibration.exerciseProperties
|
|
91
95
|
});
|
|
92
96
|
|
|
93
97
|
sets.push(workoutSet);
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
context.
|
|
100
|
+
context.addSets(sets);
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
/**
|
|
@@ -109,6 +113,85 @@ export default class WorkoutSetService {
|
|
|
109
113
|
);
|
|
110
114
|
}
|
|
111
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Finds the previous microcycle's first set for an exercise to use for autoregulation.
|
|
118
|
+
*
|
|
119
|
+
* Uses the mesocycle's fixed exercise-to-session mapping to go directly to the
|
|
120
|
+
* correct session and exercise position rather than iterating all sessions.
|
|
121
|
+
*
|
|
122
|
+
* Returns undefined if the previous microcycle doesn't exist or has no sessions
|
|
123
|
+
* (the context may not have full history). Throws if the structure is present but
|
|
124
|
+
* inconsistent with the mesocycle plan.
|
|
125
|
+
*
|
|
126
|
+
* @throws {Error} If the session/exercise structure doesn't match the plan.
|
|
127
|
+
*/
|
|
128
|
+
private static findPreviousFirstSet(
|
|
129
|
+
context: WorkoutMesocyclePlanContext,
|
|
130
|
+
exerciseId: UUID,
|
|
131
|
+
microcycleIndex: number
|
|
132
|
+
): WorkoutSet | undefined {
|
|
133
|
+
if (microcycleIndex <= 0) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const previousMicrocycle = context.microcyclesInOrder[microcycleIndex - 1];
|
|
138
|
+
if (!previousMicrocycle || previousMicrocycle.sessionOrder.length === 0) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Exercise-to-session mapping is fixed for the mesocycle — look up directly
|
|
143
|
+
const sessionIndex = context.exerciseIdToSessionIndex?.get(exerciseId);
|
|
144
|
+
if (sessionIndex == null) {
|
|
145
|
+
throw new Error(`Exercise ${exerciseId} has no session mapping in the mesocycle plan`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const sessionId = previousMicrocycle.sessionOrder[sessionIndex];
|
|
149
|
+
if (!sessionId) {
|
|
150
|
+
// The previous microcycle may have fewer sessions than the plan (e.g. pruned
|
|
151
|
+
// during early deload). Treat as missing history rather than a structural error.
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const session = context.sessionMap.get(sessionId);
|
|
156
|
+
if (!session) {
|
|
157
|
+
throw new Error(`Session ${sessionId} not found in context`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Exercise order within a session is consistent — find the index from the plan
|
|
161
|
+
const plannedCTOs = context.plannedSessionExerciseCTOs?.[sessionIndex];
|
|
162
|
+
if (!plannedCTOs) {
|
|
163
|
+
throw new Error(`No planned CTOs for session index ${sessionIndex}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const exerciseIndex = plannedCTOs.findIndex((cto) => cto._id === exerciseId);
|
|
167
|
+
if (exerciseIndex === -1) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Exercise ${exerciseId} not found in planned CTOs for session ${sessionIndex}`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const seId = session.sessionExerciseOrder[exerciseIndex];
|
|
174
|
+
if (!seId) {
|
|
175
|
+
throw new Error(`No session exercise at index ${exerciseIndex} in session ${sessionId}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const sessionExercise = context.sessionExerciseMap.get(seId);
|
|
179
|
+
if (!sessionExercise) {
|
|
180
|
+
throw new Error(`Session exercise ${seId} not found in context`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const firstSetId = sessionExercise.setOrder[0];
|
|
184
|
+
if (!firstSetId) {
|
|
185
|
+
throw new Error(`Session exercise ${seId} for exercise ${exerciseId} has no sets`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const set = context.setMap.get(firstSetId);
|
|
189
|
+
if (!set) {
|
|
190
|
+
throw new Error(`Set ${firstSetId} not found in context`);
|
|
191
|
+
}
|
|
192
|
+
return set;
|
|
193
|
+
}
|
|
194
|
+
|
|
112
195
|
/**
|
|
113
196
|
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
114
197
|
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UUID } from 'crypto';
|
|
2
|
+
import type { WorkoutMuscleGroupVolumeCTO, WorkoutVolumeLandmarkEstimate } from '../../../../ctos/workout/WorkoutMuscleGroupVolumeCTO.js';
|
|
2
3
|
import type WorkoutMesocyclePlanContext from '../../Mesocycle/WorkoutMesocyclePlanContext.js';
|
|
3
4
|
/**
|
|
4
5
|
* A service for handling volume planning operations across microcycles.
|
|
@@ -19,6 +20,28 @@ import type WorkoutMesocyclePlanContext from '../../Mesocycle/WorkoutMesocyclePl
|
|
|
19
20
|
export default class WorkoutVolumePlanningService {
|
|
20
21
|
private static readonly MAX_SETS_PER_EXERCISE;
|
|
21
22
|
private static readonly MAX_SETS_PER_MUSCLE_GROUP_PER_SESSION;
|
|
23
|
+
/** Minimum average RSM required for a mesocycle to count toward MEV estimation. */
|
|
24
|
+
private static readonly MEV_RSM_THRESHOLD;
|
|
25
|
+
/** Default estimated MEV when no qualifying mesocycle history exists. */
|
|
26
|
+
private static readonly DEFAULT_MEV;
|
|
27
|
+
/** Minimum average performance score (or recovery presence) to count a mesocycle toward MRV estimation. */
|
|
28
|
+
private static readonly MRV_PERFORMANCE_THRESHOLD;
|
|
29
|
+
/** Extra sets added above the historical peak when no stressed mesocycles exist, to estimate MRV. */
|
|
30
|
+
private static readonly MRV_HEADROOM;
|
|
31
|
+
/** Default estimated MRV when no mesocycle history exists at all. */
|
|
32
|
+
private static readonly DEFAULT_MRV;
|
|
33
|
+
/** RSM bracket upper bound for "below MEV" proximity (0 to this value inclusive). */
|
|
34
|
+
private static readonly MEV_PROXIMITY_BELOW_THRESHOLD;
|
|
35
|
+
/** RSM bracket upper bound for "at MEV" proximity (above BELOW threshold up to this value inclusive). */
|
|
36
|
+
private static readonly MEV_PROXIMITY_AT_THRESHOLD;
|
|
37
|
+
/** Recommended set adjustment when volume is below MEV. */
|
|
38
|
+
private static readonly MEV_BELOW_SET_ADJUSTMENT;
|
|
39
|
+
/** Recommended set adjustment when volume is above MEV. */
|
|
40
|
+
private static readonly MEV_ABOVE_SET_ADJUSTMENT;
|
|
41
|
+
/** Maximum sets that can be added to a single exercise in one progression step. */
|
|
42
|
+
private static readonly MAX_SET_ADDITION_PER_EXERCISE;
|
|
43
|
+
/** Maximum total sets to distribute across a muscle group in one progression step. */
|
|
44
|
+
private static readonly MAX_TOTAL_SET_ADDITIONS;
|
|
22
45
|
/**
|
|
23
46
|
* Calculates the set plan for an entire microcycle.
|
|
24
47
|
*/
|
|
@@ -26,6 +49,42 @@ export default class WorkoutVolumePlanningService {
|
|
|
26
49
|
exerciseIdToSetCount: Map<UUID, number>;
|
|
27
50
|
recoveryExerciseIds: Set<UUID>;
|
|
28
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Estimates MEV, MRV, and MAV for a muscle group based on historical data
|
|
54
|
+
* across completed mesocycles.
|
|
55
|
+
*
|
|
56
|
+
* @param volumeCTO The WorkoutMuscleGroupVolumeCTO containing mesocycle
|
|
57
|
+
* history for this muscle group.
|
|
58
|
+
*/
|
|
59
|
+
static estimateVolumeLandmarks(volumeCTO: WorkoutMuscleGroupVolumeCTO): WorkoutVolumeLandmarkEstimate;
|
|
60
|
+
/**
|
|
61
|
+
* Evaluates MEV (Minimum Effective Volume) proximity for a muscle group based on
|
|
62
|
+
* RSM scores from the first microcycle. Called when generating the second microcycle to adjust
|
|
63
|
+
* the volume baseline.
|
|
64
|
+
*
|
|
65
|
+
* Returns `null` when the first microcycle is incomplete or has no RSM data for the muscle group.
|
|
66
|
+
*
|
|
67
|
+
* @param context The mesocycle planning context containing microcycle/session/exercise data.
|
|
68
|
+
* @param muscleGroupId The muscle group to evaluate.
|
|
69
|
+
*/
|
|
70
|
+
static evaluateMevProximity(context: WorkoutMesocyclePlanContext, muscleGroupId: UUID): {
|
|
71
|
+
/** 'below' = RSM 0-3, 'at' = RSM 4-6, 'above' = RSM 7-9 */
|
|
72
|
+
proximity: 'below' | 'at' | 'above';
|
|
73
|
+
/**
|
|
74
|
+
* Recommended total set adjustment for this muscle group.
|
|
75
|
+
* Positive = add sets, negative = remove sets, 0 = no change.
|
|
76
|
+
* Range: -2 to +3
|
|
77
|
+
*/
|
|
78
|
+
recommendedSetAdjustment: number;
|
|
79
|
+
/** The average RSM across session exercises targeting this muscle group. */
|
|
80
|
+
averageRsm: number;
|
|
81
|
+
} | null;
|
|
82
|
+
/**
|
|
83
|
+
* Applies MEV proximity adjustments based on RSM data from the first microcycle.
|
|
84
|
+
* Adjusts set counts per muscle group when the first microcycle indicates volume
|
|
85
|
+
* was below or above MEV.
|
|
86
|
+
*/
|
|
87
|
+
private static applyMevProximityAdjustments;
|
|
29
88
|
/**
|
|
30
89
|
* Calculates the set count for each exercise in a particular muscle group for this microcycle.
|
|
31
90
|
*
|
|
@@ -40,8 +99,19 @@ export default class WorkoutVolumePlanningService {
|
|
|
40
99
|
* for the entire microcycle, regardless of which session those exercises are in.
|
|
41
100
|
*
|
|
42
101
|
* Baseline: 2 sets per exercise in the muscle group.
|
|
43
|
-
* Progression: add 1
|
|
44
|
-
*
|
|
102
|
+
* Progression: add 1 set per muscle group every `progressionInterval` microcycles.
|
|
103
|
+
* - MuscleGain (interval 1): every microcycle
|
|
104
|
+
* - Cut (interval 2): every other microcycle
|
|
105
|
+
* - Resensitization (interval 0): no progression (flat 2 sets per exercise)
|
|
106
|
+
*
|
|
107
|
+
* @param microcycleIndex The index of the current microcycle.
|
|
108
|
+
* @param totalExercisesInMuscleGroupForMicrocycle Total exercises in the muscle group for
|
|
109
|
+
* this microcycle.
|
|
110
|
+
* @param exerciseIndexInMuscleGroupForMicrocycle Index of the current exercise within the
|
|
111
|
+
* muscle group.
|
|
112
|
+
* @param isDeloadMicrocycle Whether this is a deload microcycle.
|
|
113
|
+
* @param progressionInterval Number of microcycles between each set addition. 0 means no
|
|
114
|
+
* progression.
|
|
45
115
|
*/
|
|
46
116
|
private static calculateBaselineSetCount;
|
|
47
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkoutVolumePlanningService.d.ts","sourceRoot":"","sources":["../../../../../src/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"WorkoutVolumePlanningService.d.ts","sourceRoot":"","sources":["../../../../../src/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC,OAAO,KAAK,EACV,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,yDAAyD,CAAC;AAEjE,OAAO,KAAK,2BAA2B,MAAM,gDAAgD,CAAC;AAI9F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,OAAO,4BAA4B;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAK;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qCAAqC,CAAM;IAEnE,mFAAmF;IACnF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAK;IAE9C,yEAAyE;IACzE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IAExC,2GAA2G;IAC3G,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAO;IAExD,qGAAqG;IACrG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAK;IAEzC,qEAAqE;IACrE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAK;IAExC,qFAAqF;IACrF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAK;IAE1D,yGAAyG;IACzG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAK;IAEvD,2DAA2D;IAC3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IAErD,2DAA2D;IAC3D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAM;IAEtD,mFAAmF;IACnF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAK;IAE1D,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IAEpD;;OAEG;IACH,MAAM,CAAC,6BAA6B,CAClC,OAAO,EAAE,2BAA2B,EACpC,eAAe,EAAE,MAAM,EACvB,kBAAkB,EAAE,OAAO,GAC1B;QAAE,oBAAoB,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAAC,mBAAmB,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;KAAE;IAuC9E;;;;;;OAMG;IACH,MAAM,CAAC,uBAAuB,CAC5B,SAAS,EAAE,2BAA2B,GACrC,6BAA6B;IAkDhC;;;;;;;;;OASG;IACH,MAAM,CAAC,oBAAoB,CACzB,OAAO,EAAE,2BAA2B,EACpC,aAAa,EAAE,IAAI,GAClB;QACD,2DAA2D;QAC3D,SAAS,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC;QAEpC;;;;WAIG;QACH,wBAAwB,EAAE,MAAM,CAAC;QAEjC,4EAA4E;QAC5E,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI;IA+CR;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,4BAA4B;IA4B3C;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,6CAA6C;IA0Q5D;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,MAAM,CAAC,yBAAyB;CAgCzC"}
|