@dgpholdings/greatoak-shared 1.2.86 → 1.2.88
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/README.md +148 -148
- package/dist/__mocks__/catalog.fixture.d.ts +2 -0
- package/dist/__mocks__/catalog.fixture.js +208 -0
- package/dist/__mocks__/exercises.mock.d.ts +4 -11
- package/dist/__mocks__/exercises.mock.js +82 -41
- package/dist/__mocks__/sessions.mock.d.ts +28 -0
- package/dist/__mocks__/sessions.mock.js +394 -0
- package/dist/__mocks__/testIds.d.ts +9 -0
- package/dist/__mocks__/testIds.js +13 -0
- package/dist/__mocks__/user.mock.js +3 -1
- package/dist/constants/goalJourney.d.ts +108 -0
- package/dist/constants/goalJourney.js +443 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/types/TApiAiExerciseAnalysis.d.ts +2 -1
- package/dist/types/TApiClientConstellation.d.ts +33 -0
- package/dist/types/TApiClientConstellation.js +13 -0
- package/dist/types/TApiExercise.d.ts +5 -3
- package/dist/types/TApiUser.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/adoptionEngine/scaleProPlan.util.js +9 -4
- package/dist/utils/constellation/computeNormalisedLoad.d.ts +48 -0
- package/dist/utils/constellation/computeNormalisedLoad.js +150 -0
- package/dist/utils/constellation/computeNormalisedLoad.test.d.ts +1 -0
- package/dist/utils/constellation/computeNormalisedLoad.test.js +218 -0
- package/dist/utils/constellation/evaluateConstellation.d.ts +27 -0
- package/dist/utils/constellation/evaluateConstellation.js +135 -0
- package/dist/utils/constellation/evaluateConstellation.test.d.ts +1 -0
- package/dist/utils/constellation/evaluateConstellation.test.js +93 -0
- package/dist/utils/constellation/index.d.ts +18 -0
- package/dist/utils/constellation/index.js +29 -0
- package/dist/utils/constellation/levelThresholds.d.ts +99 -0
- package/dist/utils/constellation/levelThresholds.js +123 -0
- package/dist/utils/constellation/starFoundation.d.ts +25 -0
- package/dist/utils/constellation/starFoundation.js +54 -0
- package/dist/utils/constellation/starFoundation.test.d.ts +1 -0
- package/dist/utils/constellation/starFoundation.test.js +75 -0
- package/dist/utils/constellation/stars/consistency.d.ts +29 -0
- package/dist/utils/constellation/stars/consistency.js +142 -0
- package/dist/utils/constellation/stars/consistency.test.d.ts +1 -0
- package/dist/utils/constellation/stars/consistency.test.js +94 -0
- package/dist/utils/constellation/stars/lowerBody.d.ts +17 -0
- package/dist/utils/constellation/stars/lowerBody.js +30 -0
- package/dist/utils/constellation/stars/pull.d.ts +11 -0
- package/dist/utils/constellation/stars/pull.js +24 -0
- package/dist/utils/constellation/stars/push.d.ts +11 -0
- package/dist/utils/constellation/stars/push.js +24 -0
- package/dist/utils/constellation/stars/quality.d.ts +19 -0
- package/dist/utils/constellation/stars/quality.js +98 -0
- package/dist/utils/constellation/stars/quality.test.d.ts +1 -0
- package/dist/utils/constellation/stars/quality.test.js +113 -0
- package/dist/utils/constellation/stars/recovery.d.ts +29 -0
- package/dist/utils/constellation/stars/recovery.js +169 -0
- package/dist/utils/constellation/stars/recovery.test.d.ts +1 -0
- package/dist/utils/constellation/stars/recovery.test.js +131 -0
- package/dist/utils/constellation/strengthStar.test.d.ts +1 -0
- package/dist/utils/constellation/strengthStar.test.js +190 -0
- package/dist/utils/constellation/strengthStarHelpers.d.ts +41 -0
- package/dist/utils/constellation/strengthStarHelpers.js +104 -0
- package/dist/utils/constellation/types.d.ts +124 -0
- package/dist/utils/constellation/types.js +18 -0
- package/dist/utils/exerciseRecord/recordValidator.integration.test.js +1 -1
- package/dist/utils/exerciseRecord/recordValidator.js +1 -1
- package/dist/utils/exerciseRecord/recordValidator.test.js +8 -8
- package/dist/utils/index.d.ts +5 -3
- package/dist/utils/index.js +1 -0
- package/dist/utils/scoringWorkout/calculateQualityScore.d.ts +59 -36
- package/dist/utils/scoringWorkout/calculateQualityScore.js +234 -233
- package/dist/utils/scoringWorkout/computeMuscleFatigueMap.d.ts +8 -5
- package/dist/utils/scoringWorkout/computeMuscleFatigueMap.js +72 -88
- package/dist/utils/scoringWorkout/constants.d.ts +20 -6
- package/dist/utils/scoringWorkout/constants.js +23 -9
- package/dist/utils/scoringWorkout/helpers.d.ts +7 -0
- package/dist/utils/scoringWorkout/helpers.js +24 -18
- package/dist/utils/scoringWorkout/index.d.ts +12 -8
- package/dist/utils/scoringWorkout/index.js +23 -15
- package/dist/utils/scoringWorkout/parseRecords.js +4 -3
- package/dist/utils/scoringWorkout/scoringWorkout.integration.test.js +223 -185
- package/dist/utils/scoringWorkout/types.d.ts +34 -14
- package/package.json +31 -31
- package/dist/utils/exerciseRecord/__mocks__/exercises.mock.d.ts +0 -30
- package/dist/utils/exerciseRecord/__mocks__/exercises.mock.js +0 -138
- package/dist/utils/scaleProPlan.util.d.ts +0 -9
- package/dist/utils/scaleProPlan.util.js +0 -139
- package/dist/utils/scoring/calculateCalories.d.ts +0 -67
- package/dist/utils/scoring/calculateCalories.js +0 -345
- package/dist/utils/scoring/calculateMuscleFatiue.d.ts +0 -67
- package/dist/utils/scoring/calculateMuscleFatiue.js +0 -310
- package/dist/utils/scoring/calculateQualityScore.d.ts +0 -71
- package/dist/utils/scoring/calculateQualityScore.js +0 -334
- package/dist/utils/scoring/calculateTotalVolume.d.ts +0 -15
- package/dist/utils/scoring/calculateTotalVolume.js +0 -73
- package/dist/utils/scoring/constants.d.ts +0 -211
- package/dist/utils/scoring/constants.js +0 -247
- package/dist/utils/scoring/helpers.d.ts +0 -119
- package/dist/utils/scoring/helpers.js +0 -229
- package/dist/utils/scoring/index.d.ts +0 -28
- package/dist/utils/scoring/index.js +0 -47
- package/dist/utils/scoring/parseRecords.d.ts +0 -98
- package/dist/utils/scoring/parseRecords.js +0 -284
- package/dist/utils/scoring/types.d.ts +0 -86
- package/dist/utils/scoring/types.js +0 -11
- package/dist/utils/scoring.utils.d.ts +0 -14
- package/dist/utils/scoring.utils.js +0 -243
- /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.d.ts → calculateMuscleFatigue.d.ts} +0 -0
- /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.js → calculateMuscleFatigue.js} +0 -0
|
@@ -31,32 +31,49 @@ export type TMuscleFatigueResult = Record<string, TMuscleFatigueEntry>;
|
|
|
31
31
|
/**
|
|
32
32
|
* Quality score breakdown — lets the UI show users WHY they got their score.
|
|
33
33
|
* Each sub-score is 0–100.
|
|
34
|
+
*
|
|
35
|
+
* Note: restDiscipline is 0 (and restDisciplineActive is false on IScoreResult)
|
|
36
|
+
* when the user did not log rest timing data. Check restDisciplineActive before
|
|
37
|
+
* rendering the rest discipline bar in the UI.
|
|
34
38
|
*/
|
|
35
39
|
export interface IQualityBreakdown {
|
|
36
|
-
/** Did the user complete all sets? */
|
|
40
|
+
/** Did the user complete all planned sets? */
|
|
37
41
|
completion: number;
|
|
38
42
|
/** Were sets consistent in output (or intentionally progressive)? */
|
|
39
43
|
consistency: number;
|
|
40
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Was effort in the productive RPE/RIR zone?
|
|
46
|
+
* RPE and RIR are optional — users may choose not to log them.
|
|
47
|
+
* When absent, this sub-score is EFFORT_NO_DATA_SCORE (50) — neutral,
|
|
48
|
+
* never penalising omission.
|
|
49
|
+
*/
|
|
41
50
|
effortAdequacy: number;
|
|
42
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Were rest periods within the exercise's optimal range?
|
|
53
|
+
* 0 when no rest data was logged (restDisciplineActive = false).
|
|
54
|
+
*/
|
|
43
55
|
restDiscipline: number;
|
|
44
56
|
}
|
|
45
57
|
/**
|
|
46
|
-
* The final result returned by
|
|
58
|
+
* The final result returned by calculateExerciseScoreV2.
|
|
59
|
+
*
|
|
60
|
+
* Option A flat shape — one exercise produces one score and one breakdown.
|
|
61
|
+
* Goal-specific adaptation and progression logic live in the gate system
|
|
62
|
+
* and quick plan generator, not in the per-exercise score.
|
|
47
63
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* -
|
|
64
|
+
* Fields:
|
|
65
|
+
* score — 0–100 quality of execution
|
|
66
|
+
* qualityBreakdown — per-component breakdown (completion/consistency/effort/rest)
|
|
67
|
+
* muscleScores — per-muscle fatigue (keyed by EBodyParts key, 0–100)
|
|
68
|
+
* calorieBurn — estimated kcal (placeholder: 0 until Phase 3)
|
|
69
|
+
* restDisciplineActive — false when no rest data logged; use to gate UI rendering
|
|
51
70
|
*/
|
|
52
|
-
export interface
|
|
71
|
+
export interface IScoreResult {
|
|
53
72
|
score: number;
|
|
54
73
|
qualityBreakdown: IQualityBreakdown;
|
|
55
|
-
}
|
|
56
|
-
export interface IScoreResult {
|
|
57
74
|
muscleScores: Record<string, number>;
|
|
58
75
|
calorieBurn: number;
|
|
59
|
-
|
|
76
|
+
restDisciplineActive: boolean;
|
|
60
77
|
}
|
|
61
78
|
export type TTrainingAgeBracket = "beginner" | "intermediate" | "advanced";
|
|
62
79
|
/**
|
|
@@ -85,7 +102,10 @@ export interface IParsedSet {
|
|
|
85
102
|
type: TRecord["type"];
|
|
86
103
|
/** Active work duration for this set in seconds (estimated or measured) */
|
|
87
104
|
activeDurationSecs: number;
|
|
88
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Rest duration after this set in seconds (validated or fallback).
|
|
107
|
+
* null = last set in the exercise, or timing data was not logged.
|
|
108
|
+
*/
|
|
89
109
|
restDurationSecs: number | null;
|
|
90
110
|
/**
|
|
91
111
|
* Effort fraction: 0.0 (no effort) to 1.0 (max effort).
|
|
@@ -100,13 +120,13 @@ export interface IParsedSet {
|
|
|
100
120
|
auxWeightKg?: number;
|
|
101
121
|
/** duration: hold time in seconds */
|
|
102
122
|
durationSecs?: number;
|
|
103
|
-
/** cardio-machine: speed */
|
|
123
|
+
/** cardio-machine: speed in km/h */
|
|
104
124
|
speed?: number;
|
|
105
125
|
/** cardio-machine: incline percentage (Treadmill) */
|
|
106
126
|
inclinePercentage?: number;
|
|
107
127
|
/** cardio-machine: resistance level (Elliptical, Cycling) */
|
|
108
128
|
resistanceLevel?: number;
|
|
109
|
-
/** cardio-machine / cardio-free: distance
|
|
129
|
+
/** cardio-machine / cardio-free: distance in km */
|
|
110
130
|
distance?: number;
|
|
111
131
|
/** cardio-free / cardio-machine: session duration in seconds */
|
|
112
132
|
cardioDurationSecs?: number;
|
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dgpholdings/greatoak-shared",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Shared TypeScript types and utilities for @dgpholdings projects",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"types": "./dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist/**/*"
|
|
9
|
-
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"pub": "npm run test && npm run build && npm version patch && npm publish",
|
|
13
|
-
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
14
|
-
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
15
|
-
"test": "vitest run src",
|
|
16
|
-
"test:watch": "vitest src"
|
|
17
|
-
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "https://gitlab.com/greatoak/shared.git"
|
|
21
|
-
},
|
|
22
|
-
"author": "Siddhartha Chowdhury",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"typescript": "^5.8.2",
|
|
26
|
-
"vitest": "^4.1.3"
|
|
27
|
-
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"react-native-uuid": "^2.0.3"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dgpholdings/greatoak-shared",
|
|
3
|
+
"version": "1.2.88",
|
|
4
|
+
"description": "Shared TypeScript types and utilities for @dgpholdings projects",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"pub": "npm run test && npm run build && npm version patch && npm publish",
|
|
13
|
+
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
14
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
15
|
+
"test": "vitest run src",
|
|
16
|
+
"test:watch": "vitest src"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://gitlab.com/greatoak/shared.git"
|
|
21
|
+
},
|
|
22
|
+
"author": "Siddhartha Chowdhury",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.8.2",
|
|
26
|
+
"vitest": "^4.1.3"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"react-native-uuid": "^2.0.3"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { TExercise } from "../../../types";
|
|
2
|
-
/**
|
|
3
|
-
* MOCK EXERCISE: Weight-Reps
|
|
4
|
-
* Simulates a standard compound lift (e.g., Barbell Squat).
|
|
5
|
-
*/
|
|
6
|
-
export declare const mockExerciseWeightReps: TExercise;
|
|
7
|
-
/**
|
|
8
|
-
* MOCK EXERCISE: Reps-Only
|
|
9
|
-
* Simulates a bodyweight movement (e.g., Push-up).
|
|
10
|
-
*/
|
|
11
|
-
export declare const mockExerciseRepsOnly: TExercise;
|
|
12
|
-
/**
|
|
13
|
-
* MOCK EXERCISE: Duration
|
|
14
|
-
* Simulates an isometric hold (e.g., Plank).
|
|
15
|
-
*/
|
|
16
|
-
export declare const mockExerciseDuration: TExercise;
|
|
17
|
-
/**
|
|
18
|
-
* MOCK EXERCISE: Cardio-Machine
|
|
19
|
-
* Simulates a machine cardio session (e.g., Treadmill).
|
|
20
|
-
*/
|
|
21
|
-
export declare const mockExerciseCardioMachine: TExercise;
|
|
22
|
-
/**
|
|
23
|
-
* MOCK EXERCISE: Cardio-Free
|
|
24
|
-
* Simulates an outdoor/untracked cardio session (e.g., Outdoor Run).
|
|
25
|
-
*/
|
|
26
|
-
export declare const mockExerciseCardioFree: TExercise;
|
|
27
|
-
/**
|
|
28
|
-
* Helper dictionary containing all mock exercises mapped by their ID.
|
|
29
|
-
*/
|
|
30
|
-
export declare const mockExercisesDictionary: Record<string, TExercise>;
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mockExercisesDictionary = exports.mockExerciseCardioFree = exports.mockExerciseCardioMachine = exports.mockExerciseDuration = exports.mockExerciseRepsOnly = exports.mockExerciseWeightReps = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* MOCK EXERCISE: Weight-Reps
|
|
6
|
-
* Simulates a standard compound lift (e.g., Barbell Squat).
|
|
7
|
-
*/
|
|
8
|
-
exports.mockExerciseWeightReps = {
|
|
9
|
-
exerciseId: "mock-exercise-weight-reps-123",
|
|
10
|
-
name: "Generic Barbell Squat",
|
|
11
|
-
bodyPart: ["Legs"],
|
|
12
|
-
recordType: "weight-reps",
|
|
13
|
-
primaryMuscles: ["quadriceps"],
|
|
14
|
-
secondaryMuscles: ["glutes-maximus"],
|
|
15
|
-
trainingTypes: ["weight"],
|
|
16
|
-
difficultyLevel: 2,
|
|
17
|
-
hypertrophyLevel: 4,
|
|
18
|
-
strengthGainLevel: 4,
|
|
19
|
-
flexibilityLevel: 1,
|
|
20
|
-
calorieBurnLevel: 3,
|
|
21
|
-
stabilityLevel: 2,
|
|
22
|
-
enduranceLevel: 1,
|
|
23
|
-
metabolicData: {
|
|
24
|
-
baseMET: 6.0,
|
|
25
|
-
metRange: [4.0, 8.0],
|
|
26
|
-
compoundMultiplier: 1.5,
|
|
27
|
-
muscleGroupFactor: 2.0,
|
|
28
|
-
intensityScaling: "exponential",
|
|
29
|
-
epocFactor: 0.15,
|
|
30
|
-
},
|
|
31
|
-
timingGuardrails: {
|
|
32
|
-
type: "weight-reps",
|
|
33
|
-
stressRestBonus: 5,
|
|
34
|
-
fatigueMultiplier: 1.2,
|
|
35
|
-
setupTypicalSecs: 10,
|
|
36
|
-
restPeriods: {
|
|
37
|
-
minimum: 60,
|
|
38
|
-
typical: 120,
|
|
39
|
-
maximum: 300,
|
|
40
|
-
optimalRange: [90, 180],
|
|
41
|
-
},
|
|
42
|
-
singleRep: {
|
|
43
|
-
min: 2.0,
|
|
44
|
-
max: 5.0,
|
|
45
|
-
typical: 3.5,
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
youtubeVideoUrl: ["https://youtube.com/watch?v=mock1"],
|
|
49
|
-
modelVideoUrl: "https://example.com/mock-video.mp4",
|
|
50
|
-
thumbnailUrl: "https://example.com/mock-thumb.png",
|
|
51
|
-
instructionsHtml: "<ul><li>Mock instruction 1</li></ul>",
|
|
52
|
-
importantTipsHtml: "<ul><li>Mock tip 1</li></ul>",
|
|
53
|
-
popularityIndex: 90,
|
|
54
|
-
};
|
|
55
|
-
/**
|
|
56
|
-
* MOCK EXERCISE: Reps-Only
|
|
57
|
-
* Simulates a bodyweight movement (e.g., Push-up).
|
|
58
|
-
*/
|
|
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
|
-
type: "reps-only",
|
|
61
|
-
stressRestBonus: 2,
|
|
62
|
-
fatigueMultiplier: 1.05,
|
|
63
|
-
setupTypicalSecs: 5,
|
|
64
|
-
restPeriods: {
|
|
65
|
-
minimum: 30,
|
|
66
|
-
typical: 60,
|
|
67
|
-
maximum: 180,
|
|
68
|
-
optimalRange: [45, 90],
|
|
69
|
-
},
|
|
70
|
-
singleRep: {
|
|
71
|
-
min: 1.0,
|
|
72
|
-
max: 3.0,
|
|
73
|
-
typical: 1.8,
|
|
74
|
-
},
|
|
75
|
-
} });
|
|
76
|
-
/**
|
|
77
|
-
* MOCK EXERCISE: Duration
|
|
78
|
-
* Simulates an isometric hold (e.g., Plank).
|
|
79
|
-
*/
|
|
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
|
-
type: "duration",
|
|
82
|
-
stressRestBonus: 3,
|
|
83
|
-
fatigueMultiplier: 1.1,
|
|
84
|
-
setupTypicalSecs: 5,
|
|
85
|
-
restPeriods: {
|
|
86
|
-
minimum: 30,
|
|
87
|
-
typical: 60,
|
|
88
|
-
maximum: 120,
|
|
89
|
-
optimalRange: [45, 60],
|
|
90
|
-
},
|
|
91
|
-
setDuration: {
|
|
92
|
-
min: 15,
|
|
93
|
-
max: 300,
|
|
94
|
-
typical: 60,
|
|
95
|
-
},
|
|
96
|
-
} });
|
|
97
|
-
/**
|
|
98
|
-
* MOCK EXERCISE: Cardio-Machine
|
|
99
|
-
* Simulates a machine cardio session (e.g., Treadmill).
|
|
100
|
-
*/
|
|
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
|
-
type: "cardio-machine",
|
|
103
|
-
stressRestBonus: 0,
|
|
104
|
-
fatigueMultiplier: 1.0,
|
|
105
|
-
setupTypicalSecs: 15,
|
|
106
|
-
restPeriods: {
|
|
107
|
-
minimum: 0,
|
|
108
|
-
typical: 0,
|
|
109
|
-
maximum: 300,
|
|
110
|
-
optimalRange: [0, 60],
|
|
111
|
-
},
|
|
112
|
-
} });
|
|
113
|
-
/**
|
|
114
|
-
* MOCK EXERCISE: Cardio-Free
|
|
115
|
-
* Simulates an outdoor/untracked cardio session (e.g., Outdoor Run).
|
|
116
|
-
*/
|
|
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
|
-
type: "cardio-free",
|
|
119
|
-
stressRestBonus: 0,
|
|
120
|
-
fatigueMultiplier: 1.0,
|
|
121
|
-
setupTypicalSecs: 0,
|
|
122
|
-
restPeriods: {
|
|
123
|
-
minimum: 0,
|
|
124
|
-
typical: 0,
|
|
125
|
-
maximum: 300,
|
|
126
|
-
optimalRange: [0, 0],
|
|
127
|
-
},
|
|
128
|
-
} });
|
|
129
|
-
/**
|
|
130
|
-
* Helper dictionary containing all mock exercises mapped by their ID.
|
|
131
|
-
*/
|
|
132
|
-
exports.mockExercisesDictionary = {
|
|
133
|
-
[exports.mockExerciseWeightReps.exerciseId]: exports.mockExerciseWeightReps,
|
|
134
|
-
[exports.mockExerciseRepsOnly.exerciseId]: exports.mockExerciseRepsOnly,
|
|
135
|
-
[exports.mockExerciseDuration.exerciseId]: exports.mockExerciseDuration,
|
|
136
|
-
[exports.mockExerciseCardioMachine.exerciseId]: exports.mockExerciseCardioMachine,
|
|
137
|
-
[exports.mockExerciseCardioFree.exerciseId]: exports.mockExerciseCardioFree,
|
|
138
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { TProPlan, TUserMetric, TExercise } from "../types";
|
|
2
|
-
/**
|
|
3
|
-
* Calculates the BMI (Body Mass Index).
|
|
4
|
-
*/
|
|
5
|
-
export declare const calculateBMI: (weightKg: number, heightCm: number) => number;
|
|
6
|
-
/**
|
|
7
|
-
* The Adoption Engine: Safely scales a Master Pro Plan to fit an individual user's body type.
|
|
8
|
-
*/
|
|
9
|
-
export declare const scaleProPlan: (masterPlan: TProPlan, userMetric: TUserMetric, exercisesDictionary: Record<string, TExercise>) => TProPlan;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.scaleProPlan = exports.calculateBMI = void 0;
|
|
4
|
-
const NO_THRESHOLD = Infinity;
|
|
5
|
-
/**
|
|
6
|
-
* Calculates the BMI (Body Mass Index).
|
|
7
|
-
*/
|
|
8
|
-
const calculateBMI = (weightKg, heightCm) => {
|
|
9
|
-
if (!weightKg || !heightCm || heightCm <= 0)
|
|
10
|
-
return 0;
|
|
11
|
-
const heightM = heightCm / 100;
|
|
12
|
-
return Number((weightKg / (heightM * heightM)).toFixed(1));
|
|
13
|
-
};
|
|
14
|
-
exports.calculateBMI = calculateBMI;
|
|
15
|
-
/**
|
|
16
|
-
* Rounds a calculated weight load to the nearest sensible gym increment (e.g. 2.5kg).
|
|
17
|
-
*/
|
|
18
|
-
const roundToNearest = (val, increment) => Math.round(val / increment) * increment;
|
|
19
|
-
/**
|
|
20
|
-
* Calculates effective BMI threshold based on the user's fitness activity level.
|
|
21
|
-
* Active users carry more muscle, so their BMI threshold for bodyweight safety triggers is raised.
|
|
22
|
-
*/
|
|
23
|
-
const getAdjustedBMIThreshold = (baseThreshold, activityLevel) => {
|
|
24
|
-
if (!baseThreshold)
|
|
25
|
-
return NO_THRESHOLD;
|
|
26
|
-
switch (activityLevel) {
|
|
27
|
-
case "very-active":
|
|
28
|
-
return baseThreshold + 5; // e.g. Obese trigger moves from 30 to 35
|
|
29
|
-
case "moderately-active":
|
|
30
|
-
return baseThreshold + 3; // e.g. Obese trigger moves from 30 to 33
|
|
31
|
-
case "lightly-active":
|
|
32
|
-
return baseThreshold + 1;
|
|
33
|
-
case "sedentary":
|
|
34
|
-
default:
|
|
35
|
-
return baseThreshold; // No adjustment
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* Modifies an individual record based on fallback logic (when no regression exercise exists, or for softer triggers).
|
|
40
|
-
* Reduces volume or duration by roughly 30%.
|
|
41
|
-
*/
|
|
42
|
-
const applyFallbackScalingToRecord = (record) => {
|
|
43
|
-
const scaledRecord = Object.assign({}, record);
|
|
44
|
-
const SCALE_FACTOR = 0.7;
|
|
45
|
-
// Scale reps
|
|
46
|
-
if (scaledRecord.type === "reps-only" || scaledRecord.type === "weight-reps") {
|
|
47
|
-
const currentReps = parseInt(scaledRecord.reps, 10);
|
|
48
|
-
if (!isNaN(currentReps) && currentReps > 0) {
|
|
49
|
-
scaledRecord.reps = Math.max(1, Math.round(currentReps * SCALE_FACTOR)).toString();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Scale duration for all time-based records
|
|
53
|
-
if (scaledRecord.type === "duration" ||
|
|
54
|
-
scaledRecord.type === "cardio-machine" ||
|
|
55
|
-
scaledRecord.type === "cardio-free") {
|
|
56
|
-
const [mm, ss] = scaledRecord.durationMmSs.split(":").map(Number);
|
|
57
|
-
if (!isNaN(mm) && !isNaN(ss)) {
|
|
58
|
-
const totalSecs = mm * 60 + ss;
|
|
59
|
-
const newSecs = Math.max(5, Math.round(totalSecs * SCALE_FACTOR)); // minimum 5 seconds
|
|
60
|
-
const newMm = Math.floor(newSecs / 60).toString();
|
|
61
|
-
const newMmPadded = newMm.length < 2 ? "0" + newMm : newMm;
|
|
62
|
-
const newSs = (newSecs % 60).toString();
|
|
63
|
-
const newSsPadded = newSs.length < 2 ? "0" + newSs : newSs;
|
|
64
|
-
scaledRecord.durationMmSs = `${newMmPadded}:${newSsPadded}`;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return scaledRecord;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* The Adoption Engine: Safely scales a Master Pro Plan to fit an individual user's body type.
|
|
71
|
-
*/
|
|
72
|
-
const scaleProPlan = (masterPlan, userMetric, exercisesDictionary) => {
|
|
73
|
-
// We don't mutate the original
|
|
74
|
-
const tailoredPlan = JSON.parse(JSON.stringify(masterPlan));
|
|
75
|
-
const { weightKg, heightCm, gender, fitnessLevel } = userMetric;
|
|
76
|
-
const bmi = weightKg && heightCm ? (0, exports.calculateBMI)(weightKg, heightCm) : 0;
|
|
77
|
-
tailoredPlan.days.forEach((day) => {
|
|
78
|
-
day.exercises = day.exercises.map((templateExercise) => {
|
|
79
|
-
const exerciseMeta = exercisesDictionary[templateExercise.exerciseId];
|
|
80
|
-
// If we can't find metadata, return as-is
|
|
81
|
-
if (!exerciseMeta)
|
|
82
|
-
return templateExercise;
|
|
83
|
-
let isModified = false;
|
|
84
|
-
let newExerciseId = templateExercise.exerciseId;
|
|
85
|
-
let newRecords = [...templateExercise.initialRecords];
|
|
86
|
-
// 1. Check Safety Triggers (BMI & Bodyweight Dependency)
|
|
87
|
-
if (bmi > 0 &&
|
|
88
|
-
(exerciseMeta.bodyweightDependency === "high" || exerciseMeta.bodyweightDependency === "medium") &&
|
|
89
|
-
exerciseMeta.bmiThresholds) {
|
|
90
|
-
const obeseTrigger = getAdjustedBMIThreshold(exerciseMeta.bmiThresholds.obeseTrigger, fitnessLevel);
|
|
91
|
-
const overweightTrigger = getAdjustedBMIThreshold(exerciseMeta.bmiThresholds.overweightTrigger, fitnessLevel);
|
|
92
|
-
if (bmi >= obeseTrigger) {
|
|
93
|
-
// Tier 1: Hard swap to regression (if available) AND scale down volume
|
|
94
|
-
newRecords = newRecords.map(applyFallbackScalingToRecord);
|
|
95
|
-
isModified = true;
|
|
96
|
-
if (exerciseMeta.regressionExerciseId && exercisesDictionary[exerciseMeta.regressionExerciseId]) {
|
|
97
|
-
newExerciseId = exerciseMeta.regressionExerciseId;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (bmi >= overweightTrigger) {
|
|
101
|
-
// Tier 2: Softer response. Keep the exercise, but scale down the volume/duration.
|
|
102
|
-
newRecords = newRecords.map(applyFallbackScalingToRecord);
|
|
103
|
-
isModified = true;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// 2. Check Load Multipliers
|
|
107
|
-
if (weightKg && exerciseMeta.weightMultiplier && newRecords.length > 0) {
|
|
108
|
-
const multiplier = gender === "male"
|
|
109
|
-
? exerciseMeta.weightMultiplier.male
|
|
110
|
-
: gender === "female"
|
|
111
|
-
? exerciseMeta.weightMultiplier.female
|
|
112
|
-
: exerciseMeta.weightMultiplier.default;
|
|
113
|
-
if (multiplier && multiplier > 0) {
|
|
114
|
-
// Round to nearest 2.5kg to match real-world gym equipment
|
|
115
|
-
const suggestedLoadKg = roundToNearest(weightKg * multiplier, 2.5);
|
|
116
|
-
newRecords = newRecords.map((record) => {
|
|
117
|
-
if (record.type === "weight-reps") {
|
|
118
|
-
isModified = true;
|
|
119
|
-
return Object.assign(Object.assign({}, record), { kg: suggestedLoadKg.toString() });
|
|
120
|
-
}
|
|
121
|
-
// Guard against applying load to a falsy "0" aux weight
|
|
122
|
-
if (record.type === "reps-only" || record.type === "duration" || record.type === "cardio-free") {
|
|
123
|
-
const currentAux = parseFloat(record.auxWeightKg || "0");
|
|
124
|
-
if (!isNaN(currentAux) && currentAux > 0) {
|
|
125
|
-
isModified = true;
|
|
126
|
-
return Object.assign(Object.assign({}, record), { auxWeightKg: suggestedLoadKg.toString() });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return record;
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Return the tailored template exercise
|
|
134
|
-
return Object.assign(Object.assign({}, templateExercise), { exerciseId: newExerciseId, initialRecords: newRecords, isAutoScaled: isModified || undefined });
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
return tailoredPlan;
|
|
138
|
-
};
|
|
139
|
-
exports.scaleProPlan = scaleProPlan;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================================
|
|
3
|
-
* FITFRIX EXERCISE SCORING SYSTEM — Pillar 1: Calorie Burn
|
|
4
|
-
* ============================================================================
|
|
5
|
-
*
|
|
6
|
-
* Estimates energy expenditure (kcal) for an exercise using the MET system.
|
|
7
|
-
*
|
|
8
|
-
* Formula (per set):
|
|
9
|
-
* calories = effectiveMET × userWeightKg × (activeDurationSecs / 3600)
|
|
10
|
-
*
|
|
11
|
-
* Where effectiveMET is the exercise's baseMET adjusted for:
|
|
12
|
-
* - Effort level (RPE/RIR → scales within metRange)
|
|
13
|
-
* - Weight intensity (for weight-reps: how heavy relative to bodyweight)
|
|
14
|
-
* - Duration category (for duration: short/medium/long multiplier)
|
|
15
|
-
* - Speed (for cardio: interpolated from pace or speed range)
|
|
16
|
-
* - Compound multiplier (multi-joint exercises burn more)
|
|
17
|
-
*
|
|
18
|
-
* After summing all sets:
|
|
19
|
-
* - Rest calories are added (elevated MET during rest periods)
|
|
20
|
-
* - EPOC (afterburn) is added as a percentage of work calories
|
|
21
|
-
*
|
|
22
|
-
* References:
|
|
23
|
-
* - Ainsworth BE et al. "Compendium of Physical Activities" (2011)
|
|
24
|
-
* - Katch, McArdle & Katch, "Exercise Physiology" (8th ed.)
|
|
25
|
-
*/
|
|
26
|
-
import type { IParsedSet, IUserContext } from "./types";
|
|
27
|
-
interface IMetabolicData {
|
|
28
|
-
baseMET: number;
|
|
29
|
-
metRange: [number, number];
|
|
30
|
-
compoundMultiplier: number;
|
|
31
|
-
muscleGroupFactor: number;
|
|
32
|
-
intensityScaling: "linear" | "exponential" | "plateau";
|
|
33
|
-
epocFactor: number;
|
|
34
|
-
weightFactors?: {
|
|
35
|
-
lightWeight: number;
|
|
36
|
-
moderateWeight: number;
|
|
37
|
-
heavyWeight: number;
|
|
38
|
-
};
|
|
39
|
-
durationFactors?: {
|
|
40
|
-
shortDuration: number;
|
|
41
|
-
mediumDuration: number;
|
|
42
|
-
longDuration: number;
|
|
43
|
-
};
|
|
44
|
-
paceFactors?: Record<string, number>;
|
|
45
|
-
lightWeight?: number;
|
|
46
|
-
moderateWeight?: number;
|
|
47
|
-
heavyWeight?: number;
|
|
48
|
-
shortDuration?: number;
|
|
49
|
-
mediumDuration?: number;
|
|
50
|
-
longDuration?: number;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Calculate total calorie burn for an exercise.
|
|
54
|
-
*
|
|
55
|
-
* @param sets Parsed & validated sets (from parseRecords)
|
|
56
|
-
* @param metabolicData Exercise's metabolic configuration
|
|
57
|
-
* @param user Validated user context
|
|
58
|
-
* @param difficultyLevel Exercise difficulty (0–4), used for bodyweight estimation
|
|
59
|
-
* @returns Total calories burned (kcal), rounded to 1 decimal
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* const calories = calculateCalories(parsedSets, exercise.metabolicData, userCtx, exercise.difficultyLevel);
|
|
63
|
-
* // → 6.4 (for 3 light bench press sets)
|
|
64
|
-
* // → 142.3 (for a 20-min treadmill run)
|
|
65
|
-
*/
|
|
66
|
-
export declare function calculateCalories(sets: IParsedSet[], metabolicData: IMetabolicData, user: IUserContext, difficultyLevel: number, scoringSpecialHandling?: "plyometric" | "stretch-mobility" | "continuous-duration" | "loaded-carry"): number;
|
|
67
|
-
export {};
|