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.
@@ -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,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 user's preferred increment for the given equipment type
28
+ * Gets base increment for the given equipment type from user settings
27
29
  */
28
- function getPreferredIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
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); // Use dumbbell increment for weighted bodyweight
45
+ DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement);
44
46
  default:
45
- return 2.5; // Fallback
47
+ return 2.5; // Default fallback
46
48
  }
47
49
  }
48
50
  /**
49
- * Gets equipment-specific increments from user settings
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 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);
53
+ function calculateVolume(sets, reps, weight) {
54
+ return sets * reps * weight;
78
55
  }
79
56
  /**
80
- * Determines the appropriate standard increment based on exercise type, weight and experience level
57
+ * Handle bodyweight exercises specially
81
58
  */
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) {
59
+ function handleBodyweightExercise(data) {
145
60
  switch (data.rating) {
146
61
  case 1: // Very easy
147
62
  return {
148
- newWeight: 0,
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: 0,
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: 0,
74
+ newWeight: data.weight,
164
75
  newReps: data.reps,
165
76
  };
166
77
  case 5: // Too hard
167
78
  return {
168
- newWeight: 0,
79
+ newWeight: data.weight,
169
80
  newReps: Math.max(1, data.reps - 2),
170
81
  };
171
82
  default:
172
83
  return {
173
- newWeight: 0,
84
+ newWeight: data.weight,
174
85
  newReps: data.reps,
175
86
  };
176
87
  }
177
88
  }
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
- }
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 { newWeight: data.weight, newReps: data.reps };
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 { newWeight: data.weight, newReps: data.reps };
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 calculateStrengthProgression(data, userSettings);
131
+ return {
132
+ newWeight: Math.max(0, data.weight + weightChange),
133
+ newReps: data.reps,
134
+ };
265
135
  }
266
- return calculateHypertrophyProgression(data, userSettings);
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 availableIncrements = getEquipmentIncrements(equipmentType, userSettings);
274
- const preferredIncrement = availableIncrements[0];
275
- // Round to nearest multiple of preferred increment
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.1.0",
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": "Your Name",
18
+ "author": "Victor Norman",
15
19
  "license": "MIT",
16
20
  "files": ["dist/**/*"],
17
21
  "devDependencies": {