fitness-progression-calculator 1.1.0 → 1.3.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.
@@ -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
  /**
@@ -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,22 @@ 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
+ const LIGHT_DUMBBELL_MAX = 10; // Maximum weight for light dumbbells
24
+ const LIGHT_DUMBBELL_INCREMENT = 1.0; // Increment for light dumbbells
25
+ // Helper functions
22
26
  function isSpecialBodyweightExercise(exerciseName) {
23
27
  return SPECIAL_BODYWEIGHT_EXERCISES.includes(exerciseName.toUpperCase());
24
28
  }
25
29
  /**
26
- * Gets user's preferred increment for the given equipment type
30
+ * Gets base increment for the given equipment type from user settings
27
31
  */
28
- function getPreferredIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
32
+ function getBaseIncrement(equipmentType, currentWeight, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
33
+ // Special case for light dumbbells
34
+ if (equipmentType === "DUMBBELL" && currentWeight < LIGHT_DUMBBELL_MAX) {
35
+ return LIGHT_DUMBBELL_INCREMENT;
36
+ }
29
37
  switch (equipmentType) {
30
38
  case "BARBELL":
31
39
  return (userSettings.barbellIncrement ||
@@ -40,238 +48,176 @@ function getPreferredIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_S
40
48
  DEFAULT_EQUIPMENT_SETTINGS.machineIncrement);
41
49
  case "BODYWEIGHT":
42
50
  return (userSettings.dumbbellIncrement ||
43
- DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement); // Use dumbbell increment for weighted bodyweight
51
+ DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement);
44
52
  default:
45
- return 2.5; // Fallback
53
+ return 2.5; // Default fallback
46
54
  }
47
55
  }
48
56
  /**
49
- * Gets equipment-specific increments from user settings
57
+ * Rounds a weight to the nearest multiple of the increment
50
58
  */
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
- ];
59
+ function roundToIncrementMultiple(weight, equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
60
+ const increment = getBaseIncrement(equipmentType, weight, userSettings);
61
+ return Math.round(weight / increment) * increment;
67
62
  }
68
63
  /**
69
- * Finds the closest available increment to the desired raw increment
64
+ * Calculate the total volume for a given exercise configuration
70
65
  */
71
- function findClosestIncrement(rawIncrement, availableIncrements) {
72
- // If rawIncrement is smaller than smallest available increment, return smallest
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);
66
+ function calculateVolume(sets, reps, weight) {
67
+ return sets * reps * weight;
78
68
  }
79
69
  /**
80
- * Determines the appropriate standard increment based on exercise type, weight and experience level
70
+ * Handle bodyweight exercises specially
81
71
  */
82
- function getStandardIncrement(currentWeight, equipmentType, isCompound, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
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) {
72
+ function handleBodyweightExercise(data) {
145
73
  switch (data.rating) {
146
74
  case 1: // Very easy
147
75
  return {
148
- newWeight: 0,
76
+ newWeight: data.weight,
149
77
  newReps: Math.min(data.reps + 2, MAX_REPS),
150
78
  };
151
79
  case 2: // Easy
152
- return {
153
- newWeight: 0,
154
- newReps: Math.min(data.reps + 1, MAX_REPS),
155
- };
156
80
  case 3: // Moderate
157
81
  return {
158
- newWeight: 0,
82
+ newWeight: data.weight,
159
83
  newReps: Math.min(data.reps + 1, MAX_REPS),
160
84
  };
161
85
  case 4: // Hard
162
86
  return {
163
- newWeight: 0,
87
+ newWeight: data.weight,
164
88
  newReps: data.reps,
165
89
  };
166
90
  case 5: // Too hard
167
91
  return {
168
- newWeight: 0,
92
+ newWeight: data.weight,
169
93
  newReps: Math.max(1, data.reps - 2),
170
94
  };
171
95
  default:
172
96
  return {
173
- newWeight: 0,
97
+ newWeight: data.weight,
174
98
  newReps: data.reps,
175
99
  };
176
100
  }
177
101
  }
178
- function calculateStrengthProgression(data, userSettings) {
179
- // Handle special bodyweight exercises differently
180
- if (data.equipment_type === "BODYWEIGHT" &&
181
- isSpecialBodyweightExercise(data.exercise_name)) {
182
- return calculateBodyweightProgression(data);
183
- }
184
- const increment = getWeightIncrement(data.weight, data.equipment_type, data.is_compound, data.rating, userSettings);
185
- // Apply the calculated increment
186
- return {
187
- newWeight: Math.max(0, data.weight + increment),
188
- newReps: data.reps,
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
- }
102
+ /**
103
+ * Calculate weight change based on rating
104
+ */
105
+ function getWeightChangeForRating(rating, baseIncrement) {
106
+ switch (rating) {
107
+ case 1: // Very easy
108
+ return baseIncrement * 3;
109
+ case 2: // Easy
110
+ return baseIncrement * 2;
111
+ case 3: // Moderate
112
+ return baseIncrement;
247
113
  case 4: // Hard
248
- return { newWeight: data.weight, newReps: data.reps };
114
+ return 0;
249
115
  case 5: // Too hard
250
- return {
251
- newWeight: Math.max(0, data.weight + increment),
252
- newReps: data.reps,
253
- };
116
+ return -baseIncrement;
254
117
  default:
255
- return { newWeight: data.weight, newReps: data.reps };
118
+ return 0;
256
119
  }
257
120
  }
258
121
  /**
259
122
  * Main function to calculate progression based on exercise data and program type
260
- * Now accepts optional userSettings for equipment-specific increments
261
123
  */
262
- function calculateProgression(data, programType, userSettings) {
124
+ function calculateProgression(data, programType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
125
+ console.log(`Calculate progression for ${data.exercise_name}:`, {
126
+ currentWeight: data.weight,
127
+ currentReps: data.reps,
128
+ rating: data.rating,
129
+ equipmentType: data.equipment_type,
130
+ programType,
131
+ });
132
+ // Special handling for bodyweight exercises
133
+ if (data.equipment_type === "BODYWEIGHT" &&
134
+ isSpecialBodyweightExercise(data.exercise_name)) {
135
+ return handleBodyweightExercise(data);
136
+ }
137
+ // Get the base increment for this equipment type
138
+ const baseIncrement = getBaseIncrement(data.equipment_type, data.weight, userSettings);
139
+ console.log(`Using ${data.equipment_type} increment: ${baseIncrement}kg`);
140
+ // Calculate weight change based on rating
141
+ const weightChange = getWeightChangeForRating(data.rating, baseIncrement);
142
+ // For strength programs, focus on weight increases
263
143
  if (programType === "STRENGTH") {
264
- return calculateStrengthProgression(data, userSettings);
144
+ const rawNewWeight = Math.max(0, data.weight + weightChange);
145
+ // Round the weight to the nearest multiple of the increment
146
+ const roundedWeight = roundToIncrementMultiple(rawNewWeight, data.equipment_type, userSettings);
147
+ return {
148
+ newWeight: roundedWeight,
149
+ newReps: data.reps,
150
+ };
265
151
  }
266
- return calculateHypertrophyProgression(data, userSettings);
152
+ // HYPERTROPHY program logic
153
+ // For ratings 4 (Hard) and 5 (Too Hard), no rep increase, only potential weight decrease
154
+ if (data.rating >= 4) {
155
+ const rawNewWeight = Math.max(0, data.weight + weightChange);
156
+ const roundedWeight = roundToIncrementMultiple(rawNewWeight, data.equipment_type, userSettings);
157
+ return {
158
+ newWeight: roundedWeight,
159
+ newReps: data.reps,
160
+ };
161
+ }
162
+ // If we've reached max reps, cycle back to starting reps with increased weight
163
+ if (data.reps >= MAX_REPS) {
164
+ // Calculate how much weight to increase to maintain volume
165
+ // Current volume: sets * maxReps * currentWeight
166
+ // New volume: sets * startingReps * newWeight
167
+ // So, newWeight = currentWeight * (maxReps / startingReps)
168
+ // Ensure the weight increases by at least the rating-based increment
169
+ const volumeBasedIncrement = data.weight * (MAX_REPS / STARTING_HYPERTROPHY_REPS) - data.weight;
170
+ const actualIncrement = Math.max(weightChange, volumeBasedIncrement);
171
+ const rawNewWeight = Math.max(0, data.weight + actualIncrement);
172
+ const roundedWeight = roundToIncrementMultiple(rawNewWeight, data.equipment_type, userSettings);
173
+ return {
174
+ newWeight: roundedWeight,
175
+ newReps: STARTING_HYPERTROPHY_REPS,
176
+ };
177
+ }
178
+ // For ratings 1-3 with non-maxed reps, compare volume increases
179
+ // Option 1: Increase weight
180
+ const rawNewWeight = Math.max(0, data.weight + weightChange);
181
+ const roundedNewWeight = roundToIncrementMultiple(rawNewWeight, data.equipment_type, userSettings);
182
+ const volumeWithWeightIncrease = calculateVolume(data.sets, data.reps, roundedNewWeight);
183
+ // Option 2: Increase reps
184
+ const newReps = data.reps + 1;
185
+ const volumeWithRepIncrease = calculateVolume(data.sets, newReps, data.weight);
186
+ // Calculate volume change for each option
187
+ const currentVolume = calculateVolume(data.sets, data.reps, data.weight);
188
+ const volumeChangeWithWeight = volumeWithWeightIncrease - currentVolume;
189
+ const volumeChangeWithReps = volumeWithRepIncrease - currentVolume;
190
+ console.log("Volume comparison:", {
191
+ currentVolume,
192
+ volumeWithWeightIncrease,
193
+ volumeWithRepIncrease,
194
+ volumeChangeWithWeight,
195
+ volumeChangeWithReps,
196
+ });
197
+ // For very easy (rating 1), always increase both weight and reps if compound
198
+ if (data.rating === 1 && data.is_compound) {
199
+ return {
200
+ newWeight: roundedNewWeight,
201
+ newReps: Math.min(newReps, MAX_REPS),
202
+ };
203
+ }
204
+ // For other cases, choose the option with the smaller volume increase
205
+ // This helps make sure progression is steady and not too aggressive
206
+ if (volumeChangeWithWeight <= volumeChangeWithReps) {
207
+ return {
208
+ newWeight: roundedNewWeight,
209
+ newReps: data.reps,
210
+ };
211
+ }
212
+ return {
213
+ newWeight: data.weight,
214
+ newReps: newReps,
215
+ };
267
216
  }
268
217
  /**
269
218
  * Helper function to round weight to closest available increment
270
219
  * Useful for client-side adjustments
271
220
  */
272
- function roundToClosestIncrement(weight, equipmentType, userSettings) {
273
- const availableIncrements = getEquipmentIncrements(equipmentType, userSettings);
274
- const preferredIncrement = availableIncrements[0];
275
- // Round to nearest multiple of preferred increment
276
- return Math.round(weight / preferredIncrement) * preferredIncrement;
221
+ function roundToClosestIncrement(weight, equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
222
+ return roundToIncrementMultiple(weight, equipmentType, userSettings);
277
223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitness-progression-calculator",
3
- "version": "1.1.0",
3
+ "version": "1.3.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": "Your Name",
18
+ "author": "Victor Norman",
15
19
  "license": "MIT",
16
20
  "files": ["dist/**/*"],
17
21
  "devDependencies": {