@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/lib/browser.d.ts +11 -8
  3. package/lib/browser.d.ts.map +1 -1
  4. package/lib/browser.js +6 -4
  5. package/lib/browser.js.map +1 -1
  6. package/lib/browser.ts +23 -9
  7. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts +14 -0
  8. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.d.ts.map +1 -1
  9. package/lib/ctos/workout/WorkoutMuscleGroupVolumeCTO.ts +18 -0
  10. package/lib/documents/workout/README.md +43 -9
  11. package/lib/documents/workout/WorkoutSet.d.ts +8 -0
  12. package/lib/documents/workout/WorkoutSet.d.ts.map +1 -1
  13. package/lib/documents/workout/WorkoutSet.ts +9 -0
  14. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts +40 -0
  15. package/lib/services/workout/Exercise/WorkoutExerciseService.d.ts.map +1 -1
  16. package/lib/services/workout/Exercise/WorkoutExerciseService.js +134 -1
  17. package/lib/services/workout/Exercise/WorkoutExerciseService.js.map +1 -1
  18. package/lib/services/workout/Exercise/WorkoutExerciseService.ts +204 -1
  19. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts +15 -0
  20. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.d.ts.map +1 -1
  21. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js +18 -1
  22. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.js.map +1 -1
  23. package/lib/services/workout/ExerciseCalibration/WorkoutExerciseCalibrationService.ts +19 -1
  24. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts +30 -4
  25. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.d.ts.map +1 -1
  26. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js +58 -4
  27. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.js.map +1 -1
  28. package/lib/services/workout/Mesocycle/WorkoutMesocyclePlanContext.ts +69 -3
  29. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts +45 -1
  30. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.d.ts.map +1 -1
  31. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js +201 -11
  32. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.js.map +1 -1
  33. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.ts +285 -9
  34. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts +33 -0
  35. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.d.ts.map +1 -0
  36. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js +24 -0
  37. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.js.map +1 -0
  38. package/lib/services/workout/Mesocycle/WorkoutMesocycleService.types.ts +36 -0
  39. package/lib/services/workout/Session/WorkoutSessionService.d.ts.map +1 -1
  40. package/lib/services/workout/Session/WorkoutSessionService.js +1 -11
  41. package/lib/services/workout/Session/WorkoutSessionService.js.map +1 -1
  42. package/lib/services/workout/Session/WorkoutSessionService.ts +1 -17
  43. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts +14 -2
  44. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.d.ts.map +1 -1
  45. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js +17 -3
  46. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.js.map +1 -1
  47. package/lib/services/workout/SessionExercise/WorkoutSessionExerciseService.ts +28 -3
  48. package/lib/services/workout/Set/WorkoutSetService.d.ts +17 -5
  49. package/lib/services/workout/Set/WorkoutSetService.d.ts.map +1 -1
  50. package/lib/services/workout/Set/WorkoutSetService.js +83 -16
  51. package/lib/services/workout/Set/WorkoutSetService.js.map +1 -1
  52. package/lib/services/workout/Set/WorkoutSetService.ts +107 -24
  53. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts +161 -11
  54. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.d.ts.map +1 -1
  55. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js +364 -127
  56. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.js.map +1 -1
  57. package/lib/services/workout/util/VolumePlanning/WorkoutVolumePlanningService.ts +551 -160
  58. 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 as
29
- * `(actualReps - plannedReps) + (rir - plannedRir)`. The per-set score is:
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;IAqC7D;;;;;;;;;;;;;;;;;;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"}
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 as
34
- * `(actualReps - plannedReps) + (rir - plannedRir)`. The per-set score is:
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 - set.plannedReps + (set.rir - set.plannedRir);
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":"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"}
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 as
40
- * `(actualReps - plannedReps) + (rir - plannedRir)`. The per-set score is:
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 = set.actualReps - set.plannedReps + (set.rir - set.plannedRir);
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 { WorkoutExercise } from '../../../documents/workout/WorkoutExercise.js';
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, exercise, calibration, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }: {
16
+ static generateSetsForSessionExercise({ context, exerciseCTO, session, sessionExercise, microcycleIndex, sessionIndex, setCount, targetRir, isDeloadMicrocycle }: {
17
17
  context: WorkoutMesocyclePlanContext;
18
- exercise: WorkoutExercise;
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,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;AACnG,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG7F,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,GAAG,IAAI,CAAC;QACzB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,IAAI;IAsDR;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IAQ5C;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB;CAuDxC"}
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, 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}`);
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 { targetWeight: firstSetWeight, targetReps: firstSetReps } = WorkoutExerciseService.calculateTargetRepsAndWeightForFirstSet({
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.FIRST_MICROCYCLE_RIR
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: exercise.userId,
39
- workoutExerciseId: exercise._id,
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: calibration.exerciseProperties
50
+ exerciseProperties: bestCalibration.exerciseProperties
46
51
  });
47
52
  sets.push(workoutSet);
48
53
  }
49
- context.setsToCreate.push(...sets);
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":"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"}
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
- exercise,
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
- exercise: WorkoutExercise;
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 equipment = context.equipmentMap.get(exercise.workoutEquipmentTypeId);
47
- if (!equipment) {
43
+ const { equipmentType, bestCalibration } = exerciseCTO;
44
+ if (!bestCalibration) {
48
45
  throw new Error(
49
- `Equipment type not found for exercise ${exercise._id}, ${exercise.exerciseName}`
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 { targetWeight: firstSetWeight, targetReps: firstSetReps } =
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.FIRST_MICROCYCLE_RIR
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
- exercise.repRange,
74
- equipment,
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: exercise.userId,
84
- workoutExerciseId: exercise._id,
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: calibration.exerciseProperties
94
+ exerciseProperties: bestCalibration.exerciseProperties
91
95
  });
92
96
 
93
97
  sets.push(workoutSet);
94
98
  }
95
99
 
96
- context.setsToCreate.push(...sets);
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.