@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,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"}
|