fitness-progression-calculator 1.1.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.
@@ -0,0 +1,32 @@
1
+ interface ExerciseData {
2
+ sets: number;
3
+ reps: number;
4
+ weight: number;
5
+ rating: number;
6
+ equipment_type: "DUMBBELL" | "BARBELL" | "CABLE" | "MACHINE" | "BODYWEIGHT";
7
+ is_compound: boolean;
8
+ exercise_name: string;
9
+ }
10
+ interface ProgressionResult {
11
+ newWeight: number;
12
+ newReps: number;
13
+ deload?: boolean;
14
+ }
15
+ interface UserEquipmentSettings {
16
+ barbellIncrement: number;
17
+ dumbbellIncrement: number;
18
+ cableIncrement: number;
19
+ machineIncrement: number;
20
+ experienceLevel?: "BEGINNER" | "INTERMEDIATE" | "ADVANCED";
21
+ }
22
+ /**
23
+ * Main function to calculate progression based on exercise data and program type
24
+ * Now accepts optional userSettings for equipment-specific increments
25
+ */
26
+ export declare function calculateProgression(data: ExerciseData, programType: "STRENGTH" | "HYPERTROPHY", userSettings?: UserEquipmentSettings): ProgressionResult;
27
+ /**
28
+ * Helper function to round weight to closest available increment
29
+ * Useful for client-side adjustments
30
+ */
31
+ export declare function roundToClosestIncrement(weight: number, equipmentType: ExerciseData["equipment_type"], userSettings?: UserEquipmentSettings): number;
32
+ export type { ExerciseData, ProgressionResult, UserEquipmentSettings };
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ // services/progressionCalculator.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.calculateProgression = calculateProgression;
5
+ exports.roundToClosestIncrement = roundToClosestIncrement;
6
+ const MAX_REPS = 20;
7
+ const BODYWEIGHT_MAX_REPS = 15; // Before considering adding weight
8
+ const SPECIAL_BODYWEIGHT_EXERCISES = [
9
+ "PULL UP",
10
+ "CHIN UP",
11
+ "DIP",
12
+ "PUSH UP",
13
+ "PUSH UP DEFICIT",
14
+ ];
15
+ const DEFAULT_EQUIPMENT_SETTINGS = {
16
+ barbellIncrement: 2.5,
17
+ dumbbellIncrement: 2.0,
18
+ cableIncrement: 2.5,
19
+ machineIncrement: 5.0,
20
+ experienceLevel: "BEGINNER",
21
+ };
22
+ function isSpecialBodyweightExercise(exerciseName) {
23
+ return SPECIAL_BODYWEIGHT_EXERCISES.includes(exerciseName.toUpperCase());
24
+ }
25
+ /**
26
+ * Gets user's preferred increment for the given equipment type
27
+ */
28
+ function getPreferredIncrement(equipmentType, userSettings = DEFAULT_EQUIPMENT_SETTINGS) {
29
+ switch (equipmentType) {
30
+ case "BARBELL":
31
+ return (userSettings.barbellIncrement ||
32
+ DEFAULT_EQUIPMENT_SETTINGS.barbellIncrement);
33
+ case "DUMBBELL":
34
+ return (userSettings.dumbbellIncrement ||
35
+ DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement);
36
+ case "CABLE":
37
+ return (userSettings.cableIncrement || DEFAULT_EQUIPMENT_SETTINGS.cableIncrement);
38
+ case "MACHINE":
39
+ return (userSettings.machineIncrement ||
40
+ DEFAULT_EQUIPMENT_SETTINGS.machineIncrement);
41
+ case "BODYWEIGHT":
42
+ return (userSettings.dumbbellIncrement ||
43
+ DEFAULT_EQUIPMENT_SETTINGS.dumbbellIncrement); // Use dumbbell increment for weighted bodyweight
44
+ default:
45
+ return 2.5; // Fallback
46
+ }
47
+ }
48
+ /**
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
70
+ */
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);
78
+ }
79
+ /**
80
+ * Determines the appropriate standard increment based on exercise type, weight and experience level
81
+ */
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) {
145
+ switch (data.rating) {
146
+ case 1: // Very easy
147
+ return {
148
+ newWeight: 0,
149
+ newReps: Math.min(data.reps + 2, MAX_REPS),
150
+ };
151
+ case 2: // Easy
152
+ return {
153
+ newWeight: 0,
154
+ newReps: Math.min(data.reps + 1, MAX_REPS),
155
+ };
156
+ case 3: // Moderate
157
+ return {
158
+ newWeight: 0,
159
+ newReps: Math.min(data.reps + 1, MAX_REPS),
160
+ };
161
+ case 4: // Hard
162
+ return {
163
+ newWeight: 0,
164
+ newReps: data.reps,
165
+ };
166
+ case 5: // Too hard
167
+ return {
168
+ newWeight: 0,
169
+ newReps: Math.max(1, data.reps - 2),
170
+ };
171
+ default:
172
+ return {
173
+ newWeight: 0,
174
+ newReps: data.reps,
175
+ };
176
+ }
177
+ }
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
+ }
247
+ case 4: // Hard
248
+ return { newWeight: data.weight, newReps: data.reps };
249
+ case 5: // Too hard
250
+ return {
251
+ newWeight: Math.max(0, data.weight + increment),
252
+ newReps: data.reps,
253
+ };
254
+ default:
255
+ return { newWeight: data.weight, newReps: data.reps };
256
+ }
257
+ }
258
+ /**
259
+ * Main function to calculate progression based on exercise data and program type
260
+ * Now accepts optional userSettings for equipment-specific increments
261
+ */
262
+ function calculateProgression(data, programType, userSettings) {
263
+ if (programType === "STRENGTH") {
264
+ return calculateStrengthProgression(data, userSettings);
265
+ }
266
+ return calculateHypertrophyProgression(data, userSettings);
267
+ }
268
+ /**
269
+ * Helper function to round weight to closest available increment
270
+ * Useful for client-side adjustments
271
+ */
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;
277
+ }
@@ -0,0 +1,5 @@
1
+ import type { ExerciseData, ProgressionResult, ProgramType, UserEquipmentSettings } from "./types";
2
+ import { roundToClosestIncrement } from "./calculator";
3
+ export declare function calculateProgression(data: ExerciseData, programType: ProgramType, userSettings?: UserEquipmentSettings): ProgressionResult;
4
+ export type { ExerciseData, ProgressionResult, ProgramType, UserEquipmentSettings, };
5
+ export { roundToClosestIncrement };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.roundToClosestIncrement = void 0;
4
+ exports.calculateProgression = calculateProgression;
5
+ const calculator_1 = require("./calculator");
6
+ Object.defineProperty(exports, "roundToClosestIncrement", { enumerable: true, get: function () { return calculator_1.roundToClosestIncrement; } });
7
+ // Re-export the main function
8
+ function calculateProgression(data, programType, userSettings) {
9
+ return (0, calculator_1.calculateProgression)(data, programType, userSettings);
10
+ }
@@ -0,0 +1,22 @@
1
+ export interface UserEquipmentSettings {
2
+ barbellIncrement: number;
3
+ dumbbellIncrement: number;
4
+ cableIncrement: number;
5
+ machineIncrement: number;
6
+ experienceLevel?: "BEGINNER" | "INTERMEDIATE" | "ADVANCED";
7
+ }
8
+ export interface ExerciseData {
9
+ sets: number;
10
+ reps: number;
11
+ weight: number;
12
+ rating: number;
13
+ equipment_type: "DUMBBELL" | "BARBELL" | "CABLE" | "MACHINE" | "BODYWEIGHT";
14
+ is_compound: boolean;
15
+ exercise_name: string;
16
+ }
17
+ export interface ProgressionResult {
18
+ newWeight: number;
19
+ newReps: number;
20
+ deload?: boolean;
21
+ }
22
+ export type ProgramType = "STRENGTH" | "HYPERTROPHY";
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "fitness-progression-calculator",
3
+ "version": "1.1.0",
4
+ "description": "Workout progression calculator for fitness applications",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "module": "dist/index.js",
8
+ "react-native": "dist/index.js",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepare": "npm run build"
12
+ },
13
+ "keywords": ["fitness", "workout", "progression"],
14
+ "author": "Your Name",
15
+ "license": "MIT",
16
+ "files": ["dist/**/*"],
17
+ "devDependencies": {
18
+ "@types/node": "^18.0.0",
19
+ "typescript": "^4.9.5"
20
+ }
21
+ }