@dgpholdings/greatoak-shared 1.2.86 → 1.2.87

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.
Files changed (75) hide show
  1. package/README.md +148 -148
  2. package/dist/__mocks__/exercises.mock.js +1 -0
  3. package/dist/types/TApiAiExerciseAnalysis.d.ts +2 -1
  4. package/dist/types/TApiClientConstellation.d.ts +33 -0
  5. package/dist/types/TApiClientConstellation.js +13 -0
  6. package/dist/types/TApiExercise.d.ts +5 -3
  7. package/dist/types/index.d.ts +1 -0
  8. package/dist/utils/constellation/computeNormalisedLoad.d.ts +48 -0
  9. package/dist/utils/constellation/computeNormalisedLoad.js +150 -0
  10. package/dist/utils/constellation/evaluateConstellation.d.ts +27 -0
  11. package/dist/utils/constellation/evaluateConstellation.js +135 -0
  12. package/dist/utils/constellation/index.d.ts +17 -0
  13. package/dist/utils/constellation/index.js +26 -0
  14. package/dist/utils/constellation/levelThresholds.d.ts +99 -0
  15. package/dist/utils/constellation/levelThresholds.js +123 -0
  16. package/dist/utils/constellation/starFoundation.d.ts +25 -0
  17. package/dist/utils/constellation/starFoundation.js +54 -0
  18. package/dist/utils/constellation/stars/consistency.d.ts +29 -0
  19. package/dist/utils/constellation/stars/consistency.js +142 -0
  20. package/dist/utils/constellation/stars/lowerBody.d.ts +17 -0
  21. package/dist/utils/constellation/stars/lowerBody.js +30 -0
  22. package/dist/utils/constellation/stars/pull.d.ts +11 -0
  23. package/dist/utils/constellation/stars/pull.js +24 -0
  24. package/dist/utils/constellation/stars/push.d.ts +11 -0
  25. package/dist/utils/constellation/stars/push.js +24 -0
  26. package/dist/utils/constellation/stars/quality.d.ts +19 -0
  27. package/dist/utils/constellation/stars/quality.js +98 -0
  28. package/dist/utils/constellation/stars/recovery.d.ts +29 -0
  29. package/dist/utils/constellation/stars/recovery.js +169 -0
  30. package/dist/utils/constellation/strengthStarHelpers.d.ts +41 -0
  31. package/dist/utils/constellation/strengthStarHelpers.js +104 -0
  32. package/dist/utils/constellation/types.d.ts +124 -0
  33. package/dist/utils/constellation/types.js +18 -0
  34. package/dist/utils/index.d.ts +5 -3
  35. package/dist/utils/index.js +1 -0
  36. package/dist/utils/scoringWorkout/calculateQualityScore.d.ts +59 -36
  37. package/dist/utils/scoringWorkout/calculateQualityScore.js +234 -233
  38. package/dist/utils/scoringWorkout/computeMuscleFatigueMap.d.ts +8 -5
  39. package/dist/utils/scoringWorkout/computeMuscleFatigueMap.js +72 -88
  40. package/dist/utils/scoringWorkout/constants.d.ts +20 -6
  41. package/dist/utils/scoringWorkout/constants.js +23 -9
  42. package/dist/utils/scoringWorkout/helpers.d.ts +7 -0
  43. package/dist/utils/scoringWorkout/helpers.js +24 -18
  44. package/dist/utils/scoringWorkout/index.d.ts +12 -8
  45. package/dist/utils/scoringWorkout/index.js +23 -15
  46. package/dist/utils/scoringWorkout/parseRecords.js +4 -3
  47. package/dist/utils/scoringWorkout/scoringWorkout.integration.test.js +210 -172
  48. package/dist/utils/scoringWorkout/types.d.ts +34 -14
  49. package/package.json +31 -31
  50. package/dist/utils/exerciseRecord/__mocks__/exercises.mock.d.ts +0 -30
  51. package/dist/utils/exerciseRecord/__mocks__/exercises.mock.js +0 -138
  52. package/dist/utils/scaleProPlan.util.d.ts +0 -9
  53. package/dist/utils/scaleProPlan.util.js +0 -139
  54. package/dist/utils/scoring/calculateCalories.d.ts +0 -67
  55. package/dist/utils/scoring/calculateCalories.js +0 -345
  56. package/dist/utils/scoring/calculateMuscleFatiue.d.ts +0 -67
  57. package/dist/utils/scoring/calculateMuscleFatiue.js +0 -310
  58. package/dist/utils/scoring/calculateQualityScore.d.ts +0 -71
  59. package/dist/utils/scoring/calculateQualityScore.js +0 -334
  60. package/dist/utils/scoring/calculateTotalVolume.d.ts +0 -15
  61. package/dist/utils/scoring/calculateTotalVolume.js +0 -73
  62. package/dist/utils/scoring/constants.d.ts +0 -211
  63. package/dist/utils/scoring/constants.js +0 -247
  64. package/dist/utils/scoring/helpers.d.ts +0 -119
  65. package/dist/utils/scoring/helpers.js +0 -229
  66. package/dist/utils/scoring/index.d.ts +0 -28
  67. package/dist/utils/scoring/index.js +0 -47
  68. package/dist/utils/scoring/parseRecords.d.ts +0 -98
  69. package/dist/utils/scoring/parseRecords.js +0 -284
  70. package/dist/utils/scoring/types.d.ts +0 -86
  71. package/dist/utils/scoring/types.js +0 -11
  72. package/dist/utils/scoring.utils.d.ts +0 -14
  73. package/dist/utils/scoring.utils.js +0 -243
  74. /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.d.ts → calculateMuscleFatigue.d.ts} +0 -0
  75. /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.js → calculateMuscleFatigue.js} +0 -0
@@ -1,345 +0,0 @@
1
- "use strict";
2
- // calculateCalories.ts
3
- /**
4
- * ============================================================================
5
- * FITFRIX EXERCISE SCORING SYSTEM — Pillar 1: Calorie Burn
6
- * ============================================================================
7
- *
8
- * Estimates energy expenditure (kcal) for an exercise using the MET system.
9
- *
10
- * Formula (per set):
11
- * calories = effectiveMET × userWeightKg × (activeDurationSecs / 3600)
12
- *
13
- * Where effectiveMET is the exercise's baseMET adjusted for:
14
- * - Effort level (RPE/RIR → scales within metRange)
15
- * - Weight intensity (for weight-reps: how heavy relative to bodyweight)
16
- * - Duration category (for duration: short/medium/long multiplier)
17
- * - Speed (for cardio: interpolated from pace or speed range)
18
- * - Compound multiplier (multi-joint exercises burn more)
19
- *
20
- * After summing all sets:
21
- * - Rest calories are added (elevated MET during rest periods)
22
- * - EPOC (afterburn) is added as a percentage of work calories
23
- *
24
- * References:
25
- * - Ainsworth BE et al. "Compendium of Physical Activities" (2011)
26
- * - Katch, McArdle & Katch, "Exercise Physiology" (8th ed.)
27
- */
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.calculateCalories = calculateCalories;
30
- const constants_1 = require("./constants");
31
- const helpers_1 = require("./helpers");
32
- // ---------------------------------------------------------------------------
33
- // Main Calorie Calculation
34
- // ---------------------------------------------------------------------------
35
- /**
36
- * Calculate total calorie burn for an exercise.
37
- *
38
- * @param sets Parsed & validated sets (from parseRecords)
39
- * @param metabolicData Exercise's metabolic configuration
40
- * @param user Validated user context
41
- * @param difficultyLevel Exercise difficulty (0–4), used for bodyweight estimation
42
- * @returns Total calories burned (kcal), rounded to 1 decimal
43
- *
44
- * @example
45
- * const calories = calculateCalories(parsedSets, exercise.metabolicData, userCtx, exercise.difficultyLevel);
46
- * // → 6.4 (for 3 light bench press sets)
47
- * // → 142.3 (for a 20-min treadmill run)
48
- */
49
- function calculateCalories(sets, metabolicData, user, difficultyLevel, scoringSpecialHandling) {
50
- if (sets.length === 0)
51
- return 0;
52
- // Stretch/mobility: no meaningful work calories — only passive rest MET applies
53
- if (scoringSpecialHandling === "stretch-mobility") {
54
- const totalRestSecs = sets.reduce((sum, s) => { var _a; return sum + ((_a = s.restDurationSecs) !== null && _a !== void 0 ? _a : 0); }, 0);
55
- return Math.round(constants_1.REST_MET * user.weightKg * (totalRestSecs / 3600) * 10) / 10;
56
- }
57
- // Normalize metRange so [0] = min, [1] = max (sample data has them reversed)
58
- const [metMin, metMax] = normalizeMetRange(metabolicData.metRange, metabolicData.baseMET);
59
- let totalWorkCalories = 0;
60
- let totalRestCalories = 0;
61
- for (const set of sets) {
62
- // 1. Calculate effective MET for this set
63
- const effectiveMET = calculateEffectiveMET(set, metabolicData, metMin, metMax, user, difficultyLevel, scoringSpecialHandling);
64
- // 2. Work calories: MET × weight × time
65
- const workHours = set.activeDurationSecs / 3600;
66
- totalWorkCalories += effectiveMET * user.weightKg * workHours;
67
- // 3. Rest calories: elevated resting MET during recovery
68
- if (set.restDurationSecs !== null && set.restDurationSecs > 0) {
69
- const restHours = set.restDurationSecs / 3600;
70
- totalRestCalories += constants_1.REST_MET * user.weightKg * restHours;
71
- }
72
- }
73
- // 4. EPOC (Excess Post-exercise Oxygen Consumption) — afterburn effect
74
- const epocCalories = totalWorkCalories * metabolicData.epocFactor;
75
- // 5. Total = work + rest + afterburn
76
- const total = totalWorkCalories + totalRestCalories + epocCalories;
77
- return Math.round(total * 10) / 10; // Round to 1 decimal place
78
- }
79
- // ---------------------------------------------------------------------------
80
- // Effective MET Calculation (per set)
81
- // ---------------------------------------------------------------------------
82
- /**
83
- * Calculate the effective MET for a single set based on its type and intensity.
84
- *
85
- * The general approach:
86
- * 1. Interpolate base MET within [metMin, metMax] using effort fraction
87
- * 2. Apply type-specific intensity multiplier
88
- * 3. Apply compound multiplier
89
- *
90
- * The effort fraction (from RPE/RIR) determines WHERE in the MET range
91
- * this set falls. Low effort → closer to metMin, high effort → closer to metMax.
92
- */
93
- function calculateEffectiveMET(set, metabolicData, metMin, metMax, user, difficultyLevel, scoringSpecialHandling) {
94
- var _a, _b, _c, _d, _e;
95
- // Start with effort-scaled MET
96
- // effortFraction is 0.5–1.3 (from helpers), we normalize to 0–1 for interpolation
97
- const effortNormalized = (0, helpers_1.clamp)((set.effortFraction - 0.5) / 0.8, 0, 1);
98
- let met = metMin + (metMax - metMin) * effortNormalized;
99
- // Continuous-duration (battle ropes, high knees, jump rope): these behave like
100
- // cardio metabolically — bypass the static-hold duration formula entirely
101
- if (scoringSpecialHandling === "continuous-duration") {
102
- met = metMin + (metMax - metMin) * effortNormalized;
103
- met *= metabolicData.compoundMultiplier;
104
- return (0, helpers_1.clamp)(met, 1, 25);
105
- }
106
- // Apply type-specific intensity scaling
107
- switch (set.type) {
108
- case "weight-reps":
109
- met *= getWeightMultiplier((_a = set.kg) !== null && _a !== void 0 ? _a : 0, user.weightKg, metabolicData);
110
- break;
111
- case "reps-only":
112
- met *= getRepsOnlyMultiplier((_b = set.auxWeightKg) !== null && _b !== void 0 ? _b : 0, user.weightKg, difficultyLevel);
113
- break;
114
- case "duration":
115
- met *= getDurationMultiplier((_c = set.durationSecs) !== null && _c !== void 0 ? _c : 0, (_d = set.auxWeightKg) !== null && _d !== void 0 ? _d : 0, user.weightKg, metabolicData);
116
- break;
117
- case "cardio-machine":
118
- met = getCardioMachineMET(set, metabolicData, metMin, metMax, effortNormalized);
119
- break;
120
- case "cardio-free":
121
- met = getCardioFreeMET(set, metabolicData, metMin, metMax, effortNormalized);
122
- // Loaded carry: scale MET up by the weight being carried relative to bodyweight
123
- if (scoringSpecialHandling === "loaded-carry") {
124
- const auxWeightKg = (_e = set.auxWeightKg) !== null && _e !== void 0 ? _e : 0;
125
- if (auxWeightKg > 0) {
126
- met *= 1 + (auxWeightKg / user.weightKg) * constants_1.LOADED_CARRY_WEIGHT_MET_SCALE;
127
- }
128
- }
129
- break;
130
- }
131
- // Apply compound multiplier (multi-joint exercises have higher energy cost)
132
- met *= metabolicData.compoundMultiplier;
133
- // Safety clamp: MET should never go below 1 or above 25
134
- return (0, helpers_1.clamp)(met, 1, 25);
135
- }
136
- // ---------------------------------------------------------------------------
137
- // Type-Specific Multipliers
138
- // ---------------------------------------------------------------------------
139
- /**
140
- * Weight multiplier for weight-reps exercises.
141
- *
142
- * Classifies the weight as light/moderate/heavy relative to bodyweight
143
- * and returns the corresponding multiplier.
144
- *
145
- * Example: 10kg at 75kg bodyweight → ratio 0.13 → "light" → 0.8 multiplier
146
- */
147
- function getWeightMultiplier(kg, userWeightKg, metabolicData) {
148
- const ratio = kg / userWeightKg;
149
- // Resolve weight factors (handle both nested and flat shapes)
150
- const factors = resolveWeightFactors(metabolicData);
151
- if (ratio < constants_1.WEIGHT_CATEGORY_THRESHOLDS.lightMax) {
152
- return factors.light;
153
- }
154
- else if (ratio < constants_1.WEIGHT_CATEGORY_THRESHOLDS.moderateMax) {
155
- return factors.moderate;
156
- }
157
- else {
158
- return factors.heavy;
159
- }
160
- }
161
- /**
162
- * Multiplier for reps-only (bodyweight) exercises.
163
- *
164
- * The "load" is the user's bodyweight (scaled by difficulty) + any aux weight.
165
- * We return a gentle multiplier that boosts MET slightly for added weight.
166
- *
167
- * Example: Push-up (difficulty 1) with 2.5kg vest at 75kg bodyweight
168
- * bodyweightLoad = (1/4) × 0.65 × 75 = 12.19 kg equivalent
169
- * totalLoad = 12.19 + 2.5 = 14.69
170
- * ratio = 14.69 / 75 = 0.196
171
- * multiplier = 1.0 + 0.196 × 0.5 = 1.098
172
- */
173
- function getRepsOnlyMultiplier(auxWeightKg, userWeightKg, difficultyLevel) {
174
- const bodyweightLoad = (difficultyLevel / 4) * constants_1.BW_FRACTION_SCALE * userWeightKg;
175
- const totalLoad = bodyweightLoad + auxWeightKg;
176
- const ratio = totalLoad / userWeightKg;
177
- // Gentle scaling: aux weight adds intensity but not dramatically
178
- return 1.0 + ratio * 0.5;
179
- }
180
- /**
181
- * Multiplier for duration (isometric/hold) exercises.
182
- *
183
- * Two factors:
184
- * 1. Duration category (short/medium/long) — longer holds are metabolically harder
185
- * 2. Aux weight boost — holding weight during a plank increases energy cost
186
- */
187
- function getDurationMultiplier(durationSecs, auxWeightKg, userWeightKg, metabolicData) {
188
- // Resolve duration factors (handle both nested and flat shapes)
189
- const factors = resolveDurationFactors(metabolicData);
190
- let durationMultiplier;
191
- if (durationSecs < constants_1.DURATION_CATEGORY_THRESHOLDS.shortMax) {
192
- durationMultiplier = factors.short;
193
- }
194
- else if (durationSecs <= constants_1.DURATION_CATEGORY_THRESHOLDS.mediumMax) {
195
- durationMultiplier = factors.medium;
196
- }
197
- else {
198
- durationMultiplier = factors.long;
199
- }
200
- // Aux weight boosts MET (holding a plate during plank, weighted vest, etc.)
201
- const auxBoost = 1.0 + (auxWeightKg / userWeightKg) * 0.3;
202
- return durationMultiplier * auxBoost;
203
- }
204
- /**
205
- * MET calculation for cardio-machine exercises (treadmill, stationary bike, etc.)
206
- *
207
- * Uses average speed to interpolate within the MET range.
208
- * If paceFactors are available, we interpolate from the pace table instead.
209
- */
210
- function getCardioMachineMET(set, metabolicData, metMin, metMax, effortNormalized) {
211
- var _a;
212
- // Average speed from the set data
213
- const avgSpeed = (_a = set.speed) !== null && _a !== void 0 ? _a : 0;
214
- if (avgSpeed > 0) {
215
- // If we have pace factors, use them for more accurate MET lookup
216
- if (metabolicData.paceFactors) {
217
- return interpolatePaceFactors(avgSpeed, metabolicData.paceFactors);
218
- }
219
- // Otherwise, interpolate linearly within metRange based on speed
220
- const speedFraction = (0, helpers_1.clamp)((avgSpeed - constants_1.CARDIO_SPEED_RANGE.min) /
221
- (constants_1.CARDIO_SPEED_RANGE.max - constants_1.CARDIO_SPEED_RANGE.min), 0, 1);
222
- return metMin + (metMax - metMin) * speedFraction;
223
- }
224
- // No speed data: fall back to effort-based interpolation
225
- return metMin + (metMax - metMin) * effortNormalized;
226
- }
227
- /**
228
- * MET calculation for cardio-free exercises (outdoor running, cycling, etc.)
229
- *
230
- * Derives speed from distance ÷ time, then interpolates MET.
231
- */
232
- function getCardioFreeMET(set, metabolicData, metMin, metMax, effortNormalized) {
233
- var _a, _b;
234
- const distance = (_a = set.distance) !== null && _a !== void 0 ? _a : 0;
235
- const durationSecs = (_b = set.cardioDurationSecs) !== null && _b !== void 0 ? _b : 0;
236
- if (distance > 0 && durationSecs > 0) {
237
- // Speed in km/h
238
- const speed = distance / (durationSecs / 3600);
239
- if (metabolicData.paceFactors) {
240
- return interpolatePaceFactors(speed, metabolicData.paceFactors);
241
- }
242
- const speedFraction = (0, helpers_1.clamp)((speed - constants_1.CARDIO_SPEED_RANGE.min) /
243
- (constants_1.CARDIO_SPEED_RANGE.max - constants_1.CARDIO_SPEED_RANGE.min), 0, 1);
244
- return metMin + (metMax - metMin) * speedFraction;
245
- }
246
- // No distance/time data: fall back to effort-based
247
- return metMin + (metMax - metMin) * effortNormalized;
248
- }
249
- // ---------------------------------------------------------------------------
250
- // Resolution Helpers (handle flat vs nested metabolicData shapes)
251
- // ---------------------------------------------------------------------------
252
- /**
253
- * Resolve weight factors from metabolicData.
254
- * Sample data sometimes has them nested under `weightFactors`, sometimes flat.
255
- */
256
- function resolveWeightFactors(metabolicData) {
257
- var _a, _b;
258
- if (metabolicData.weightFactors) {
259
- return {
260
- light: metabolicData.weightFactors.lightWeight,
261
- moderate: metabolicData.weightFactors.moderateWeight,
262
- heavy: metabolicData.weightFactors.heavyWeight,
263
- };
264
- }
265
- // Check flat shape
266
- if (metabolicData.lightWeight !== undefined) {
267
- return {
268
- light: metabolicData.lightWeight,
269
- moderate: (_a = metabolicData.moderateWeight) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_WEIGHT_FACTORS.moderate,
270
- heavy: (_b = metabolicData.heavyWeight) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_WEIGHT_FACTORS.heavy,
271
- };
272
- }
273
- return constants_1.DEFAULT_WEIGHT_FACTORS;
274
- }
275
- /**
276
- * Resolve duration factors from metabolicData.
277
- * Same flat-vs-nested handling as weight factors.
278
- */
279
- function resolveDurationFactors(metabolicData) {
280
- var _a, _b;
281
- if (metabolicData.durationFactors) {
282
- return {
283
- short: metabolicData.durationFactors.shortDuration,
284
- medium: metabolicData.durationFactors.mediumDuration,
285
- long: metabolicData.durationFactors.longDuration,
286
- };
287
- }
288
- // Check flat shape
289
- if (metabolicData.shortDuration !== undefined) {
290
- return {
291
- short: metabolicData.shortDuration,
292
- medium: (_a = metabolicData.mediumDuration) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_DURATION_FACTORS.medium,
293
- long: (_b = metabolicData.longDuration) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_DURATION_FACTORS.long,
294
- };
295
- }
296
- return constants_1.DEFAULT_DURATION_FACTORS;
297
- }
298
- /**
299
- * Interpolate MET from a pace factors table.
300
- *
301
- * paceFactors is a map of speed (km/h) → MET value.
302
- * We find the two closest speeds and linearly interpolate between them.
303
- *
304
- * Example:
305
- * paceFactors = { "5.0": 4.3, "8.0": 8.3, "12.0": 11.0, "16.0": 15.0 }
306
- * speed = 10 km/h → interpolate between 8.0 (8.3 MET) and 12.0 (11.0 MET)
307
- * result ≈ 9.65 MET
308
- */
309
- function interpolatePaceFactors(speed, paceFactors) {
310
- const entries = Object.keys(paceFactors)
311
- .map((k) => [parseFloat(k), paceFactors[k]])
312
- .filter(([k]) => !isNaN(k))
313
- .sort((a, b) => a[0] - b[0]);
314
- if (entries.length === 0)
315
- return 3; // fallback moderate MET
316
- // Below minimum pace
317
- if (speed <= entries[0][0])
318
- return entries[0][1];
319
- // Above maximum pace
320
- if (speed >= entries[entries.length - 1][0])
321
- return entries[entries.length - 1][1];
322
- // Find surrounding entries and interpolate
323
- for (let i = 0; i < entries.length - 1; i++) {
324
- const [speedLow, metLow] = entries[i];
325
- const [speedHigh, metHigh] = entries[i + 1];
326
- if (speed >= speedLow && speed <= speedHigh) {
327
- const fraction = (speed - speedLow) / (speedHigh - speedLow);
328
- return metLow + (metHigh - metLow) * fraction;
329
- }
330
- }
331
- return entries[entries.length - 1][1]; // shouldn't reach here
332
- }
333
- /**
334
- * Normalize metRange so [0] is always min and [1] is always max.
335
- * Sample data has metRange as [5, 2] (max first) for some exercises.
336
- */
337
- function normalizeMetRange(metRange, baseMET) {
338
- const min = Math.min(metRange[0], metRange[1]);
339
- const max = Math.max(metRange[0], metRange[1]);
340
- // If range seems invalid, create one around baseMET
341
- if (min === max || max === 0) {
342
- return [baseMET * 0.7, baseMET * 1.5];
343
- }
344
- return [min, max];
345
- }
@@ -1,67 +0,0 @@
1
- /**
2
- * ============================================================================
3
- * FITFRIX EXERCISE SCORING SYSTEM — Pillar 2: Muscle Fatigue
4
- * ============================================================================
5
- *
6
- * Calculates per-muscle fatigue scores (0–100) for a single exercise.
7
- *
8
- * HOW IT WORKS:
9
- *
10
- * 1. For each set, compute a "fatigue stimulus" — how much mechanical work
11
- * the muscles experienced. This is type-specific:
12
- * weight-reps: kg × reps
13
- * reps-only: (bodyweightLoad + auxWeight) × reps
14
- * duration: durationSecs × (difficultyLevel + 1) × auxBoost
15
- * cardio: durationSecs × speedFactor
16
- *
17
- * 2. Multiply by effort (RPE/RIR → 0.5–1.3) and the exercise's
18
- * fatigueMultiplier from timingGuardrails.
19
- *
20
- * 3. Accumulate across sets with DIMINISHING RETURNS — later sets contribute
21
- * less because the muscle is pre-fatigued and can't generate as much force.
22
- * Decay: setDecay = 1 / (1 + 0.15 × setIndex)
23
- *
24
- * 4. Distribute the accumulated fatigue to muscles:
25
- * Primary muscles → 100% of stimulus
26
- * Secondary muscles → 35% of stimulus
27
- *
28
- * 5. Normalize to 0–100 using an EXERCISE-AWARE reference maximum.
29
- * The reference max is what "5 hard sets of THIS exercise" would produce,
30
- * scaled by the exercise's difficultyLevel so bicep curls and squats
31
- * get fair scores.
32
- *
33
- * IMPORTANT: This function scores ONE exercise. The caller aggregates
34
- * across exercises for full-workout muscle fatigue maps.
35
- */
36
- import type { IParsedSet, IUserContext } from "./types";
37
- import type { ITimingGuardrails } from "./parseRecords";
38
- /** Muscle key from EBodyParts (e.g., "pectoralis-major", "quadriceps") */
39
- type MuscleKey = string;
40
- /**
41
- * Minimal exercise metadata needed for fatigue calculation.
42
- */
43
- interface IFatigueExerciseData {
44
- primaryMuscles: MuscleKey[];
45
- secondaryMuscles: MuscleKey[];
46
- difficultyLevel: number;
47
- metabolicData: {
48
- compoundMultiplier: number;
49
- muscleGroupFactor: number;
50
- };
51
- scoringSpecialHandling?: "plyometric" | "stretch-mobility" | "continuous-duration" | "loaded-carry";
52
- }
53
- /**
54
- * Calculate per-muscle fatigue scores for an exercise.
55
- *
56
- * @param sets Parsed & validated sets (from parseRecords)
57
- * @param exercise Exercise metadata (muscles, difficulty, metabolic)
58
- * @param user Validated user context
59
- * @param timingGuardrails For fatigueMultiplier
60
- * @returns Map of muscle key → fatigue score (0–100)
61
- *
62
- * @example
63
- * const fatigue = calculateMuscleFatigue(parsedSets, exercise, userCtx, exercise.timingGuardrails);
64
- * // → { "pectoralis-major": 24, "pectoralis-minor": 24, "deltoids-anterior": 8, ... }
65
- */
66
- export declare function calculateMuscleFatigue(sets: IParsedSet[], exercise: IFatigueExerciseData, user: IUserContext, timingGuardrails?: ITimingGuardrails): Record<string, number>;
67
- export {};