@dgpholdings/greatoak-shared 1.2.86 → 1.2.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -148
- package/dist/__mocks__/exercises.mock.js +1 -0
- package/dist/types/TApiAiExerciseAnalysis.d.ts +2 -1
- package/dist/types/TApiClientConstellation.d.ts +33 -0
- package/dist/types/TApiClientConstellation.js +13 -0
- package/dist/types/TApiExercise.d.ts +5 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/constellation/computeNormalisedLoad.d.ts +48 -0
- package/dist/utils/constellation/computeNormalisedLoad.js +150 -0
- package/dist/utils/constellation/evaluateConstellation.d.ts +27 -0
- package/dist/utils/constellation/evaluateConstellation.js +135 -0
- package/dist/utils/constellation/index.d.ts +17 -0
- package/dist/utils/constellation/index.js +26 -0
- package/dist/utils/constellation/levelThresholds.d.ts +99 -0
- package/dist/utils/constellation/levelThresholds.js +123 -0
- package/dist/utils/constellation/starFoundation.d.ts +25 -0
- package/dist/utils/constellation/starFoundation.js +54 -0
- package/dist/utils/constellation/stars/consistency.d.ts +29 -0
- package/dist/utils/constellation/stars/consistency.js +142 -0
- package/dist/utils/constellation/stars/lowerBody.d.ts +17 -0
- package/dist/utils/constellation/stars/lowerBody.js +30 -0
- package/dist/utils/constellation/stars/pull.d.ts +11 -0
- package/dist/utils/constellation/stars/pull.js +24 -0
- package/dist/utils/constellation/stars/push.d.ts +11 -0
- package/dist/utils/constellation/stars/push.js +24 -0
- package/dist/utils/constellation/stars/quality.d.ts +19 -0
- package/dist/utils/constellation/stars/quality.js +98 -0
- package/dist/utils/constellation/stars/recovery.d.ts +29 -0
- package/dist/utils/constellation/stars/recovery.js +169 -0
- package/dist/utils/constellation/strengthStarHelpers.d.ts +41 -0
- package/dist/utils/constellation/strengthStarHelpers.js +104 -0
- package/dist/utils/constellation/types.d.ts +124 -0
- package/dist/utils/constellation/types.js +18 -0
- package/dist/utils/index.d.ts +5 -3
- package/dist/utils/index.js +1 -0
- package/dist/utils/scoringWorkout/calculateQualityScore.d.ts +59 -36
- package/dist/utils/scoringWorkout/calculateQualityScore.js +234 -233
- package/dist/utils/scoringWorkout/computeMuscleFatigueMap.d.ts +8 -5
- package/dist/utils/scoringWorkout/computeMuscleFatigueMap.js +72 -88
- package/dist/utils/scoringWorkout/constants.d.ts +20 -6
- package/dist/utils/scoringWorkout/constants.js +23 -9
- package/dist/utils/scoringWorkout/helpers.d.ts +7 -0
- package/dist/utils/scoringWorkout/helpers.js +24 -18
- package/dist/utils/scoringWorkout/index.d.ts +12 -8
- package/dist/utils/scoringWorkout/index.js +23 -15
- package/dist/utils/scoringWorkout/parseRecords.js +4 -3
- package/dist/utils/scoringWorkout/scoringWorkout.integration.test.js +210 -172
- package/dist/utils/scoringWorkout/types.d.ts +34 -14
- package/package.json +31 -31
- package/dist/utils/exerciseRecord/__mocks__/exercises.mock.d.ts +0 -30
- package/dist/utils/exerciseRecord/__mocks__/exercises.mock.js +0 -138
- package/dist/utils/scaleProPlan.util.d.ts +0 -9
- package/dist/utils/scaleProPlan.util.js +0 -139
- package/dist/utils/scoring/calculateCalories.d.ts +0 -67
- package/dist/utils/scoring/calculateCalories.js +0 -345
- package/dist/utils/scoring/calculateMuscleFatiue.d.ts +0 -67
- package/dist/utils/scoring/calculateMuscleFatiue.js +0 -310
- package/dist/utils/scoring/calculateQualityScore.d.ts +0 -71
- package/dist/utils/scoring/calculateQualityScore.js +0 -334
- package/dist/utils/scoring/calculateTotalVolume.d.ts +0 -15
- package/dist/utils/scoring/calculateTotalVolume.js +0 -73
- package/dist/utils/scoring/constants.d.ts +0 -211
- package/dist/utils/scoring/constants.js +0 -247
- package/dist/utils/scoring/helpers.d.ts +0 -119
- package/dist/utils/scoring/helpers.js +0 -229
- package/dist/utils/scoring/index.d.ts +0 -28
- package/dist/utils/scoring/index.js +0 -47
- package/dist/utils/scoring/parseRecords.d.ts +0 -98
- package/dist/utils/scoring/parseRecords.js +0 -284
- package/dist/utils/scoring/types.d.ts +0 -86
- package/dist/utils/scoring/types.js +0 -11
- package/dist/utils/scoring.utils.d.ts +0 -14
- package/dist/utils/scoring.utils.js +0 -243
- /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.d.ts → calculateMuscleFatigue.d.ts} +0 -0
- /package/dist/utils/scoringWorkout/{calculateMuscleFatiue.js → calculateMuscleFatigue.js} +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* FITFRIX CONSTELLATION — Model & API Contract
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Purely live: every value is recomputed from raw records each call. Nothing is
|
|
7
|
+
* persisted. brightness (0-100) is the only signal; tier is derived from it.
|
|
8
|
+
*
|
|
9
|
+
* i18n: all user-facing text is a TLocalizedText ({ translationKey, params }).
|
|
10
|
+
* Evaluators never emit raw display strings — the client renders the key with
|
|
11
|
+
* locale-correct formatting. Numeric measures send value + unit only.
|
|
12
|
+
*
|
|
13
|
+
* Scoring is computed ONCE by the orchestrator and shared via TStarContext
|
|
14
|
+
* (scoredSessions), so quality and recovery never re-run the scoring engine.
|
|
15
|
+
*/
|
|
16
|
+
import type { TExercise, TUserMetric, TRecord } from "../../types";
|
|
17
|
+
import type { TLevel } from "./levelThresholds";
|
|
18
|
+
export interface TLocalizedText {
|
|
19
|
+
translationKey: string;
|
|
20
|
+
/** Ordered interpolation values, e.g. [10] -> "about {0}kg more". */
|
|
21
|
+
params?: (string | number)[];
|
|
22
|
+
}
|
|
23
|
+
export type TStarId = "consistency" | "quality" | "push" | "pull" | "lowerBody" | "recovery";
|
|
24
|
+
export type TFigurePosition = "heart" | "core" | "leftArm" | "rightArm" | "base" | "crown";
|
|
25
|
+
/** 0 dormant - 1 faint - 2 rising - 3 full. Derived from brightness. */
|
|
26
|
+
export type TStarTier = 0 | 1 | 2 | 3;
|
|
27
|
+
/** Unit codes a measure can carry. Keep in sync with the client's unit labels. */
|
|
28
|
+
export type TMeasureUnit = "bw_ratio" | "sessions" | "cycles" | "days";
|
|
29
|
+
/**
|
|
30
|
+
* A numeric measure. The client formats `value` for locale and renders `unit`
|
|
31
|
+
* via its translation. `display` is a localized template if the unit needs
|
|
32
|
+
* prose around the number (e.g. "{0} of {1} days"); for plain values the client
|
|
33
|
+
* may format value+unit directly.
|
|
34
|
+
*/
|
|
35
|
+
export interface TStarMeasure {
|
|
36
|
+
value: number;
|
|
37
|
+
unit: TMeasureUnit;
|
|
38
|
+
display: TLocalizedText;
|
|
39
|
+
}
|
|
40
|
+
export interface TResolvedStar {
|
|
41
|
+
id: TStarId;
|
|
42
|
+
displayName: TLocalizedText;
|
|
43
|
+
color: string;
|
|
44
|
+
figurePosition: TFigurePosition;
|
|
45
|
+
brightness: number;
|
|
46
|
+
tier: TStarTier;
|
|
47
|
+
objective: TLocalizedText;
|
|
48
|
+
rationale: TLocalizedText;
|
|
49
|
+
currentState: TStarMeasure;
|
|
50
|
+
target: TStarMeasure;
|
|
51
|
+
gap: TLocalizedText;
|
|
52
|
+
}
|
|
53
|
+
export interface TConstellationFigure {
|
|
54
|
+
/** 0-100, avg brightness across stars -> aura intensity. */
|
|
55
|
+
auraIntensity: number;
|
|
56
|
+
/** Count of stars currently at full brightness (tier 3). */
|
|
57
|
+
fullStarCount: number;
|
|
58
|
+
}
|
|
59
|
+
export interface TConstellationState {
|
|
60
|
+
stars: TResolvedStar[];
|
|
61
|
+
figure: TConstellationFigure;
|
|
62
|
+
currentLevel: TLevel;
|
|
63
|
+
evaluatedAt: string;
|
|
64
|
+
}
|
|
65
|
+
export interface TSessionRecord {
|
|
66
|
+
date: number;
|
|
67
|
+
exercises: {
|
|
68
|
+
exerciseId: string;
|
|
69
|
+
records: TRecord[];
|
|
70
|
+
}[];
|
|
71
|
+
}
|
|
72
|
+
export interface TConstellationInput {
|
|
73
|
+
sessions: TSessionRecord[];
|
|
74
|
+
/** Full catalog - UNFILTERED (incl. inactive). */
|
|
75
|
+
exerciseCatalog: Record<string, TExercise>;
|
|
76
|
+
user: Partial<TUserMetric>;
|
|
77
|
+
now: number;
|
|
78
|
+
currentLevel?: number;
|
|
79
|
+
}
|
|
80
|
+
/** One exercise within a session, scored once by the orchestrator. */
|
|
81
|
+
export interface TScoredExercise {
|
|
82
|
+
exerciseId: string;
|
|
83
|
+
/** Quality score 0-100 from the scoring engine, or null if unscorable. */
|
|
84
|
+
score: number | null;
|
|
85
|
+
/** Per-muscle scores (fatigue input), empty if unscorable. */
|
|
86
|
+
muscleScores: Record<string, number>;
|
|
87
|
+
}
|
|
88
|
+
/** A session with each exercise pre-scored. */
|
|
89
|
+
export interface TScoredSession {
|
|
90
|
+
date: number;
|
|
91
|
+
exercises: TScoredExercise[];
|
|
92
|
+
}
|
|
93
|
+
export interface TStarDynamicDetail {
|
|
94
|
+
currentState: TStarMeasure;
|
|
95
|
+
target: TStarMeasure;
|
|
96
|
+
gap: TLocalizedText;
|
|
97
|
+
}
|
|
98
|
+
export interface TStarEvaluation {
|
|
99
|
+
brightness: number;
|
|
100
|
+
detail: TStarDynamicDetail;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Everything a star needs to evaluate. Scoring is pre-computed
|
|
104
|
+
* (scoredSessions) so quality/recovery don't re-run the engine. Raw sessions
|
|
105
|
+
* remain available for stars that work off dates/records directly (consistency,
|
|
106
|
+
* strength).
|
|
107
|
+
*/
|
|
108
|
+
export interface TStarContext {
|
|
109
|
+
sessions: TSessionRecord[];
|
|
110
|
+
scoredSessions: TScoredSession[];
|
|
111
|
+
exerciseCatalog: Record<string, TExercise>;
|
|
112
|
+
user: Partial<TUserMetric>;
|
|
113
|
+
now: number;
|
|
114
|
+
level: TLevel;
|
|
115
|
+
}
|
|
116
|
+
export interface TStarDefinition {
|
|
117
|
+
id: TStarId;
|
|
118
|
+
displayName: TLocalizedText;
|
|
119
|
+
color: string;
|
|
120
|
+
figurePosition: TFigurePosition;
|
|
121
|
+
objective: TLocalizedText;
|
|
122
|
+
rationale: TLocalizedText;
|
|
123
|
+
evaluate: (ctx: TStarContext) => TStarEvaluation;
|
|
124
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// utils/constellation/types.ts — Model & API Contract
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* FITFRIX CONSTELLATION — Model & API Contract
|
|
6
|
+
* ============================================================================
|
|
7
|
+
*
|
|
8
|
+
* Purely live: every value is recomputed from raw records each call. Nothing is
|
|
9
|
+
* persisted. brightness (0-100) is the only signal; tier is derived from it.
|
|
10
|
+
*
|
|
11
|
+
* i18n: all user-facing text is a TLocalizedText ({ translationKey, params }).
|
|
12
|
+
* Evaluators never emit raw display strings — the client renders the key with
|
|
13
|
+
* locale-correct formatting. Numeric measures send value + unit only.
|
|
14
|
+
*
|
|
15
|
+
* Scoring is computed ONCE by the orchestrator and shared via TStarContext
|
|
16
|
+
* (scoredSessions), so quality and recovery never re-run the scoring engine.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -7,9 +7,11 @@ export { toError } from "./toError.util";
|
|
|
7
7
|
export { generatePlanCode } from "./planCode.util";
|
|
8
8
|
export { maskEmail, isAnonymousEmail, isEmail } from "./email.utils";
|
|
9
9
|
export { NOOP } from "./noop.utils";
|
|
10
|
-
export { calculateExerciseScoreV2, calculateTotalVolume, deriveTrainingAgeBracket, computeMuscleFatigueMap } from "./scoringWorkout";
|
|
11
|
-
export type { IHistoricalContext, TTrainingAgeBracket, TEnrichedSessionScore, TEnrichedExerciseRecord, TMuscleFatigueEntry, TMuscleFatigueResult } from "./scoringWorkout";
|
|
12
|
-
export { scaleProPlan, calculateBMI, calculateDayPlanDuration, calculateExerciseDurationSecs } from "./adoptionEngine/scaleProPlan.util";
|
|
10
|
+
export { calculateExerciseScoreV2, calculateTotalVolume, deriveTrainingAgeBracket, computeMuscleFatigueMap, } from "./scoringWorkout";
|
|
11
|
+
export type { IHistoricalContext, TTrainingAgeBracket, TEnrichedSessionScore, TEnrichedExerciseRecord, TMuscleFatigueEntry, TMuscleFatigueResult, } from "./scoringWorkout";
|
|
12
|
+
export { scaleProPlan, calculateBMI, calculateDayPlanDuration, calculateExerciseDurationSecs, } from "./adoptionEngine/scaleProPlan.util";
|
|
13
13
|
export * from "./exerciseRecord/workoutMath";
|
|
14
14
|
export * from "./exerciseRecord/recordValidator";
|
|
15
15
|
export * from "./metricConversions";
|
|
16
|
+
export * from "./constellation";
|
|
17
|
+
export type * from "./constellation/types";
|
package/dist/utils/index.js
CHANGED
|
@@ -51,3 +51,4 @@ Object.defineProperty(exports, "calculateExerciseDurationSecs", { enumerable: tr
|
|
|
51
51
|
__exportStar(require("./exerciseRecord/workoutMath"), exports);
|
|
52
52
|
__exportStar(require("./exerciseRecord/recordValidator"), exports);
|
|
53
53
|
__exportStar(require("./metricConversions"), exports);
|
|
54
|
+
__exportStar(require("./constellation"), exports);
|
|
@@ -8,33 +8,54 @@
|
|
|
8
8
|
* Quality is about execution: did you finish, stay consistent, push hard
|
|
9
9
|
* enough, and rest appropriately?
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* THREE ALWAYS-ACTIVE COMPONENTS + ONE CONDITIONAL:
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* │ Component │
|
|
15
|
-
*
|
|
16
|
-
* │ Completion │ 20%
|
|
17
|
-
* │ Consistency │ 35%
|
|
18
|
-
* │
|
|
19
|
-
* │
|
|
20
|
-
* │
|
|
21
|
-
* │
|
|
22
|
-
*
|
|
13
|
+
* ┌─────────────────────┬────────────┬──────────────────────────────────────────┐
|
|
14
|
+
* │ Component │ Base weight│ What it measures │
|
|
15
|
+
* ├─────────────────────┼────────────┼──────────────────────────────────────────┤
|
|
16
|
+
* │ Completion │ 20% │ Completed sets / planned sets │
|
|
17
|
+
* │ Consistency │ 35% │ Stable or intentionally progressive sets │
|
|
18
|
+
* │ Effort Adequacy │ 30% │ RPE/RIR proximity to productive failure │
|
|
19
|
+
* │ Rest Discipline │ 15% │ Rest periods within optimal windows │
|
|
20
|
+
* │ (conditional) │ or 0% │ Only scored when rest data exists. │
|
|
21
|
+
* │ │ │ Absent → weight redistributed to others. │
|
|
22
|
+
* └─────────────────────┴────────────┴──────────────────────────────────────────┘
|
|
23
23
|
*
|
|
24
|
-
*
|
|
24
|
+
* EFFORT SCORING (UPDATED):
|
|
25
|
+
* RPE/RIR is now captured on EVERY set via the post-set "How did that feel?"
|
|
26
|
+
* one-tap modal (Warm-up=RPE4 / Challenging=RPE7 / Maximum=RPE9). Because effort
|
|
27
|
+
* is now always present, scoring is GRADED and MONOTONIC: harder sets score
|
|
28
|
+
* strictly higher than easier ones, so Gate 2 can distinguish "going through the
|
|
29
|
+
* motions" from "genuinely training" — which the old flat "in-band = 100" logic
|
|
30
|
+
* could not (RPE 7 and RPE 9 both scored 100).
|
|
25
31
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
32
|
+
* RPE 9 → 100 RPE 7 → ~81 RPE 4 → ~53 RPE 1 → 25 (floor 20)
|
|
33
|
+
* RIR 0 → 100 RIR 3 → 76 RIR 6 → 52 RIR 10 → 20 (floor 20)
|
|
34
|
+
*
|
|
35
|
+
* A genuine warm-up-effort working set now drags the score down instead of
|
|
36
|
+
* floating at a neutral 50 — an "easy on every set" session correctly fails
|
|
37
|
+
* Gate 2. RIR is preferred over RPE when both are present (more objective).
|
|
38
|
+
*
|
|
39
|
+
* REST DISCIPLINE:
|
|
40
|
+
* Strict timing mode is not implemented in the app, so rest data is absent in
|
|
41
|
+
* most sessions. When present, rest contributes meaningfully. When absent, the
|
|
42
|
+
* 15% weight is redistributed proportionally across the three active components
|
|
43
|
+
* via dynamic normalisation — score cannot be biased by uncollected data.
|
|
44
|
+
*
|
|
45
|
+
* EFFORT-WEIGHTED AVERAGING:
|
|
46
|
+
* Effort adequacy weights the FINAL set most heavily. The last working set is
|
|
47
|
+
* where adequacy is truly determined — a strong finish (or a sandbagged one)
|
|
48
|
+
* is the clearest signal of whether the session stimulated adaptation.
|
|
49
|
+
*
|
|
50
|
+
* SINGLE SCORE OUTPUT:
|
|
51
|
+
* Returns one score, not per-goal. Goal-specific progression logic lives in the
|
|
52
|
+
* gate system and quick plan generator upstream.
|
|
31
53
|
*/
|
|
32
|
-
import type { IParsedSet,
|
|
54
|
+
import type { IParsedSet, IQualityBreakdown, IHistoricalContext } from "./types";
|
|
33
55
|
import type { ITimingGuardrails } from "./parseRecords";
|
|
34
|
-
import type { TAiFitnessGoal } from "../../constants/AiExerciseVocabulary";
|
|
35
56
|
/**
|
|
36
57
|
* Raw record shape — we need the original RPE/RIR strings and isDone flag
|
|
37
|
-
* that
|
|
58
|
+
* that are not present in IParsedSet (which contains only completed sets).
|
|
38
59
|
*/
|
|
39
60
|
interface IRawRecord {
|
|
40
61
|
type: string;
|
|
@@ -51,23 +72,25 @@ interface IRawRecord {
|
|
|
51
72
|
distance?: string;
|
|
52
73
|
restDurationSecs?: number;
|
|
53
74
|
}
|
|
75
|
+
export interface IQualityScoreResult {
|
|
76
|
+
/** Overall quality score 0–100. */
|
|
77
|
+
score: number;
|
|
78
|
+
/** Per-component breakdown — each sub-score is 0–100. */
|
|
79
|
+
qualityBreakdown: IQualityBreakdown;
|
|
80
|
+
/**
|
|
81
|
+
* True when rest discipline was included in the weighted score.
|
|
82
|
+
* False when no rest data was logged — weight redistributed to other components.
|
|
83
|
+
* Use this flag to gate the rest discipline bar in the UI.
|
|
84
|
+
*/
|
|
85
|
+
restDisciplineActive: boolean;
|
|
86
|
+
}
|
|
54
87
|
/**
|
|
55
|
-
* Calculate the overall quality score and its breakdown.
|
|
56
|
-
*
|
|
57
|
-
* @param parsedSets Cleaned sets (from parseRecords) — only completed sets
|
|
58
|
-
* @param rawRecords Original TRecord[] — needed for completion count (includes skipped)
|
|
59
|
-
* @param timingGuardrails Exercise's guardrails — for rest period validation
|
|
60
|
-
* @param isStrictTimingModeScoring If false, ignores the rest discipline penalty.
|
|
61
|
-
* @param userContext User context for dynamic weighting (P2-3)
|
|
62
|
-
* @param historicalContext Optional history for progressive overload detection (P2-6)
|
|
63
|
-
* @returns { score: 0–100, breakdown: { completion, consistency, effortAdequacy, restDiscipline } }
|
|
88
|
+
* Calculate the overall quality score and its component breakdown.
|
|
64
89
|
*
|
|
65
|
-
* @
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
90
|
+
* @param parsedSets Cleaned completed sets (output of parseRecords)
|
|
91
|
+
* @param rawRecords Original TRecord[] — needed for completion count and RPE/RIR
|
|
92
|
+
* @param timingGuardrails Exercise DB guardrails — for rest period validation
|
|
93
|
+
* @param historicalContext Optional cross-session context for overload detection
|
|
69
94
|
*/
|
|
70
|
-
export declare function calculateQualityScore(parsedSets: IParsedSet[], rawRecords: IRawRecord[], timingGuardrails: ITimingGuardrails | undefined,
|
|
71
|
-
scoresByGoal: Partial<Record<TAiFitnessGoal, import("./types").IScoreByGoal>>;
|
|
72
|
-
};
|
|
95
|
+
export declare function calculateQualityScore(parsedSets: IParsedSet[], rawRecords: IRawRecord[], timingGuardrails: ITimingGuardrails | undefined, historicalContext?: IHistoricalContext): IQualityScoreResult;
|
|
73
96
|
export {};
|