@dofek/training 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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/dist/activity-icons.d.ts +10 -0
  4. package/dist/activity-icons.d.ts.map +1 -0
  5. package/dist/activity-icons.js +102 -0
  6. package/dist/activity-icons.js.map +1 -0
  7. package/dist/climbing-grades.d.ts +8 -0
  8. package/dist/climbing-grades.d.ts.map +1 -0
  9. package/dist/climbing-grades.js +80 -0
  10. package/dist/climbing-grades.js.map +1 -0
  11. package/dist/derived-cardio.d.ts +17 -0
  12. package/dist/derived-cardio.d.ts.map +1 -0
  13. package/dist/derived-cardio.js +84 -0
  14. package/dist/derived-cardio.js.map +1 -0
  15. package/dist/endurance-types.d.ts +21 -0
  16. package/dist/endurance-types.d.ts.map +1 -0
  17. package/dist/endurance-types.js +39 -0
  18. package/dist/endurance-types.js.map +1 -0
  19. package/dist/grade-adjusted-pace.d.ts +34 -0
  20. package/dist/grade-adjusted-pace.d.ts.map +1 -0
  21. package/dist/grade-adjusted-pace.js +41 -0
  22. package/dist/grade-adjusted-pace.js.map +1 -0
  23. package/dist/muscle-groups.d.ts +35 -0
  24. package/dist/muscle-groups.d.ts.map +1 -0
  25. package/dist/muscle-groups.js +135 -0
  26. package/dist/muscle-groups.js.map +1 -0
  27. package/dist/pmc.d.ts +77 -0
  28. package/dist/pmc.d.ts.map +1 -0
  29. package/dist/pmc.js +83 -0
  30. package/dist/pmc.js.map +1 -0
  31. package/dist/power-analysis.d.ts +82 -0
  32. package/dist/power-analysis.d.ts.map +1 -0
  33. package/dist/power-analysis.js +162 -0
  34. package/dist/power-analysis.js.map +1 -0
  35. package/dist/training-distribution.d.ts +43 -0
  36. package/dist/training-distribution.d.ts.map +1 -0
  37. package/dist/training-distribution.js +68 -0
  38. package/dist/training-distribution.js.map +1 -0
  39. package/dist/training-load.d.ts +66 -0
  40. package/dist/training-load.d.ts.map +1 -0
  41. package/dist/training-load.js +89 -0
  42. package/dist/training-load.js.map +1 -0
  43. package/dist/training.d.ts +61 -0
  44. package/dist/training.d.ts.map +1 -0
  45. package/dist/training.js +564 -0
  46. package/dist/training.js.map +1 -0
  47. package/dist/workout-recommendation.d.ts +86 -0
  48. package/dist/workout-recommendation.d.ts.map +1 -0
  49. package/dist/workout-recommendation.js +338 -0
  50. package/dist/workout-recommendation.js.map +1 -0
  51. package/package.json +52 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Asher Cohen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # @dofek/training
2
+
3
+ Framework-neutral TypeScript calculations for training load, cycling power,
4
+ performance modeling, activity normalization, and workout recommendations.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @dofek/training
10
+ ```
11
+
12
+ Requires Node.js 22.14 or newer.
13
+
14
+ ## Usage
15
+
16
+ There is no root export; import the module subpath you need.
17
+
18
+ ```ts
19
+ import {
20
+ computeHrTss,
21
+ computeTrimp,
22
+ } from "@dofek/training/pmc";
23
+
24
+ const durationMinutes = 60;
25
+ const averageHeartRate = 150;
26
+ const maximumHeartRate = 190;
27
+ const restingHeartRate = 55;
28
+
29
+ console.log({
30
+ trimp: computeTrimp(
31
+ durationMinutes,
32
+ averageHeartRate,
33
+ maximumHeartRate,
34
+ restingHeartRate,
35
+ ),
36
+ hrTss: computeHrTss(
37
+ durationMinutes,
38
+ averageHeartRate,
39
+ maximumHeartRate,
40
+ restingHeartRate,
41
+ ),
42
+ });
43
+ ```
44
+
45
+ ## Public API
46
+
47
+ Every public module is imported as `@dofek/training/<subpath>`.
48
+
49
+ | Subpath | Purpose |
50
+ | --- | --- |
51
+ | `activity-icons` | Normalize activity names into framework-neutral icon categories |
52
+ | `climbing-grades` | Parse V-scale and Yosemite Decimal System climbing grades |
53
+ | `derived-cardio` | Cycling and submaximal walking/running VO2 max estimates and validation |
54
+ | `endurance-types` | Endurance and indoor-cycling type guards |
55
+ | `grade-adjusted-pace` | Grade cost factor and adjusted running pace |
56
+ | `muscle-groups` | Muscle-group expansion, totals, intensities, and display colors |
57
+ | `pmc` | TRIMP, heart-rate/power stress, FTP estimate, learned stress model, and performance-management calculations |
58
+ | `power-analysis` | Power curves, normalized power, critical-power fit, regression, and duration constants |
59
+ | `training` | Canonical activity types, provider normalization, and weekly-volume grouping |
60
+ | `training-distribution` | [Karvonen](https://pubmed.ncbi.nlm.nih.gov/13470504/) and [Treff](https://www.frontiersin.org/journals/physiology/articles/10.3389/fphys.2019.00707/full) weekly intensity-distribution summaries |
61
+ | `training-load` | `TrainingStressCalculator` stateful facade |
62
+ | `workout-recommendation` | Workout recommendation types and calculation |
63
+
64
+ ## Model details
65
+
66
+ ### Workout recommendations
67
+
68
+ `recommendNextWorkout` combines recent activity, readiness, workload ratio,
69
+ muscle-group freshness, and intensity distribution. Its low/moderate/high
70
+ distribution model is informed by the three-zone observations in
71
+ [Seiler and Kjerland (2006)](https://pubmed.ncbi.nlm.nih.gov/16430681/);
72
+ the exact readiness gates, recovery windows, and recommendation rules are Dofek
73
+ product heuristics.
74
+
75
+ ### Training stress and performance
76
+
77
+ - `computeTrimp` applies the package's exponential heart-rate-load formula.
78
+ - `computeHrTss` normalizes that load to the package's one-hour threshold
79
+ reference.
80
+ - Power stress uses duration, FTP, and normalized power.
81
+ - Stress-model helpers fit a personal linear relationship from paired heart-rate
82
+ and power observations.
83
+ - FTP estimation uses 95% of the best 20-minute average power, matching the
84
+ [TrainingPeaks threshold procedure](https://help.trainingpeaks.com/hc/en-us/articles/204071934-How-to-Calculate-Threshold-Values-for-Power-Heart-Rate-or-Pace).
85
+
86
+ ### Power and curve analysis
87
+
88
+ - Power curves use prefix sums to find the best average power across the
89
+ package's standard durations.
90
+ - Normalized power uses 30-second rolling averages and fourth-power weighting,
91
+ following the
92
+ [TrainingPeaks calculation](https://help.trainingpeaks.com/hc/en-us/articles/204071804-Normalized-Power).
93
+ - Critical power fits the two-parameter relationship introduced by
94
+ [Monod and Scherrer (1965)](https://doi.org/10.1080/00140136508930810)
95
+ using efforts from 120 through 600 seconds.
96
+
97
+ ### Terrain adjustment
98
+
99
+ Grade-adjusted pace uses a bounded approximation derived from the slope-cost
100
+ work of
101
+ [Minetti et al. (2002)](https://pubmed.ncbi.nlm.nih.gov/12183501/).
102
+ It is an estimate, not a substitute for measured effort.
103
+
104
+ ### Activity normalization
105
+
106
+ Canonical activity types map provider-specific labels into one vocabulary.
107
+ Endurance filters and weekly-volume grouping keep downstream analysis
108
+ provider-neutral.
109
+
110
+ ## Errors and configuration
111
+
112
+ The package performs no network requests and requires no authentication or
113
+ environment variables. Several calculations return zero or `null` when data is
114
+ invalid or insufficient; callers should validate data at external boundaries
115
+ and handle the nullable return types declared by each function. Training
116
+ calculations are estimates and are not medical advice.
117
+
118
+ ## License and contributing
119
+
120
+ [MIT](./LICENSE). Source is in the
121
+ [Dofek repository](https://github.com/Asherlc/dofek/tree/main/packages/training).
122
+ Please [open an issue](https://github.com/Asherlc/dofek/issues) for bugs or API
123
+ proposals, or [submit a pull request](https://github.com/Asherlc/dofek/pulls).
@@ -0,0 +1,10 @@
1
+ export type ActivityIconCategory = "cycling" | "running" | "swimming" | "walking" | "strength" | "yoga" | "hiit" | "elliptical" | "rowing" | "winter" | "climbing" | "water" | "team" | "dance" | "other";
2
+ export interface ActivityIconInfo {
3
+ category: ActivityIconCategory;
4
+ emoji: string;
5
+ gradientFrom: string;
6
+ gradientTo: string;
7
+ }
8
+ export declare function resolveActivityIconCategory(activityType: string): ActivityIconCategory;
9
+ export declare function getActivityIconInfo(activityType: string): ActivityIconInfo;
10
+ //# sourceMappingURL=activity-icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-icons.d.ts","sourceRoot":"","sources":["../src/activity-icons.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,SAAS,GACT,UAAU,GACV,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,MAAM,GACN,OAAO,GACP,OAAO,CAAC;AAEZ,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AA2BD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CA4EtF;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,gBAAgB,CAM1E"}
@@ -0,0 +1,102 @@
1
+ import { isCyclingActivity } from "./training.js";
2
+ const CATEGORY_INFO = {
3
+ cycling: { emoji: "\u{1F6B4}", gradientFrom: "#2563eb", gradientTo: "#1d4ed8" },
4
+ running: { emoji: "\u{1F3C3}", gradientFrom: "#ea580c", gradientTo: "#c2410c" },
5
+ swimming: { emoji: "\u{1F3CA}", gradientFrom: "#0891b2", gradientTo: "#0e7490" },
6
+ walking: { emoji: "\u{1F6B6}", gradientFrom: "#16a34a", gradientTo: "#15803d" },
7
+ strength: { emoji: "\u{1F3CB}\u{FE0F}", gradientFrom: "#7c3aed", gradientTo: "#6d28d9" },
8
+ yoga: { emoji: "\u{1F9D8}", gradientFrom: "#db2777", gradientTo: "#be185d" },
9
+ hiit: { emoji: "\u{1F4A5}", gradientFrom: "#dc2626", gradientTo: "#b91c1c" },
10
+ elliptical: {
11
+ emoji: "\u{1F3C3}\u{200D}\u{2642}\u{FE0F}",
12
+ gradientFrom: "#059669",
13
+ gradientTo: "#047857",
14
+ },
15
+ rowing: { emoji: "\u{1F6A3}", gradientFrom: "#0284c7", gradientTo: "#0369a1" },
16
+ winter: { emoji: "\u{26F7}\u{FE0F}", gradientFrom: "#6366f1", gradientTo: "#4f46e5" },
17
+ climbing: { emoji: "\u{1F9D7}", gradientFrom: "#78716c", gradientTo: "#57534e" },
18
+ water: { emoji: "\u{1F3C4}", gradientFrom: "#06b6d4", gradientTo: "#0891b2" },
19
+ team: { emoji: "\u{26BD}", gradientFrom: "#65a30d", gradientTo: "#4d7c0f" },
20
+ dance: { emoji: "\u{1F483}", gradientFrom: "#d946ef", gradientTo: "#a21caf" },
21
+ other: { emoji: "\u{26A1}", gradientFrom: "#6b7280", gradientTo: "#4b5563" },
22
+ };
23
+ export function resolveActivityIconCategory(activityType) {
24
+ const lower = activityType.trim().toLowerCase();
25
+ if (isCyclingActivity(lower))
26
+ return "cycling";
27
+ if (lower.includes("run") || lower.includes("trail"))
28
+ return "running";
29
+ if (lower.includes("swim") || lower.includes("triathlon"))
30
+ return "swimming";
31
+ if (lower.includes("walk") || lower.includes("hiking") || lower.includes("stair"))
32
+ return "walking";
33
+ if (lower.includes("strength") ||
34
+ lower.includes("weight") ||
35
+ lower.includes("functional_fitness") ||
36
+ lower.includes("bootcamp") ||
37
+ lower.includes("core") ||
38
+ lower.includes("circuit") ||
39
+ lower.includes("box") ||
40
+ lower.includes("martial")) {
41
+ return "strength";
42
+ }
43
+ if (lower.includes("yoga") ||
44
+ lower.includes("pilates") ||
45
+ lower.includes("meditation") ||
46
+ lower.includes("mind") ||
47
+ lower.includes("stretch")) {
48
+ return "yoga";
49
+ }
50
+ if (lower.includes("hiit") || lower.includes("cross_training") || lower.includes("cardio")) {
51
+ return "hiit";
52
+ }
53
+ if (lower.includes("elliptical"))
54
+ return "elliptical";
55
+ if (lower.includes("rowing"))
56
+ return "rowing";
57
+ if (lower.includes("ski") ||
58
+ lower.includes("skating") ||
59
+ lower.includes("snow") ||
60
+ lower.includes("curling") ||
61
+ lower.includes("ice_hockey")) {
62
+ return "winter";
63
+ }
64
+ if (lower.includes("climb"))
65
+ return "climbing";
66
+ if (lower.includes("surf") ||
67
+ lower.includes("kayak") ||
68
+ lower.includes("paddle") ||
69
+ lower.includes("aqua") ||
70
+ lower.includes("water_polo")) {
71
+ return "water";
72
+ }
73
+ if (lower.includes("tennis") ||
74
+ lower.includes("basketball") ||
75
+ lower.includes("soccer") ||
76
+ lower.includes("football") ||
77
+ lower.includes("hockey") ||
78
+ lower.includes("ball") ||
79
+ lower.includes("golf") ||
80
+ lower.includes("pickleball") ||
81
+ lower.includes("badminton") ||
82
+ lower.includes("volleyball") ||
83
+ lower.includes("cricket") ||
84
+ lower.includes("rugby") ||
85
+ lower.includes("lacrosse") ||
86
+ lower.includes("handball") ||
87
+ lower.includes("baseball") ||
88
+ lower.includes("softball")) {
89
+ return "team";
90
+ }
91
+ if (lower === "dance" || lower === "dancing")
92
+ return "dance";
93
+ return "other";
94
+ }
95
+ export function getActivityIconInfo(activityType) {
96
+ const category = resolveActivityIconCategory(activityType);
97
+ return {
98
+ category,
99
+ ...CATEGORY_INFO[category],
100
+ };
101
+ }
102
+ //# sourceMappingURL=activity-icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-icons.js","sourceRoot":"","sources":["../src/activity-icons.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export type ClimbingGradeSystem = "v_scale" | "yds";
2
+ export interface ParsedClimbingGrade {
3
+ gradeSystem: ClimbingGradeSystem;
4
+ grade: string;
5
+ sortValue: number;
6
+ }
7
+ export declare function parseClimbingGrade(input: string): ParsedClimbingGrade | null;
8
+ //# sourceMappingURL=climbing-grades.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"climbing-grades.d.ts","sourceRoot":"","sources":["../src/climbing-grades.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,KAAK,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,mBAAmB,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAsGD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAE5E"}
@@ -0,0 +1,80 @@
1
+ const vScalePattern = /^V(?<grade>B|0|[1-9]\d*)$/i;
2
+ const yosemiteDecimalSystemPattern = /^5\.(?<majorGrade>\d{1,2})(?<modifier>[ABCD]|[+-])?$/i;
3
+ const yosemiteLetterSortOffsets = new Map([
4
+ ["a", 1],
5
+ ["b", 2],
6
+ ["c", 3],
7
+ ["d", 4],
8
+ ]);
9
+ class ClimbingGradeParser {
10
+ parse(input) {
11
+ const trimmedInput = input.trim();
12
+ return (this.#parseVScaleGrade(trimmedInput) ?? this.#parseYosemiteDecimalSystemGrade(trimmedInput));
13
+ }
14
+ #parseVScaleGrade(input) {
15
+ const match = vScalePattern.exec(input);
16
+ const matchedGrade = match?.groups?.grade;
17
+ if (!matchedGrade) {
18
+ return null;
19
+ }
20
+ if (matchedGrade.toUpperCase() === "B") {
21
+ return {
22
+ gradeSystem: "v_scale",
23
+ grade: "VB",
24
+ sortValue: -1,
25
+ };
26
+ }
27
+ const sortValue = Number.parseInt(matchedGrade, 10);
28
+ if (!Number.isSafeInteger(sortValue)) {
29
+ return null;
30
+ }
31
+ return {
32
+ gradeSystem: "v_scale",
33
+ grade: `V${sortValue}`,
34
+ sortValue,
35
+ };
36
+ }
37
+ #parseYosemiteDecimalSystemGrade(input) {
38
+ const match = yosemiteDecimalSystemPattern.exec(input);
39
+ const matchedMajorGrade = match?.groups?.majorGrade;
40
+ if (!matchedMajorGrade) {
41
+ return null;
42
+ }
43
+ const majorGrade = Number.parseInt(matchedMajorGrade, 10);
44
+ if (!Number.isInteger(majorGrade) || majorGrade < 0 || majorGrade > 15) {
45
+ return null;
46
+ }
47
+ const modifier = match.groups?.modifier?.toLowerCase();
48
+ const sortValue = this.#computeYosemiteDecimalSystemSortValue(majorGrade, modifier);
49
+ if (sortValue === null) {
50
+ return null;
51
+ }
52
+ return {
53
+ gradeSystem: "yds",
54
+ grade: `5.${majorGrade}${modifier ?? ""}`,
55
+ sortValue,
56
+ };
57
+ }
58
+ #computeYosemiteDecimalSystemSortValue(majorGrade, modifier) {
59
+ const baseSortValue = 5000 + majorGrade * 10;
60
+ if (!modifier) {
61
+ return baseSortValue;
62
+ }
63
+ if (modifier === "-") {
64
+ return baseSortValue - 3;
65
+ }
66
+ if (modifier === "+") {
67
+ return baseSortValue + 5;
68
+ }
69
+ const letterSortOffset = yosemiteLetterSortOffsets.get(modifier);
70
+ if (letterSortOffset === undefined) {
71
+ return null;
72
+ }
73
+ return baseSortValue + letterSortOffset;
74
+ }
75
+ }
76
+ const climbingGradeParser = new ClimbingGradeParser();
77
+ export function parseClimbingGrade(input) {
78
+ return climbingGradeParser.parse(input);
79
+ }
80
+ //# sourceMappingURL=climbing-grades.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"climbing-grades.js","sourceRoot":"","sources":["../src/climbing-grades.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface CyclingVo2MaxInput {
2
+ fiveMinutePowerWatts: number;
3
+ weightKg: number | null;
4
+ }
5
+ export interface AcsmVo2MaxInput {
6
+ speedMetersPerMinute: number;
7
+ gradeFraction: number;
8
+ averageHeartRate: number;
9
+ restingHeartRate: number;
10
+ maxHeartRate: number;
11
+ }
12
+ export declare function estimateCyclingVo2Max(input: CyclingVo2MaxInput): number | null;
13
+ export declare function estimateSubmaximalAcsmVo2Max(input: AcsmVo2MaxInput): number | null;
14
+ export declare function averageVo2MaxEstimates(estimates: readonly (number | null)[]): number | null;
15
+ export declare function isValidVo2MaxEstimate(estimate: number | null): estimate is number;
16
+ export declare function isSupportedOutdoorVo2MaxActivityType(activityType: string): boolean;
17
+ //# sourceMappingURL=derived-cardio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derived-cardio.d.ts","sourceRoot":"","sources":["../src/derived-cardio.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAsBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,GAAG,IAAI,CAoB9E;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CA+ClF;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,GAAG,IAAI,CAa3F;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,IAAI,MAAM,CAEjF;AAED,wBAAgB,oCAAoC,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAIlF"}
@@ -0,0 +1,84 @@
1
+ const MIN_CYCLING_POWER_WATTS = 50;
2
+ const MAX_CYCLING_POWER_WATTS = 700;
3
+ const CYCLING_POWER_TO_OXYGEN_FACTOR = 10.8;
4
+ const CYCLING_RESTING_OXYGEN_COST = 7;
5
+ const MIN_ACSM_SPEED_METERS_PER_MINUTE = 40;
6
+ const MAX_ACSM_SPEED_METERS_PER_MINUTE = 450;
7
+ const MIN_ACSM_GRADE = -0.15;
8
+ const MAX_ACSM_GRADE = 0.15;
9
+ const RUNNING_SPEED_THRESHOLD_METERS_PER_MINUTE = 134;
10
+ const MIN_HEART_RATE_RESERVE_FRACTION = 0.6;
11
+ const MAX_HEART_RATE_RESERVE_FRACTION = 1;
12
+ const SUPPORTED_OUTDOOR_VO2_MAX_ACTIVITY_TYPES = [
13
+ "running",
14
+ "trail_running",
15
+ "walking",
16
+ "hiking",
17
+ ];
18
+ export function estimateCyclingVo2Max(input) {
19
+ if (!Number.isFinite(input.fiveMinutePowerWatts)) {
20
+ return null;
21
+ }
22
+ if (input.weightKg === null || !Number.isFinite(input.weightKg) || input.weightKg <= 0) {
23
+ return null;
24
+ }
25
+ if (input.fiveMinutePowerWatts < MIN_CYCLING_POWER_WATTS ||
26
+ input.fiveMinutePowerWatts > MAX_CYCLING_POWER_WATTS) {
27
+ return null;
28
+ }
29
+ return ((input.fiveMinutePowerWatts / input.weightKg) * CYCLING_POWER_TO_OXYGEN_FACTOR +
30
+ CYCLING_RESTING_OXYGEN_COST);
31
+ }
32
+ export function estimateSubmaximalAcsmVo2Max(input) {
33
+ if (!Number.isFinite(input.speedMetersPerMinute) ||
34
+ !Number.isFinite(input.gradeFraction) ||
35
+ !Number.isFinite(input.averageHeartRate) ||
36
+ !Number.isFinite(input.restingHeartRate) ||
37
+ !Number.isFinite(input.maxHeartRate)) {
38
+ return null;
39
+ }
40
+ if (input.speedMetersPerMinute < MIN_ACSM_SPEED_METERS_PER_MINUTE ||
41
+ input.speedMetersPerMinute > MAX_ACSM_SPEED_METERS_PER_MINUTE) {
42
+ return null;
43
+ }
44
+ if (input.gradeFraction < MIN_ACSM_GRADE || input.gradeFraction > MAX_ACSM_GRADE) {
45
+ return null;
46
+ }
47
+ const heartRateReserve = input.maxHeartRate - input.restingHeartRate;
48
+ if (heartRateReserve <= 0) {
49
+ return null;
50
+ }
51
+ const intensityFraction = (input.averageHeartRate - input.restingHeartRate) / heartRateReserve;
52
+ if (intensityFraction < MIN_HEART_RATE_RESERVE_FRACTION ||
53
+ intensityFraction >= MAX_HEART_RATE_RESERVE_FRACTION) {
54
+ return null;
55
+ }
56
+ const oxygenCost = input.speedMetersPerMinute >= RUNNING_SPEED_THRESHOLD_METERS_PER_MINUTE
57
+ ? getRunningOxygenCost(input.speedMetersPerMinute, input.gradeFraction)
58
+ : getWalkingOxygenCost(input.speedMetersPerMinute, input.gradeFraction);
59
+ if (oxygenCost <= 0 || !Number.isFinite(oxygenCost)) {
60
+ return null;
61
+ }
62
+ return oxygenCost / intensityFraction;
63
+ }
64
+ export function averageVo2MaxEstimates(estimates) {
65
+ const validEstimates = estimates.filter(isValidVo2MaxEstimate);
66
+ if (validEstimates.length === 0) {
67
+ return null;
68
+ }
69
+ const totalEstimate = validEstimates.reduce((runningTotal, estimate) => runningTotal + estimate, 0);
70
+ return totalEstimate / validEstimates.length;
71
+ }
72
+ export function isValidVo2MaxEstimate(estimate) {
73
+ return estimate !== null && Number.isFinite(estimate) && estimate > 0;
74
+ }
75
+ export function isSupportedOutdoorVo2MaxActivityType(activityType) {
76
+ return SUPPORTED_OUTDOOR_VO2_MAX_ACTIVITY_TYPES.some((supportedActivityType) => supportedActivityType === activityType);
77
+ }
78
+ function getWalkingOxygenCost(speedMetersPerMinute, gradeFraction) {
79
+ return 0.1 * speedMetersPerMinute + 1.8 * speedMetersPerMinute * gradeFraction + 3.5;
80
+ }
81
+ function getRunningOxygenCost(speedMetersPerMinute, gradeFraction) {
82
+ return 0.2 * speedMetersPerMinute + 0.9 * speedMetersPerMinute * gradeFraction + 3.5;
83
+ }
84
+ //# sourceMappingURL=derived-cardio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derived-cardio.js","sourceRoot":"","sources":["../src/derived-cardio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Endurance activity type classification.
3
+ *
4
+ * Activity types considered "endurance" for training analysis.
5
+ * Used to filter training queries (HR zones, polarization, ramp rate, etc.)
6
+ * so that strength training, yoga, etc. don't skew intensity metrics.
7
+ */
8
+ export declare const ENDURANCE_ACTIVITY_TYPES: readonly ["cycling", "road_cycling", "mountain_biking", "gravel_cycling", "indoor_cycling", "virtual_cycling", "e_bike_cycling", "cyclocross", "track_cycling", "bmx", "hand_cycling", "running", "swimming", "walking", "hiking"];
9
+ export type EnduranceActivityType = (typeof ENDURANCE_ACTIVITY_TYPES)[number];
10
+ /** Check whether an activity type is endurance (cardio). */
11
+ export declare function isEnduranceActivity(activityType: string): activityType is EnduranceActivityType;
12
+ /**
13
+ * Activity types that are performed on a stationary trainer / indoors.
14
+ * Speed and distance are not physically meaningful for these — the values
15
+ * are simulated by the device or platform.
16
+ */
17
+ export declare const INDOOR_CYCLING_TYPES: readonly ["indoor_cycling", "virtual_cycling"];
18
+ export type IndoorCyclingType = (typeof INDOOR_CYCLING_TYPES)[number];
19
+ /** Check whether an activity type is indoor cycling (stationary trainer or virtual ride). */
20
+ export declare function isIndoorCycling(activityType: string): activityType is IndoorCyclingType;
21
+ //# sourceMappingURL=endurance-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endurance-types.d.ts","sourceRoot":"","sources":["../src/endurance-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,wBAAwB,oOAgB3B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,IAAI,qBAAqB,CAE/F;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,gDAAiD,CAAC;AAEnF,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,IAAI,iBAAiB,CAEvF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Endurance activity type classification.
3
+ *
4
+ * Activity types considered "endurance" for training analysis.
5
+ * Used to filter training queries (HR zones, polarization, ramp rate, etc.)
6
+ * so that strength training, yoga, etc. don't skew intensity metrics.
7
+ */
8
+ export const ENDURANCE_ACTIVITY_TYPES = [
9
+ "cycling",
10
+ "road_cycling",
11
+ "mountain_biking",
12
+ "gravel_cycling",
13
+ "indoor_cycling",
14
+ "virtual_cycling",
15
+ "e_bike_cycling",
16
+ "cyclocross",
17
+ "track_cycling",
18
+ "bmx",
19
+ "hand_cycling",
20
+ "running",
21
+ "swimming",
22
+ "walking",
23
+ "hiking",
24
+ ];
25
+ /** Check whether an activity type is endurance (cardio). */
26
+ export function isEnduranceActivity(activityType) {
27
+ return ENDURANCE_ACTIVITY_TYPES.some((t) => t === activityType);
28
+ }
29
+ /**
30
+ * Activity types that are performed on a stationary trainer / indoors.
31
+ * Speed and distance are not physically meaningful for these — the values
32
+ * are simulated by the device or platform.
33
+ */
34
+ export const INDOOR_CYCLING_TYPES = ["indoor_cycling", "virtual_cycling"];
35
+ /** Check whether an activity type is indoor cycling (stationary trainer or virtual ride). */
36
+ export function isIndoorCycling(activityType) {
37
+ return INDOOR_CYCLING_TYPES.some((t) => t === activityType);
38
+ }
39
+ //# sourceMappingURL=endurance-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endurance-types.js","sourceRoot":"","sources":["../src/endurance-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,SAAS;IACT,cAAc;IACd,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,gBAAgB;IAChB,YAAY;IACZ,eAAe;IACf,KAAK;IACL,cAAc;IACd,SAAS;IACT,UAAU;IACV,SAAS;IACT,QAAQ;CACA,CAAC;AAIX,4DAA4D;AAC5D,MAAM,UAAU,mBAAmB,CAAC,YAAoB;IACtD,OAAO,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,gBAAgB,EAAE,iBAAiB,CAAU,CAAC;AAInF,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Minetti cost factor model for grade-adjusted pace.
3
+ *
4
+ * Normalizes walking/hiking/trail-running pace to account for terrain grade,
5
+ * allowing meaningful comparison between hilly and flat activities.
6
+ *
7
+ * Reference: Minetti et al. (2002) "Energy cost of walking and running at
8
+ * extreme uphill and downhill slopes"
9
+ */
10
+ /**
11
+ * Compute the energy cost factor for a given terrain grade using a simplified
12
+ * Minetti model.
13
+ *
14
+ * - Uphill (grade > 0): cost increases linearly at 3.5× the grade fraction.
15
+ * - Downhill (grade < 0): cost decreases at 1.8× the grade fraction, floored
16
+ * at 0.5 (steep downhill is still effortful due to braking forces).
17
+ * - Flat (grade = 0): cost factor is 1 (no adjustment).
18
+ *
19
+ * @param grade - Terrain grade as a decimal fraction (e.g. 0.10 = 10% grade).
20
+ */
21
+ export declare function minettiCostFactor(grade: number): number;
22
+ /**
23
+ * Compute grade-adjusted pace by dividing actual pace by the Minetti cost factor.
24
+ *
25
+ * A steep uphill activity with a slow actual pace will produce a faster
26
+ * grade-adjusted pace (the athlete was working harder than flat pace suggests).
27
+ * Conversely, a fast downhill pace will adjust slower.
28
+ *
29
+ * @param actualPaceMinPerKm - Actual pace in minutes per kilometer.
30
+ * @param grade - Terrain grade as a decimal fraction (e.g. 0.10 = 10% grade).
31
+ * @returns Grade-adjusted pace in minutes per kilometer.
32
+ */
33
+ export declare function computeGradeAdjustedPace(actualPaceMinPerKm: number, grade: number): number;
34
+ //# sourceMappingURL=grade-adjusted-pace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade-adjusted-pace.d.ts","sourceRoot":"","sources":["../src/grade-adjusted-pace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,kBAAkB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1F"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minetti cost factor model for grade-adjusted pace.
3
+ *
4
+ * Normalizes walking/hiking/trail-running pace to account for terrain grade,
5
+ * allowing meaningful comparison between hilly and flat activities.
6
+ *
7
+ * Reference: Minetti et al. (2002) "Energy cost of walking and running at
8
+ * extreme uphill and downhill slopes"
9
+ */
10
+ /**
11
+ * Compute the energy cost factor for a given terrain grade using a simplified
12
+ * Minetti model.
13
+ *
14
+ * - Uphill (grade > 0): cost increases linearly at 3.5× the grade fraction.
15
+ * - Downhill (grade < 0): cost decreases at 1.8× the grade fraction, floored
16
+ * at 0.5 (steep downhill is still effortful due to braking forces).
17
+ * - Flat (grade = 0): cost factor is 1 (no adjustment).
18
+ *
19
+ * @param grade - Terrain grade as a decimal fraction (e.g. 0.10 = 10% grade).
20
+ */
21
+ export function minettiCostFactor(grade) {
22
+ if (grade >= 0) {
23
+ return 1 + grade * 3.5;
24
+ }
25
+ return Math.max(0.5, 1 - Math.abs(grade) * 1.8);
26
+ }
27
+ /**
28
+ * Compute grade-adjusted pace by dividing actual pace by the Minetti cost factor.
29
+ *
30
+ * A steep uphill activity with a slow actual pace will produce a faster
31
+ * grade-adjusted pace (the athlete was working harder than flat pace suggests).
32
+ * Conversely, a fast downhill pace will adjust slower.
33
+ *
34
+ * @param actualPaceMinPerKm - Actual pace in minutes per kilometer.
35
+ * @param grade - Terrain grade as a decimal fraction (e.g. 0.10 = 10% grade).
36
+ * @returns Grade-adjusted pace in minutes per kilometer.
37
+ */
38
+ export function computeGradeAdjustedPace(actualPaceMinPerKm, grade) {
39
+ return actualPaceMinPerKm / minettiCostFactor(grade);
40
+ }
41
+ //# sourceMappingURL=grade-adjusted-pace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade-adjusted-pace.js","sourceRoot":"","sources":["../src/grade-adjusted-pace.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,KAAK,GAAG,GAAG,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,kBAA0B,EAAE,KAAa;IAChF,OAAO,kBAAkB,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;AACvD,CAAC"}