@dgpholdings/greatoak-shared 1.1.22 → 1.1.24
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.
|
@@ -4,16 +4,29 @@ type TUserProfile = {
|
|
|
4
4
|
gender?: "male" | "female" | "non-binary" | "unspecified";
|
|
5
5
|
weightKg?: number;
|
|
6
6
|
};
|
|
7
|
+
type TScoreBreakdown = {
|
|
8
|
+
baseScore: number;
|
|
9
|
+
plus: {
|
|
10
|
+
[reason: string]: number;
|
|
11
|
+
}[];
|
|
12
|
+
minus: {
|
|
13
|
+
[reason: string]: number;
|
|
14
|
+
}[];
|
|
15
|
+
finalScore: number;
|
|
16
|
+
};
|
|
17
|
+
type TParams = {
|
|
18
|
+
record: TRefinedRecord;
|
|
19
|
+
userProfile?: TUserProfile;
|
|
20
|
+
avgRestInBetweenDurationSecs?: number;
|
|
21
|
+
exerciseDefaultDifficultyMultiplier?: number;
|
|
22
|
+
};
|
|
7
23
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- Daily workload
|
|
15
|
-
- Volume comparisons
|
|
16
|
-
- Weekly fatigue trends
|
|
24
|
+
* Compute the training stress score for a given record.
|
|
25
|
+
*
|
|
26
|
+
* @param record - A refined workout set record
|
|
27
|
+
* @param userProfile - Optional user demographics (age, gender, weight)
|
|
28
|
+
* @param avgRestInBetweenDurationSecs - Average rest between sets in session
|
|
29
|
+
* @returns An object with baseScore, applied boosts, penalties, and final score
|
|
17
30
|
*/
|
|
18
|
-
export declare const computeScoreFromRecord: (
|
|
31
|
+
export declare const computeScoreFromRecord: ({ exerciseDefaultDifficultyMultiplier, avgRestInBetweenDurationSecs, record, userProfile, }: TParams) => TScoreBreakdown;
|
|
19
32
|
export {};
|
|
@@ -3,60 +3,63 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.computeScoreFromRecord = void 0;
|
|
4
4
|
const time_util_1 = require("./time.util");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- Daily workload
|
|
13
|
-
- Volume comparisons
|
|
14
|
-
- Weekly fatigue trends
|
|
6
|
+
* Compute the training stress score for a given record.
|
|
7
|
+
*
|
|
8
|
+
* @param record - A refined workout set record
|
|
9
|
+
* @param userProfile - Optional user demographics (age, gender, weight)
|
|
10
|
+
* @param avgRestInBetweenDurationSecs - Average rest between sets in session
|
|
11
|
+
* @returns An object with baseScore, applied boosts, penalties, and final score
|
|
15
12
|
*/
|
|
16
|
-
const computeScoreFromRecord = (record, userProfile) => {
|
|
17
|
-
var _a, _b, _c, _d, _e
|
|
18
|
-
if (!record.isDone)
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Step 1: Determine effort factor
|
|
26
|
-
if (typeof rpe === "number") {
|
|
27
|
-
effortFactor = rpe;
|
|
13
|
+
const computeScoreFromRecord = ({ exerciseDefaultDifficultyMultiplier = 1, avgRestInBetweenDurationSecs, record, userProfile, }) => {
|
|
14
|
+
var _a, _b, _c, _d, _e;
|
|
15
|
+
if (!record.isDone) {
|
|
16
|
+
return {
|
|
17
|
+
baseScore: 0,
|
|
18
|
+
plus: [],
|
|
19
|
+
minus: [],
|
|
20
|
+
finalScore: 0,
|
|
21
|
+
};
|
|
28
22
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
// Step 2: Determine load
|
|
23
|
+
// Step 1: Compute load
|
|
24
|
+
let load = 0;
|
|
33
25
|
switch (record.type) {
|
|
34
|
-
case "weight-reps":
|
|
26
|
+
case "weight-reps":
|
|
35
27
|
load = record.kg * record.reps;
|
|
36
28
|
break;
|
|
37
|
-
}
|
|
38
29
|
case "duration": {
|
|
39
|
-
const durationSecs = (0, time_util_1.mmssToSecs)((
|
|
40
|
-
const auxWeight = (
|
|
30
|
+
const durationSecs = (0, time_util_1.mmssToSecs)((_a = record.durationSecs) !== null && _a !== void 0 ? _a : "00:00");
|
|
31
|
+
const auxWeight = (_b = record.auxWeightKg) !== null && _b !== void 0 ? _b : 0;
|
|
41
32
|
load = auxWeight > 0 ? auxWeight * durationSecs : durationSecs;
|
|
42
33
|
break;
|
|
43
34
|
}
|
|
44
35
|
case "reps-only": {
|
|
45
|
-
const reps = (
|
|
46
|
-
const auxWeight = (
|
|
47
|
-
|
|
36
|
+
const reps = (_c = record.reps) !== null && _c !== void 0 ? _c : 0;
|
|
37
|
+
const auxWeight = (_d = record.auxWeightKg) !== null && _d !== void 0 ? _d : 0;
|
|
38
|
+
const baseWeight = auxWeight > 0 ? auxWeight : (_e = userProfile === null || userProfile === void 0 ? void 0 : userProfile.weightKg) !== null && _e !== void 0 ? _e : 0;
|
|
39
|
+
load = baseWeight * reps;
|
|
48
40
|
break;
|
|
49
41
|
}
|
|
50
|
-
default:
|
|
51
|
-
return 0;
|
|
52
42
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
// Step 2: Compute effort
|
|
44
|
+
let effortFactor = 0;
|
|
45
|
+
if (typeof record.rpe === "number") {
|
|
46
|
+
effortFactor = record.rpe;
|
|
47
|
+
}
|
|
48
|
+
else if (typeof record.rir === "number") {
|
|
49
|
+
effortFactor = 10 - record.rir;
|
|
50
|
+
}
|
|
51
|
+
// Step 3: Base score using log scale
|
|
57
52
|
const base = Math.log10(1 + Math.max(0, load));
|
|
58
|
-
const
|
|
59
|
-
|
|
53
|
+
const plus = [];
|
|
54
|
+
const minus = [];
|
|
55
|
+
let rawScore = base;
|
|
56
|
+
// Step 4: Effort Boost
|
|
57
|
+
// Clamp effort factor to [0,10] range to avoid invalid boosts
|
|
58
|
+
const boundedEffortFactor = Math.max(0, Math.min(effortFactor, 10));
|
|
59
|
+
const effortBoost = 1 + boundedEffortFactor * 0.05;
|
|
60
|
+
rawScore *= effortBoost;
|
|
61
|
+
plus.push({ effortBoost: parseFloat((effortBoost - 1).toFixed(10)) });
|
|
62
|
+
// Step 5: User Profile Scaling
|
|
60
63
|
let adjustment = 1;
|
|
61
64
|
if ((userProfile === null || userProfile === void 0 ? void 0 : userProfile.gender) === "female")
|
|
62
65
|
adjustment *= 1.1;
|
|
@@ -66,5 +69,36 @@ const computeFinalScore = (load, effortFactor, userProfile) => {
|
|
|
66
69
|
const normalizedWeight = 70;
|
|
67
70
|
adjustment *= normalizedWeight / userProfile.weightKg;
|
|
68
71
|
}
|
|
69
|
-
|
|
72
|
+
if (adjustment !== 1) {
|
|
73
|
+
rawScore *= adjustment;
|
|
74
|
+
plus.push({ profileAdjustment: parseFloat((adjustment - 1).toFixed(10)) });
|
|
75
|
+
}
|
|
76
|
+
// Step 6: Rest Penalty
|
|
77
|
+
if (typeof record.restInBetweenDurationSecs === "number" &&
|
|
78
|
+
typeof avgRestInBetweenDurationSecs === "number") {
|
|
79
|
+
const restUsed = record.restInBetweenDurationSecs;
|
|
80
|
+
const grace = 5 + effortFactor * 2;
|
|
81
|
+
const cap = grace + 25;
|
|
82
|
+
const restOver = restUsed - avgRestInBetweenDurationSecs;
|
|
83
|
+
if (restOver > grace) {
|
|
84
|
+
const cappedExcess = Math.min(restOver, cap);
|
|
85
|
+
const penaltyRatio = (cappedExcess - grace) / (cap - grace); // 0–1
|
|
86
|
+
const restPenalty = parseFloat((penaltyRatio * 0.2 * rawScore).toFixed(10));
|
|
87
|
+
rawScore -= restPenalty;
|
|
88
|
+
minus.push({ restPenalty });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let finalScore = parseFloat(rawScore.toFixed(2));
|
|
92
|
+
// Adjusting exerciseDefaultDifficultyMultiplier
|
|
93
|
+
finalScore *= exerciseDefaultDifficultyMultiplier;
|
|
94
|
+
plus.push({
|
|
95
|
+
exerciseDefaultDifficultyMultiplier: parseFloat(exerciseDefaultDifficultyMultiplier.toFixed(5)),
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
baseScore: parseFloat(base.toFixed(2)),
|
|
99
|
+
plus,
|
|
100
|
+
minus,
|
|
101
|
+
finalScore,
|
|
102
|
+
};
|
|
70
103
|
};
|
|
104
|
+
exports.computeScoreFromRecord = computeScoreFromRecord;
|