fitness-progression-calculator 1.1.0 → 1.2.0
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/dist/calculator.d.ts +0 -1
- package/dist/calculator.js +117 -190
- package/package.json +6 -2
package/dist/calculator.d.ts
CHANGED
@@ -21,7 +21,6 @@ interface UserEquipmentSettings {
|
|
21
21
|
}
|
22
22
|
/**
|
23
23
|
* Main function to calculate progression based on exercise data and program type
|
24
|
-
* Now accepts optional userSettings for equipment-specific increments
|
25
24
|
*/
|
26
25
|
export declare function calculateProgression(data: ExerciseData, programType: "STRENGTH" | "HYPERTROPHY", userSettings?: UserEquipmentSettings): ProgressionResult;
|
27
26
|
/**
|
package/dist/calculator.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
"use strict";
|
2
|
-
// services/progressionCalculator.ts
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
4
3
|
exports.calculateProgression = calculateProgression;
|
5
4
|
exports.roundToClosestIncrement = roundToClosestIncrement;
|
@@ -19,13 +18,16 @@ const DEFAULT_EQUIPMENT_SETTINGS = {
|
|
19
18
|
machineIncrement: 5.0,
|
20
19
|
experienceLevel: "BEGINNER",
|
21
20
|
};
|
21
|
+
// Constants
|
22
|
+
const STARTING_HYPERTROPHY_REPS = 10; // Rep count to cycle back to after reaching MAX_REPS
|
23
|
+
// Helper functions
|
22
24
|
function isSpecialBodyweightExercise(exerciseName) {
|
23
25
|
return SPECIAL_BODYWEIGHT_EXERCISES.includes(exerciseName.toUpperCase());
|
24
26
|
}
|
25
27
|
/**
|
26
|
-
* Gets
|
28
|
+
* Gets base increment for the given equipment type from user settings
|
27
29
|
*/
|
28
|
-
function
|
30
|
+
function getBaseIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
|
29
31
|
switch (equipmentType) {
|
30
32
|
case "BARBELL":
|
31
33
|
return (userSettings.barbellIncrement ||
|
@@ -40,238 +42,163 @@ function getPreferredIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_S
|
|
40
42
|
DEFAULT_EQUIPMENT_SETTINGS.machineIncrement);
|
41
43
|
case "BODYWEIGHT":
|
42
44
|
return (userSettings.dumbbellIncrement ||
|
43
|
-
DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement);
|
45
|
+
DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement);
|
44
46
|
default:
|
45
|
-
return 2.5; //
|
47
|
+
return 2.5; // Default fallback
|
46
48
|
}
|
47
49
|
}
|
48
50
|
/**
|
49
|
-
*
|
50
|
-
*/
|
51
|
-
function getEquipmentIncrements(equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
|
52
|
-
// Define standard increments available for each equipment type
|
53
|
-
const standardIncrements = {
|
54
|
-
BARBELL: [1.25, 2.5, 5.0, 10.0],
|
55
|
-
DUMBBELL: [1.0, 2.0, 2.5, 5.0],
|
56
|
-
CABLE: [2.0, 2.5, 5.0, 10.0],
|
57
|
-
MACHINE: [2.5, 5.0, 10.0],
|
58
|
-
BODYWEIGHT: [1.0, 2.5, 5.0], // For weighted bodyweight exercises
|
59
|
-
};
|
60
|
-
// Get user's preferred increment for this equipment type
|
61
|
-
const preferredIncrement = getPreferredIncrement(equipmentType, userSettings);
|
62
|
-
// Return equipment's standard increments with preferred increment first
|
63
|
-
return [
|
64
|
-
preferredIncrement,
|
65
|
-
...standardIncrements[equipmentType].filter((inc) => inc !== preferredIncrement),
|
66
|
-
];
|
67
|
-
}
|
68
|
-
/**
|
69
|
-
* Finds the closest available increment to the desired raw increment
|
51
|
+
* Calculate the total volume for a given exercise configuration
|
70
52
|
*/
|
71
|
-
function
|
72
|
-
|
73
|
-
if (rawIncrement <= availableIncrements[0]) {
|
74
|
-
return availableIncrements[0];
|
75
|
-
}
|
76
|
-
// Find closest increment by minimizing absolute difference
|
77
|
-
return availableIncrements.reduce((prev, curr) => Math.abs(curr - rawIncrement) < Math.abs(prev - rawIncrement) ? curr : prev);
|
53
|
+
function calculateVolume(sets, reps, weight) {
|
54
|
+
return sets * reps * weight;
|
78
55
|
}
|
79
56
|
/**
|
80
|
-
*
|
57
|
+
* Handle bodyweight exercises specially
|
81
58
|
*/
|
82
|
-
function
|
83
|
-
// Get available increments for this equipment type
|
84
|
-
const availableIncrements = getEquipmentIncrements(equipmentType, userSettings);
|
85
|
-
const sortedIncrements = [...availableIncrements].sort((a, b) => a - b);
|
86
|
-
const smallestIncrement = sortedIncrements[0] || 1.25;
|
87
|
-
// Base increment size on exercise type and current weight
|
88
|
-
let rawIncrement;
|
89
|
-
if (isCompound) {
|
90
|
-
// Compound movements (bench, squat, deadlift):
|
91
|
-
if (currentWeight < 40) {
|
92
|
-
rawIncrement = 5.0; // Under 40kg: 5kg increments
|
93
|
-
}
|
94
|
-
else if (currentWeight < 100) {
|
95
|
-
rawIncrement = 2.5; // 40-100kg: 2.5kg increments
|
96
|
-
}
|
97
|
-
else {
|
98
|
-
rawIncrement = 1.25; // Over 100kg: 1.25kg increments
|
99
|
-
}
|
100
|
-
}
|
101
|
-
else {
|
102
|
-
// Isolation movements (curls, lateral raises):
|
103
|
-
if (currentWeight < 15) {
|
104
|
-
rawIncrement = 2.5; // Under 15kg: 2.5kg increments
|
105
|
-
}
|
106
|
-
else {
|
107
|
-
rawIncrement = 1.25; // Over 15kg: 1.25kg increments
|
108
|
-
}
|
109
|
-
}
|
110
|
-
// Modify based on experience level
|
111
|
-
const experienceLevel = (userSettings === null || userSettings === void 0 ? void 0 : userSettings.experienceLevel) || "BEGINNER";
|
112
|
-
if (experienceLevel === "INTERMEDIATE" && currentWeight > 60) {
|
113
|
-
// Intermediate lifters progress slower at higher weights
|
114
|
-
rawIncrement = Math.max(smallestIncrement, rawIncrement * 0.5);
|
115
|
-
}
|
116
|
-
else if (experienceLevel === "ADVANCED") {
|
117
|
-
// Advanced lifters progress slower overall
|
118
|
-
rawIncrement = Math.max(smallestIncrement, rawIncrement * 0.5);
|
119
|
-
}
|
120
|
-
// Find the closest available increment
|
121
|
-
return findClosestIncrement(rawIncrement, availableIncrements);
|
122
|
-
}
|
123
|
-
/**
|
124
|
-
* Gets weight increment based on difficulty rating, equipment, weight and experience level
|
125
|
-
*/
|
126
|
-
function getWeightIncrement(currentWeight, equipment, isCompound, rating, userSettings) {
|
127
|
-
// Get the standard increment for this exercise/weight
|
128
|
-
const standardIncrement = getStandardIncrement(currentWeight, equipment, isCompound, userSettings);
|
129
|
-
// Modify based on rating
|
130
|
-
switch (rating) {
|
131
|
-
case 1: // Very Easy - double the increment
|
132
|
-
return standardIncrement * 2;
|
133
|
-
case 2: // Easy - standard increment
|
134
|
-
case 3: // Moderate - standard increment
|
135
|
-
return standardIncrement;
|
136
|
-
case 4: // Hard - no increase
|
137
|
-
return 0;
|
138
|
-
case 5: // Too Hard - decrease
|
139
|
-
return -standardIncrement;
|
140
|
-
default:
|
141
|
-
return standardIncrement;
|
142
|
-
}
|
143
|
-
}
|
144
|
-
function calculateBodyweightProgression(data) {
|
59
|
+
function handleBodyweightExercise(data) {
|
145
60
|
switch (data.rating) {
|
146
61
|
case 1: // Very easy
|
147
62
|
return {
|
148
|
-
newWeight:
|
63
|
+
newWeight: data.weight,
|
149
64
|
newReps: Math.min(data.reps + 2, MAX_REPS),
|
150
65
|
};
|
151
66
|
case 2: // Easy
|
152
|
-
return {
|
153
|
-
newWeight: 0,
|
154
|
-
newReps: Math.min(data.reps + 1, MAX_REPS),
|
155
|
-
};
|
156
67
|
case 3: // Moderate
|
157
68
|
return {
|
158
|
-
newWeight:
|
69
|
+
newWeight: data.weight,
|
159
70
|
newReps: Math.min(data.reps + 1, MAX_REPS),
|
160
71
|
};
|
161
72
|
case 4: // Hard
|
162
73
|
return {
|
163
|
-
newWeight:
|
74
|
+
newWeight: data.weight,
|
164
75
|
newReps: data.reps,
|
165
76
|
};
|
166
77
|
case 5: // Too hard
|
167
78
|
return {
|
168
|
-
newWeight:
|
79
|
+
newWeight: data.weight,
|
169
80
|
newReps: Math.max(1, data.reps - 2),
|
170
81
|
};
|
171
82
|
default:
|
172
83
|
return {
|
173
|
-
newWeight:
|
84
|
+
newWeight: data.weight,
|
174
85
|
newReps: data.reps,
|
175
86
|
};
|
176
87
|
}
|
177
88
|
}
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
};
|
190
|
-
}
|
191
|
-
function calculateVolumeIncrease(data, type, userSettings) {
|
192
|
-
const increment = getWeightIncrement(data.weight, data.equipment_type, data.is_compound, Math.min(data.rating, 3), // Cap at 3 to always get a positive increment for comparison
|
193
|
-
userSettings);
|
194
|
-
const currentVolume = data.sets * data.reps * data.weight;
|
195
|
-
if (type === "weight") {
|
196
|
-
return data.sets * data.reps * (data.weight + increment) - currentVolume;
|
197
|
-
}
|
198
|
-
// Only calculate rep increase if we're not at max reps
|
199
|
-
if (data.reps >= MAX_REPS) {
|
200
|
-
return Number.POSITIVE_INFINITY; // Force weight increase by making rep increase unfavorable
|
201
|
-
}
|
202
|
-
return data.sets * (data.reps + 1) * data.weight - currentVolume;
|
203
|
-
}
|
204
|
-
function calculateHypertrophyProgression(data, userSettings) {
|
205
|
-
// Handle special bodyweight exercises differently
|
206
|
-
if (data.equipment_type === "BODYWEIGHT" &&
|
207
|
-
isSpecialBodyweightExercise(data.exercise_name)) {
|
208
|
-
return calculateBodyweightProgression(data);
|
209
|
-
}
|
210
|
-
const increment = getWeightIncrement(data.weight, data.equipment_type, data.is_compound, data.rating, userSettings);
|
211
|
-
// For compound movements (except special bodyweight exercises), prioritize weight increases
|
212
|
-
if (data.is_compound) {
|
213
|
-
return calculateStrengthProgression(data, userSettings);
|
214
|
-
}
|
215
|
-
// For isolation exercises, implement progressive overload based on rating
|
216
|
-
switch (data.rating) {
|
217
|
-
case 1: {
|
218
|
-
// Very easy
|
219
|
-
const newReps = data.reps >= MAX_REPS ? data.reps : data.reps + 1;
|
220
|
-
return {
|
221
|
-
newWeight: data.weight + increment,
|
222
|
-
newReps: newReps,
|
223
|
-
};
|
224
|
-
}
|
225
|
-
case 2: {
|
226
|
-
// Easy - increase either weight or reps (larger increase)
|
227
|
-
if (data.reps >= MAX_REPS) {
|
228
|
-
return { newWeight: data.weight + increment, newReps: data.reps };
|
229
|
-
}
|
230
|
-
const volumeIncreaseWeight = calculateVolumeIncrease(data, "weight", userSettings);
|
231
|
-
const volumeIncreaseReps = calculateVolumeIncrease(data, "reps", userSettings);
|
232
|
-
return volumeIncreaseWeight > volumeIncreaseReps
|
233
|
-
? { newWeight: data.weight + increment, newReps: data.reps }
|
234
|
-
: { newWeight: data.weight, newReps: data.reps + 1 };
|
235
|
-
}
|
236
|
-
case 3: {
|
237
|
-
// Moderate - choose smallest increase between weight and reps
|
238
|
-
if (data.reps >= MAX_REPS) {
|
239
|
-
return { newWeight: data.weight + increment, newReps: data.reps };
|
240
|
-
}
|
241
|
-
const weightIncrease = calculateVolumeIncrease(data, "weight", userSettings);
|
242
|
-
const repIncrease = calculateVolumeIncrease(data, "reps", userSettings);
|
243
|
-
return weightIncrease < repIncrease
|
244
|
-
? { newWeight: data.weight + increment, newReps: data.reps }
|
245
|
-
: { newWeight: data.weight, newReps: data.reps + 1 };
|
246
|
-
}
|
89
|
+
/**
|
90
|
+
* Calculate weight change based on rating
|
91
|
+
*/
|
92
|
+
function getWeightChangeForRating(rating, baseIncrement) {
|
93
|
+
switch (rating) {
|
94
|
+
case 1: // Very easy
|
95
|
+
return baseIncrement * 3;
|
96
|
+
case 2: // Easy
|
97
|
+
return baseIncrement * 2;
|
98
|
+
case 3: // Moderate
|
99
|
+
return baseIncrement;
|
247
100
|
case 4: // Hard
|
248
|
-
return
|
101
|
+
return 0;
|
249
102
|
case 5: // Too hard
|
250
|
-
return
|
251
|
-
newWeight: Math.max(0, data.weight + increment),
|
252
|
-
newReps: data.reps,
|
253
|
-
};
|
103
|
+
return -baseIncrement;
|
254
104
|
default:
|
255
|
-
return
|
105
|
+
return 0;
|
256
106
|
}
|
257
107
|
}
|
258
108
|
/**
|
259
109
|
* Main function to calculate progression based on exercise data and program type
|
260
|
-
* Now accepts optional userSettings for equipment-specific increments
|
261
110
|
*/
|
262
|
-
function calculateProgression(data, programType, userSettings) {
|
111
|
+
function calculateProgression(data, programType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
|
112
|
+
console.log(`Calculate progression for ${data.exercise_name}:`, {
|
113
|
+
currentWeight: data.weight,
|
114
|
+
currentReps: data.reps,
|
115
|
+
rating: data.rating,
|
116
|
+
equipmentType: data.equipment_type,
|
117
|
+
programType,
|
118
|
+
});
|
119
|
+
// Special handling for bodyweight exercises
|
120
|
+
if (data.equipment_type === "BODYWEIGHT" &&
|
121
|
+
isSpecialBodyweightExercise(data.exercise_name)) {
|
122
|
+
return handleBodyweightExercise(data);
|
123
|
+
}
|
124
|
+
// Get the base increment for this equipment type
|
125
|
+
const baseIncrement = getBaseIncrement(data.equipment_type, userSettings);
|
126
|
+
console.log(`Using ${data.equipment_type} increment: ${baseIncrement}kg`);
|
127
|
+
// Calculate weight change based on rating
|
128
|
+
const weightChange = getWeightChangeForRating(data.rating, baseIncrement);
|
129
|
+
// For strength programs, focus on weight increases
|
263
130
|
if (programType === "STRENGTH") {
|
264
|
-
return
|
131
|
+
return {
|
132
|
+
newWeight: Math.max(0, data.weight + weightChange),
|
133
|
+
newReps: data.reps,
|
134
|
+
};
|
265
135
|
}
|
266
|
-
|
136
|
+
// HYPERTROPHY program logic
|
137
|
+
// For ratings 4 (Hard) and 5 (Too Hard), no rep increase, only potential weight decrease
|
138
|
+
if (data.rating >= 4) {
|
139
|
+
return {
|
140
|
+
newWeight: Math.max(0, data.weight + weightChange),
|
141
|
+
newReps: data.reps,
|
142
|
+
};
|
143
|
+
}
|
144
|
+
// If we've reached max reps, cycle back to starting reps with increased weight
|
145
|
+
if (data.reps >= MAX_REPS) {
|
146
|
+
// Calculate how much weight to increase to maintain volume
|
147
|
+
// Current volume: sets * maxReps * currentWeight
|
148
|
+
// New volume: sets * startingReps * newWeight
|
149
|
+
// So, newWeight = currentWeight * (maxReps / startingReps)
|
150
|
+
// Ensure the weight increases by at least the rating-based increment
|
151
|
+
const volumeBasedIncrement = data.weight * (MAX_REPS / STARTING_HYPERTROPHY_REPS) - data.weight;
|
152
|
+
const actualIncrement = Math.max(weightChange, volumeBasedIncrement);
|
153
|
+
return {
|
154
|
+
newWeight: Math.max(0, data.weight + actualIncrement),
|
155
|
+
newReps: STARTING_HYPERTROPHY_REPS,
|
156
|
+
};
|
157
|
+
}
|
158
|
+
// For ratings 1-3 with non-maxed reps, compare volume increases
|
159
|
+
// Option 1: Increase weight
|
160
|
+
const newWeight = Math.max(0, data.weight + weightChange);
|
161
|
+
const volumeWithWeightIncrease = calculateVolume(data.sets, data.reps, newWeight);
|
162
|
+
// Option 2: Increase reps
|
163
|
+
const newReps = data.reps + 1;
|
164
|
+
const volumeWithRepIncrease = calculateVolume(data.sets, newReps, data.weight);
|
165
|
+
// Calculate volume change for each option
|
166
|
+
const currentVolume = calculateVolume(data.sets, data.reps, data.weight);
|
167
|
+
const volumeChangeWithWeight = volumeWithWeightIncrease - currentVolume;
|
168
|
+
const volumeChangeWithReps = volumeWithRepIncrease - currentVolume;
|
169
|
+
console.log("Volume comparison:", {
|
170
|
+
currentVolume,
|
171
|
+
volumeWithWeightIncrease,
|
172
|
+
volumeWithRepIncrease,
|
173
|
+
volumeChangeWithWeight,
|
174
|
+
volumeChangeWithReps,
|
175
|
+
});
|
176
|
+
// For very easy (rating 1), always increase both weight and reps if compound
|
177
|
+
if (data.rating === 1 && data.is_compound) {
|
178
|
+
return {
|
179
|
+
newWeight: newWeight,
|
180
|
+
newReps: Math.min(newReps, MAX_REPS),
|
181
|
+
};
|
182
|
+
}
|
183
|
+
// For other cases, choose the option with the smaller volume increase
|
184
|
+
// This helps make sure progression is steady and not too aggressive
|
185
|
+
if (volumeChangeWithWeight <= volumeChangeWithReps) {
|
186
|
+
return {
|
187
|
+
newWeight: newWeight,
|
188
|
+
newReps: data.reps,
|
189
|
+
};
|
190
|
+
}
|
191
|
+
return {
|
192
|
+
newWeight: data.weight,
|
193
|
+
newReps: newReps,
|
194
|
+
};
|
267
195
|
}
|
268
196
|
/**
|
269
197
|
* Helper function to round weight to closest available increment
|
270
198
|
* Useful for client-side adjustments
|
271
199
|
*/
|
272
|
-
function roundToClosestIncrement(weight, equipmentType, userSettings) {
|
273
|
-
const
|
274
|
-
|
275
|
-
|
276
|
-
return Math.round(weight / preferredIncrement) * preferredIncrement;
|
200
|
+
function roundToClosestIncrement(weight, equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
|
201
|
+
const baseIncrement = getBaseIncrement(equipmentType, userSettings);
|
202
|
+
// Round to nearest multiple of baseIncrement
|
203
|
+
return Math.round(weight / baseIncrement) * baseIncrement;
|
277
204
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "fitness-progression-calculator",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.2.0",
|
4
4
|
"description": "Workout progression calculator for fitness applications",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"types": "dist/index.d.ts",
|
@@ -10,8 +10,12 @@
|
|
10
10
|
"build": "tsc",
|
11
11
|
"prepare": "npm run build"
|
12
12
|
},
|
13
|
+
"repository": {
|
14
|
+
"type": "git",
|
15
|
+
"url": "git+https://github.com/VictorNorm/fitness-progression-calculator.git"
|
16
|
+
},
|
13
17
|
"keywords": ["fitness", "workout", "progression"],
|
14
|
-
"author": "
|
18
|
+
"author": "Victor Norman",
|
15
19
|
"license": "MIT",
|
16
20
|
"files": ["dist/**/*"],
|
17
21
|
"devDependencies": {
|