@dgpholdings/greatoak-shared 1.2.87 → 1.2.89

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 (37) hide show
  1. package/dist/__mocks__/catalog.fixture.d.ts +2 -0
  2. package/dist/__mocks__/catalog.fixture.js +208 -0
  3. package/dist/__mocks__/exercises.mock.d.ts +4 -11
  4. package/dist/__mocks__/exercises.mock.js +82 -42
  5. package/dist/__mocks__/sessions.mock.d.ts +28 -0
  6. package/dist/__mocks__/sessions.mock.js +394 -0
  7. package/dist/__mocks__/testIds.d.ts +9 -0
  8. package/dist/__mocks__/testIds.js +13 -0
  9. package/dist/__mocks__/user.mock.js +3 -1
  10. package/dist/constants/goalJourney.d.ts +108 -0
  11. package/dist/constants/goalJourney.js +443 -0
  12. package/dist/constants/index.d.ts +1 -0
  13. package/dist/constants/index.js +1 -0
  14. package/dist/types/TApiUser.d.ts +4 -2
  15. package/dist/utils/adoptionEngine/scaleProPlan.util.js +9 -4
  16. package/dist/utils/constellation/computeNormalisedLoad.test.d.ts +1 -0
  17. package/dist/utils/constellation/computeNormalisedLoad.test.js +218 -0
  18. package/dist/utils/constellation/evaluateConstellation.js +1 -1
  19. package/dist/utils/constellation/evaluateConstellation.test.d.ts +1 -0
  20. package/dist/utils/constellation/evaluateConstellation.test.js +93 -0
  21. package/dist/utils/constellation/index.d.ts +1 -0
  22. package/dist/utils/constellation/index.js +4 -1
  23. package/dist/utils/constellation/starFoundation.test.d.ts +1 -0
  24. package/dist/utils/constellation/starFoundation.test.js +75 -0
  25. package/dist/utils/constellation/stars/consistency.test.d.ts +1 -0
  26. package/dist/utils/constellation/stars/consistency.test.js +94 -0
  27. package/dist/utils/constellation/stars/quality.test.d.ts +1 -0
  28. package/dist/utils/constellation/stars/quality.test.js +113 -0
  29. package/dist/utils/constellation/stars/recovery.test.d.ts +1 -0
  30. package/dist/utils/constellation/stars/recovery.test.js +131 -0
  31. package/dist/utils/constellation/strengthStar.test.d.ts +1 -0
  32. package/dist/utils/constellation/strengthStar.test.js +190 -0
  33. package/dist/utils/exerciseRecord/recordValidator.integration.test.js +1 -1
  34. package/dist/utils/exerciseRecord/recordValidator.js +1 -1
  35. package/dist/utils/exerciseRecord/recordValidator.test.js +8 -8
  36. package/dist/utils/scoringWorkout/scoringWorkout.integration.test.js +19 -19
  37. package/package.json +1 -1
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // shared/src/utils/constellation/stars/recovery.test.ts
4
+ const starFoundation_1 = require("../starFoundation");
5
+ const recovery_1 = require("../stars/recovery");
6
+ const vitest_1 = require("vitest");
7
+ const NOW = Date.UTC(2026, 5, 1);
8
+ const day = (d) => NOW - d * 86400000;
9
+ // Exercises with muscles in specific zones (upper: pec/lat; lower: quad/ham/glute; core: abs).
10
+ const catalog = {
11
+ bench: {
12
+ exerciseId: "bench",
13
+ name: "Bench",
14
+ primaryMuscles: ["pectoralis-major"],
15
+ secondaryMuscles: ["deltoids-anterior"],
16
+ difficultyLevel: 3,
17
+ movementPattern: "push_horizontal",
18
+ status: "active",
19
+ },
20
+ squat: {
21
+ exerciseId: "squat",
22
+ name: "Squat",
23
+ primaryMuscles: ["quadriceps"],
24
+ secondaryMuscles: ["glutes-maximus"],
25
+ difficultyLevel: 3,
26
+ movementPattern: "squat",
27
+ status: "active",
28
+ },
29
+ row: {
30
+ exerciseId: "row",
31
+ name: "Row",
32
+ primaryMuscles: ["latissimus-dorsi"],
33
+ secondaryMuscles: [],
34
+ difficultyLevel: 3,
35
+ movementPattern: "pull_horizontal",
36
+ status: "active",
37
+ },
38
+ };
39
+ const scoredEx = (id, muscles) => ({
40
+ exerciseId: id,
41
+ score: 75,
42
+ muscleScores: Object.fromEntries(muscles.map((m) => [m, 70])),
43
+ });
44
+ const ctx = (scoredSessions, level = 1) => ({
45
+ sessions: [],
46
+ scoredSessions,
47
+ exerciseCatalog: catalog,
48
+ user: { weightKg: 80, gender: "male" },
49
+ now: NOW,
50
+ level,
51
+ });
52
+ (0, vitest_1.describe)("recovery — no data", () => {
53
+ (0, vitest_1.it)("empty history → noData gap, 0 brightness", () => {
54
+ const r = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx([]));
55
+ (0, vitest_1.expect)(r.brightness).toBe(0);
56
+ (0, vitest_1.expect)(r.gap.translationKey).toBe("constellation.recovery.gap.noData");
57
+ });
58
+ });
59
+ (0, vitest_1.describe)("recovery — earns cycles from trained zones", () => {
60
+ (0, vitest_1.it)("a split lifter (alternating zones, spaced) earns recovery cycles", () => {
61
+ // train each zone, leave gaps so it recovers, train again → transitions
62
+ const s = [];
63
+ const sched = [
64
+ [40, "bench", ["pectoralis-major"]],
65
+ [37, "row", ["latissimus-dorsi"]],
66
+ [34, "squat", ["quadriceps"]],
67
+ [27, "bench", ["pectoralis-major"]],
68
+ [24, "row", ["latissimus-dorsi"]],
69
+ [21, "squat", ["quadriceps"]],
70
+ [14, "bench", ["pectoralis-major"]],
71
+ [11, "row", ["latissimus-dorsi"]],
72
+ [8, "squat", ["quadriceps"]],
73
+ ];
74
+ for (const [d, id, m] of sched)
75
+ s.push({ date: day(d), exercises: [scoredEx(id, m)] });
76
+ const r = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx(s));
77
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
78
+ (0, vitest_1.expect)(r.currentState.value).toBeGreaterThanOrEqual(1);
79
+ });
80
+ });
81
+ (0, vitest_1.describe)("recovery — penalises the daily grinder", () => {
82
+ (0, vitest_1.it)("training all zones every day → low/zero recovery", () => {
83
+ const s = [];
84
+ for (let d = 40; d >= 0; d--) {
85
+ s.push({
86
+ date: day(d),
87
+ exercises: [
88
+ scoredEx("bench", ["pectoralis-major"]),
89
+ scoredEx("squat", ["quadriceps"]),
90
+ scoredEx("row", ["latissimus-dorsi"]),
91
+ ],
92
+ });
93
+ }
94
+ const r = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx(s));
95
+ (0, vitest_1.expect)(r.brightness).toBeLessThan(50);
96
+ });
97
+ });
98
+ (0, vitest_1.describe)("recovery — untrained zones earn no free credit", () => {
99
+ (0, vitest_1.it)("training only upper does not manufacture lower/core cycles", () => {
100
+ // only upper trained, spaced so it cycles a few times
101
+ const s = [];
102
+ for (const d of [40, 33, 26, 19, 12, 5])
103
+ s.push({
104
+ date: day(d),
105
+ exercises: [scoredEx("bench", ["pectoralis-major"])],
106
+ });
107
+ const r = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx(s));
108
+ // cycles come ONLY from the upper zone; if lower/core counted freely,
109
+ // brightness would be far higher. Bounded sanity check:
110
+ (0, vitest_1.expect)(r.currentState.value).toBeGreaterThanOrEqual(1);
111
+ (0, vitest_1.expect)(r.brightness).toBeLessThanOrEqual(100);
112
+ });
113
+ });
114
+ (0, vitest_1.describe)("recovery — level scaling", () => {
115
+ (0, vitest_1.it)("same history is dimmer at L2 (needs more cycles)", () => {
116
+ const s = [];
117
+ const sched = [
118
+ [40, "bench", ["pectoralis-major"]],
119
+ [34, "squat", ["quadriceps"]],
120
+ [27, "bench", ["pectoralis-major"]],
121
+ [21, "squat", ["quadriceps"]],
122
+ [14, "bench", ["pectoralis-major"]],
123
+ [8, "squat", ["quadriceps"]],
124
+ ];
125
+ for (const [d, id, m] of sched)
126
+ s.push({ date: day(d), exercises: [scoredEx(id, m)] });
127
+ const l1 = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx(s, 1));
128
+ const l2 = (0, starFoundation_1.resolveStar)(recovery_1.recoveryStar, ctx(s, 2));
129
+ (0, vitest_1.expect)(l2.brightness).toBeLessThanOrEqual(l1.brightness);
130
+ });
131
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // shared/src/utils/constellation/__tests__/strengthStars.test.ts
4
+ const starFoundation_1 = require("./starFoundation");
5
+ const push_1 = require("./stars/push");
6
+ const pull_1 = require("./stars/pull");
7
+ const lowerBody_1 = require("./stars/lowerBody");
8
+ const vitest_1 = require("vitest");
9
+ const NOW = Date.UTC(2026, 5, 1);
10
+ const day = (d) => NOW - d * 86400000;
11
+ const wr = (kg, reps) => ({ type: "weight-reps", kg, reps, isDone: true, isStrictMode: false });
12
+ const ro = (reps, aux = "0") => ({
13
+ type: "reps-only",
14
+ reps,
15
+ auxWeightKg: aux,
16
+ isDone: true,
17
+ isStrictMode: false,
18
+ });
19
+ const ex = (id, mp, over = {}) => ({
20
+ exerciseId: id,
21
+ name: id,
22
+ primaryMuscles: [],
23
+ secondaryMuscles: [],
24
+ difficultyLevel: 3,
25
+ movementPattern: mp,
26
+ status: "active",
27
+ ...over,
28
+ });
29
+ const catalog = {
30
+ bench: ex("bench", "push_horizontal"),
31
+ ohp: ex("ohp", "push_vertical"),
32
+ row: ex("row", "pull_horizontal"),
33
+ pullup: ex("pullup", "pull_vertical", {
34
+ weightMultiplier: { male: 1.0, female: 0.95, default: 0.97 },
35
+ }),
36
+ squat: ex("squat", "squat"),
37
+ rdl: ex("rdl", "hinge"),
38
+ lunge: ex("lunge", "lunge"),
39
+ pistol: ex("pistol", "squat", {
40
+ weightMultiplier: { male: 0.95, female: 0.9, default: 0.9 },
41
+ }),
42
+ };
43
+ const ctx = (sessions, user = {}, level = 1) => ({
44
+ sessions,
45
+ scoredSessions: [],
46
+ exerciseCatalog: catalog,
47
+ user: { weightKg: 80, gender: "male", ...user },
48
+ now: NOW,
49
+ level,
50
+ });
51
+ (0, vitest_1.describe)("push star — pattern filtering & thresholds", () => {
52
+ (0, vitest_1.it)("lights from a strong bench (L1 0.60 male)", () => {
53
+ const r = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx([
54
+ {
55
+ date: day(2),
56
+ exercises: [{ exerciseId: "bench", records: [wr("60", "5")] }],
57
+ },
58
+ ]));
59
+ // 60x5 → 70/80 = 0.875 vs 0.60 → capped 100
60
+ (0, vitest_1.expect)(r.brightness).toBe(100);
61
+ (0, vitest_1.expect)(r.tier).toBe(3);
62
+ (0, vitest_1.expect)(r.gap.translationKey).toBe("constellation.push.gap.full");
63
+ });
64
+ (0, vitest_1.it)("counts vertical push (OHP) too", () => {
65
+ const r = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx([
66
+ {
67
+ date: day(2),
68
+ exercises: [{ exerciseId: "ohp", records: [wr("40", "5")] }],
69
+ },
70
+ ]));
71
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
72
+ });
73
+ (0, vitest_1.it)("ignores pull movements", () => {
74
+ const r = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx([
75
+ {
76
+ date: day(2),
77
+ exercises: [{ exerciseId: "row", records: [wr("100", "5")] }],
78
+ },
79
+ ]));
80
+ (0, vitest_1.expect)(r.brightness).toBe(0);
81
+ });
82
+ (0, vitest_1.it)("partial brightness below threshold gives a toGo gap", () => {
83
+ const r = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx([
84
+ {
85
+ date: day(2),
86
+ exercises: [{ exerciseId: "bench", records: [wr("25", "5")] }],
87
+ },
88
+ ]));
89
+ // 25x5 → 29.2/80 = 0.365 vs 0.60 → ~61%
90
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
91
+ (0, vitest_1.expect)(r.brightness).toBeLessThan(100);
92
+ (0, vitest_1.expect)(r.gap.translationKey).toBe("constellation.push.gap.toGo");
93
+ });
94
+ });
95
+ (0, vitest_1.describe)("strength — gendered thresholds", () => {
96
+ (0, vitest_1.it)("female has a lower bar (same lift lights more)", () => {
97
+ const sessions = [
98
+ {
99
+ date: day(2),
100
+ exercises: [{ exerciseId: "bench", records: [wr("30", "5")] }],
101
+ },
102
+ ];
103
+ const male = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, { gender: "male" }));
104
+ const female = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, { gender: "female" }));
105
+ (0, vitest_1.expect)(female.brightness).toBeGreaterThanOrEqual(male.brightness);
106
+ });
107
+ (0, vitest_1.it)("unmentioned gender resolves to the male (higher) bar", () => {
108
+ const sessions = [
109
+ {
110
+ date: day(2),
111
+ exercises: [{ exerciseId: "bench", records: [wr("30", "5")] }],
112
+ },
113
+ ];
114
+ const male = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, { gender: "male" }));
115
+ const unmentioned = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, { gender: undefined }));
116
+ (0, vitest_1.expect)(unmentioned.brightness).toBe(male.brightness);
117
+ });
118
+ });
119
+ (0, vitest_1.describe)("strength — level scaling", () => {
120
+ (0, vitest_1.it)("a fixed lift is dimmer at L2 than L1 (higher bar)", () => {
121
+ const sessions = [
122
+ {
123
+ date: day(2),
124
+ exercises: [{ exerciseId: "bench", records: [wr("40", "5")] }],
125
+ },
126
+ ];
127
+ // 40x5 → 46.7/80 = 0.583. L1 push 0.60 → ~97%; L2 push 1.0 → ~58%
128
+ const l1 = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, {}, 1));
129
+ const l2 = (0, starFoundation_1.resolveStar)(push_1.pushStar, ctx(sessions, {}, 2));
130
+ (0, vitest_1.expect)(l2.brightness).toBeLessThan(l1.brightness);
131
+ });
132
+ });
133
+ (0, vitest_1.describe)("pull star", () => {
134
+ (0, vitest_1.it)("lights from rows", () => {
135
+ const r = (0, starFoundation_1.resolveStar)(pull_1.pullStar, ctx([
136
+ {
137
+ date: day(2),
138
+ exercises: [{ exerciseId: "row", records: [wr("50", "5")] }],
139
+ },
140
+ ]));
141
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
142
+ });
143
+ (0, vitest_1.it)("bodyweight pull-ups light via weightMultiplier", () => {
144
+ const r = (0, starFoundation_1.resolveStar)(pull_1.pullStar, ctx([
145
+ {
146
+ date: day(2),
147
+ exercises: [{ exerciseId: "pullup", records: [ro("8")] }],
148
+ },
149
+ ]));
150
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
151
+ });
152
+ });
153
+ (0, vitest_1.describe)("lowerBody star — combined patterns & home reachability", () => {
154
+ (0, vitest_1.it)("squat, hinge, and lunge all count", () => {
155
+ for (const id of ["squat", "rdl", "lunge"]) {
156
+ const r = (0, starFoundation_1.resolveStar)(lowerBody_1.lowerBodyStar, ctx([
157
+ {
158
+ date: day(2),
159
+ exercises: [{ exerciseId: id, records: [wr("80", "5")] }],
160
+ },
161
+ ]));
162
+ (0, vitest_1.expect)(r.brightness).toBeGreaterThan(0);
163
+ }
164
+ });
165
+ (0, vitest_1.it)("HOME user reaches full lower body via bodyweight pistol squats", () => {
166
+ const r = (0, starFoundation_1.resolveStar)(lowerBody_1.lowerBodyStar, ctx([
167
+ {
168
+ date: day(2),
169
+ exercises: [{ exerciseId: "pistol", records: [ro("8")] }],
170
+ },
171
+ ], { weightKg: 75 }));
172
+ // pistol 0.95*75=71.25; 1RM=71.25*(1+8/30)=90.25; /75=1.20 vs L1 1.0 → 100
173
+ (0, vitest_1.expect)(r.brightness).toBe(100);
174
+ });
175
+ (0, vitest_1.it)("takes the best across the three patterns", () => {
176
+ const sessions = [
177
+ {
178
+ date: day(5),
179
+ exercises: [{ exerciseId: "squat", records: [wr("50", "5")] }],
180
+ },
181
+ {
182
+ date: day(2),
183
+ exercises: [{ exerciseId: "rdl", records: [wr("100", "5")] }],
184
+ },
185
+ ];
186
+ const r = (0, starFoundation_1.resolveStar)(lowerBody_1.lowerBodyStar, ctx(sessions));
187
+ // rdl 100x5 → 116.7/80 = 1.46 → capped 100
188
+ (0, vitest_1.expect)(r.brightness).toBe(100);
189
+ });
190
+ });
@@ -41,7 +41,7 @@ const exercises_mock_1 = require("../../__mocks__/exercises.mock");
41
41
  const mockTemplate = templateExercises_mock_1.mockTemplateExercisesDictionary.cardioFree;
42
42
  const baseExercise = exercises_mock_1.mockExercisesDictionary[mockTemplate.exerciseId];
43
43
  // Artificially disable enableDistance to prove regression safety
44
- const hostileConfig = Object.assign(Object.assign({}, mockTemplate.config), { enableDistance: false });
44
+ const hostileConfig = { ...mockTemplate.config, enableDistance: false };
45
45
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(mockTemplate.initialRecords[0], baseExercise, hostileConfig);
46
46
  (0, vitest_1.expect)(result.isValid).toBe(true);
47
47
  // Distance is structurally required for cardio-free, the config cannot turn it off.
@@ -18,7 +18,7 @@ const validateAndSanitizeRecord = (record, baseExercise, config) => {
18
18
  return { isValid: false, sanitizedRecord: null, errors };
19
19
  }
20
20
  // 3. Create a clean clone for sanitization
21
- const sanitized = Object.assign({}, record);
21
+ const sanitized = { ...record };
22
22
  // 4. Config-Driven Sanitization
23
23
  // If a field is disabled in the TExerciseConfig, we remove it from the payload.
24
24
  if (!config.enableRpe) {
@@ -85,7 +85,7 @@ const recordValidator_1 = require("./recordValidator");
85
85
  (0, vitest_1.expect)(result.sanitizedRecord).toHaveProperty('reps', '0');
86
86
  });
87
87
  (0, vitest_1.it)('validates numeric integrity of reps-only and collects error', () => {
88
- const baseRepExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'reps-only' });
88
+ const baseRepExercise = { ...mockBaseExercise, recordType: 'reps-only' };
89
89
  const record = { type: 'reps-only', isDone: true, isStrictMode: false, reps: 'abc', auxWeightKg: '20' };
90
90
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseRepExercise, mockConfigAllEnabled);
91
91
  (0, vitest_1.expect)(result.isValid).toBe(false);
@@ -94,7 +94,7 @@ const recordValidator_1 = require("./recordValidator");
94
94
  (0, vitest_1.expect)(result.errors[0]).toContain('Reps must be a parseable number');
95
95
  });
96
96
  (0, vitest_1.it)('zeroes out required auxWeightKg instead of deleting it on reps-only when disabled', () => {
97
- const baseRepExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'reps-only' });
97
+ const baseRepExercise = { ...mockBaseExercise, recordType: 'reps-only' };
98
98
  const record = { type: 'reps-only', isDone: true, isStrictMode: false, reps: '10', auxWeightKg: '20' };
99
99
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseRepExercise, mockConfigAllDisabled);
100
100
  (0, vitest_1.expect)(result.isValid).toBe(true);
@@ -102,14 +102,14 @@ const recordValidator_1 = require("./recordValidator");
102
102
  (0, vitest_1.expect)(result.sanitizedRecord).toHaveProperty('auxWeightKg', '');
103
103
  });
104
104
  (0, vitest_1.it)('validates duration type successfully (Happy Path)', () => {
105
- const baseDurationExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'duration' });
105
+ const baseDurationExercise = { ...mockBaseExercise, recordType: 'duration' };
106
106
  const record = { type: 'duration', isDone: true, isStrictMode: false, durationMmSs: '05:00', auxWeightKg: '0' };
107
107
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseDurationExercise, mockConfigAllEnabled);
108
108
  (0, vitest_1.expect)(result.isValid).toBe(true);
109
109
  (0, vitest_1.expect)(result.sanitizedRecord).toHaveProperty('durationMmSs', '05:00');
110
110
  });
111
111
  (0, vitest_1.it)('rejects cardio-machine records with zero duration', () => {
112
- const baseMachineExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'cardio-machine' });
112
+ const baseMachineExercise = { ...mockBaseExercise, recordType: 'cardio-machine' };
113
113
  const record = { type: 'cardio-machine', isDone: true, isStrictMode: false, durationMmSs: '00:00' };
114
114
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseMachineExercise, mockConfigAllEnabled);
115
115
  (0, vitest_1.expect)(result.isValid).toBe(false);
@@ -117,7 +117,7 @@ const recordValidator_1 = require("./recordValidator");
117
117
  (0, vitest_1.expect)(result.errors[0]).toContain('Duration (MM:SS) must be present and non-zero');
118
118
  });
119
119
  (0, vitest_1.it)('strips optional fields from cardio-machine when disabled', () => {
120
- const baseMachineExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'cardio-machine' });
120
+ const baseMachineExercise = { ...mockBaseExercise, recordType: 'cardio-machine' };
121
121
  const record = {
122
122
  type: 'cardio-machine',
123
123
  isDone: true,
@@ -139,7 +139,7 @@ const recordValidator_1 = require("./recordValidator");
139
139
  (0, vitest_1.expect)(result.sanitizedRecord).toHaveProperty('durationMmSs', '15:00');
140
140
  });
141
141
  (0, vitest_1.it)('validates duration format for cardio-free', () => {
142
- const baseCardioExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'cardio-free' });
142
+ const baseCardioExercise = { ...mockBaseExercise, recordType: 'cardio-free' };
143
143
  // Missing durationMmSs
144
144
  const record = { type: 'cardio-free', isDone: true, isStrictMode: false, distance: '5', durationMmSs: '00:00' };
145
145
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseCardioExercise, mockConfigAllEnabled);
@@ -148,7 +148,7 @@ const recordValidator_1 = require("./recordValidator");
148
148
  (0, vitest_1.expect)(result.errors[0]).toContain('Duration (MM:SS) must be present and non-zero');
149
149
  });
150
150
  (0, vitest_1.it)('validates distance format for cardio-free', () => {
151
- const baseCardioExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'cardio-free' });
151
+ const baseCardioExercise = { ...mockBaseExercise, recordType: 'cardio-free' };
152
152
  const record = { type: 'cardio-free', isDone: true, isStrictMode: false, distance: 'invalid', durationMmSs: '15:00' };
153
153
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseCardioExercise, mockConfigAllEnabled);
154
154
  (0, vitest_1.expect)(result.isValid).toBe(false);
@@ -156,7 +156,7 @@ const recordValidator_1 = require("./recordValidator");
156
156
  (0, vitest_1.expect)(result.errors[0]).toContain('Distance must be a parseable number');
157
157
  });
158
158
  (0, vitest_1.it)('retains required distance on cardio-free even if enableDistance is false (Regression)', () => {
159
- const baseCardioExercise = Object.assign(Object.assign({}, mockBaseExercise), { recordType: 'cardio-free' });
159
+ const baseCardioExercise = { ...mockBaseExercise, recordType: 'cardio-free' };
160
160
  const record = { type: 'cardio-free', isDone: true, isStrictMode: false, distance: '5', durationMmSs: '15:00' };
161
161
  const result = (0, recordValidator_1.validateAndSanitizeRecord)(record, baseCardioExercise, mockConfigAllDisabled);
162
162
  (0, vitest_1.expect)(result.isValid).toBe(true);
@@ -1,15 +1,4 @@
1
1
  "use strict";
2
- var __rest = (this && this.__rest) || function (s, e) {
3
- var t = {};
4
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
- t[p] = s[p];
6
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
- t[p[i]] = s[p[i]];
10
- }
11
- return t;
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
3
  const vitest_1 = require("vitest");
15
4
  const index_1 = require("./index");
@@ -25,10 +14,10 @@ const user_mock_1 = require("../../__mocks__/user.mock");
25
14
  // parseRecords should null-out the last done set's rest regardless of isStrictMode.
26
15
  const records = template.initialRecords.map((r, i) => {
27
16
  if (i === 1) {
28
- const { restDurationSecs } = r, rest = __rest(r, ["restDurationSecs"]);
29
- return Object.assign(Object.assign({}, rest), { isStrictMode: true });
17
+ const { restDurationSecs, ...rest } = r;
18
+ return { ...rest, isStrictMode: true };
30
19
  }
31
- return Object.assign(Object.assign({}, r), { isStrictMode: true, restDurationSecs: 120 });
20
+ return { ...r, isStrictMode: true, restDurationSecs: 120 };
32
21
  });
33
22
  // Direct parseRecords proof: last done set (index 1) must have null rest.
34
23
  const parsed = (0, parseRecords_1.parseRecords)(records, exercise.timingGuardrails);
@@ -68,7 +57,11 @@ const user_mock_1 = require("../../__mocks__/user.mock");
68
57
  const exercise = exercises_mock_1.mockExerciseWeightReps;
69
58
  // restDurationSecs present → hasRestData = true → rest IS scored
70
59
  // 600s > acceptableMax so parseRecords clamps to typical (120s) → optimal → 100
71
- const records = template.initialRecords.map((r, i) => (Object.assign(Object.assign({}, r), { isStrictMode: true, restDurationSecs: i === 0 ? 600 : 120 })));
60
+ const records = template.initialRecords.map((r, i) => ({
61
+ ...r,
62
+ isStrictMode: true,
63
+ restDurationSecs: i === 0 ? 600 : 120,
64
+ }));
72
65
  const { qualityBreakdown, restDisciplineActive } = (0, index_1.calculateExerciseScoreV2)({
73
66
  exercise,
74
67
  record: records,
@@ -83,7 +76,11 @@ const user_mock_1 = require("../../__mocks__/user.mock");
83
76
  // Set 0: 350s → outside acceptableMax (300s) → 50
84
77
  // Set 1: 120s → optimal → 100
85
78
  // avg = 75, plus stressRestBonus (5) = 80
86
- const records = template.initialRecords.map((r, i) => (Object.assign(Object.assign({}, r), { isStrictMode: true, restDurationSecs: i === 0 ? 350 : 120 })));
79
+ const records = template.initialRecords.map((r, i) => ({
80
+ ...r,
81
+ isStrictMode: true,
82
+ restDurationSecs: i === 0 ? 350 : 120,
83
+ }));
87
84
  const { qualityBreakdown, restDisciplineActive } = (0, index_1.calculateExerciseScoreV2)({
88
85
  exercise,
89
86
  record: records,
@@ -158,7 +155,10 @@ const user_mock_1 = require("../../__mocks__/user.mock");
158
155
  });
159
156
  (0, vitest_1.it)("stretch-mobility = zero fatigue: muscleScores should be empty", () => {
160
157
  const template = templateExercises_mock_1.mockTemplateExercisesDictionary.duration;
161
- const exercise = Object.assign(Object.assign({}, exercises_mock_1.mockExerciseDuration), { scoringSpecialHandling: "stretch-mobility" });
158
+ const exercise = {
159
+ ...exercises_mock_1.mockExerciseDuration,
160
+ scoringSpecialHandling: "stretch-mobility",
161
+ };
162
162
  const { muscleScores } = (0, index_1.calculateExerciseScoreV2)({
163
163
  exercise,
164
164
  record: template.initialRecords,
@@ -332,12 +332,12 @@ const user_mock_1 = require("../../__mocks__/user.mock");
332
332
  const sedentaryResult = (0, index_1.calculateExerciseScoreV2)({
333
333
  exercise: exercises_mock_1.mockExerciseWeightReps,
334
334
  record: template.initialRecords,
335
- user: Object.assign(Object.assign({}, user_mock_1.mockUser), { fitnessLevel: "sedentary" }),
335
+ user: { ...user_mock_1.mockUser, fitnessLevel: "sedentary" },
336
336
  });
337
337
  const veryActiveResult = (0, index_1.calculateExerciseScoreV2)({
338
338
  exercise: exercises_mock_1.mockExerciseWeightReps,
339
339
  record: template.initialRecords,
340
- user: Object.assign(Object.assign({}, user_mock_1.mockUser), { fitnessLevel: "very-active" }),
340
+ user: { ...user_mock_1.mockUser, fitnessLevel: "very-active" },
341
341
  });
342
342
  // sedentary: activityScale 0.70 → lower referenceMax → higher normalised fatigue
343
343
  // very-active: activityScale 1.20 → higher referenceMax → lower normalised fatigue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dgpholdings/greatoak-shared",
3
- "version": "1.2.87",
3
+ "version": "1.2.89",
4
4
  "description": "Shared TypeScript types and utilities for @dgpholdings projects",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",