@aneuhold/core-ts-db-lib 4.0.4 → 4.1.1
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 +33 -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/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 +78 -0
- package/lib/documents/workout/WorkoutMesocycle.js.map +1 -0
- package/lib/documents/workout/WorkoutMesocycle.ts +95 -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/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 +4 -3
|
@@ -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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
|
|
2
|
+
import WorkoutSFRService from '../util/SFR/WorkoutSFRService.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A service for handling operations related to {@link WorkoutSessionExercise}s.
|
|
6
|
+
*/
|
|
7
|
+
export default class WorkoutSessionExerciseService {
|
|
8
|
+
/**
|
|
9
|
+
* Calculates the total Raw Stimulus Magnitude for a specific exercise within a session.
|
|
10
|
+
*
|
|
11
|
+
* @param sessionExercise The workout session exercise.
|
|
12
|
+
*/
|
|
13
|
+
static getRsmTotal(sessionExercise: WorkoutSessionExercise): number | null {
|
|
14
|
+
return WorkoutSFRService.getRsmTotal(sessionExercise.rsm);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculates the total fatigue score for a specific exercise within a session.
|
|
19
|
+
*
|
|
20
|
+
* @param sessionExercise The workout session exercise.
|
|
21
|
+
*/
|
|
22
|
+
static getFatigueTotal(sessionExercise: WorkoutSessionExercise): number | null {
|
|
23
|
+
return WorkoutSFRService.getFatigueTotal(sessionExercise.fatigue);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculates the Stimulus to Fatigue Ratio (SFR) for a specific exercise.
|
|
28
|
+
*
|
|
29
|
+
* @param sessionExercise The workout session exercise.
|
|
30
|
+
*/
|
|
31
|
+
static getSFR(sessionExercise: WorkoutSessionExercise): number | null {
|
|
32
|
+
return WorkoutSFRService.getSFR(sessionExercise.rsm, sessionExercise.fatigue);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Uses the soreness/performance table from the workout model notes to recommend whether to add
|
|
37
|
+
* sets next microcycle or employ recovery sessions.
|
|
38
|
+
*
|
|
39
|
+
* Interpretation:
|
|
40
|
+
* - Returns `-1` when recovery sessions should be employed.
|
|
41
|
+
* - Returns `0` when no sets should be added.
|
|
42
|
+
* - Returns a non-negative integer when sets should be added.
|
|
43
|
+
* - Returns `null` when insufficient data is available.
|
|
44
|
+
*
|
|
45
|
+
* The table is:
|
|
46
|
+
*
|
|
47
|
+
* | Soreness Score ↓ \ Performance Score → | 0 | 1 | 2 | 3 |
|
|
48
|
+
|---|---|---|---|---|
|
|
49
|
+
| **0** | Add 1–3 sets | Add 0–2 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
|
|
50
|
+
| **1** | Add 1–2 sets | Add 0–1 sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
|
|
51
|
+
| **2** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
|
|
52
|
+
| **3** | Do not add sets | Do not add sets | Do not add sets | Employ recovery sessions (see Fatigue Management) |
|
|
53
|
+
*/
|
|
54
|
+
static getRecommendedSetAdditionsOrRecovery(
|
|
55
|
+
workoutSessionExercise: WorkoutSessionExercise
|
|
56
|
+
): number | null {
|
|
57
|
+
const { performanceScore, sorenessScore } = workoutSessionExercise;
|
|
58
|
+
if (sorenessScore == null || performanceScore == null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Table mapping (sorenessScore rows, performanceScore columns).
|
|
63
|
+
// Values are representative set additions (midpoint of table ranges), or -1 for recovery.
|
|
64
|
+
const table: number[][] = [
|
|
65
|
+
// Soreness 0: [Add 1-3, Add 0-2, Do not add, Recovery]
|
|
66
|
+
[2, 1, 0, -1],
|
|
67
|
+
// Soreness 1: [Add 1-2, Add 0-1, Do not add, Recovery]
|
|
68
|
+
[1, 0, 0, -1],
|
|
69
|
+
// Soreness 2: [Do not add, Do not add, Do not add, Recovery]
|
|
70
|
+
[0, 0, 0, -1],
|
|
71
|
+
// Soreness 3: [Do not add, Do not add, Do not add, Recovery]
|
|
72
|
+
[0, 0, 0, -1]
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
return table[sorenessScore]?.[performanceScore] ?? null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
|
|
2
|
+
import type { WorkoutExerciseCalibration } from '../../../documents/workout/WorkoutExerciseCalibration.js';
|
|
3
|
+
import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
|
|
4
|
+
import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
|
|
5
|
+
import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
|
|
6
|
+
export default class WorkoutSetService {
|
|
7
|
+
/**
|
|
8
|
+
* Generates a list of workout sets for a given session exercise based on progression logic.
|
|
9
|
+
*
|
|
10
|
+
* This handles the "micro" decisions of load selection:
|
|
11
|
+
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
12
|
+
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
13
|
+
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
14
|
+
*/
|
|
15
|
+
static generateSetsForSessionExercise({ context, exercise, calibration, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }: {
|
|
16
|
+
context: WorkoutMesocyclePlanContext;
|
|
17
|
+
exercise: WorkoutExercise;
|
|
18
|
+
calibration: WorkoutExerciseCalibration;
|
|
19
|
+
session: WorkoutSession;
|
|
20
|
+
sessionExercise: WorkoutSessionExercise;
|
|
21
|
+
microcycleIndex: number;
|
|
22
|
+
sessionIndex: number;
|
|
23
|
+
setCount: number;
|
|
24
|
+
targetRir: number;
|
|
25
|
+
isDeloadMicrocycle: boolean;
|
|
26
|
+
}): void;
|
|
27
|
+
/**
|
|
28
|
+
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
29
|
+
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
30
|
+
*
|
|
31
|
+
* This needs to be checked with the source material to see if it needs to be adjusted based
|
|
32
|
+
* on actual reps performed in previous sets.
|
|
33
|
+
*/
|
|
34
|
+
private static generateSetRepsAndWeight;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=WorkoutSetService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkoutSetService.d.ts","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EAChB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0DAA0D,CAAC;AAC3G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8CAA8C,CAAC;AACnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sDAAsD,CAAC;AAInG,OAAO,KAAK,2BAA2B,MAAM,6CAA6C,CAAC;AAE3F,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC;;;;;;;OAOG;IACH,MAAM,CAAC,8BAA8B,CAAC,EACpC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,kBAAkB,EACnB,EAAE;QACD,OAAO,EAAE,2BAA2B,CAAC;QACrC,QAAQ,EAAE,eAAe,CAAC;QAC1B,WAAW,EAAE,0BAA0B,CAAC;QACxC,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,CAAC;QAClB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,IAAI;IAoDR;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAiDxC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { WorkoutSetSchema } from '../../../documents/workout/WorkoutSet.js';
|
|
2
|
+
import WorkoutEquipmentTypeService from '../EquipmentType/WorkoutEquipmentTypeService.js';
|
|
3
|
+
import WorkoutExerciseService from '../Exercise/WorkoutExerciseService.js';
|
|
4
|
+
export default class WorkoutSetService {
|
|
5
|
+
/**
|
|
6
|
+
* Generates a list of workout sets for a given session exercise based on progression logic.
|
|
7
|
+
*
|
|
8
|
+
* This handles the "micro" decisions of load selection:
|
|
9
|
+
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
10
|
+
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
11
|
+
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
12
|
+
*/
|
|
13
|
+
static generateSetsForSessionExercise({ context, exercise, calibration, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }) {
|
|
14
|
+
const equipment = context.equipmentMap.get(exercise.workoutEquipmentTypeId);
|
|
15
|
+
if (!equipment) {
|
|
16
|
+
throw new Error(`Equipment type not found for exercise ${exercise._id}, ${exercise.exerciseName}`);
|
|
17
|
+
}
|
|
18
|
+
const sets = [];
|
|
19
|
+
// Calculate progressed targets for the first set
|
|
20
|
+
const { targetWeight: firstSetWeight, targetReps: firstSetReps } = WorkoutExerciseService.calculateTargetRepsAndWeightForFirstSet({
|
|
21
|
+
exercise,
|
|
22
|
+
calibration,
|
|
23
|
+
equipment,
|
|
24
|
+
microcycleIndex,
|
|
25
|
+
firstMicrocycleRir: context.FIRST_MICROCYCLE_RIR
|
|
26
|
+
});
|
|
27
|
+
for (let setIndex = 0; setIndex < setCount; setIndex++) {
|
|
28
|
+
const { plannedReps, plannedWeight } = this.generateSetRepsAndWeight(
|
|
29
|
+
// If there is a previous set, base off that, otherwise use first set targets
|
|
30
|
+
sets[setIndex - 1]?.plannedReps || firstSetReps, sets[setIndex - 1]?.plannedWeight || firstSetWeight, setIndex, exercise.repRange, equipment, {
|
|
31
|
+
isDeloadMicrocycle,
|
|
32
|
+
sessionIndex,
|
|
33
|
+
plannedSessionCountPerMicrocycle: context.mesocycle.plannedSessionCountPerMicrocycle
|
|
34
|
+
});
|
|
35
|
+
const workoutSet = WorkoutSetSchema.parse({
|
|
36
|
+
userId: exercise.userId,
|
|
37
|
+
workoutExerciseId: exercise._id,
|
|
38
|
+
workoutSessionId: session._id,
|
|
39
|
+
workoutSessionExerciseId: sessionExercise._id,
|
|
40
|
+
plannedReps,
|
|
41
|
+
plannedWeight,
|
|
42
|
+
plannedRir: targetRir,
|
|
43
|
+
exerciseProperties: calibration.exerciseProperties
|
|
44
|
+
});
|
|
45
|
+
sets.push(workoutSet);
|
|
46
|
+
}
|
|
47
|
+
context.setsToCreate.push(...sets);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
51
|
+
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
52
|
+
*
|
|
53
|
+
* This needs to be checked with the source material to see if it needs to be adjusted based
|
|
54
|
+
* on actual reps performed in previous sets.
|
|
55
|
+
*/
|
|
56
|
+
static generateSetRepsAndWeight(firstSetOrPreviousSetReps, firstSetOrPreviousSetWeight, setIndex, repRange, equipment, deloadInfo) {
|
|
57
|
+
const repRangeValues = WorkoutExerciseService.getRepRangeValues(repRange);
|
|
58
|
+
let currentReps = firstSetOrPreviousSetReps;
|
|
59
|
+
let currentWeight = firstSetOrPreviousSetWeight;
|
|
60
|
+
// Ideally, drop 2 reps per set within the session (19 -> 17 -> 15, etc.)
|
|
61
|
+
// But if that would go below the min reps, keep it at min reps.
|
|
62
|
+
if (firstSetOrPreviousSetReps - 2 < repRangeValues.min && setIndex > 0) {
|
|
63
|
+
// Reduce weight by 2% using the same technique as progression
|
|
64
|
+
const twoPercentDecrease = currentWeight / 1.02;
|
|
65
|
+
const reducedWeight = WorkoutEquipmentTypeService.findNearestWeight(equipment, twoPercentDecrease, 'down');
|
|
66
|
+
if (reducedWeight !== null) {
|
|
67
|
+
currentWeight = reducedWeight;
|
|
68
|
+
}
|
|
69
|
+
else if (firstSetOrPreviousSetReps - 2 > 5) {
|
|
70
|
+
// If we can't reduce weight, but we can reduce reps without going too low,
|
|
71
|
+
// then do that.
|
|
72
|
+
currentReps = firstSetOrPreviousSetReps - 2;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (setIndex > 0) {
|
|
76
|
+
currentReps = firstSetOrPreviousSetReps - 2;
|
|
77
|
+
}
|
|
78
|
+
// Apply deload modifications, but only if the set is the first in the session
|
|
79
|
+
if (deloadInfo.isDeloadMicrocycle && setIndex === 0) {
|
|
80
|
+
currentReps = Math.floor(firstSetOrPreviousSetReps / 2);
|
|
81
|
+
// First half of deload microcycle: same weight, half reps/sets
|
|
82
|
+
// Second half: half weight too
|
|
83
|
+
if (deloadInfo.sessionIndex >= Math.floor(deloadInfo.plannedSessionCountPerMicrocycle / 2)) {
|
|
84
|
+
currentWeight = Math.floor(currentWeight / 2);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { plannedReps: currentReps, plannedWeight: currentWeight };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=WorkoutSetService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkoutSetService.js","sourceRoot":"","sources":["../../../../src/services/workout/Set/WorkoutSetService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
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';
|
|
7
|
+
import type { WorkoutSession } from '../../../documents/workout/WorkoutSession.js';
|
|
8
|
+
import type { WorkoutSessionExercise } from '../../../documents/workout/WorkoutSessionExercise.js';
|
|
9
|
+
import { WorkoutSetSchema, type WorkoutSet } from '../../../documents/workout/WorkoutSet.js';
|
|
10
|
+
import WorkoutEquipmentTypeService from '../EquipmentType/WorkoutEquipmentTypeService.js';
|
|
11
|
+
import WorkoutExerciseService from '../Exercise/WorkoutExerciseService.js';
|
|
12
|
+
import type WorkoutMesocyclePlanContext from '../Mesocycle/WorkoutMesocyclePlanContext.js';
|
|
13
|
+
|
|
14
|
+
export default class WorkoutSetService {
|
|
15
|
+
/**
|
|
16
|
+
* Generates a list of workout sets for a given session exercise based on progression logic.
|
|
17
|
+
*
|
|
18
|
+
* This handles the "micro" decisions of load selection:
|
|
19
|
+
* - Calculating the initial target weight/reps based on microcycle progression.
|
|
20
|
+
* - Handling intra-session fatigue (dropping reps/weight across sets).
|
|
21
|
+
* - Applying Deload phase modifications (cutting volume/intensity).
|
|
22
|
+
*/
|
|
23
|
+
static generateSetsForSessionExercise({
|
|
24
|
+
context,
|
|
25
|
+
exercise,
|
|
26
|
+
calibration,
|
|
27
|
+
session,
|
|
28
|
+
sessionExercise,
|
|
29
|
+
microcycleIndex,
|
|
30
|
+
sessionIndex,
|
|
31
|
+
setCount,
|
|
32
|
+
targetRir,
|
|
33
|
+
isDeloadMicrocycle
|
|
34
|
+
}: {
|
|
35
|
+
context: WorkoutMesocyclePlanContext;
|
|
36
|
+
exercise: WorkoutExercise;
|
|
37
|
+
calibration: WorkoutExerciseCalibration;
|
|
38
|
+
session: WorkoutSession;
|
|
39
|
+
sessionExercise: WorkoutSessionExercise;
|
|
40
|
+
microcycleIndex: number;
|
|
41
|
+
sessionIndex: number;
|
|
42
|
+
setCount: number;
|
|
43
|
+
targetRir: number;
|
|
44
|
+
isDeloadMicrocycle: boolean;
|
|
45
|
+
}): void {
|
|
46
|
+
const equipment = context.equipmentMap.get(exercise.workoutEquipmentTypeId);
|
|
47
|
+
if (!equipment) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Equipment type not found for exercise ${exercise._id}, ${exercise.exerciseName}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sets: WorkoutSet[] = [];
|
|
54
|
+
|
|
55
|
+
// Calculate progressed targets for the first set
|
|
56
|
+
const { targetWeight: firstSetWeight, targetReps: firstSetReps } =
|
|
57
|
+
WorkoutExerciseService.calculateTargetRepsAndWeightForFirstSet({
|
|
58
|
+
exercise,
|
|
59
|
+
calibration,
|
|
60
|
+
equipment,
|
|
61
|
+
microcycleIndex,
|
|
62
|
+
firstMicrocycleRir: context.FIRST_MICROCYCLE_RIR
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (let setIndex = 0; setIndex < setCount; setIndex++) {
|
|
66
|
+
const { plannedReps, plannedWeight } = this.generateSetRepsAndWeight(
|
|
67
|
+
// If there is a previous set, base off that, otherwise use first set targets
|
|
68
|
+
sets[setIndex - 1]?.plannedReps || firstSetReps,
|
|
69
|
+
sets[setIndex - 1]?.plannedWeight || firstSetWeight,
|
|
70
|
+
setIndex,
|
|
71
|
+
exercise.repRange,
|
|
72
|
+
equipment,
|
|
73
|
+
{
|
|
74
|
+
isDeloadMicrocycle,
|
|
75
|
+
sessionIndex,
|
|
76
|
+
plannedSessionCountPerMicrocycle: context.mesocycle.plannedSessionCountPerMicrocycle
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const workoutSet = WorkoutSetSchema.parse({
|
|
81
|
+
userId: exercise.userId,
|
|
82
|
+
workoutExerciseId: exercise._id,
|
|
83
|
+
workoutSessionId: session._id,
|
|
84
|
+
workoutSessionExerciseId: sessionExercise._id,
|
|
85
|
+
plannedReps,
|
|
86
|
+
plannedWeight,
|
|
87
|
+
plannedRir: targetRir,
|
|
88
|
+
exerciseProperties: calibration.exerciseProperties
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
sets.push(workoutSet);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
context.setsToCreate.push(...sets);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generates the planned reps and weight for a specific set within a session exercise, only
|
|
99
|
+
* taking into account simple -2 reps drop per set logic, and deload modifications.
|
|
100
|
+
*
|
|
101
|
+
* This needs to be checked with the source material to see if it needs to be adjusted based
|
|
102
|
+
* on actual reps performed in previous sets.
|
|
103
|
+
*/
|
|
104
|
+
private static generateSetRepsAndWeight(
|
|
105
|
+
firstSetOrPreviousSetReps: number,
|
|
106
|
+
firstSetOrPreviousSetWeight: number,
|
|
107
|
+
setIndex: number,
|
|
108
|
+
repRange: ExerciseRepRange,
|
|
109
|
+
equipment: WorkoutEquipmentType,
|
|
110
|
+
deloadInfo: {
|
|
111
|
+
isDeloadMicrocycle: boolean;
|
|
112
|
+
sessionIndex: number;
|
|
113
|
+
plannedSessionCountPerMicrocycle: number;
|
|
114
|
+
}
|
|
115
|
+
) {
|
|
116
|
+
const repRangeValues = WorkoutExerciseService.getRepRangeValues(repRange);
|
|
117
|
+
let currentReps = firstSetOrPreviousSetReps;
|
|
118
|
+
let currentWeight = firstSetOrPreviousSetWeight;
|
|
119
|
+
|
|
120
|
+
// Ideally, drop 2 reps per set within the session (19 -> 17 -> 15, etc.)
|
|
121
|
+
// But if that would go below the min reps, keep it at min reps.
|
|
122
|
+
if (firstSetOrPreviousSetReps - 2 < repRangeValues.min && setIndex > 0) {
|
|
123
|
+
// Reduce weight by 2% using the same technique as progression
|
|
124
|
+
const twoPercentDecrease = currentWeight / 1.02;
|
|
125
|
+
const reducedWeight = WorkoutEquipmentTypeService.findNearestWeight(
|
|
126
|
+
equipment,
|
|
127
|
+
twoPercentDecrease,
|
|
128
|
+
'down'
|
|
129
|
+
);
|
|
130
|
+
if (reducedWeight !== null) {
|
|
131
|
+
currentWeight = reducedWeight;
|
|
132
|
+
} else if (firstSetOrPreviousSetReps - 2 > 5) {
|
|
133
|
+
// If we can't reduce weight, but we can reduce reps without going too low,
|
|
134
|
+
// then do that.
|
|
135
|
+
currentReps = firstSetOrPreviousSetReps - 2;
|
|
136
|
+
}
|
|
137
|
+
} else if (setIndex > 0) {
|
|
138
|
+
currentReps = firstSetOrPreviousSetReps - 2;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Apply deload modifications, but only if the set is the first in the session
|
|
142
|
+
if (deloadInfo.isDeloadMicrocycle && setIndex === 0) {
|
|
143
|
+
currentReps = Math.floor(firstSetOrPreviousSetReps / 2);
|
|
144
|
+
// First half of deload microcycle: same weight, half reps/sets
|
|
145
|
+
// Second half: half weight too
|
|
146
|
+
if (deloadInfo.sessionIndex >= Math.floor(deloadInfo.plannedSessionCountPerMicrocycle / 2)) {
|
|
147
|
+
currentWeight = Math.floor(currentWeight / 2);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { plannedReps: currentReps, plannedWeight: currentWeight };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Fatigue } from '../../../../embedded-types/workout/Fatigue.js';
|
|
2
|
+
import type { RSM } from '../../../../embedded-types/workout/Rsm.js';
|
|
3
|
+
/**
|
|
4
|
+
* A service for handling Stimulus to Fatigue Ratio (SFR) calculations.
|
|
5
|
+
*
|
|
6
|
+
* This service works with any object that has RSM and Fatigue data.
|
|
7
|
+
*/
|
|
8
|
+
export default class WorkoutSFRService {
|
|
9
|
+
/**
|
|
10
|
+
* Calculates the total Raw Stimulus Magnitude.
|
|
11
|
+
*
|
|
12
|
+
* @param rsm The RSM data.
|
|
13
|
+
*/
|
|
14
|
+
static getRsmTotal(rsm: RSM | null | undefined): number | null;
|
|
15
|
+
/**
|
|
16
|
+
* Calculates the total fatigue score.
|
|
17
|
+
*
|
|
18
|
+
* @param fatigue The fatigue data.
|
|
19
|
+
*/
|
|
20
|
+
static getFatigueTotal(fatigue: Fatigue | null | undefined): number | null;
|
|
21
|
+
/**
|
|
22
|
+
* Calculates the Stimulus to Fatigue Ratio (SFR).
|
|
23
|
+
*
|
|
24
|
+
* @param rsm The RSM data.
|
|
25
|
+
* @param fatigue The fatigue data.
|
|
26
|
+
*/
|
|
27
|
+
static getSFR(rsm: RSM | null | undefined, fatigue: Fatigue | null | undefined): number | null;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=WorkoutSFRService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkoutSFRService.d.ts","sourceRoot":"","sources":["../../../../../src/services/workout/util/SFR/WorkoutSFRService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+CAA+C,CAAC;AAC7E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,2CAA2C,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAiB;IACpC;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAO9D;;;;OAIG;IACH,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAc1E;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;CAc/F"}
|