@dgpholdings/greatoak-shared 1.1.61 → 1.1.63

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.
@@ -3,3 +3,5 @@ export { mmssToSecs, isUserAllowedToUpdate, getDaysAndHoursDifference, } from ".
3
3
  export { countryToCurrencyCode } from "./billing.utils";
4
4
  export { calculateExerciseScore } from "./scoring.utils";
5
5
  export { isDefined, isDefinedNumber } from "./isDefined.utils";
6
+ export { slugifyText } from "./slugify.util";
7
+ export { toError } from "./toError.util";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isDefinedNumber = exports.isDefined = exports.calculateExerciseScore = exports.countryToCurrencyCode = exports.getDaysAndHoursDifference = exports.isUserAllowedToUpdate = exports.mmssToSecs = exports.toNumber = void 0;
3
+ exports.toError = exports.slugifyText = exports.isDefinedNumber = exports.isDefined = exports.calculateExerciseScore = exports.countryToCurrencyCode = exports.getDaysAndHoursDifference = exports.isUserAllowedToUpdate = exports.mmssToSecs = exports.toNumber = void 0;
4
4
  var number_util_1 = require("./number.util");
5
5
  Object.defineProperty(exports, "toNumber", { enumerable: true, get: function () { return number_util_1.toNumber; } });
6
6
  var time_util_1 = require("./time.util");
@@ -14,3 +14,7 @@ Object.defineProperty(exports, "calculateExerciseScore", { enumerable: true, get
14
14
  var isDefined_utils_1 = require("./isDefined.utils");
15
15
  Object.defineProperty(exports, "isDefined", { enumerable: true, get: function () { return isDefined_utils_1.isDefined; } });
16
16
  Object.defineProperty(exports, "isDefinedNumber", { enumerable: true, get: function () { return isDefined_utils_1.isDefinedNumber; } });
17
+ var slugify_util_1 = require("./slugify.util");
18
+ Object.defineProperty(exports, "slugifyText", { enumerable: true, get: function () { return slugify_util_1.slugifyText; } });
19
+ var toError_util_1 = require("./toError.util");
20
+ Object.defineProperty(exports, "toError", { enumerable: true, get: function () { return toError_util_1.toError; } });
@@ -43,6 +43,7 @@
43
43
  */
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.calculateExerciseScore = void 0;
46
+ const time_util_1 = require("./time.util");
46
47
  const calculateExerciseScore = (param) => {
47
48
  const { exercise, record, user } = param;
48
49
  // Filter only completed sets
@@ -67,16 +68,6 @@ const calculateExerciseScore = (param) => {
67
68
  };
68
69
  };
69
70
  exports.calculateExerciseScore = calculateExerciseScore;
70
- const parseMmSsToSeconds = (mmSs) => {
71
- if (!mmSs)
72
- return 0;
73
- const parts = mmSs.split(":");
74
- if (parts.length !== 2)
75
- return 0;
76
- const minutes = parseInt(parts[0]) || 0;
77
- const seconds = parseInt(parts[1]) || 0;
78
- return minutes * 60 + seconds;
79
- };
80
71
  const calculateTotalVolume = (record, user) => {
81
72
  return record.reduce((total, set) => {
82
73
  const weight = parseFloat(set.type === "weight-reps"
@@ -88,7 +79,7 @@ const calculateTotalVolume = (record, user) => {
88
79
  const duration = set.type === "duration" ||
89
80
  set.type === "cardio-machine" ||
90
81
  set.type === "cardio-free"
91
- ? parseMmSsToSeconds(set.durationMmSs)
82
+ ? (0, time_util_1.mmssToSecs)(set.durationMmSs)
92
83
  : 0;
93
84
  if (set.type === "weight-reps") {
94
85
  return total + reps * weight;
@@ -149,7 +140,7 @@ const calculateAverageIntensity = (record, exercise, user) => {
149
140
  }
150
141
  }
151
142
  else if (set.type === "duration") {
152
- const duration = parseMmSsToSeconds(set.durationMmSs);
143
+ const duration = (0, time_util_1.mmssToSecs)(set.durationMmSs);
153
144
  const weight = parseFloat(set.auxWeightKg) || 0;
154
145
  baseIntensity = Math.min(100, duration * 1.5) / 100;
155
146
  if (weight > 0) {
@@ -157,7 +148,7 @@ const calculateAverageIntensity = (record, exercise, user) => {
157
148
  }
158
149
  }
159
150
  else if (set.type === "cardio-machine" || set.type === "cardio-free") {
160
- const duration = parseMmSsToSeconds(set.durationMmSs);
151
+ const duration = (0, time_util_1.mmssToSecs)(set.durationMmSs);
161
152
  if (set.type === "cardio-machine") {
162
153
  const avgSpeed = (parseFloat(set.speedMin) + parseFloat(set.speedMax)) / 2 || 0;
163
154
  baseIntensity = Math.min(100, avgSpeed * 5.5) / 100;
@@ -0,0 +1 @@
1
+ export declare const slugifyText: (text: string) => string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.slugifyText = void 0;
4
+ const slugifyText = (text) => {
5
+ return text
6
+ .toLowerCase() // convert to lowercase
7
+ .trim() // remove leading/trailing spaces
8
+ .normalize("NFD") // normalize accented characters
9
+ .replace(/[\u0300-\u036f]/g, "") // remove diacritics
10
+ .replace(/[^a-z0-9\s-]/g, "") // remove invalid chars
11
+ .replace(/\s+/g, "-") // replace spaces with -
12
+ .replace(/-+/g, "-"); // collapse multiple dashes
13
+ };
14
+ exports.slugifyText = slugifyText;
@@ -0,0 +1 @@
1
+ export declare const toError: (err: unknown) => Error;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toError = void 0;
4
+ const toError = (err) => {
5
+ if (err instanceof Error)
6
+ return err;
7
+ if (typeof err === "string") {
8
+ return new Error(err);
9
+ }
10
+ if (typeof err === "object" && err !== null) {
11
+ const message = "message" in err && typeof err.message === "string"
12
+ ? err.message
13
+ : JSON.stringify(err);
14
+ const error = new Error(message);
15
+ error.name = err.name || "UnknownError";
16
+ return error;
17
+ }
18
+ return new Error("Unknown error");
19
+ };
20
+ exports.toError = toError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dgpholdings/greatoak-shared",
3
- "version": "1.1.61",
3
+ "version": "1.1.63",
4
4
  "description": "Shared TypeScript types and utilities for @dgpholdings projects",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,61 +0,0 @@
1
- export type TRecord =
2
- | {
3
- type: "weight-reps";
4
- kg: string;
5
- reps: string;
6
- }
7
- | {
8
- type: "duration";
9
- durationMmSs: string;
10
- goalDuration: string;
11
- }
12
- | {
13
- type: "reps-only";
14
- reps: string;
15
- };
16
- export type TRecordForm = TRecord & {
17
- isDone: boolean;
18
- };
19
- export type TRecordDuration = Extract<
20
- TRecord,
21
- {
22
- type: "duration";
23
- }
24
- >;
25
- export type TRecordWeight = Extract<
26
- TRecord,
27
- {
28
- type: "weight-reps";
29
- }
30
- >;
31
- export type TRecordBodyWeight = Extract<
32
- TRecord,
33
- {
34
- type: "reps-only";
35
- }
36
- >;
37
- export type TExerciseRecord<T extends TRecord["type"]> = {
38
- exerciseId: string;
39
- userId: string;
40
- recordType: T;
41
- records: {
42
- isoDate: string;
43
- records: Omit<
44
- Extract<
45
- TRecord,
46
- {
47
- type: T;
48
- }
49
- >,
50
- "type"
51
- >[];
52
- }[];
53
- };
54
- export type TApiExerciseRecordUpdateReq = {
55
- exerciseId: string;
56
- recordType: TRecord["type"];
57
- records: TExerciseRecord<TRecord["type"]>["records"][number]["records"];
58
- };
59
- export type TApiExerciseRecordUpdateRes = {
60
- status: 201 | 400;
61
- };
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,38 +0,0 @@
1
- import { TExercise } from "./TApiExercise";
2
- export type TTemplate = {
3
- name: string;
4
- templateId: string;
5
- exerciseIds?: string[];
6
- lastUsed?: Date;
7
- createdAt?: string;
8
- };
9
- export type TTemplateExercise = {
10
- template: TTemplate;
11
- exercise: TExercise[];
12
- };
13
- export type TApiTemplateCreateReq = {
14
- name: string;
15
- };
16
- export type TApiTemplateCreateRes = {
17
- status: number;
18
- success?: boolean;
19
- message?: string;
20
- };
21
- export type TApiTemplateUpdateReq = {
22
- templateId: string;
23
- name: string;
24
- exerciseIds: string[];
25
- };
26
- export type TApiTemplateUpdateRes = {
27
- status: number;
28
- success?: boolean;
29
- message?: string;
30
- };
31
- export type TApiTemplateListReq = {};
32
- export type TApiTemplateListRes = {
33
- templates: TTemplate[];
34
- };
35
- export type TApiTemplateDataReq = {
36
- templateId: string;
37
- };
38
- export type TApiTemplateDataRes = TTemplateExercise;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,26 +0,0 @@
1
- import { TExercise } from "./TApiExercise";
2
- import { TTemplate } from "./TApiTemplate";
3
- type TExerciseRecord = {
4
- recordDate: Date;
5
- exerciseNote?: string;
6
- score: number;
7
- records: {
8
- kg?: number;
9
- reps?: number;
10
- durationMmSs?: string;
11
- goalDuration?: string;
12
- }[];
13
- };
14
- type TTemplateData = Omit<TTemplate, "exerciseIds"> & {
15
- exercises: {
16
- exercise: TExercise;
17
- latestRecord: TExerciseRecord;
18
- }[];
19
- };
20
- export type TApiUserDataRes = {
21
- status: number;
22
- templates: TTemplateData[];
23
- msg?: string;
24
- };
25
- export type TApiUserDataReq = {};
26
- export {};
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +0,0 @@
1
- import { TBillingPlan } from "../TApiBillingPlan";
2
- export type TApiBillingPlanCreateReq = Exclude<TBillingPlan, "isActive" | "version">;
3
- export type TApiBillingPlanCreateRes = {
4
- success: boolean;
5
- };
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +0,0 @@
1
- import { TRecord, TRecordWeight, TRecordDuration, TRecordBodyWeight, TRecordDistance } from "../TApiExerciseRecord";
2
- export declare const isRecordWeightTypeGuard: (param: TRecord[]) => param is TRecordWeight[];
3
- export declare const isRecordDurationTypeGuard: (param: TRecord[]) => param is TRecordDuration[];
4
- export declare const isRecordBodyWeightTypeGuard: (param: TRecord[]) => param is TRecordBodyWeight[];
5
- export declare const isRecordDistanceTypeGuard: (param: TRecord[]) => param is TRecordDistance[];
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isRecordDistanceTypeGuard = exports.isRecordBodyWeightTypeGuard = exports.isRecordDurationTypeGuard = exports.isRecordWeightTypeGuard = void 0;
4
- const isRecordWeightTypeGuard = (param) => param[0].type === "weight-reps";
5
- exports.isRecordWeightTypeGuard = isRecordWeightTypeGuard;
6
- const isRecordDurationTypeGuard = (param) => param[0].type === "duration";
7
- exports.isRecordDurationTypeGuard = isRecordDurationTypeGuard;
8
- const isRecordBodyWeightTypeGuard = (param) => param[0].type === "reps-only";
9
- exports.isRecordBodyWeightTypeGuard = isRecordBodyWeightTypeGuard;
10
- const isRecordDistanceTypeGuard = (param) => param[0].type === "distance";
11
- exports.isRecordDistanceTypeGuard = isRecordDistanceTypeGuard;
@@ -1,48 +0,0 @@
1
- import { TRecord } from "../types";
2
- type RefinedBase = {
3
- isDone: boolean;
4
- rpe?: number;
5
- workDurationSecs?: number;
6
- restDurationSecs?: number;
7
- setNote?: string;
8
- isStrictMode: boolean;
9
- };
10
- export type TRefinedWeightRecord = RefinedBase & {
11
- type: "weight-reps";
12
- kg: number;
13
- reps: number;
14
- rir?: number;
15
- };
16
- export type TRefinedDurationRecord = RefinedBase & {
17
- type: "duration";
18
- durationMmSs: number;
19
- auxWeightKg: number;
20
- };
21
- export type TRefinedBodyWeightRecord = RefinedBase & {
22
- type: "reps-only";
23
- reps: number;
24
- auxWeightKg: number;
25
- rir?: number;
26
- };
27
- export type TRefinedCardioMachineRecord = RefinedBase & {
28
- type: "cardio-machine";
29
- speed: number;
30
- durationMmSs: number;
31
- distance: number;
32
- intensityScore?: number;
33
- };
34
- export type TRefinedCardioFreeRecord = RefinedBase & {
35
- type: "cardio-free";
36
- distance: number;
37
- durationMmSs: number;
38
- avgPaceSecsPerKm?: number;
39
- avgSpeedKmh?: number;
40
- };
41
- export type TRefinedRecord =
42
- | TRefinedWeightRecord
43
- | TRefinedDurationRecord
44
- | TRefinedBodyWeightRecord
45
- | TRefinedCardioMachineRecord
46
- | TRefinedCardioFreeRecord;
47
- export declare const refineRecordEntry: (entry: TRecord) => TRefinedRecord;
48
- export {};
@@ -1,153 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.refineRecordEntry = void 0;
4
- const isDefined_utils_1 = require("./isDefined.utils");
5
- const number_util_1 = require("./number.util");
6
- const time_util_1 = require("./time.util");
7
- const refineRecordEntry = (entry) => {
8
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
9
- const base = Object.assign(
10
- Object.assign(
11
- Object.assign(
12
- Object.assign(
13
- { isDone: entry.isDone, isStrictMode: entry.isStrictMode },
14
- (0, isDefined_utils_1.isDefinedNumber)(
15
- (0, number_util_1.toNumber)(entry.rpe)
16
- ) && { rpe: (0, number_util_1.toNumber)(entry.rpe) }
17
- ),
18
- (0, isDefined_utils_1.isDefinedNumber)(entry.workDurationSecs) && {
19
- workDurationSecs: entry.workDurationSecs,
20
- }
21
- ),
22
- (0, isDefined_utils_1.isDefinedNumber)(entry.restDurationSecs) && {
23
- restDurationSecs: entry.restDurationSecs,
24
- }
25
- ),
26
- (0, isDefined_utils_1.isDefined)(entry.setNote) && {
27
- setNote: entry.setNote,
28
- }
29
- );
30
- if (entry.type === "weight-reps") {
31
- return Object.assign(Object.assign({}, base), {
32
- type: "weight-reps",
33
- kg:
34
- (_a = (0, number_util_1.toNumber)(entry.kg)) !== null && _a !== void 0
35
- ? _a
36
- : 0,
37
- reps:
38
- (_b = (0, number_util_1.toNumber)(entry.reps)) !== null && _b !== void 0
39
- ? _b
40
- : 0,
41
- rir: (0, isDefined_utils_1.isDefinedNumber)(
42
- (0, number_util_1.toNumber)(entry.rir)
43
- )
44
- ? (0, number_util_1.toNumber)(entry.rir)
45
- : undefined,
46
- });
47
- }
48
- if (entry.type === "duration") {
49
- return Object.assign(Object.assign({}, base), {
50
- type: "duration",
51
- durationMmSs:
52
- (_c = (0, number_util_1.toNumber)(entry.durationMmSs)) !== null &&
53
- _c !== void 0
54
- ? _c
55
- : 0,
56
- auxWeightKg:
57
- (_d = (0, number_util_1.toNumber)(entry.auxWeightKg)) !== null &&
58
- _d !== void 0
59
- ? _d
60
- : 0,
61
- });
62
- }
63
- if (entry.type === "reps-only") {
64
- return Object.assign(Object.assign({}, base), {
65
- type: "reps-only",
66
- reps:
67
- (_e = (0, number_util_1.toNumber)(entry.reps)) !== null && _e !== void 0
68
- ? _e
69
- : 0,
70
- auxWeightKg:
71
- (_f = (0, number_util_1.toNumber)(entry.auxWeightKg)) !== null &&
72
- _f !== void 0
73
- ? _f
74
- : 0,
75
- rir: (0, isDefined_utils_1.isDefinedNumber)(
76
- (0, number_util_1.toNumber)(entry.rir)
77
- )
78
- ? (0, number_util_1.toNumber)(entry.rir)
79
- : undefined,
80
- });
81
- }
82
- if (entry.type === "cardio-machine") {
83
- const speed =
84
- (_g = (0, number_util_1.toNumber)(entry.speedMax)) !== null &&
85
- _g !== void 0
86
- ? _g
87
- : 1;
88
- const resistance =
89
- (_h = (0, number_util_1.toNumber)(entry.rpe)) !== null && _h !== void 0
90
- ? _h
91
- : 1;
92
- const durationMmSs =
93
- (_j = (0, number_util_1.toNumber)(
94
- (0, time_util_1.mmssToSecs)(entry.durationMmSs)
95
- )) !== null && _j !== void 0
96
- ? _j
97
- : 0; // entry.durationMmSs comes as mm:ss value naming issue
98
- const distance =
99
- (_k = (0, number_util_1.toNumber)(entry.distance)) !== null &&
100
- _k !== void 0
101
- ? _k
102
- : 1;
103
- // Calculate intensity score for progress tracking
104
- const intensityScore =
105
- speed > 0 && resistance > 0 ? speed * resistance : undefined;
106
- return Object.assign(
107
- Object.assign(Object.assign({}, base), {
108
- type: "cardio-machine",
109
- speed,
110
- distance,
111
- durationMmSs,
112
- }),
113
- (0, isDefined_utils_1.isDefinedNumber)(intensityScore) && {
114
- intensityScore,
115
- }
116
- );
117
- }
118
- if (entry.type === "cardio-free") {
119
- const distance =
120
- (_l = (0, number_util_1.toNumber)(entry.distance)) !== null &&
121
- _l !== void 0
122
- ? _l
123
- : 0;
124
- const durationMmSs =
125
- (_m = (0, number_util_1.toNumber)(entry.durationMmSs)) !== null &&
126
- _m !== void 0
127
- ? _m
128
- : 0;
129
- // Calculate pace (seconds per km)
130
- const avgPaceSecsPerKm =
131
- distance > 0 && durationMmSs > 0 ? durationMmSs / distance : undefined;
132
- // Calculate average speed (km/h)
133
- const avgSpeedKmh =
134
- distance > 0 && durationMmSs > 0
135
- ? distance / (durationMmSs / 3600)
136
- : undefined;
137
- return Object.assign(
138
- Object.assign(
139
- Object.assign(Object.assign({}, base), {
140
- type: "cardio-free",
141
- distance,
142
- durationMmSs,
143
- }),
144
- (0, isDefined_utils_1.isDefinedNumber)(avgPaceSecsPerKm) && {
145
- avgPaceSecsPerKm,
146
- }
147
- ),
148
- (0, isDefined_utils_1.isDefinedNumber)(avgSpeedKmh) && { avgSpeedKmh }
149
- );
150
- }
151
- throw new Error(`Unknown record type: ${entry.type}`);
152
- };
153
- exports.refineRecordEntry = refineRecordEntry;
@@ -1,87 +0,0 @@
1
- import { TRefinedRecord } from "./record.utils";
2
- import { TExercise, TBodyPartKeys } from "../types";
3
- import { TScoreBreakdown, TUserProfile } from "./scoring.utils";
4
- /**
5
- * EXERCISE SCORING SYSTEM DOCUMENTATION
6
- * =====================================
7
- *
8
- * What the Score Represents:
9
- * -------------------------
10
- * The exercise score is a comprehensive measure of training stress that combines:
11
- *
12
- * 1. **Energy Expenditure (40%)**: Actual calories burned during exercise + afterburn (EPOC)
13
- * 2. **Exercise Complexity (25%)**: Difficulty level, stability demands, muscle groups involved
14
- * 3. **Effort Level (20%)**: RPE/RIR indicating how hard you worked
15
- * 4. **Goal Alignment (10%)**: How well exercise matches your fitness goals
16
- * 5. **Personal Factors (5%)**: Age, gender, weight, fitness level adjustments
17
- *
18
- * Score Ranges:
19
- * -------------
20
- * - 0-20: Light effort (walking, easy stretching)
21
- * - 21-40: Moderate effort (bodyweight exercises, light weights)
22
- * - 41-60: Hard effort (challenging sets, compound movements)
23
- * - 61-80: Very hard effort (heavy weights, high-intensity cardio)
24
- * - 81-100: Extremely hard effort (max effort sets, intense HIIT)
25
- * - 100+: Elite/competition level effort
26
- *
27
- * Usage:
28
- * ------
29
- * - Each SET gets its own score
30
- * - Higher scores = higher training stress
31
- * - Use for tracking progress, planning recovery
32
- * - Compare different workouts objectively
33
- */
34
- export type TWorkoutSummary = {
35
- totalScore: number;
36
- averageScore: number;
37
- totalCalories: number;
38
- totalEpocCalories: number;
39
- workoutDuration: number;
40
- exerciseSummaries: TExerciseSummary[];
41
- muscleRecovery: TMuscleRecoveryMap;
42
- fatigueLevel: "light" | "moderate" | "hard" | "very_hard" | "extreme";
43
- recommendedRestDays: number;
44
- };
45
- export type TExerciseSummary = {
46
- exercise: TExercise;
47
- sets: TSetSummary[];
48
- totalScore: number;
49
- averageScore: number;
50
- totalCalories: number;
51
- bestSet: {
52
- setNumber: number;
53
- score: number;
54
- reason: string;
55
- };
56
- muscleActivation: {
57
- primary: number;
58
- secondary: number;
59
- };
60
- };
61
- export type TSetSummary = {
62
- setNumber: number;
63
- record: TRefinedRecord;
64
- scoreBreakdown: TScoreBreakdown;
65
- description: string;
66
- };
67
- export type TMuscleRecoveryMap = {
68
- [muscle in TBodyPartKeys[number]]: {
69
- fatigueLevel: number;
70
- recoveryHours: number;
71
- trainingStress: number;
72
- exercises: string[];
73
- };
74
- };
75
- /**
76
- * Calculate comprehensive workout summary
77
- */
78
- export declare const calculateWorkoutSummary: (exercises: Array<{
79
- exercise: TExercise;
80
- records: TRefinedRecord[];
81
- startTime?: Date;
82
- endTime?: Date;
83
- }>, userProfile?: TUserProfile) => TWorkoutSummary;
84
- /**
85
- * Generate user-friendly workout summary text
86
- */
87
- export declare const generateWorkoutSummaryText: (summary: TWorkoutSummary) => string;
@@ -1,426 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateWorkoutSummaryText = exports.calculateWorkoutSummary = void 0;
4
- const scoring_utils_1 = require("./scoring.utils");
5
- /**
6
- * Calculate comprehensive workout summary
7
- */
8
- const calculateWorkoutSummary = (exercises, userProfile) => {
9
- var _a, _b;
10
- let totalScore = 0;
11
- let totalCalories = 0;
12
- let totalEpocCalories = 0;
13
- let totalSets = 0;
14
- const exerciseSummaries = [];
15
- const muscleStressMap = new Map();
16
- // Calculate workout duration
17
- const workoutStart =
18
- ((_a = exercises[0]) === null || _a === void 0 ? void 0 : _a.startTime) ||
19
- new Date();
20
- const workoutEnd =
21
- ((_b = exercises[exercises.length - 1]) === null || _b === void 0
22
- ? void 0
23
- : _b.endTime) || new Date();
24
- const workoutDuration = Math.max(
25
- 1,
26
- (workoutEnd.getTime() - workoutStart.getTime()) / (1000 * 60)
27
- );
28
- // Process each exercise
29
- exercises.forEach((exerciseData) => {
30
- const { exercise, records } = exerciseData;
31
- const sets = [];
32
- let exerciseScore = 0;
33
- let exerciseCalories = 0;
34
- let bestSetScore = 0;
35
- let bestSetNumber = 1;
36
- let bestSetReason = "highest score";
37
- // Process each set/record
38
- records.forEach((record, index) => {
39
- if (!record.isDone) return;
40
- const scoreBreakdown = (0, scoring_utils_1.computeScoreFromRecord)({
41
- record,
42
- exercise,
43
- userProfile,
44
- isTimeIntervalModeEnabled: !!(
45
- record.workDurationSecs && record.restDurationSecs
46
- ),
47
- });
48
- const setNumber = index + 1;
49
- const setDescription = generateSetDescription(
50
- record,
51
- exercise,
52
- scoreBreakdown
53
- );
54
- sets.push({
55
- setNumber,
56
- record,
57
- scoreBreakdown,
58
- description: setDescription,
59
- });
60
- exerciseScore += scoreBreakdown.finalScore;
61
- exerciseCalories += scoreBreakdown.caloriesBurned;
62
- totalSets++;
63
- // Track best set
64
- if (scoreBreakdown.finalScore > bestSetScore) {
65
- bestSetScore = scoreBreakdown.finalScore;
66
- bestSetNumber = setNumber;
67
- bestSetReason = getBestSetReason(record, exercise);
68
- }
69
- // Accumulate muscle stress
70
- exercise.primaryMuscles.forEach((muscle) => {
71
- const currentStress = muscleStressMap.get(muscle) || 0;
72
- muscleStressMap.set(
73
- muscle,
74
- currentStress + scoreBreakdown.finalScore * 1.0
75
- ); // Primary muscles get full stress
76
- });
77
- exercise.secondaryMuscles.forEach((muscle) => {
78
- const currentStress = muscleStressMap.get(muscle) || 0;
79
- muscleStressMap.set(
80
- muscle,
81
- currentStress + scoreBreakdown.finalScore * 0.6
82
- ); // Secondary muscles get 60% stress
83
- });
84
- });
85
- if (sets.length > 0) {
86
- exerciseSummaries.push({
87
- exercise,
88
- sets,
89
- totalScore: exerciseScore,
90
- averageScore: exerciseScore / sets.length,
91
- totalCalories: exerciseCalories,
92
- bestSet: {
93
- setNumber: bestSetNumber,
94
- score: bestSetScore,
95
- reason: bestSetReason,
96
- },
97
- muscleActivation: calculateMuscleActivation(exercise, sets),
98
- });
99
- }
100
- });
101
- // Calculate totals
102
- totalScore = exerciseSummaries.reduce((sum, ex) => sum + ex.totalScore, 0);
103
- totalCalories = exerciseSummaries.reduce(
104
- (sum, ex) => sum + ex.totalCalories,
105
- 0
106
- );
107
- totalEpocCalories = exerciseSummaries.reduce(
108
- (sum, ex) =>
109
- sum +
110
- ex.sets.reduce(
111
- (setSum, set) => setSum + set.scoreBreakdown.epocCalories,
112
- 0
113
- ),
114
- 0
115
- );
116
- // Calculate muscle recovery
117
- const muscleRecovery = calculateMuscleRecovery(muscleStressMap, exercises);
118
- // Determine overall fatigue level
119
- const averageScore = totalSets > 0 ? totalScore / totalSets : 0;
120
- const fatigueLevel = getFatigueLevel(
121
- averageScore,
122
- totalScore,
123
- workoutDuration,
124
- userProfile
125
- );
126
- const recommendedRestDays = getRecommendedRestDays(
127
- fatigueLevel,
128
- muscleRecovery
129
- );
130
- return {
131
- totalScore: Math.round(totalScore),
132
- averageScore: Math.round(averageScore),
133
- totalCalories: Math.round(totalCalories),
134
- totalEpocCalories: Math.round(totalEpocCalories),
135
- workoutDuration: Math.round(workoutDuration),
136
- exerciseSummaries,
137
- muscleRecovery,
138
- fatigueLevel,
139
- recommendedRestDays,
140
- };
141
- };
142
- exports.calculateWorkoutSummary = calculateWorkoutSummary;
143
- /**
144
- * Generate human-readable set description
145
- */
146
- const generateSetDescription = (record, exercise, scoreBreakdown) => {
147
- var _a;
148
- const effort = record.rpe
149
- ? `RPE ${record.rpe}`
150
- : "rir" in record && record.rir
151
- ? `${record.rir} RIR`
152
- : "";
153
- const calories = `${Math.round(scoreBreakdown.caloriesBurned)} cal`;
154
- switch (record.type) {
155
- case "weight-reps":
156
- return `${record.kg}kg × ${record.reps} reps ${effort} • ${calories}`;
157
- case "reps-only":
158
- const auxWeight = record.auxWeightKg ? ` (+${record.auxWeightKg}kg)` : "";
159
- return `${record.reps} reps${auxWeight} ${effort} • ${calories}`;
160
- case "duration":
161
- const auxWeightDur = record.auxWeightKg
162
- ? ` (+${record.auxWeightKg}kg)`
163
- : "";
164
- const minutes = Math.floor(record.durationMmSs / 60);
165
- const seconds = record.durationMmSs % 60;
166
- const timeStr =
167
- minutes > 0
168
- ? `${minutes}:${seconds < 10 ? `0${seconds}` : `${seconds}`}`
169
- : `${seconds}s`;
170
- return `${timeStr}${auxWeightDur} ${effort} • ${calories}`;
171
- case "cardio-machine":
172
- const rpe = (_a = record.rpe) !== null && _a !== void 0 ? _a : 1;
173
- const resistance = rpe > 0 ? ` R${rpe}` : "";
174
- const minutes_cm = Math.floor(record.durationMmSs / 60);
175
- const seconds_cm = record.durationMmSs % 60;
176
- const timeStr_cm =
177
- minutes_cm > 0
178
- ? `${minutes_cm}:${
179
- seconds_cm < 10 ? `0${seconds_cm}` : `${seconds_cm}`
180
- }`
181
- : `${seconds_cm}s`;
182
- return `${record.speed} speed${resistance} × ${timeStr_cm} ${effort} • ${calories}`;
183
- case "cardio-free":
184
- const minutes_cf = Math.floor(record.durationMmSs / 60);
185
- const seconds_cf = record.durationMmSs % 60;
186
- const timeStr_cf =
187
- minutes_cf > 0
188
- ? `${minutes_cf}:${
189
- seconds_cf < 10 ? `0${seconds_cf}` : `${seconds_cf}`
190
- }`
191
- : `${seconds_cf}s`;
192
- let pace = "";
193
- if ("avgPaceSecsPerKm" in record && record.avgPaceSecsPerKm) {
194
- const paceMinutes = Math.floor(record.avgPaceSecsPerKm / 60);
195
- const paceSeconds = Math.round(record.avgPaceSecsPerKm % 60);
196
- const paceSecondsFormatted =
197
- paceSeconds < 10 ? `0${paceSeconds}` : `${paceSeconds}`;
198
- pace = ` (${paceMinutes}:${paceSecondsFormatted}/km)`;
199
- }
200
- return `${record.distance}km in ${timeStr_cf}${pace} ${effort} • ${calories}`;
201
- default:
202
- return `${effort} • ${calories}`;
203
- }
204
- };
205
- /**
206
- * Determine reason for best set
207
- */
208
- const getBestSetReason = (record, exercise) => {
209
- switch (record.type) {
210
- case "weight-reps":
211
- return "heaviest weight";
212
- case "reps-only":
213
- return "most reps";
214
- case "duration":
215
- return "longest hold";
216
- case "cardio-machine":
217
- return "highest intensity";
218
- case "cardio-free":
219
- return "fastest pace";
220
- default:
221
- return "highest effort";
222
- }
223
- };
224
- /**
225
- * Calculate muscle activation levels
226
- */
227
- const calculateMuscleActivation = (exercise, sets) => {
228
- if (sets.length === 0) return { primary: 0, secondary: 0 };
229
- const avgScore =
230
- sets.reduce((sum, set) => sum + set.scoreBreakdown.finalScore, 0) /
231
- sets.length;
232
- const maxPossibleScore = 100; // Reference point
233
- // Activation based on exercise difficulty and average set score
234
- const baseActivation = Math.min(100, (avgScore / maxPossibleScore) * 100);
235
- const difficultyBonus = exercise.difficultyLevel * 2; // Up to 20% bonus
236
- return {
237
- primary: Math.min(100, Math.round(baseActivation + difficultyBonus)),
238
- secondary: Math.min(
239
- 100,
240
- Math.round((baseActivation + difficultyBonus) * 0.7)
241
- ), // 70% of primary
242
- };
243
- };
244
- /**
245
- * Calculate muscle recovery times
246
- */
247
- const calculateMuscleRecovery = (muscleStressMap, exercises) => {
248
- const recovery = {};
249
- muscleStressMap.forEach((totalStress, muscle) => {
250
- // Base recovery time based on muscle group
251
- const baseRecovery = getMuscleBaseRecovery(muscle);
252
- // Stress level (0-100)
253
- const fatigueLevel = Math.min(100, (totalStress / 200) * 100); // 200 = very high stress threshold
254
- // Recovery time increases with fatigue level
255
- let recoveryHours = baseRecovery;
256
- if (fatigueLevel > 80) recoveryHours *= 1.5; // 50% longer for high fatigue
257
- else if (fatigueLevel > 60)
258
- recoveryHours *= 1.3; // 30% longer for moderate-high fatigue
259
- else if (fatigueLevel > 40) recoveryHours *= 1.1; // 10% longer for moderate fatigue
260
- // Find exercises that worked this muscle - using manual search instead of includes
261
- const involvedExercises = [];
262
- exercises.forEach(({ exercise }) => {
263
- let found = false;
264
- // Check primary muscles
265
- for (let i = 0; i < exercise.primaryMuscles.length; i++) {
266
- if (exercise.primaryMuscles[i] === muscle) {
267
- found = true;
268
- break;
269
- }
270
- }
271
- // Check secondary muscles if not found in primary
272
- if (!found) {
273
- for (let i = 0; i < exercise.secondaryMuscles.length; i++) {
274
- if (exercise.secondaryMuscles[i] === muscle) {
275
- found = true;
276
- break;
277
- }
278
- }
279
- }
280
- if (found) {
281
- involvedExercises.push(exercise.name);
282
- }
283
- });
284
- recovery[muscle] = {
285
- fatigueLevel: Math.round(fatigueLevel),
286
- recoveryHours: Math.round(recoveryHours),
287
- trainingStress: Math.round(totalStress),
288
- exercises: involvedExercises,
289
- };
290
- });
291
- return recovery;
292
- };
293
- /**
294
- * Get base recovery time for different muscle groups
295
- */
296
- const getMuscleBaseRecovery = (muscle) => {
297
- // Recovery times in hours based on muscle size and recovery capacity
298
- const recoveryTimes = {
299
- "pectoralis-major": 48,
300
- "pectoralis-minor": 48,
301
- "latissimus-dorsi": 48,
302
- trapezius: 36,
303
- rhomboids: 36,
304
- "deltoids-anterior": 36,
305
- "deltoids-middle": 36,
306
- "bicep-short-inner": 24,
307
- "bicep-long-outer": 24,
308
- "tricep-brachii-long": 24,
309
- "tricep-brachii-lateral": 24,
310
- quadriceps: 48,
311
- hamstrings: 48,
312
- "maximus-lower": 48,
313
- "medius-upper": 36,
314
- "calf-inner": 24,
315
- "calf-outer": 24,
316
- "abs-upper": 24,
317
- "abs-lower": 24,
318
- obliques: 24,
319
- "lower-back": 48,
320
- "fore-arm-inner": 18,
321
- "fore-arm-outer": 18,
322
- };
323
- return recoveryTimes[muscle] || 36; // Default 36 hours
324
- };
325
- /**
326
- * Determine overall fatigue level
327
- */
328
- const getFatigueLevel = (
329
- averageScore,
330
- totalScore,
331
- workoutDuration,
332
- userProfile
333
- ) => {
334
- const fitnessAdjustment = (
335
- userProfile === null || userProfile === void 0
336
- ? void 0
337
- : userProfile.fitnessLevel
338
- )
339
- ? (userProfile.fitnessLevel - 3) * 0.1 // ±20% based on fitness level
340
- : 0;
341
- const intensity = averageScore * (1 - fitnessAdjustment);
342
- const volume = totalScore / 100; // Normalize volume
343
- const density = totalScore / workoutDuration; // Score per minute
344
- if (intensity > 80 || volume > 15 || density > 8) return "extreme";
345
- if (intensity > 65 || volume > 12 || density > 6) return "very_hard";
346
- if (intensity > 50 || volume > 8 || density > 4) return "hard";
347
- if (intensity > 35 || volume > 5 || density > 2) return "moderate";
348
- return "light";
349
- };
350
- /**
351
- * Get recommended rest days based on fatigue
352
- */
353
- const getRecommendedRestDays = (fatigueLevel, muscleRecovery) => {
354
- // Base rest days by fatigue level
355
- const baseDays = {
356
- light: 0,
357
- moderate: 1,
358
- hard: 1,
359
- very_hard: 2,
360
- extreme: 3,
361
- };
362
- const restDays = baseDays[fatigueLevel];
363
- // Consider muscle recovery times - using manual iteration
364
- const recoveryHours = [];
365
- for (const muscle in muscleRecovery) {
366
- if (muscleRecovery.hasOwnProperty(muscle)) {
367
- recoveryHours.push(muscleRecovery[muscle].recoveryHours);
368
- }
369
- }
370
- let maxRecoveryHours = 0;
371
- for (let i = 0; i < recoveryHours.length; i++) {
372
- if (recoveryHours[i] > maxRecoveryHours) {
373
- maxRecoveryHours = recoveryHours[i];
374
- }
375
- }
376
- const muscleBasedDays = Math.ceil(maxRecoveryHours / 24);
377
- return Math.max(restDays, muscleBasedDays);
378
- };
379
- /**
380
- * Generate user-friendly workout summary text
381
- */
382
- const generateWorkoutSummaryText = (summary) => {
383
- const {
384
- totalScore,
385
- averageScore,
386
- totalCalories,
387
- totalEpocCalories,
388
- workoutDuration,
389
- fatigueLevel,
390
- recommendedRestDays,
391
- exerciseSummaries,
392
- } = summary;
393
- const intensityMap = {
394
- light: "Light workout",
395
- moderate: "Moderate workout",
396
- hard: "Hard workout",
397
- very_hard: "Very hard workout",
398
- extreme: "Extreme workout",
399
- };
400
- const intensityText = intensityMap[fatigueLevel];
401
- const restText =
402
- recommendedRestDays === 0
403
- ? "You can train again tomorrow"
404
- : `Recommended rest: ${recommendedRestDays} day${
405
- recommendedRestDays > 1 ? "s" : ""
406
- }`;
407
- return `
408
- 🏋️ **${intensityText}** (Score: ${totalScore})
409
- ⏱️ Duration: ${workoutDuration} minutes
410
- 🔥 Calories: ${totalCalories} + ${totalEpocCalories} afterburn
411
- 📊 Average set intensity: ${averageScore}/100
412
-
413
- 💪 **Exercises completed:**
414
- ${exerciseSummaries
415
- .map(
416
- (ex) =>
417
- `• ${ex.exercise.name}: ${ex.sets.length} sets (${Math.round(
418
- ex.totalScore
419
- )} pts)`
420
- )
421
- .join("\n")}
422
-
423
- 🛌 **Recovery:** ${restText}
424
- `.trim();
425
- };
426
- exports.generateWorkoutSummaryText = generateWorkoutSummaryText;