@dgpholdings/greatoak-shared 1.2.59 → 1.2.60
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.
|
@@ -8,6 +8,7 @@ exports.mockExercisesDictionary = exports.mockExerciseNoGuardrails = exports.moc
|
|
|
8
8
|
exports.mockExerciseWeightReps = {
|
|
9
9
|
exerciseId: "mock-exercise-weight-reps-123",
|
|
10
10
|
name: "Generic Barbell Squat",
|
|
11
|
+
status: "active",
|
|
11
12
|
bodyPart: ["Legs"],
|
|
12
13
|
recordType: "weight-reps",
|
|
13
14
|
primaryMuscles: ["quadriceps"],
|
|
@@ -56,7 +57,7 @@ exports.mockExerciseWeightReps = {
|
|
|
56
57
|
* MOCK EXERCISE: Reps-Only
|
|
57
58
|
* Simulates a bodyweight movement (e.g., Push-up).
|
|
58
59
|
*/
|
|
59
|
-
exports.mockExerciseRepsOnly = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-reps-only-456", name: "Generic Push-Up", bodyPart: ["Chest", "Core"], recordType: "reps-only", primaryMuscles: ["pectoralis-major"], secondaryMuscles: ["tricep-brachii-lateral", "abs-lower"], trainingTypes: ["body-weight"], timingGuardrails: {
|
|
60
|
+
exports.mockExerciseRepsOnly = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-reps-only-456", name: "Generic Push-Up", status: "active", bodyPart: ["Chest", "Core"], recordType: "reps-only", primaryMuscles: ["pectoralis-major"], secondaryMuscles: ["tricep-brachii-lateral", "abs-lower"], trainingTypes: ["body-weight"], timingGuardrails: {
|
|
60
61
|
type: "reps-only",
|
|
61
62
|
stressRestBonus: 2,
|
|
62
63
|
fatigueMultiplier: 1.05,
|
|
@@ -77,7 +78,7 @@ exports.mockExerciseRepsOnly = Object.assign(Object.assign({}, exports.mockExerc
|
|
|
77
78
|
* MOCK EXERCISE: Duration
|
|
78
79
|
* Simulates an isometric hold (e.g., Plank).
|
|
79
80
|
*/
|
|
80
|
-
exports.mockExerciseDuration = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-duration-789", name: "Generic Forearm Plank", bodyPart: ["Core"], recordType: "duration", primaryMuscles: ["abs-lower", "abs-upper"], secondaryMuscles: ["lower-back"], trainingTypes: ["body-weight", "isometric"], timingGuardrails: {
|
|
81
|
+
exports.mockExerciseDuration = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-duration-789", name: "Generic Forearm Plank", status: "active", bodyPart: ["Core"], recordType: "duration", primaryMuscles: ["abs-lower", "abs-upper"], secondaryMuscles: ["lower-back"], trainingTypes: ["body-weight", "isometric"], timingGuardrails: {
|
|
81
82
|
type: "duration",
|
|
82
83
|
stressRestBonus: 3,
|
|
83
84
|
fatigueMultiplier: 1.1,
|
|
@@ -98,7 +99,7 @@ exports.mockExerciseDuration = Object.assign(Object.assign({}, exports.mockExerc
|
|
|
98
99
|
* MOCK EXERCISE: Cardio-Machine
|
|
99
100
|
* Simulates a machine cardio session (e.g., Treadmill).
|
|
100
101
|
*/
|
|
101
|
-
exports.mockExerciseCardioMachine = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-cardio-machine-101", name: "Generic Treadmill Run", bodyPart: ["Legs"], recordType: "cardio-machine", primaryMuscles: ["quadriceps", "hamstrings", "calves"], secondaryMuscles: ["glutes-maximus"], trainingTypes: ["cardio"], timingGuardrails: {
|
|
102
|
+
exports.mockExerciseCardioMachine = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-cardio-machine-101", name: "Generic Treadmill Run", status: "active", bodyPart: ["Legs"], recordType: "cardio-machine", primaryMuscles: ["quadriceps", "hamstrings", "calves"], secondaryMuscles: ["glutes-maximus"], trainingTypes: ["cardio"], timingGuardrails: {
|
|
102
103
|
type: "cardio-machine",
|
|
103
104
|
stressRestBonus: 0,
|
|
104
105
|
fatigueMultiplier: 1.0,
|
|
@@ -114,7 +115,7 @@ exports.mockExerciseCardioMachine = Object.assign(Object.assign({}, exports.mock
|
|
|
114
115
|
* MOCK EXERCISE: Cardio-Free
|
|
115
116
|
* Simulates an outdoor/untracked cardio session (e.g., Outdoor Run).
|
|
116
117
|
*/
|
|
117
|
-
exports.mockExerciseCardioFree = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-cardio-free-202", name: "Generic Outdoor Jog", bodyPart: ["Legs"], recordType: "cardio-free", primaryMuscles: ["quadriceps", "hamstrings", "calves"], secondaryMuscles: ["glutes-maximus"], trainingTypes: ["cardio"], timingGuardrails: {
|
|
118
|
+
exports.mockExerciseCardioFree = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-cardio-free-202", name: "Generic Outdoor Jog", status: "active", bodyPart: ["Legs"], recordType: "cardio-free", primaryMuscles: ["quadriceps", "hamstrings", "calves"], secondaryMuscles: ["glutes-maximus"], trainingTypes: ["cardio"], timingGuardrails: {
|
|
118
119
|
type: "cardio-free",
|
|
119
120
|
stressRestBonus: 0,
|
|
120
121
|
fatigueMultiplier: 1.0,
|
|
@@ -130,7 +131,7 @@ exports.mockExerciseCardioFree = Object.assign(Object.assign({}, exports.mockExe
|
|
|
130
131
|
* MOCK EXERCISE: No Guardrails
|
|
131
132
|
* Simulates an older DB entry or custom exercise missing guardrails.
|
|
132
133
|
*/
|
|
133
|
-
exports.mockExerciseNoGuardrails = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-no-guardrails-303", name: "Legacy Exercise", timingGuardrails: undefined });
|
|
134
|
+
exports.mockExerciseNoGuardrails = Object.assign(Object.assign({}, exports.mockExerciseWeightReps), { exerciseId: "mock-exercise-no-guardrails-303", name: "Legacy Exercise", status: "active", timingGuardrails: undefined });
|
|
134
135
|
/**
|
|
135
136
|
* Helper dictionary containing all mock exercises mapped by their ID.
|
|
136
137
|
*/
|
|
@@ -123,6 +123,10 @@ export type TExercise = {
|
|
|
123
123
|
isFavorite?: boolean;
|
|
124
124
|
scoringSpecialHandling?: "plyometric" | "stretch-mobility" | "continuous-duration" | "loaded-carry";
|
|
125
125
|
isFlaggedCorrection?: boolean;
|
|
126
|
+
/** True for single-arm / single-leg exercises. The user enters weight per side,
|
|
127
|
+
* so the scoring engine doubles it to get total mechanical load. */
|
|
128
|
+
isUnilateral?: boolean;
|
|
129
|
+
status: "active" | "inactive" | "archived";
|
|
126
130
|
regressionExerciseId?: string;
|
|
127
131
|
progressionExerciseId?: string;
|
|
128
132
|
bodyweightDependency?: "high" | "medium" | "low" | "none";
|
|
@@ -135,9 +139,6 @@ export type TExercise = {
|
|
|
135
139
|
female: number;
|
|
136
140
|
default: number;
|
|
137
141
|
};
|
|
138
|
-
/** True for single-arm / single-leg exercises. The user enters weight per side,
|
|
139
|
-
* so the scoring engine doubles it to get total mechanical load. */
|
|
140
|
-
isUnilateral?: boolean;
|
|
141
142
|
};
|
|
142
143
|
export type TBodyPartExercises = Record<TBodyPart, TExercise[]>;
|
|
143
144
|
export type TApiCreateOrUpdateExerciseReq = {
|
|
@@ -64,7 +64,7 @@ function calculateMuscleFatigue(sets, exercise, user, timingGuardrails, historic
|
|
|
64
64
|
return {};
|
|
65
65
|
const fatigueMultiplier = (_a = timingGuardrails === null || timingGuardrails === void 0 ? void 0 : timingGuardrails.fatigueMultiplier) !== null && _a !== void 0 ? _a : constants_1.FALLBACK_FATIGUE_MULTIPLIER;
|
|
66
66
|
// --- Step 1–3: Compute cumulative fatigue stimulus ---
|
|
67
|
-
const cumulativeFatigue = computeCumulativeFatigue(sets, exercise.difficultyLevel, user, fatigueMultiplier, exercise.scoringSpecialHandling, exercise.isUnilateral);
|
|
67
|
+
const cumulativeFatigue = computeCumulativeFatigue(sets, exercise.difficultyLevel, user, fatigueMultiplier, exercise.scoringSpecialHandling, exercise.isUnilateral, historicalContext);
|
|
68
68
|
// --- Step 4: Distribute to muscles ---
|
|
69
69
|
const rawMuscleFatigue = distributeFatigueToMuscles(cumulativeFatigue, exercise.primaryMuscles, exercise.secondaryMuscles);
|
|
70
70
|
// --- Step 5: Normalize to 0–100 ---
|
|
@@ -88,7 +88,7 @@ function calculateMuscleFatigue(sets, exercise, user, timingGuardrails, historic
|
|
|
88
88
|
* cardio: Duration × speed factor (represents sustained effort)
|
|
89
89
|
* loaded-carry: Speed factor boosted by carried weight relative to bodyweight
|
|
90
90
|
*/
|
|
91
|
-
function computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, isUnilateral) {
|
|
91
|
+
function computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, isUnilateral, historicalContext) {
|
|
92
92
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
93
93
|
switch (set.type) {
|
|
94
94
|
case "weight-reps": {
|
|
@@ -100,15 +100,26 @@ function computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, i
|
|
|
100
100
|
case "reps-only": {
|
|
101
101
|
const reps = (_c = set.reps) !== null && _c !== void 0 ? _c : 0;
|
|
102
102
|
const auxWeightKg = (_d = set.auxWeightKg) !== null && _d !== void 0 ? _d : 0;
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
// Epley Adaptation (P4-1): when a historical 1RM equivalent is available
|
|
104
|
+
// (bodyweight × (1 + bestReps/30)), use it to derive relative intensity.
|
|
105
|
+
// This means a user doing 20/25 max pull-ups scores higher fatigue than a
|
|
106
|
+
// user doing 20/80 max — identical reps, very different physiological effort.
|
|
107
|
+
//
|
|
108
|
+
// Fallback: difficulty-based bodyweight fraction (pre-P4-1 behaviour).
|
|
109
|
+
// Used when no history exists (first session on this exercise).
|
|
110
|
+
let effectiveLoad;
|
|
111
|
+
if ((historicalContext === null || historicalContext === void 0 ? void 0 : historicalContext.estimatedOneRepMax) && user.weightKg > 0 && reps > 0) {
|
|
112
|
+
const sessionEpley = user.weightKg * (1 + reps / 30);
|
|
113
|
+
const relativeIntensity = Math.min(1.0, sessionEpley / historicalContext.estimatedOneRepMax);
|
|
114
|
+
effectiveLoad = relativeIntensity * user.weightKg + auxWeightKg;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const bodyweightLoad = (difficultyLevel / 4) * constants_1.BW_FRACTION_SCALE * user.weightKg;
|
|
118
|
+
effectiveLoad = bodyweightLoad + auxWeightKg;
|
|
111
119
|
}
|
|
120
|
+
const baseVolume = effectiveLoad * reps;
|
|
121
|
+
if (scoringSpecialHandling === "plyometric")
|
|
122
|
+
return baseVolume * constants_1.PLYOMETRIC_LOAD_MULTIPLIER;
|
|
112
123
|
return baseVolume;
|
|
113
124
|
}
|
|
114
125
|
case "duration": {
|
|
@@ -176,12 +187,12 @@ function computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, i
|
|
|
176
187
|
*
|
|
177
188
|
* @returns Single number representing total fatigue stimulus
|
|
178
189
|
*/
|
|
179
|
-
function computeCumulativeFatigue(sets, difficultyLevel, user, fatigueMultiplier, scoringSpecialHandling, isUnilateral) {
|
|
190
|
+
function computeCumulativeFatigue(sets, difficultyLevel, user, fatigueMultiplier, scoringSpecialHandling, isUnilateral, historicalContext) {
|
|
180
191
|
let cumulative = 0;
|
|
181
192
|
for (let i = 0; i < sets.length; i++) {
|
|
182
193
|
const set = sets[i];
|
|
183
194
|
// Step 1: Raw volume for this set
|
|
184
|
-
const volumeLoad = computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, isUnilateral);
|
|
195
|
+
const volumeLoad = computeVolumeLoad(set, difficultyLevel, user, scoringSpecialHandling, isUnilateral, historicalContext);
|
|
185
196
|
// Step 2: Scale by effort and exercise fatigue multiplier
|
|
186
197
|
const stimulus = volumeLoad * set.effortFraction * fatigueMultiplier;
|
|
187
198
|
// Step 3: Apply diminishing returns decay
|
|
@@ -260,9 +271,17 @@ function computeReferenceMax(exerciseType, difficultyLevel, user, muscleGroupFac
|
|
|
260
271
|
break;
|
|
261
272
|
}
|
|
262
273
|
case "reps-only": {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
274
|
+
// Epley Adaptation (P4-1): use the stored 1RM equivalent as the capacity
|
|
275
|
+
// anchor when available. This means the reference max scales to the
|
|
276
|
+
// individual's actual strength, not a population-average estimate.
|
|
277
|
+
if (historicalContext === null || historicalContext === void 0 ? void 0 : historicalContext.estimatedOneRepMax) {
|
|
278
|
+
singleSetMax = historicalContext.estimatedOneRepMax * constants_1.REFERENCE_MAX_REPS;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// Fallback: difficulty-based bodyweight fraction
|
|
282
|
+
const bodyweightLoad = (difficultyLevel / 4) * constants_1.BW_FRACTION_SCALE * user.weightKg;
|
|
283
|
+
singleSetMax = (bodyweightLoad + 5) * 15;
|
|
284
|
+
}
|
|
266
285
|
break;
|
|
267
286
|
}
|
|
268
287
|
case "duration": {
|