@dofek/zones 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.
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,87 @@
1
+ # @dofek/zones
2
+
3
+ Framework-neutral heart-rate and cycling-power zone definitions, classification,
4
+ range formatting, time-distribution mapping, and polarization analysis.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @dofek/zones
10
+ ```
11
+
12
+ Requires Node.js 22.14 or newer.
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ import {
18
+ classifyHeartRateZone,
19
+ computePolarizationIndex,
20
+ heartRateZoneBoundaries,
21
+ } from "@dofek/zones";
22
+
23
+ const maxHeartRate = 190;
24
+ const restingHeartRate = 55;
25
+
26
+ console.log(heartRateZoneBoundaries(maxHeartRate, restingHeartRate));
27
+ console.log(classifyHeartRateZone(150, maxHeartRate, restingHeartRate));
28
+ console.log(computePolarizationIndex(3_600, 300, 600));
29
+ ```
30
+
31
+ ## Public API
32
+
33
+ Both `@dofek/zones` and `@dofek/zones/zones` expose the same API:
34
+
35
+ - Heart-rate definitions, colors, boundaries, classification, range labels, and
36
+ complete time-in-zone rows.
37
+ - Cycling-power definitions, colors, FTP-relative boundaries, classification,
38
+ range labels, and complete time-in-zone rows.
39
+ - Generic zone-distribution row and label helpers.
40
+ - Three-zone polarization definitions and `computePolarizationIndex`.
41
+ - TypeScript interfaces for all definitions, boundaries, rows, and activity
42
+ zones.
43
+
44
+ ## Models and behavior
45
+
46
+ ### Heart-rate reserve
47
+
48
+ The five activity zones use heart-rate reserve:
49
+
50
+ ```text
51
+ HRR fraction = (heart rate - resting heart rate) / (maximum heart rate - resting heart rate)
52
+ ```
53
+
54
+ The boundaries are 50–60%, 60–70%, 70–80%, 80–90%, and 90–100% HRR.
55
+ This model is based on the heart-rate-reserve approach described by
56
+ [Karvonen, Kentala, and Mustala (1957)](https://pubmed.ncbi.nlm.nih.gov/13470504/).
57
+ Readings below the first boundary classify as zone 0.
58
+
59
+ ### Polarization index
60
+
61
+ `computePolarizationIndex` implements:
62
+
63
+ ```text
64
+ PI = log10((f1 / (f2 × f3)) × 100)
65
+ ```
66
+
67
+ where each `f` is the fraction of total time in one of the three intensity
68
+ zones. The function returns `null` when any zone has no time; the formula and
69
+ 2.0 classification threshold come from
70
+ [Treff et al. (2019)](https://www.frontiersin.org/journals/physiology/articles/10.3389/fphys.2019.00707/full).
71
+
72
+ ### Cycling power
73
+
74
+ The package defines seven FTP-relative cycling-power zones and exposes helpers
75
+ to convert them into watt boundaries or classify individual power readings.
76
+
77
+ The package performs no network requests and needs no authentication or runtime
78
+ configuration. Callers are responsible for supplying valid, individualized
79
+ maximum/resting heart rates and FTP values. The calculations are training
80
+ analysis tools, not medical advice.
81
+
82
+ ## License and contributing
83
+
84
+ [MIT](./LICENSE). Source is in the
85
+ [Dofek repository](https://github.com/Asherlc/dofek/tree/main/packages/zones).
86
+ Please [open an issue](https://github.com/Asherlc/dofek/issues) for bugs or API
87
+ proposals, or [submit a pull request](https://github.com/Asherlc/dofek/pulls).
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Heart rate and power zone models and utilities.
3
+ *
4
+ * Three models are supported:
5
+ * 1. **Karvonen HR zones** (%HRR) — zone 0 plus the standard 5 training zones
6
+ * 2. **Treff 3-zone** HR (%HRmax) — simplified model for polarization index
7
+ * 3. **7-zone** cycling power (%FTP) — standard model for power analysis
8
+ */
9
+ export interface HeartRateZoneDefinition {
10
+ zone: number;
11
+ label: string;
12
+ /** Lower bound as fraction of Heart Rate Reserve (e.g. 0.5 = 50% HRR) */
13
+ minPctHrr: number;
14
+ /** Upper bound as fraction of Heart Rate Reserve */
15
+ maxPctHrr: number;
16
+ color: string;
17
+ }
18
+ export interface HeartRateZoneBoundary {
19
+ zone: number;
20
+ label: string;
21
+ minBpm: number;
22
+ maxBpm: number;
23
+ color: string;
24
+ }
25
+ export interface ActivityHrZone {
26
+ zone: number;
27
+ label: string;
28
+ /** Lower bound as integer percentage of HRR (e.g. 50) */
29
+ minPct: number;
30
+ /** Upper bound as integer percentage of HRR (e.g. 60) */
31
+ maxPct: number;
32
+ seconds: number;
33
+ /** Percentage of activity zone time spent in this zone. */
34
+ percent: number;
35
+ }
36
+ export interface ZoneDistributionDatum {
37
+ zone: number;
38
+ label: string;
39
+ seconds: number;
40
+ percent: number;
41
+ }
42
+ export interface ZoneDistributionRow<ZoneItem extends ZoneDistributionDatum> {
43
+ key: string;
44
+ primaryLabel: string;
45
+ subordinateLabel: string | null;
46
+ axisLabel: string;
47
+ percentLabel: string;
48
+ color: string;
49
+ zone: ZoneItem;
50
+ }
51
+ export interface PolarizationZoneDefinition {
52
+ zone: number;
53
+ label: string;
54
+ /** Lower bound as fraction of HRmax (0 for zone 1) */
55
+ minPctHrmax: number;
56
+ /** Upper bound as fraction of HRmax (1 for zone 3) */
57
+ maxPctHrmax: number;
58
+ }
59
+ /**
60
+ * Karvonen model using % Heart Rate Reserve.
61
+ *
62
+ * HRR = maxHr - restingHr
63
+ * Zone boundary = restingHr + HRR * fraction
64
+ */
65
+ export declare const HEART_RATE_ZONES: HeartRateZoneDefinition[];
66
+ /** Ordered array of zone colors for chart series (indexed 0-5 for zones 0-5). */
67
+ export declare const HEART_RATE_ZONE_COLORS: string[];
68
+ /**
69
+ * Zone boundary fractions for SQL interpolation.
70
+ * These are the upper bounds of each zone (as %HRR fractions):
71
+ * [0.5, 0.6, 0.7, 0.8, 0.9] — the boundary between zone N and zone N+1.
72
+ *
73
+ * Use these instead of hardcoding HRR thresholds in SQL queries
74
+ * so zone definitions stay in sync across the codebase.
75
+ */
76
+ export declare const ZONE_BOUNDARIES_HRR: number[];
77
+ export declare function formatZoneDistributionAxisLabel(zoneItem: ZoneDistributionDatum): string;
78
+ export declare function formatZoneDistributionPrimaryLabel(zoneItem: ZoneDistributionDatum): string;
79
+ export declare function formatZoneDistributionSubordinateLabel(zoneItem: ZoneDistributionDatum): string | null;
80
+ export declare function formatZoneDistributionPercentLabel(zoneItem: ZoneDistributionDatum): string;
81
+ export declare function hasZoneDistributionData(zones: ZoneDistributionDatum[]): boolean;
82
+ export declare function createZoneDistributionRows<ZoneItem extends ZoneDistributionDatum>(zones: ZoneItem[], zoneColors: string[], fallbackColor?: string): ZoneDistributionRow<ZoneItem>[];
83
+ export declare function formatHeartRateZoneRangeLabel(zoneItem: ActivityHrZone): string;
84
+ /**
85
+ * Compute absolute BPM boundaries for each zone given a user's max HR and resting HR.
86
+ */
87
+ export declare function heartRateZoneBoundaries(maxHr: number, restingHr: number): HeartRateZoneBoundary[];
88
+ /**
89
+ * Classify a heart rate reading into zone 0-5.
90
+ */
91
+ export declare function classifyHeartRateZone(heartRate: number, maxHr: number, restingHr: number): number;
92
+ /**
93
+ * Compute the absolute BPM range for a specific zone number (0-5).
94
+ * Returns null if maxHr or restingHr is null.
95
+ */
96
+ export declare function computeHrRange(maxHr: number | null, restingHr: number | null, zone: number): {
97
+ min: number;
98
+ max: number;
99
+ } | null;
100
+ /**
101
+ * Map raw DB zone rows to the full heart-rate zone structure.
102
+ * Missing zones get 0 seconds. Used by activity and training routers.
103
+ */
104
+ export declare function mapHrZones(rows: {
105
+ zone: number;
106
+ seconds: number;
107
+ }[]): ActivityHrZone[];
108
+ /**
109
+ * Treff 3-zone model for polarization analysis.
110
+ * Uses %HRmax (simpler and more stable than Karvonen %HRR).
111
+ */
112
+ export declare const POLARIZATION_ZONES: PolarizationZoneDefinition[];
113
+ /**
114
+ * Compute the Treff Polarization Index from zone time distribution.
115
+ *
116
+ * PI = log10((f1 / (f2 * f3)) * 100)
117
+ * where f = fraction of total training time in each zone.
118
+ *
119
+ * PI > 2.0 indicates a well-polarized training distribution.
120
+ * Returns null if any zone has zero time.
121
+ */
122
+ export declare function computePolarizationIndex(z1Seconds: number, z2Seconds: number, z3Seconds: number): number | null;
123
+ export interface PowerZoneDefinition {
124
+ zone: number;
125
+ label: string;
126
+ /** Lower bound as fraction of FTP (e.g. 0.55 = 55% FTP). 0 for zone 1. */
127
+ minPctFtp: number;
128
+ /** Upper bound as fraction of FTP. Infinity for the open-ended top zone. */
129
+ maxPctFtp: number;
130
+ color: string;
131
+ }
132
+ export interface PowerZoneBoundary {
133
+ zone: number;
134
+ label: string;
135
+ minWatts: number;
136
+ /** null when the zone is open-ended (Z7 has no upper bound). */
137
+ maxWatts: number | null;
138
+ color: string;
139
+ }
140
+ export interface ActivityPowerZone {
141
+ zone: number;
142
+ label: string;
143
+ /** Lower bound as integer percentage of FTP (e.g. 55). */
144
+ minPct: number;
145
+ /** Upper bound as integer percentage of FTP. null for Z7 (open-ended). */
146
+ maxPct: number | null;
147
+ seconds: number;
148
+ /** Percentage of activity zone time spent in this zone. */
149
+ percent: number;
150
+ }
151
+ /**
152
+ * Standard 7-zone model using % Functional Threshold Power.
153
+ *
154
+ * Zone boundary = ftp × fraction.
155
+ */
156
+ export declare const POWER_ZONES: PowerZoneDefinition[];
157
+ /** Ordered array of zone colors for chart series (indexed 0-6 for zones 1-7). */
158
+ export declare const POWER_ZONE_COLORS: string[];
159
+ /**
160
+ * Zone boundary fractions for SQL interpolation.
161
+ * Upper bounds of each zone except the last (as %FTP fractions):
162
+ * [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].
163
+ */
164
+ export declare const ZONE_BOUNDARIES_FTP: number[];
165
+ export declare function formatPowerZoneRangeLabel(zoneItem: ActivityPowerZone, ftp: number): string;
166
+ /**
167
+ * Compute absolute wattage boundaries for each zone given an FTP value.
168
+ */
169
+ export declare function powerZoneBoundaries(ftp: number): PowerZoneBoundary[];
170
+ /**
171
+ * Classify a power reading into zone 1-7.
172
+ */
173
+ export declare function classifyPowerZone(power: number, ftp: number): number;
174
+ /**
175
+ * Map raw DB zone rows to the full 7-zone structure.
176
+ * Missing zones get 0 seconds.
177
+ */
178
+ export declare function mapPowerZones(rows: {
179
+ zone: number;
180
+ seconds: number;
181
+ }[]): ActivityPowerZone[];
182
+ //# sourceMappingURL=zones.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zones.d.ts","sourceRoot":"","sources":["../src/zones.ts"],"names":[],"mappings":"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"}
package/dist/zones.js ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Heart rate and power zone models and utilities.
3
+ *
4
+ * Three models are supported:
5
+ * 1. **Karvonen HR zones** (%HRR) — zone 0 plus the standard 5 training zones
6
+ * 2. **Treff 3-zone** HR (%HRmax) — simplified model for polarization index
7
+ * 3. **7-zone** cycling power (%FTP) — standard model for power analysis
8
+ */
9
+ import { chartColors, statusColors, textColors } from "@dofek/scoring/colors";
10
+ // ── Karvonen Heart Rate Zones ────────────────────────────────────────
11
+ /**
12
+ * Karvonen model using % Heart Rate Reserve.
13
+ *
14
+ * HRR = maxHr - restingHr
15
+ * Zone boundary = restingHr + HRR * fraction
16
+ */
17
+ export const HEART_RATE_ZONES = [
18
+ { zone: 0, label: "Below Zone 1", minPctHrr: 0, maxPctHrr: 0.5, color: textColors.neutral },
19
+ { zone: 1, label: "Recovery", minPctHrr: 0.5, maxPctHrr: 0.6, color: statusColors.info },
20
+ { zone: 2, label: "Aerobic", minPctHrr: 0.6, maxPctHrr: 0.7, color: statusColors.positive },
21
+ { zone: 3, label: "Tempo", minPctHrr: 0.7, maxPctHrr: 0.8, color: statusColors.warning },
22
+ { zone: 4, label: "Threshold", minPctHrr: 0.8, maxPctHrr: 0.9, color: statusColors.elevated },
23
+ { zone: 5, label: "VO2max", minPctHrr: 0.9, maxPctHrr: 1.0, color: statusColors.danger },
24
+ ];
25
+ /** Ordered array of zone colors for chart series (indexed 0-5 for zones 0-5). */
26
+ export const HEART_RATE_ZONE_COLORS = HEART_RATE_ZONES.map((z) => z.color);
27
+ /**
28
+ * Zone boundary fractions for SQL interpolation.
29
+ * These are the upper bounds of each zone (as %HRR fractions):
30
+ * [0.5, 0.6, 0.7, 0.8, 0.9] — the boundary between zone N and zone N+1.
31
+ *
32
+ * Use these instead of hardcoding HRR thresholds in SQL queries
33
+ * so zone definitions stay in sync across the codebase.
34
+ */
35
+ export const ZONE_BOUNDARIES_HRR = HEART_RATE_ZONES.slice(0, -1).map((z) => z.maxPctHrr);
36
+ export function formatZoneDistributionAxisLabel(zoneItem) {
37
+ const primaryLabel = formatZoneDistributionPrimaryLabel(zoneItem);
38
+ const subordinateLabel = formatZoneDistributionSubordinateLabel(zoneItem);
39
+ return subordinateLabel == null ? primaryLabel : `${primaryLabel}\n${subordinateLabel}`;
40
+ }
41
+ export function formatZoneDistributionPrimaryLabel(zoneItem) {
42
+ if (zoneItem.zone === 0)
43
+ return zoneItem.label;
44
+ return `Zone ${zoneItem.zone}`;
45
+ }
46
+ export function formatZoneDistributionSubordinateLabel(zoneItem) {
47
+ if (zoneItem.zone === 0)
48
+ return null;
49
+ return zoneItem.label;
50
+ }
51
+ export function formatZoneDistributionPercentLabel(zoneItem) {
52
+ return `${Math.round(zoneItem.percent)}%`;
53
+ }
54
+ export function hasZoneDistributionData(zones) {
55
+ return zones.some((zoneItem) => zoneItem.percent > 0);
56
+ }
57
+ export function createZoneDistributionRows(zones, zoneColors, fallbackColor = textColors.neutral) {
58
+ return zones.map((zoneItem, zoneIndex) => ({
59
+ key: String(zoneItem.zone),
60
+ primaryLabel: formatZoneDistributionPrimaryLabel(zoneItem),
61
+ subordinateLabel: formatZoneDistributionSubordinateLabel(zoneItem),
62
+ axisLabel: formatZoneDistributionAxisLabel(zoneItem),
63
+ percentLabel: formatZoneDistributionPercentLabel(zoneItem),
64
+ color: zoneColors[zoneIndex] ?? fallbackColor,
65
+ zone: zoneItem,
66
+ }));
67
+ }
68
+ export function formatHeartRateZoneRangeLabel(zoneItem) {
69
+ return `${zoneItem.minPct}-${zoneItem.maxPct}% Heart Rate Reserve`;
70
+ }
71
+ /**
72
+ * Compute absolute BPM boundaries for each zone given a user's max HR and resting HR.
73
+ */
74
+ export function heartRateZoneBoundaries(maxHr, restingHr) {
75
+ const reserve = maxHr - restingHr;
76
+ return HEART_RATE_ZONES.map((z) => ({
77
+ zone: z.zone,
78
+ label: z.label,
79
+ minBpm: Math.round(restingHr + reserve * z.minPctHrr),
80
+ maxBpm: Math.round(restingHr + reserve * z.maxPctHrr),
81
+ color: z.color,
82
+ }));
83
+ }
84
+ /**
85
+ * Classify a heart rate reading into zone 0-5.
86
+ */
87
+ export function classifyHeartRateZone(heartRate, maxHr, restingHr) {
88
+ const reserve = maxHr - restingHr;
89
+ // Walk zones in reverse to find the highest matching zone
90
+ for (let i = HEART_RATE_ZONES.length - 1; i >= 0; i--) {
91
+ const zone = HEART_RATE_ZONES[i];
92
+ if (!zone)
93
+ continue;
94
+ const threshold = restingHr + reserve * zone.minPctHrr;
95
+ if (heartRate >= threshold)
96
+ return zone.zone;
97
+ }
98
+ return 0;
99
+ }
100
+ /**
101
+ * Compute the absolute BPM range for a specific zone number (0-5).
102
+ * Returns null if maxHr or restingHr is null.
103
+ */
104
+ export function computeHrRange(maxHr, restingHr, zone) {
105
+ if (maxHr == null || restingHr == null)
106
+ return null;
107
+ const zoneDef = HEART_RATE_ZONES.find((z) => z.zone === zone);
108
+ if (!zoneDef)
109
+ return null;
110
+ const reserve = maxHr - restingHr;
111
+ return {
112
+ min: Math.round(restingHr + reserve * zoneDef.minPctHrr),
113
+ max: Math.round(restingHr + reserve * zoneDef.maxPctHrr),
114
+ };
115
+ }
116
+ /**
117
+ * Map raw DB zone rows to the full heart-rate zone structure.
118
+ * Missing zones get 0 seconds. Used by activity and training routers.
119
+ */
120
+ export function mapHrZones(rows) {
121
+ const totalSeconds = rows.reduce((sum, row) => sum + Number(row.seconds), 0);
122
+ return HEART_RATE_ZONES.map((zoneDefinition) => {
123
+ const row = rows.find((candidateRow) => Number(candidateRow.zone) === zoneDefinition.zone);
124
+ const seconds = row ? Number(row.seconds) : 0;
125
+ return {
126
+ zone: zoneDefinition.zone,
127
+ label: zoneDefinition.label,
128
+ minPct: Math.round(zoneDefinition.minPctHrr * 100),
129
+ maxPct: Math.round(zoneDefinition.maxPctHrr * 100),
130
+ seconds,
131
+ percent: totalSeconds > 0 ? Math.round((seconds / totalSeconds) * 1000) / 10 : 0,
132
+ };
133
+ });
134
+ }
135
+ // ── Treff 3-Zone Polarization Model ─────────────────────────────────
136
+ /**
137
+ * Treff 3-zone model for polarization analysis.
138
+ * Uses %HRmax (simpler and more stable than Karvonen %HRR).
139
+ */
140
+ export const POLARIZATION_ZONES = [
141
+ { zone: 1, label: "Easy", minPctHrmax: 0, maxPctHrmax: 0.8 },
142
+ { zone: 2, label: "Threshold", minPctHrmax: 0.8, maxPctHrmax: 0.9 },
143
+ { zone: 3, label: "High Intensity", minPctHrmax: 0.9, maxPctHrmax: 1.0 },
144
+ ];
145
+ /**
146
+ * Compute the Treff Polarization Index from zone time distribution.
147
+ *
148
+ * PI = log10((f1 / (f2 * f3)) * 100)
149
+ * where f = fraction of total training time in each zone.
150
+ *
151
+ * PI > 2.0 indicates a well-polarized training distribution.
152
+ * Returns null if any zone has zero time.
153
+ */
154
+ export function computePolarizationIndex(z1Seconds, z2Seconds, z3Seconds) {
155
+ if (z1Seconds <= 0 || z2Seconds <= 0 || z3Seconds <= 0)
156
+ return null;
157
+ const total = z1Seconds + z2Seconds + z3Seconds;
158
+ if (total <= 0)
159
+ return null;
160
+ const f1 = z1Seconds / total;
161
+ const f2 = z2Seconds / total;
162
+ const f3 = z3Seconds / total;
163
+ const ratio = (f1 / (f2 * f3)) * 100;
164
+ return Math.round(Math.log10(ratio) * 1000) / 1000;
165
+ }
166
+ /**
167
+ * Standard 7-zone model using % Functional Threshold Power.
168
+ *
169
+ * Zone boundary = ftp × fraction.
170
+ */
171
+ export const POWER_ZONES = [
172
+ { zone: 1, label: "Active Recovery", minPctFtp: 0, maxPctFtp: 0.55, color: chartColors.teal },
173
+ { zone: 2, label: "Endurance", minPctFtp: 0.55, maxPctFtp: 0.75, color: statusColors.info },
174
+ { zone: 3, label: "Tempo", minPctFtp: 0.75, maxPctFtp: 0.9, color: statusColors.positive },
175
+ { zone: 4, label: "Threshold", minPctFtp: 0.9, maxPctFtp: 1.05, color: statusColors.warning },
176
+ { zone: 5, label: "VO2max", minPctFtp: 1.05, maxPctFtp: 1.2, color: statusColors.elevated },
177
+ { zone: 6, label: "Anaerobic", minPctFtp: 1.2, maxPctFtp: 1.5, color: statusColors.danger },
178
+ {
179
+ zone: 7,
180
+ label: "Neuromuscular",
181
+ minPctFtp: 1.5,
182
+ maxPctFtp: Number.POSITIVE_INFINITY,
183
+ color: chartColors.purple,
184
+ },
185
+ ];
186
+ /** Ordered array of zone colors for chart series (indexed 0-6 for zones 1-7). */
187
+ export const POWER_ZONE_COLORS = POWER_ZONES.map((z) => z.color);
188
+ /**
189
+ * Zone boundary fractions for SQL interpolation.
190
+ * Upper bounds of each zone except the last (as %FTP fractions):
191
+ * [0.55, 0.75, 0.9, 1.05, 1.2, 1.5].
192
+ */
193
+ export const ZONE_BOUNDARIES_FTP = POWER_ZONES.slice(0, -1).map((z) => z.maxPctFtp);
194
+ export function formatPowerZoneRangeLabel(zoneItem, ftp) {
195
+ const minWatts = Math.round((zoneItem.minPct / 100) * ftp);
196
+ const maxWatts = zoneItem.maxPct != null ? Math.round((zoneItem.maxPct / 100) * ftp) : null;
197
+ const percentLabel = zoneItem.maxPct != null
198
+ ? `${zoneItem.minPct}-${zoneItem.maxPct}% Threshold Power`
199
+ : `>${zoneItem.minPct}% Threshold Power`;
200
+ const wattLabel = maxWatts != null ? `${minWatts}-${maxWatts} W` : `>${minWatts} W`;
201
+ return `${percentLabel} (${wattLabel})`;
202
+ }
203
+ /**
204
+ * Compute absolute wattage boundaries for each zone given an FTP value.
205
+ */
206
+ export function powerZoneBoundaries(ftp) {
207
+ return POWER_ZONES.map((z) => ({
208
+ zone: z.zone,
209
+ label: z.label,
210
+ minWatts: Math.round(ftp * z.minPctFtp),
211
+ maxWatts: Number.isFinite(z.maxPctFtp) ? Math.round(ftp * z.maxPctFtp) : null,
212
+ color: z.color,
213
+ }));
214
+ }
215
+ /**
216
+ * Classify a power reading into zone 1-7.
217
+ */
218
+ export function classifyPowerZone(power, ftp) {
219
+ for (let i = POWER_ZONES.length - 1; i >= 0; i--) {
220
+ const zone = POWER_ZONES[i];
221
+ if (!zone)
222
+ continue;
223
+ if (power >= ftp * zone.minPctFtp)
224
+ return zone.zone;
225
+ }
226
+ return 1;
227
+ }
228
+ /**
229
+ * Map raw DB zone rows to the full 7-zone structure.
230
+ * Missing zones get 0 seconds.
231
+ */
232
+ export function mapPowerZones(rows) {
233
+ const totalSeconds = rows.reduce((sum, row) => sum + Number(row.seconds), 0);
234
+ return POWER_ZONES.map((zoneDefinition) => {
235
+ const row = rows.find((candidateRow) => Number(candidateRow.zone) === zoneDefinition.zone);
236
+ const seconds = row ? Number(row.seconds) : 0;
237
+ return {
238
+ zone: zoneDefinition.zone,
239
+ label: zoneDefinition.label,
240
+ minPct: Math.round(zoneDefinition.minPctFtp * 100),
241
+ maxPct: Number.isFinite(zoneDefinition.maxPctFtp)
242
+ ? Math.round(zoneDefinition.maxPctFtp * 100)
243
+ : null,
244
+ seconds,
245
+ percent: totalSeconds > 0 ? Math.round((seconds / totalSeconds) * 1000) / 10 : 0,
246
+ };
247
+ });
248
+ }
249
+ //# sourceMappingURL=zones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zones.js","sourceRoot":"","sources":["../src/zones.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@dofek/zones",
3
+ "version": "0.1.0",
4
+ "description": "Heart-rate and cycling-power zone calculations and classification",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Asherlc/dofek.git",
10
+ "directory": "packages/zones"
11
+ },
12
+ "homepage": "https://github.com/Asherlc/dofek/tree/main/packages/zones#readme",
13
+ "bugs": "https://github.com/Asherlc/dofek/issues",
14
+ "keywords": [
15
+ "heart-rate",
16
+ "power",
17
+ "cycling",
18
+ "training-zones",
19
+ "fitness"
20
+ ],
21
+ "engines": {
22
+ "node": ">=22.14.0"
23
+ },
24
+ "sideEffects": false,
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/zones.d.ts",
33
+ "import": "./dist/zones.js"
34
+ },
35
+ "./zones": {
36
+ "types": "./dist/zones.d.ts",
37
+ "import": "./dist/zones.js"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@dofek/scoring": "0.1.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "prepack": "pnpm run build",
49
+ "test": "vitest run src",
50
+ "typecheck": "tsc --noEmit"
51
+ },
52
+ "gitHead": "ac95258c753c7e75b57da7ae47426d22b78e8c01"
53
+ }