@dgpholdings/greatoak-shared 1.1.62 → 1.1.64
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.
- package/dist/types/adminApi/TApiAttachment.d.ts +6 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +5 -1
- package/dist/utils/slugify.util.d.ts +1 -0
- package/dist/utils/slugify.util.js +14 -0
- package/dist/utils/toError.util.d.ts +1 -0
- package/dist/utils/toError.util.js +20 -0
- package/package.json +1 -1
- package/dist/types/TApiRecord.d.ts +0 -61
- package/dist/types/TApiRecord.js +0 -2
- package/dist/types/TApiTemplate.d.ts +0 -38
- package/dist/types/TApiTemplate.js +0 -2
- package/dist/types/TApiUserData.d.ts +0 -26
- package/dist/types/TApiUserData.js +0 -2
- package/dist/types/adminApi/TApiBillingCreate.d.ts +0 -5
- package/dist/types/adminApi/TApiBillingCreate.js +0 -2
- package/dist/types/typeGuards/recordTypeGuards.d.ts +0 -5
- package/dist/types/typeGuards/recordTypeGuards.js +0 -11
- package/dist/utils/record.utils.d.ts +0 -48
- package/dist/utils/record.utils.js +0 -153
- package/dist/utils/workoutSummary.util.d.ts +0 -87
- package/dist/utils/workoutSummary.util.js +0 -426
|
@@ -9,12 +9,17 @@ export type TApiAttachmentDeleteResponse = {
|
|
|
9
9
|
success: boolean;
|
|
10
10
|
};
|
|
11
11
|
export type TAttachment = {
|
|
12
|
-
|
|
12
|
+
attachmentId: string;
|
|
13
13
|
name: string;
|
|
14
14
|
originalName: string;
|
|
15
15
|
path: string;
|
|
16
|
+
destination: string;
|
|
16
17
|
type: string;
|
|
17
18
|
size: number;
|
|
19
|
+
created_at?: Date;
|
|
20
|
+
isTemplateAsset: boolean;
|
|
21
|
+
uploadType: string;
|
|
22
|
+
slug: string;
|
|
18
23
|
};
|
|
19
24
|
export type TApiAttachmentsAllResponse = {
|
|
20
25
|
videos: TAttachment[];
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/utils/index.js
CHANGED
|
@@ -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; } });
|
|
@@ -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,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
|
-
};
|
package/dist/types/TApiRecord.js
DELETED
|
@@ -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,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,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;
|