@aneuhold/core-ts-db-lib 4.1.12 → 4.1.14
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 +38 -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/README.md +43 -9
- 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 +30 -4
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +58 -4
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -1
- package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +69 -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 +161 -11
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -1
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +364 -127
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -1
- package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +551 -160
- package/package.json +1 -1
|
@@ -25,8 +25,8 @@ export default class WorkoutSessionExerciseService {
|
|
|
25
25
|
/**
|
|
26
26
|
* Calculates the performance score (0-3) for an exercise based on its sets.
|
|
27
27
|
*
|
|
28
|
-
* For each set with complete data, a surplus is computed
|
|
29
|
-
*
|
|
28
|
+
* For each set with complete data, a surplus is computed via
|
|
29
|
+
* {@link calculateSetSurplus}. The per-set score is:
|
|
30
30
|
* - 0: surplus >= 2 (exceeded expectations)
|
|
31
31
|
* - 1: surplus 0-1 (on target)
|
|
32
32
|
* - 2: surplus < 0 but hit target reps (declining)
|
|
@@ -36,6 +36,18 @@ export default class WorkoutSessionExerciseService {
|
|
|
36
36
|
* `null` if no sets have complete planned and actual data.
|
|
37
37
|
*/
|
|
38
38
|
static getPerformanceScore(sets: WorkoutSet[]): number | null;
|
|
39
|
+
/**
|
|
40
|
+
* Calculates the surplus for a single set: how much the user exceeded or
|
|
41
|
+
* fell short of the plan. Positive means exceeded, negative means fell short.
|
|
42
|
+
*
|
|
43
|
+
* Formula: `(actualReps - plannedReps) + (rir - plannedRir)`
|
|
44
|
+
*
|
|
45
|
+
* @param actualReps The actual reps performed.
|
|
46
|
+
* @param plannedReps The planned reps.
|
|
47
|
+
* @param rir The actual RIR (reps in reserve).
|
|
48
|
+
* @param plannedRir The planned RIR.
|
|
49
|
+
*/
|
|
50
|
+
static calculateSetSurplus(actualReps: number, plannedReps: number, rir: number, plannedRir: number): number;
|
|
39
51
|
/**
|
|
40
52
|
* Uses the soreness/performance table from the workout model notes to recommend whether to add
|
|
41
53
|
* sets next microcycle or employ recovery sessions.
|
|
@@ -1 +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;AACnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E;;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;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI;
|
|
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;AACnG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG3E;;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;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,IAAI;IA0C7D;;;;;;;;;;OAUG;IACH,MAAM,CAAC,mBAAmB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GACjB,MAAM;IAIT;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,oCAAoC,CACzC,sBAAsB,EAAE,sBAAsB,GAC7C,MAAM,GAAG,IAAI;IAsBhB;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,OAAO;IAI5D;;;;;OAKG;IACH,MAAM,CAAC,0BAA0B,CAC/B,eAAe,EAAE,sBAAsB,EACvC,YAAY,EAAE,UAAU,EAAE,GACzB,OAAO;IAUV;;;;OAIG;IACH,MAAM,CAAC,0BAA0B,CAC/B,eAAe,EAAE,sBAAsB,EACvC,YAAY,EAAE,UAAU,EAAE,GACzB,OAAO;CAYX"}
|
|
@@ -30,8 +30,8 @@ export default class WorkoutSessionExerciseService {
|
|
|
30
30
|
/**
|
|
31
31
|
* Calculates the performance score (0-3) for an exercise based on its sets.
|
|
32
32
|
*
|
|
33
|
-
* For each set with complete data, a surplus is computed
|
|
34
|
-
*
|
|
33
|
+
* For each set with complete data, a surplus is computed via
|
|
34
|
+
* {@link calculateSetSurplus}. The per-set score is:
|
|
35
35
|
* - 0: surplus >= 2 (exceeded expectations)
|
|
36
36
|
* - 1: surplus 0-1 (on target)
|
|
37
37
|
* - 2: surplus < 0 but hit target reps (declining)
|
|
@@ -53,7 +53,7 @@ export default class WorkoutSessionExerciseService {
|
|
|
53
53
|
setScores.push(3);
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
const surplus = set.actualReps
|
|
56
|
+
const surplus = this.calculateSetSurplus(set.actualReps, set.plannedReps, set.rir, set.plannedRir);
|
|
57
57
|
if (surplus >= 2) {
|
|
58
58
|
setScores.push(0);
|
|
59
59
|
}
|
|
@@ -70,6 +70,20 @@ export default class WorkoutSessionExerciseService {
|
|
|
70
70
|
const average = setScores.reduce((sum, score) => sum + score, 0) / setScores.length;
|
|
71
71
|
return Math.round(average);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Calculates the surplus for a single set: how much the user exceeded or
|
|
75
|
+
* fell short of the plan. Positive means exceeded, negative means fell short.
|
|
76
|
+
*
|
|
77
|
+
* Formula: `(actualReps - plannedReps) + (rir - plannedRir)`
|
|
78
|
+
*
|
|
79
|
+
* @param actualReps The actual reps performed.
|
|
80
|
+
* @param plannedReps The planned reps.
|
|
81
|
+
* @param rir The actual RIR (reps in reserve).
|
|
82
|
+
* @param plannedRir The planned RIR.
|
|
83
|
+
*/
|
|
84
|
+
static calculateSetSurplus(actualReps, plannedReps, rir, plannedRir) {
|
|
85
|
+
return actualReps - plannedReps + (rir - plannedRir);
|
|
86
|
+
}
|
|
73
87
|
/**
|
|
74
88
|
* Uses the soreness/performance table from the workout model notes to recommend whether to add
|
|
75
89
|
* sets next microcycle or employ recovery sessions.
|
|
@@ -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.
|