@dofek/scoring 0.1.0

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/dist/breathwork.d.ts +27 -0
  4. package/dist/breathwork.d.ts.map +1 -0
  5. package/dist/breathwork.js +65 -0
  6. package/dist/breathwork.js.map +1 -0
  7. package/dist/colors.d.ts +74 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +74 -0
  10. package/dist/colors.js.map +1 -0
  11. package/dist/healthspan-years.d.ts +22 -0
  12. package/dist/healthspan-years.d.ts.map +1 -0
  13. package/dist/healthspan-years.js +33 -0
  14. package/dist/healthspan-years.js.map +1 -0
  15. package/dist/loading-policy.d.ts +8 -0
  16. package/dist/loading-policy.d.ts.map +1 -0
  17. package/dist/loading-policy.js +9 -0
  18. package/dist/loading-policy.js.map +1 -0
  19. package/dist/menstrual-cycle.d.ts +24 -0
  20. package/dist/menstrual-cycle.d.ts.map +1 -0
  21. package/dist/menstrual-cycle.js +32 -0
  22. package/dist/menstrual-cycle.js.map +1 -0
  23. package/dist/query-cache.d.ts +2 -0
  24. package/dist/query-cache.d.ts.map +1 -0
  25. package/dist/query-cache.js +2 -0
  26. package/dist/query-cache.js.map +1 -0
  27. package/dist/scoring.d.ts +114 -0
  28. package/dist/scoring.d.ts.map +1 -0
  29. package/dist/scoring.js +325 -0
  30. package/dist/scoring.js.map +1 -0
  31. package/dist/sleep-performance.d.ts +36 -0
  32. package/dist/sleep-performance.d.ts.map +1 -0
  33. package/dist/sleep-performance.js +59 -0
  34. package/dist/sleep-performance.js.map +1 -0
  35. package/dist/strain-target.d.ts +18 -0
  36. package/dist/strain-target.d.ts.map +1 -0
  37. package/dist/strain-target.js +59 -0
  38. package/dist/strain-target.js.map +1 -0
  39. package/dist/tokens.d.ts +99 -0
  40. package/dist/tokens.d.ts.map +1 -0
  41. package/dist/tokens.js +106 -0
  42. package/dist/tokens.js.map +1 -0
  43. package/package.json +83 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,325 @@
1
+ import { statusColors, textColors } from "./colors.js";
2
+ /**
3
+ * Whoop-like 0–21 strain score with display classification.
4
+ *
5
+ * Construct directly with a strain value or use `fromRawLoad()` to convert
6
+ * raw daily training load (duration_min × avg_hr/max_hr) using logarithmic
7
+ * scaling that produces diminishing returns at higher loads.
8
+ */
9
+ export class StrainScore {
10
+ static #SCALE_FACTOR = 2.775;
11
+ static #MAX = 21;
12
+ static #ACUTE_LOAD_DAYS = 7;
13
+ value;
14
+ constructor(value) {
15
+ this.value = value;
16
+ }
17
+ get color() {
18
+ if (this.value > 17)
19
+ return statusColors.danger;
20
+ if (this.value >= 14)
21
+ return statusColors.warning;
22
+ if (this.value >= 10)
23
+ return statusColors.positive;
24
+ return textColors.secondary;
25
+ }
26
+ get label() {
27
+ if (this.value > 17)
28
+ return "All Out";
29
+ if (this.value >= 14)
30
+ return "High";
31
+ if (this.value >= 10)
32
+ return "Moderate";
33
+ return "Light";
34
+ }
35
+ get description() {
36
+ if (this.value > 17)
37
+ return "Maximum effort reached. Prioritize recovery tonight.";
38
+ if (this.value >= 14)
39
+ return "Significant training load. Monitor fatigue closely.";
40
+ if (this.value >= 10)
41
+ return "A productive training day with manageable load.";
42
+ return "Low load day. You have room for more intensity.";
43
+ }
44
+ static fromRawLoad(rawLoad) {
45
+ if (rawLoad <= 0)
46
+ return new StrainScore(0);
47
+ const strain = StrainScore.#SCALE_FACTOR * Math.log(1 + rawLoad);
48
+ const value = Math.round(Math.min(strain, StrainScore.#MAX) * 10) / 10;
49
+ return new StrainScore(value);
50
+ }
51
+ static fromPhysiologicalLoad(physiologicalLoad) {
52
+ return StrainScore.fromRawLoad(physiologicalLoad);
53
+ }
54
+ static fromAcuteLoad(acuteLoad) {
55
+ return StrainScore.fromRawLoad(acuteLoad / StrainScore.#ACUTE_LOAD_DAYS);
56
+ }
57
+ }
58
+ /** Get the color for a recovery/readiness score (0-100) */
59
+ export const SCORE_WARNING_THRESHOLD = 50;
60
+ export const SCORE_RECOVERED_THRESHOLD = 70;
61
+ /** Visual score zones used by charts and cards to keep threshold bands consistent. */
62
+ export const SCORE_ZONES = [
63
+ { min: 0, max: SCORE_WARNING_THRESHOLD, status: "danger" },
64
+ { min: SCORE_WARNING_THRESHOLD, max: SCORE_RECOVERED_THRESHOLD, status: "warning" },
65
+ { min: SCORE_RECOVERED_THRESHOLD, max: 100, status: "positive" },
66
+ ];
67
+ export function scoreColor(score) {
68
+ if (score > SCORE_RECOVERED_THRESHOLD)
69
+ return statusColors.positive;
70
+ if (score >= SCORE_WARNING_THRESHOLD)
71
+ return statusColors.warning;
72
+ return statusColors.danger;
73
+ }
74
+ /** Get a human-readable label for a recovery score (0-100) */
75
+ export function scoreLabel(score) {
76
+ if (score > SCORE_RECOVERED_THRESHOLD)
77
+ return "Recovered";
78
+ if (score >= SCORE_WARNING_THRESHOLD)
79
+ return "Moderate";
80
+ return "Poor";
81
+ }
82
+ /** Get a contextual description for a recovery score explaining what it means */
83
+ export function scoreDescription(score) {
84
+ if (score > SCORE_RECOVERED_THRESHOLD)
85
+ return "You're well recovered and ready for high-intensity training.";
86
+ if (score >= SCORE_WARNING_THRESHOLD)
87
+ return "Your body is at a moderate recovery level. Steady effort is recommended.";
88
+ return "Recovery is low. Focus on rest and keep today's effort light.";
89
+ }
90
+ /** Strain zone classification with display properties. */
91
+ export class StrainZone {
92
+ zone;
93
+ constructor(zone) {
94
+ this.zone = zone;
95
+ }
96
+ get color() {
97
+ if (this.zone === "optimal")
98
+ return statusColors.positive;
99
+ if (this.zone === "overreaching")
100
+ return statusColors.danger;
101
+ if (this.zone === "restoring")
102
+ return statusColors.info;
103
+ return textColors.secondary;
104
+ }
105
+ get label() {
106
+ if (this.zone === "optimal")
107
+ return "Optimal";
108
+ if (this.zone === "overreaching")
109
+ return "Overreaching";
110
+ if (this.zone === "restoring")
111
+ return "Restoring";
112
+ return this.zone;
113
+ }
114
+ }
115
+ /** Workload ratio (ACWR) with display classification. */
116
+ export class WorkloadRatio {
117
+ value;
118
+ constructor(value) {
119
+ this.value = value;
120
+ }
121
+ get color() {
122
+ if (this.value == null)
123
+ return textColors.secondary;
124
+ if (this.value >= 0.8 && this.value <= 1.3)
125
+ return statusColors.positive;
126
+ if (this.value >= 0.5 && this.value <= 1.5)
127
+ return statusColors.warning;
128
+ return statusColors.danger;
129
+ }
130
+ get hint() {
131
+ if (this.value == null)
132
+ return null;
133
+ if (this.value >= 0.8 && this.value <= 1.3)
134
+ return "Optimal training zone";
135
+ if (this.value < 0.8)
136
+ return "Detraining risk - increase load gradually";
137
+ if (this.value <= 1.5)
138
+ return "High load - monitor recovery closely";
139
+ return "Injury risk zone - consider rest";
140
+ }
141
+ }
142
+ /** Aggregate weekly volume rows into a summary with relative fractions */
143
+ export function aggregateWeeklyVolume(rows) {
144
+ const weekMap = new Map();
145
+ for (const row of rows) {
146
+ weekMap.set(row.week, (weekMap.get(row.week) ?? 0) + row.hours);
147
+ }
148
+ const entries = Array.from(weekMap.entries())
149
+ .sort(([a], [b]) => a.localeCompare(b))
150
+ .slice(-4);
151
+ const maxHours = Math.max(...entries.map(([, h]) => h), 1);
152
+ return entries.map(([week, hours]) => ({
153
+ week,
154
+ hours,
155
+ fraction: hours / maxHours,
156
+ }));
157
+ }
158
+ /** Determine trend direction from two values */
159
+ export function trendDirection(current, previous) {
160
+ if (current > previous)
161
+ return "up";
162
+ if (current < previous)
163
+ return "down";
164
+ return "stable";
165
+ }
166
+ /** Stress score (0-3 Whoop-like scale) with display classification. */
167
+ export class StressScore {
168
+ value;
169
+ constructor(value) {
170
+ this.value = value;
171
+ }
172
+ get color() {
173
+ if (this.value <= 0.5)
174
+ return statusColors.positive;
175
+ if (this.value <= 1.5)
176
+ return statusColors.warning;
177
+ if (this.value <= 2.5)
178
+ return statusColors.elevated;
179
+ return statusColors.danger;
180
+ }
181
+ get label() {
182
+ if (this.value <= 0.5)
183
+ return "Low";
184
+ if (this.value <= 1.5)
185
+ return "Moderate";
186
+ if (this.value <= 2.5)
187
+ return "High";
188
+ return "Very High";
189
+ }
190
+ }
191
+ /** Get the color for a trend direction */
192
+ export function trendColor(trend) {
193
+ if (trend === "improving")
194
+ return statusColors.positive;
195
+ if (trend === "worsening" || trend === "declining")
196
+ return statusColors.danger;
197
+ return textColors.neutral;
198
+ }
199
+ /** Get the color for a categorical readiness level (high/moderate/low) */
200
+ export function readinessLevelColor(level) {
201
+ if (level === "high")
202
+ return statusColors.positive;
203
+ if (level === "moderate")
204
+ return statusColors.warning;
205
+ if (level === "low")
206
+ return statusColors.danger;
207
+ return textColors.neutral;
208
+ }
209
+ /** Get the color for a health metric status (excellent/good/fair/poor) */
210
+ export function healthStatusColor(status) {
211
+ if (status === "excellent")
212
+ return statusColors.positive;
213
+ if (status === "good")
214
+ return statusColors.info;
215
+ if (status === "fair")
216
+ return statusColors.warning;
217
+ return statusColors.danger;
218
+ }
219
+ /** Get the color for a ramp rate value (always uses absolute value) */
220
+ export function rampRateColor(rate) {
221
+ const absRate = Math.abs(rate);
222
+ if (absRate < 5)
223
+ return statusColors.positive;
224
+ if (absRate <= 7)
225
+ return statusColors.warning;
226
+ return statusColors.danger;
227
+ }
228
+ /**
229
+ * Map a z-score to a 0-100 recovery score using an asymmetric sigmoid.
230
+ * Tuned to match Whoop's recovery scoring:
231
+ * z=0 (at baseline mean) → 62 (average day feels "recovered")
232
+ * z=+1 → ~80, z=-1 → ~40
233
+ * z=+2 → ~93, z=-2 → ~18
234
+ * Uses separate scales for positive/negative z to handle the asymmetric center.
235
+ */
236
+ export function zScoreToRecoveryScore(zScore) {
237
+ const center = 62;
238
+ const sigmoidSteepness = 1.1;
239
+ const sigmoid = 1 / (1 + Math.exp(-zScore * sigmoidSteepness));
240
+ const scaleUp = 100 - center; // 38: maps sigmoid 0.5→1.0 to 62→100
241
+ const scaleDown = center; // 62: maps sigmoid 0.0→0.5 to 0→62
242
+ const score = sigmoid >= 0.5
243
+ ? center + scaleUp * ((sigmoid - 0.5) / 0.5)
244
+ : center - scaleDown * ((0.5 - sigmoid) / 0.5);
245
+ return Math.max(0, Math.min(100, Math.round(score)));
246
+ }
247
+ /** Get the color for a sleep performance tier */
248
+ export function sleepTierColor(tier) {
249
+ if (tier === "Excellent")
250
+ return statusColors.positive;
251
+ if (tier === "Good")
252
+ return statusColors.info;
253
+ if (tier === "Fair")
254
+ return statusColors.warning;
255
+ return statusColors.danger;
256
+ }
257
+ /** Get a contextual description for a sleep performance tier */
258
+ export function sleepTierDescription(tier) {
259
+ if (tier === "Excellent")
260
+ return "Exceptional sleep that fully supports recovery.";
261
+ if (tier === "Good")
262
+ return "Solid sleep. You got most of what your body needed.";
263
+ if (tier === "Fair")
264
+ return "Below your sleep need. Recovery may be impacted.";
265
+ return "Significantly short on sleep. Prioritize rest tonight.";
266
+ }
267
+ /** Get the color for sleep debt in minutes */
268
+ export function sleepDebtColor(minutes) {
269
+ if (minutes <= 0)
270
+ return statusColors.positive;
271
+ if (minutes < 120)
272
+ return statusColors.warning;
273
+ return statusColors.danger;
274
+ }
275
+ /** Get the color for a sleep performance percentage (actual vs target) */
276
+ export function sleepPerformanceColor(percentage) {
277
+ if (percentage >= 95)
278
+ return statusColors.positive;
279
+ if (percentage >= 85)
280
+ return statusColors.warning;
281
+ return statusColors.danger;
282
+ }
283
+ /** Form zone boundaries (intervals.icu defaults) */
284
+ export const FORM_ZONE_TRANSITION = 25;
285
+ export const FORM_ZONE_FRESH = 5;
286
+ export const FORM_ZONE_GREY = -10;
287
+ export const FORM_ZONE_OPTIMAL = -30;
288
+ /** Colors for form (training stress balance) zones */
289
+ export const FORM_ZONE_COLORS = {
290
+ transition: "#60a5fa",
291
+ fresh: statusColors.positive,
292
+ grey: "#a1a1aa",
293
+ optimal: statusColors.positive,
294
+ highRisk: statusColors.danger,
295
+ };
296
+ /** Form zone (training stress balance) classification with display properties. */
297
+ export class FormZone {
298
+ value;
299
+ constructor(value) {
300
+ this.value = value;
301
+ }
302
+ get color() {
303
+ if (this.value > FORM_ZONE_TRANSITION)
304
+ return FORM_ZONE_COLORS.transition;
305
+ if (this.value > FORM_ZONE_FRESH)
306
+ return FORM_ZONE_COLORS.fresh;
307
+ if (this.value > FORM_ZONE_GREY)
308
+ return FORM_ZONE_COLORS.grey;
309
+ if (this.value > FORM_ZONE_OPTIMAL)
310
+ return FORM_ZONE_COLORS.optimal;
311
+ return FORM_ZONE_COLORS.highRisk;
312
+ }
313
+ get label() {
314
+ if (this.value > FORM_ZONE_TRANSITION)
315
+ return "Transition";
316
+ if (this.value > FORM_ZONE_FRESH)
317
+ return "Fresh";
318
+ if (this.value > FORM_ZONE_GREY)
319
+ return "Grey Zone";
320
+ if (this.value > FORM_ZONE_OPTIMAL)
321
+ return "Optimal";
322
+ return "High Risk";
323
+ }
324
+ }
325
+ //# sourceMappingURL=scoring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ export type SleepTier = "Excellent" | "Good" | "Fair" | "Poor";
2
+ export interface SleepPerformanceResult {
3
+ /** Performance score 0-100 */
4
+ score: number;
5
+ /** Tier label */
6
+ tier: SleepTier;
7
+ /** Component scores used to calculate performance */
8
+ components?: SleepPerformanceComponents;
9
+ }
10
+ export interface SleepPerformanceComponents {
11
+ hours: number;
12
+ efficiency: number;
13
+ consistency: number | null;
14
+ lowStress: number | null;
15
+ }
16
+ export interface SleepPerformanceComponentInput {
17
+ consistency?: number | null;
18
+ lowStress?: number | null;
19
+ }
20
+ /**
21
+ * Compute sleep performance from actual vs needed sleep and efficiency.
22
+ * When consistency and stress components are supplied, score is the equal
23
+ * average of all available provider-agnostic component scores.
24
+ * Otherwise, preserves the original 70% sufficiency + 30% efficiency blend.
25
+ * Tiers: Excellent (90+), Good (70-89), Fair (50-69), Poor (<50).
26
+ */
27
+ export declare function computeSleepPerformance(actualMinutes: number, neededMinutes: number, efficiency: number, componentInput?: SleepPerformanceComponentInput): SleepPerformanceResult;
28
+ /**
29
+ * Compute recommended bedtime given a wake time and sleep need.
30
+ * @param wakeTime - Wake time in "HH:MM" format
31
+ * @param sleepNeedMinutes - Total sleep need in minutes
32
+ * @param fallAsleepMinutes - Estimated time to fall asleep (default 15)
33
+ * @returns Recommended bedtime in "HH:MM" format
34
+ */
35
+ export declare function computeRecommendedBedtime(wakeTime: string, sleepNeedMinutes: number, fallAsleepMinutes?: number): string;
36
+ //# sourceMappingURL=sleep-performance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-performance.d.ts","sourceRoot":"","sources":["../src/sleep-performance.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,qDAAqD;IACrD,UAAU,CAAC,EAAE,0BAA0B,CAAC;CACzC;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,8BAA8B;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAaD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,8BAA8B,GAC9C,sBAAsB,CA6BxB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,SAAK,GACrB,MAAM,CAWR"}
@@ -0,0 +1,59 @@
1
+ function clampPercent(value) {
2
+ return Math.min(Math.max(value, 0), 100);
3
+ }
4
+ function tierFromScore(score) {
5
+ if (score >= 90)
6
+ return "Excellent";
7
+ if (score >= 70)
8
+ return "Good";
9
+ if (score >= 50)
10
+ return "Fair";
11
+ return "Poor";
12
+ }
13
+ /**
14
+ * Compute sleep performance from actual vs needed sleep and efficiency.
15
+ * When consistency and stress components are supplied, score is the equal
16
+ * average of all available provider-agnostic component scores.
17
+ * Otherwise, preserves the original 70% sufficiency + 30% efficiency blend.
18
+ * Tiers: Excellent (90+), Good (70-89), Fair (50-69), Poor (<50).
19
+ */
20
+ export function computeSleepPerformance(actualMinutes, neededMinutes, efficiency, componentInput) {
21
+ const rawSufficiency = neededMinutes > 0 ? Math.min(actualMinutes / neededMinutes, 1) * 100 : 100;
22
+ const hours = Math.round(rawSufficiency);
23
+ const normalizedEfficiency = Math.round(clampPercent(efficiency));
24
+ const consistency = componentInput?.consistency != null
25
+ ? Math.round(clampPercent(componentInput.consistency))
26
+ : null;
27
+ const lowStress = componentInput?.lowStress != null ? Math.round(clampPercent(componentInput.lowStress)) : null;
28
+ const components = {
29
+ hours,
30
+ efficiency: normalizedEfficiency,
31
+ consistency,
32
+ lowStress,
33
+ };
34
+ const componentScores = [hours, normalizedEfficiency, consistency, lowStress].filter((value) => value != null);
35
+ const hasExtraComponents = consistency != null || lowStress != null;
36
+ const score = !hasExtraComponents
37
+ ? Math.round(rawSufficiency * 0.7 + normalizedEfficiency * 0.3)
38
+ : Math.round(componentScores.reduce((sum, value) => sum + value, 0) / componentScores.length);
39
+ const clampedScore = Math.min(Math.max(score, 0), 100);
40
+ return { score: clampedScore, tier: tierFromScore(clampedScore), components };
41
+ }
42
+ /**
43
+ * Compute recommended bedtime given a wake time and sleep need.
44
+ * @param wakeTime - Wake time in "HH:MM" format
45
+ * @param sleepNeedMinutes - Total sleep need in minutes
46
+ * @param fallAsleepMinutes - Estimated time to fall asleep (default 15)
47
+ * @returns Recommended bedtime in "HH:MM" format
48
+ */
49
+ export function computeRecommendedBedtime(wakeTime, sleepNeedMinutes, fallAsleepMinutes = 15) {
50
+ const [wakeHours, wakeMinutes] = wakeTime.split(":").map(Number);
51
+ const wakeTotalMinutes = (wakeHours ?? 0) * 60 + (wakeMinutes ?? 0);
52
+ const bedtimeTotalMinutes = wakeTotalMinutes - sleepNeedMinutes - fallAsleepMinutes;
53
+ // Normalize to 0-1440 range (wrap around midnight)
54
+ const normalized = ((bedtimeTotalMinutes % 1440) + 1440) % 1440;
55
+ const hours = Math.floor(normalized / 60);
56
+ const minutes = normalized % 60;
57
+ return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
58
+ }
59
+ //# sourceMappingURL=sleep-performance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-performance.js","sourceRoot":"","sources":["../src/sleep-performance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export interface StrainTargetResult {
2
+ /** Recommended strain target for the day (0-21 scale) */
3
+ targetStrain: number;
4
+ /** Zone label */
5
+ zone: "Push" | "Maintain" | "Recovery";
6
+ /** Human-readable explanation */
7
+ explanation: string;
8
+ }
9
+ /**
10
+ * Compute a daily strain target based on readiness, chronic load, and acute load.
11
+ *
12
+ * - High readiness (70+): target 14-18, Push zone
13
+ * - Moderate readiness (50-69): target 10-14, Maintain zone
14
+ * - Low readiness (<50): target 4-10, Recovery zone
15
+ * - If ACWR (acute/chronic) > 1.3, caps the target to prevent injury
16
+ */
17
+ export declare function computeStrainTarget(readinessScore: number, chronicLoad: number, acuteLoad: number): StrainTargetResult;
18
+ //# sourceMappingURL=strain-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strain-target.d.ts","sourceRoot":"","sources":["../src/strain-target.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB;IACjB,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;IACvC,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAoDpB"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Compute a daily strain target based on readiness, chronic load, and acute load.
3
+ *
4
+ * - High readiness (70+): target 14-18, Push zone
5
+ * - Moderate readiness (50-69): target 10-14, Maintain zone
6
+ * - Low readiness (<50): target 4-10, Recovery zone
7
+ * - If ACWR (acute/chronic) > 1.3, caps the target to prevent injury
8
+ */
9
+ export function computeStrainTarget(readinessScore, chronicLoad, acuteLoad) {
10
+ let zone;
11
+ let minStrain;
12
+ let maxStrain;
13
+ if (readinessScore >= 70) {
14
+ zone = "Push";
15
+ minStrain = 14;
16
+ maxStrain = 18;
17
+ }
18
+ else if (readinessScore >= 50) {
19
+ zone = "Maintain";
20
+ minStrain = 10;
21
+ maxStrain = 14;
22
+ }
23
+ else {
24
+ zone = "Recovery";
25
+ minStrain = 4;
26
+ maxStrain = 10;
27
+ }
28
+ // Interpolate within the zone based on readiness
29
+ const zoneReadinessMin = zone === "Push" ? 70 : zone === "Maintain" ? 50 : 0;
30
+ const zoneReadinessMax = zone === "Push" ? 100 : zone === "Maintain" ? 69 : 49;
31
+ const fraction = Math.min(1, Math.max(0, (readinessScore - zoneReadinessMin) / (zoneReadinessMax - zoneReadinessMin)));
32
+ let targetStrain = minStrain + fraction * (maxStrain - minStrain);
33
+ // Cap if ACWR is too high (injury risk)
34
+ let explanation;
35
+ const acwr = chronicLoad > 0 ? acuteLoad / chronicLoad : 0;
36
+ if (acwr > 1.3 && chronicLoad > 0) {
37
+ const cappedTarget = Math.min(targetStrain, 12);
38
+ if (cappedTarget < targetStrain) {
39
+ targetStrain = cappedTarget;
40
+ zone = targetStrain < 10 ? "Recovery" : "Maintain";
41
+ }
42
+ explanation = `Your acute:chronic workload ratio (${acwr.toFixed(2)}) is elevated. Strain target capped to reduce injury risk.`;
43
+ }
44
+ else if (zone === "Push") {
45
+ explanation = `Recovery is strong (${readinessScore}). Push for a high-strain day to build fitness.`;
46
+ }
47
+ else if (zone === "Maintain") {
48
+ explanation = `Moderate recovery (${readinessScore}). Aim for a steady training day.`;
49
+ }
50
+ else {
51
+ explanation = `Recovery is low (${readinessScore}). Keep it light and focus on restoration.`;
52
+ }
53
+ return {
54
+ targetStrain: Math.round(targetStrain * 10) / 10,
55
+ zone,
56
+ explanation,
57
+ };
58
+ }
59
+ //# sourceMappingURL=strain-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strain-target.js","sourceRoot":"","sources":["../src/strain-target.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,cAAsB,EACtB,WAAmB,EACnB,SAAiB;IAEjB,IAAI,IAAgC,CAAC;IACrC,IAAI,SAAiB,CAAC;IACtB,IAAI,SAAiB,CAAC;IAEtB,IAAI,cAAc,IAAI,EAAE,EAAE,CAAC;QACzB,IAAI,GAAG,MAAM,CAAC;QACd,SAAS,GAAG,EAAE,CAAC;QACf,SAAS,GAAG,EAAE,CAAC;IACjB,CAAC;SAAM,IAAI,cAAc,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,GAAG,UAAU,CAAC;QAClB,SAAS,GAAG,EAAE,CAAC;QACf,SAAS,GAAG,EAAE,CAAC;IACjB,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,UAAU,CAAC;QAClB,SAAS,GAAG,CAAC,CAAC;QACd,SAAS,GAAG,EAAE,CAAC;IACjB,CAAC;IAED,iDAAiD;IACjD,MAAM,gBAAgB,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,gBAAgB,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CACvB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,CAAC,CACzF,CAAC;IACF,IAAI,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAElE,wCAAwC;IACxC,IAAI,WAAmB,CAAC;IACxB,MAAM,IAAI,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAE3D,IAAI,IAAI,GAAG,GAAG,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAChD,IAAI,YAAY,GAAG,YAAY,EAAE,CAAC;YAChC,YAAY,GAAG,YAAY,CAAC;YAC5B,IAAI,GAAG,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;QACrD,CAAC;QACD,WAAW,GAAG,sCAAsC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,4DAA4D,CAAC;IAClI,CAAC;SAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,WAAW,GAAG,uBAAuB,cAAc,iDAAiD,CAAC;IACvG,CAAC;SAAM,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,WAAW,GAAG,sBAAsB,cAAc,mCAAmC,CAAC;IACxF,CAAC;SAAM,CAAC;QACN,WAAW,GAAG,oBAAoB,cAAc,4CAA4C,CAAC;IAC/F,CAAC;IAED,OAAO;QACL,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,GAAG,EAAE;QAChD,IAAI;QACJ,WAAW;KACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Design tokens shared across web and iOS.
3
+ *
4
+ * Platform-agnostic values — no React, no HTML, no React Native.
5
+ * Web uses these via Tailwind @theme + CSS. iOS uses them in StyleSheets.
6
+ *
7
+ * Colors live in ./colors.ts (separate module for historical reasons).
8
+ */
9
+ export declare const fontFamily: {
10
+ /** Body text — Inter on web, system default on iOS until fonts load */
11
+ readonly body: "Inter";
12
+ /** Monospace for numbers, metrics, code — DM Mono on web, Menlo on iOS */
13
+ readonly mono: "DM Mono";
14
+ };
15
+ export declare const fontSize: {
16
+ readonly xs: 11;
17
+ readonly sm: 13;
18
+ readonly base: 14;
19
+ readonly lg: 16;
20
+ readonly xl: 20;
21
+ readonly "2xl": 24;
22
+ readonly "3xl": 30;
23
+ readonly "4xl": 36;
24
+ readonly "5xl": 48;
25
+ };
26
+ export declare const fontWeight: {
27
+ readonly normal: "400";
28
+ readonly medium: "500";
29
+ readonly semibold: "600";
30
+ readonly bold: "700";
31
+ readonly extrabold: "800";
32
+ };
33
+ export declare const duration: {
34
+ /** Micro-interactions: hover, press, toggle */
35
+ readonly fast: 150;
36
+ /** Standard transitions: fade, slide */
37
+ readonly normal: 300;
38
+ /** Content reveals: card appear, section expand */
39
+ readonly slow: 500;
40
+ /** Count-up numbers */
41
+ readonly countUp: 800;
42
+ /** Ring draw-in, chart bars grow */
43
+ readonly chart: 1200;
44
+ /** Logo heartbeat cycle */
45
+ readonly heartbeat: 3000;
46
+ };
47
+ export declare const easing: {
48
+ /** Standard ease-out for most transitions */
49
+ readonly out: "cubic-bezier(0.16, 1, 0.3, 1)";
50
+ /** Ease-in-out for looping animations */
51
+ readonly inOut: "cubic-bezier(0.4, 0, 0.2, 1)";
52
+ /** ECharts cubic out */
53
+ readonly echartsOut: "cubicOut";
54
+ };
55
+ export declare const spacing: {
56
+ /** Tight: between related items (icon + label) */
57
+ readonly xs: 4;
58
+ /** Default gap between cards in a row */
59
+ readonly sm: 8;
60
+ /** Section padding, card internal spacing */
61
+ readonly md: 16;
62
+ /** Between dashboard sections */
63
+ readonly lg: 24;
64
+ /** Page margins */
65
+ readonly xl: 32;
66
+ };
67
+ export declare const radius: {
68
+ /** Pill shapes, status dots */
69
+ readonly full: 9999;
70
+ /** Cards, major containers */
71
+ readonly xl: 16;
72
+ /** Secondary containers, modals */
73
+ readonly lg: 12;
74
+ /** Buttons, inputs */
75
+ readonly md: 8;
76
+ /** Small elements, badges */
77
+ readonly sm: 4;
78
+ };
79
+ export declare const chart: {
80
+ /** Default chart height in px */
81
+ readonly defaultHeight: 250;
82
+ /** Stagger delay between bar animations (ms) */
83
+ readonly barStaggerDelay: 50;
84
+ /** Grid padding defaults (px) */
85
+ readonly grid: {
86
+ readonly top: 30;
87
+ readonly right: 12;
88
+ readonly bottom: 30;
89
+ readonly left: 40;
90
+ };
91
+ /** Grid padding when chart has dual y-axes */
92
+ readonly gridDualAxis: {
93
+ readonly top: 30;
94
+ readonly right: 60;
95
+ readonly bottom: 30;
96
+ readonly left: 50;
97
+ };
98
+ };
99
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,eAAO,MAAM,UAAU;IACrB,uEAAuE;;IAEvE,0EAA0E;;CAElE,CAAC;AAEX,eAAO,MAAM,QAAQ;;;;;;;;;;CAUX,CAAC;AAEX,eAAO,MAAM,UAAU;;;;;;CAMb,CAAC;AAIX,eAAO,MAAM,QAAQ;IACnB,+CAA+C;;IAE/C,wCAAwC;;IAExC,mDAAmD;;IAEnD,uBAAuB;;IAEvB,oCAAoC;;IAEpC,2BAA2B;;CAEnB,CAAC;AAEX,eAAO,MAAM,MAAM;IACjB,6CAA6C;;IAE7C,yCAAyC;;IAEzC,wBAAwB;;CAEhB,CAAC;AAIX,eAAO,MAAM,OAAO;IAClB,kDAAkD;;IAElD,yCAAyC;;IAEzC,6CAA6C;;IAE7C,iCAAiC;;IAEjC,mBAAmB;;CAEX,CAAC;AAIX,eAAO,MAAM,MAAM;IACjB,+BAA+B;;IAE/B,8BAA8B;;IAE9B,mCAAmC;;IAEnC,sBAAsB;;IAEtB,6BAA6B;;CAErB,CAAC;AAMX,eAAO,MAAM,KAAK;IAChB,iCAAiC;;IAEjC,gDAAgD;;IAEhD,iCAAiC;;;;;;;IAOjC,8CAA8C;;;;;;;CAOtC,CAAC"}