@swimedge/metrics 1.0.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/README.md +57 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +412 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @swimedge/metrics
|
|
2
|
+
|
|
3
|
+
Shared metrics registry for SwimEdge - used by frontend, backend, and watch app.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Frontend (React Native)
|
|
8
|
+
```bash
|
|
9
|
+
cd SwimEdge
|
|
10
|
+
npm install ../shared/metrics
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Backend (Node.js)
|
|
14
|
+
```bash
|
|
15
|
+
cd backend-api
|
|
16
|
+
npm install ../shared/metrics
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Watch App (Swift - via JSON export)
|
|
20
|
+
```bash
|
|
21
|
+
cd shared/metrics
|
|
22
|
+
npm run export-json
|
|
23
|
+
# Copy metrics.json to watch app
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { METRICS_REGISTRY, MetricsHelper, MetricCategory } from '@swimedge/metrics';
|
|
30
|
+
|
|
31
|
+
// Get metric definition
|
|
32
|
+
const metric = MetricsHelper.getMetric('totalStrokeCount');
|
|
33
|
+
console.log(metric.label); // "Total Strokes"
|
|
34
|
+
console.log(metric.validationMin); // 0
|
|
35
|
+
console.log(metric.validationMax); // 50000
|
|
36
|
+
|
|
37
|
+
// Get all session metrics
|
|
38
|
+
const sessionMetrics = MetricsHelper.getSessionMetrics();
|
|
39
|
+
|
|
40
|
+
// Get metrics by category
|
|
41
|
+
const techniqueMetrics = MetricsHelper.getMetricsByCategory(MetricCategory.TECHNIQUE);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Benefits
|
|
45
|
+
|
|
46
|
+
- ✅ Single source of truth
|
|
47
|
+
- ✅ Type-safe across all platforms
|
|
48
|
+
- ✅ Validation rules included
|
|
49
|
+
- ✅ Icons and descriptions embedded
|
|
50
|
+
- ✅ Easy to maintain
|
|
51
|
+
|
|
52
|
+
## Metrics Count
|
|
53
|
+
|
|
54
|
+
- Session-level: 18 metrics
|
|
55
|
+
- Lap-level: 3 metrics
|
|
56
|
+
- Aggregated: 2 metrics
|
|
57
|
+
- Total: 23+ core metrics
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @swimedge/metrics
|
|
3
|
+
* Shared metrics registry for frontend, backend, and watch app
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SwimType {
|
|
6
|
+
INDOOR = "indoor",
|
|
7
|
+
OPEN_WATER = "open_water"
|
|
8
|
+
}
|
|
9
|
+
export interface SwimSessionLaps {
|
|
10
|
+
lapSplitTimes?: number[];
|
|
11
|
+
lapTurnTimes?: number[];
|
|
12
|
+
lapStrokeTypes?: string[];
|
|
13
|
+
lapHeartRates?: number[];
|
|
14
|
+
lapStrokeCounts?: number[];
|
|
15
|
+
lapPaces?: number[];
|
|
16
|
+
lapCalories?: number[];
|
|
17
|
+
lapDistances?: number[];
|
|
18
|
+
lapSwolfScores?: number[];
|
|
19
|
+
lapStrokeRates?: number[];
|
|
20
|
+
lapDistancesPerStroke?: number[];
|
|
21
|
+
}
|
|
22
|
+
export interface SwimSession extends SwimSessionLaps {
|
|
23
|
+
id: string;
|
|
24
|
+
watchSessionId?: string;
|
|
25
|
+
date: string;
|
|
26
|
+
distance: number;
|
|
27
|
+
laps: number;
|
|
28
|
+
time: number;
|
|
29
|
+
activeTime?: number;
|
|
30
|
+
restTime?: number;
|
|
31
|
+
calories: number;
|
|
32
|
+
avgHeartRate?: number;
|
|
33
|
+
maxHeartRate?: number;
|
|
34
|
+
minHeartRate?: number;
|
|
35
|
+
avgPace: string;
|
|
36
|
+
totalStrokeCount?: number;
|
|
37
|
+
avgStrokeRate?: number;
|
|
38
|
+
avgSwolfScore?: number;
|
|
39
|
+
poolLength: number;
|
|
40
|
+
source: string;
|
|
41
|
+
swimType?: SwimType;
|
|
42
|
+
primaryStrokeType?: string;
|
|
43
|
+
waterTemp?: number;
|
|
44
|
+
poolLocation?: {
|
|
45
|
+
name: string;
|
|
46
|
+
address: string;
|
|
47
|
+
coordinates?: {
|
|
48
|
+
lat: number;
|
|
49
|
+
lng: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
notes?: string;
|
|
53
|
+
trainingTypes?: string[];
|
|
54
|
+
perceivedEffort?: string;
|
|
55
|
+
equipment?: string[];
|
|
56
|
+
avgDistancePerStroke?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface AggregatedSwimMetrics {
|
|
59
|
+
totalDistance: number;
|
|
60
|
+
longestDistance: number;
|
|
61
|
+
avgPace: string;
|
|
62
|
+
bestPace: string;
|
|
63
|
+
avgDistance: number;
|
|
64
|
+
avgHeartRate: number;
|
|
65
|
+
avgRestTime: number;
|
|
66
|
+
avgLaps: number;
|
|
67
|
+
sessionCount: number;
|
|
68
|
+
maxHeartRate: number;
|
|
69
|
+
avgRecoveryHeartRate: number;
|
|
70
|
+
totalCalories: number;
|
|
71
|
+
totalStrokeCount: number;
|
|
72
|
+
avgStrokeRate: number;
|
|
73
|
+
avgDistancePerStroke: number;
|
|
74
|
+
avgSwolfScore: number;
|
|
75
|
+
minHeartRate: number;
|
|
76
|
+
primaryStrokeType: string;
|
|
77
|
+
totalWorkoutTime: number;
|
|
78
|
+
swimTypeDistribution: {
|
|
79
|
+
counts: Record<string, number>;
|
|
80
|
+
percentages: Record<string, number>;
|
|
81
|
+
preferred: string;
|
|
82
|
+
total: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export declare enum MetricCategory {
|
|
86
|
+
PERFORMANCE = "performance",
|
|
87
|
+
HEALTH = "health",
|
|
88
|
+
TECHNIQUE = "technique",
|
|
89
|
+
ENERGY = "energy"
|
|
90
|
+
}
|
|
91
|
+
export interface MetricDefinition {
|
|
92
|
+
key: string;
|
|
93
|
+
label: string;
|
|
94
|
+
unit?: string;
|
|
95
|
+
category: MetricCategory;
|
|
96
|
+
description: string;
|
|
97
|
+
icon?: string;
|
|
98
|
+
isPerSession?: boolean;
|
|
99
|
+
isPerLap?: boolean;
|
|
100
|
+
formula?: string;
|
|
101
|
+
source?: string;
|
|
102
|
+
healthKitSource?: string;
|
|
103
|
+
availableFrom: {
|
|
104
|
+
watch: boolean;
|
|
105
|
+
manual: boolean;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export declare const METRICS_REGISTRY: Record<keyof SwimSession, MetricDefinition>;
|
|
109
|
+
export declare const MetricsHelper: {
|
|
110
|
+
getMetric: (key: keyof SwimSession) => MetricDefinition;
|
|
111
|
+
getSessionMetrics: () => MetricDefinition[];
|
|
112
|
+
getLapMetrics: () => MetricDefinition[];
|
|
113
|
+
getMetricsByCategory: (category: MetricCategory) => MetricDefinition[];
|
|
114
|
+
getAllMetricKeys: () => string[];
|
|
115
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @swimedge/metrics
|
|
4
|
+
* Shared metrics registry for frontend, backend, and watch app
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.MetricsHelper = exports.METRICS_REGISTRY = exports.MetricCategory = exports.SwimType = void 0;
|
|
8
|
+
var SwimType;
|
|
9
|
+
(function (SwimType) {
|
|
10
|
+
SwimType["INDOOR"] = "indoor";
|
|
11
|
+
SwimType["OPEN_WATER"] = "open_water";
|
|
12
|
+
})(SwimType || (exports.SwimType = SwimType = {}));
|
|
13
|
+
var MetricCategory;
|
|
14
|
+
(function (MetricCategory) {
|
|
15
|
+
MetricCategory["PERFORMANCE"] = "performance";
|
|
16
|
+
MetricCategory["HEALTH"] = "health";
|
|
17
|
+
MetricCategory["TECHNIQUE"] = "technique";
|
|
18
|
+
MetricCategory["ENERGY"] = "energy";
|
|
19
|
+
})(MetricCategory || (exports.MetricCategory = MetricCategory = {}));
|
|
20
|
+
exports.METRICS_REGISTRY = {
|
|
21
|
+
distance: {
|
|
22
|
+
key: 'distance',
|
|
23
|
+
label: 'Distance',
|
|
24
|
+
unit: 'm',
|
|
25
|
+
category: MetricCategory.PERFORMANCE,
|
|
26
|
+
description: 'Total distance swum',
|
|
27
|
+
icon: 'resize-outline',
|
|
28
|
+
isPerSession: true,
|
|
29
|
+
source: 'HealthKit: HKWorkout distance',
|
|
30
|
+
healthKitSource: 'HKWorkout.totalDistance',
|
|
31
|
+
availableFrom: { watch: true, manual: true },
|
|
32
|
+
},
|
|
33
|
+
time: {
|
|
34
|
+
key: 'time',
|
|
35
|
+
label: 'Duration',
|
|
36
|
+
unit: 's',
|
|
37
|
+
category: MetricCategory.PERFORMANCE,
|
|
38
|
+
description: 'Total workout duration',
|
|
39
|
+
icon: 'time-outline',
|
|
40
|
+
isPerSession: true,
|
|
41
|
+
source: 'HealthKit: HKWorkout duration',
|
|
42
|
+
healthKitSource: 'HKWorkout.duration',
|
|
43
|
+
availableFrom: { watch: true, manual: true },
|
|
44
|
+
},
|
|
45
|
+
activeTime: {
|
|
46
|
+
key: 'activeTime',
|
|
47
|
+
label: 'Active Time',
|
|
48
|
+
unit: 's',
|
|
49
|
+
category: MetricCategory.PERFORMANCE,
|
|
50
|
+
description: 'Time actively swimming',
|
|
51
|
+
icon: 'play-outline',
|
|
52
|
+
isPerSession: true,
|
|
53
|
+
source: 'Calculated from stroke detection with 5-second timeout',
|
|
54
|
+
availableFrom: { watch: true, manual: false },
|
|
55
|
+
},
|
|
56
|
+
restTime: {
|
|
57
|
+
key: 'restTime',
|
|
58
|
+
label: 'Rest Time',
|
|
59
|
+
unit: 's',
|
|
60
|
+
category: MetricCategory.PERFORMANCE,
|
|
61
|
+
description: 'Time resting',
|
|
62
|
+
icon: 'pause-outline',
|
|
63
|
+
isPerSession: true,
|
|
64
|
+
formula: 'Total Duration - Active Time',
|
|
65
|
+
availableFrom: { watch: true, manual: false },
|
|
66
|
+
},
|
|
67
|
+
laps: {
|
|
68
|
+
key: 'laps',
|
|
69
|
+
label: 'Laps',
|
|
70
|
+
unit: '',
|
|
71
|
+
category: MetricCategory.PERFORMANCE,
|
|
72
|
+
description: 'Pool lengths',
|
|
73
|
+
icon: 'layers-outline',
|
|
74
|
+
isPerSession: true,
|
|
75
|
+
formula: 'Distance ÷ Pool Length',
|
|
76
|
+
availableFrom: { watch: true, manual: true },
|
|
77
|
+
},
|
|
78
|
+
avgPace: {
|
|
79
|
+
key: 'avgPace',
|
|
80
|
+
label: 'Pace',
|
|
81
|
+
unit: '/100m',
|
|
82
|
+
category: MetricCategory.PERFORMANCE,
|
|
83
|
+
description: 'Average pace',
|
|
84
|
+
icon: 'speedometer-outline',
|
|
85
|
+
isPerSession: true,
|
|
86
|
+
formula: '(Active Time ÷ Distance) × 100',
|
|
87
|
+
availableFrom: { watch: true, manual: true },
|
|
88
|
+
},
|
|
89
|
+
poolLength: {
|
|
90
|
+
key: 'poolLength',
|
|
91
|
+
label: 'Pool Length',
|
|
92
|
+
unit: 'm',
|
|
93
|
+
category: MetricCategory.PERFORMANCE,
|
|
94
|
+
description: 'Pool length',
|
|
95
|
+
icon: 'resize-outline',
|
|
96
|
+
isPerSession: true,
|
|
97
|
+
source: 'User configuration setting',
|
|
98
|
+
availableFrom: { watch: true, manual: true },
|
|
99
|
+
},
|
|
100
|
+
swimType: {
|
|
101
|
+
key: 'swimType',
|
|
102
|
+
label: 'Swim Type',
|
|
103
|
+
unit: '',
|
|
104
|
+
category: MetricCategory.PERFORMANCE,
|
|
105
|
+
description: 'Indoor/Open Water',
|
|
106
|
+
icon: 'water-outline',
|
|
107
|
+
isPerSession: true,
|
|
108
|
+
source: 'User selection or workout location type',
|
|
109
|
+
availableFrom: { watch: true, manual: true },
|
|
110
|
+
},
|
|
111
|
+
waterTemp: {
|
|
112
|
+
key: 'waterTemp',
|
|
113
|
+
label: 'Water Temp',
|
|
114
|
+
unit: '°C',
|
|
115
|
+
category: MetricCategory.PERFORMANCE,
|
|
116
|
+
description: 'Water temperature',
|
|
117
|
+
icon: 'thermometer-outline',
|
|
118
|
+
isPerSession: true,
|
|
119
|
+
source: 'Apple Watch water temperature sensor',
|
|
120
|
+
availableFrom: { watch: true, manual: false },
|
|
121
|
+
},
|
|
122
|
+
avgHeartRate: {
|
|
123
|
+
key: 'avgHeartRate',
|
|
124
|
+
label: 'Avg Heart Rate',
|
|
125
|
+
unit: 'bpm',
|
|
126
|
+
category: MetricCategory.HEALTH,
|
|
127
|
+
description: 'Average heart rate',
|
|
128
|
+
icon: 'heart-outline',
|
|
129
|
+
isPerSession: true,
|
|
130
|
+
source: 'HealthKit: HKQuantityTypeIdentifierHeartRate',
|
|
131
|
+
healthKitSource: 'HKQuantityTypeIdentifierHeartRate',
|
|
132
|
+
availableFrom: { watch: true, manual: false },
|
|
133
|
+
},
|
|
134
|
+
maxHeartRate: {
|
|
135
|
+
key: 'maxHeartRate',
|
|
136
|
+
label: 'Max Heart Rate',
|
|
137
|
+
unit: 'bpm',
|
|
138
|
+
category: MetricCategory.HEALTH,
|
|
139
|
+
description: 'Maximum heart rate',
|
|
140
|
+
icon: 'trending-up-outline',
|
|
141
|
+
isPerSession: true,
|
|
142
|
+
source: 'HealthKit: HKQuantityTypeIdentifierHeartRate',
|
|
143
|
+
healthKitSource: 'HKQuantityTypeIdentifierHeartRate',
|
|
144
|
+
availableFrom: { watch: true, manual: false },
|
|
145
|
+
},
|
|
146
|
+
minHeartRate: {
|
|
147
|
+
key: 'minHeartRate',
|
|
148
|
+
label: 'Min Heart Rate',
|
|
149
|
+
unit: 'bpm',
|
|
150
|
+
category: MetricCategory.HEALTH,
|
|
151
|
+
description: 'Minimum heart rate',
|
|
152
|
+
icon: 'trending-down-outline',
|
|
153
|
+
isPerSession: true,
|
|
154
|
+
source: 'HealthKit: HKQuantityTypeIdentifierHeartRate',
|
|
155
|
+
healthKitSource: 'HKQuantityTypeIdentifierHeartRate',
|
|
156
|
+
availableFrom: { watch: true, manual: false },
|
|
157
|
+
},
|
|
158
|
+
calories: {
|
|
159
|
+
key: 'calories',
|
|
160
|
+
label: 'Calories',
|
|
161
|
+
unit: 'kcal',
|
|
162
|
+
category: MetricCategory.ENERGY,
|
|
163
|
+
description: 'Total calories',
|
|
164
|
+
icon: 'flame-outline',
|
|
165
|
+
isPerSession: true,
|
|
166
|
+
source: 'HealthKit: HKQuantityTypeIdentifierActiveEnergyBurned',
|
|
167
|
+
healthKitSource: 'HKQuantityTypeIdentifierActiveEnergyBurned',
|
|
168
|
+
availableFrom: { watch: true, manual: true },
|
|
169
|
+
},
|
|
170
|
+
totalStrokeCount: {
|
|
171
|
+
key: 'totalStrokeCount',
|
|
172
|
+
label: 'Total Strokes',
|
|
173
|
+
unit: '',
|
|
174
|
+
category: MetricCategory.TECHNIQUE,
|
|
175
|
+
description: 'Total strokes',
|
|
176
|
+
icon: 'hand-left-outline',
|
|
177
|
+
isPerSession: true,
|
|
178
|
+
source: 'Apple Watch accelerometer and gyroscope stroke detection',
|
|
179
|
+
availableFrom: { watch: true, manual: false },
|
|
180
|
+
},
|
|
181
|
+
avgStrokeRate: {
|
|
182
|
+
key: 'avgStrokeRate',
|
|
183
|
+
label: 'Stroke Rate',
|
|
184
|
+
unit: 'SPM',
|
|
185
|
+
category: MetricCategory.TECHNIQUE,
|
|
186
|
+
description: 'Strokes per minute',
|
|
187
|
+
icon: 'repeat-outline',
|
|
188
|
+
isPerSession: true,
|
|
189
|
+
formula: 'Total Strokes ÷ (Active Time in Minutes)',
|
|
190
|
+
availableFrom: { watch: true, manual: true },
|
|
191
|
+
},
|
|
192
|
+
avgDistancePerStroke: {
|
|
193
|
+
key: 'avgDistancePerStroke',
|
|
194
|
+
label: 'Distance/Stroke',
|
|
195
|
+
unit: 'm',
|
|
196
|
+
category: MetricCategory.TECHNIQUE,
|
|
197
|
+
description: 'Distance per stroke',
|
|
198
|
+
icon: 'arrow-forward-outline',
|
|
199
|
+
isPerSession: true,
|
|
200
|
+
formula: 'Distance ÷ Total Strokes',
|
|
201
|
+
availableFrom: { watch: true, manual: true },
|
|
202
|
+
},
|
|
203
|
+
avgSwolfScore: {
|
|
204
|
+
key: 'avgSwolfScore',
|
|
205
|
+
label: 'SWOLF',
|
|
206
|
+
unit: '',
|
|
207
|
+
category: MetricCategory.TECHNIQUE,
|
|
208
|
+
description: 'Efficiency score',
|
|
209
|
+
icon: 'trophy-outline',
|
|
210
|
+
isPerSession: true,
|
|
211
|
+
formula: 'Average of (Lap Time in Seconds + Lap Stroke Count) for each lap',
|
|
212
|
+
availableFrom: { watch: true, manual: true },
|
|
213
|
+
},
|
|
214
|
+
primaryStrokeType: {
|
|
215
|
+
key: 'primaryStrokeType',
|
|
216
|
+
label: 'Primary Stroke',
|
|
217
|
+
unit: '',
|
|
218
|
+
category: MetricCategory.TECHNIQUE,
|
|
219
|
+
description: 'Main stroke',
|
|
220
|
+
icon: 'hand-right-outline',
|
|
221
|
+
isPerSession: true,
|
|
222
|
+
source: 'Apple Watch motion pattern recognition',
|
|
223
|
+
availableFrom: { watch: true, manual: true },
|
|
224
|
+
},
|
|
225
|
+
lapSplitTimes: {
|
|
226
|
+
key: 'lapSplitTimes',
|
|
227
|
+
label: 'Split Times',
|
|
228
|
+
unit: 's',
|
|
229
|
+
category: MetricCategory.PERFORMANCE,
|
|
230
|
+
description: 'Lap times',
|
|
231
|
+
icon: 'stopwatch-outline',
|
|
232
|
+
isPerLap: true,
|
|
233
|
+
source: 'Tracked per lap completion',
|
|
234
|
+
availableFrom: { watch: true, manual: false },
|
|
235
|
+
},
|
|
236
|
+
lapHeartRates: {
|
|
237
|
+
key: 'lapHeartRates',
|
|
238
|
+
label: 'Lap HR',
|
|
239
|
+
unit: 'bpm',
|
|
240
|
+
category: MetricCategory.HEALTH,
|
|
241
|
+
description: 'HR per lap',
|
|
242
|
+
icon: 'heart-outline',
|
|
243
|
+
isPerLap: true,
|
|
244
|
+
source: 'HealthKit: HKQuantityTypeIdentifierHeartRate sampled at lap completion',
|
|
245
|
+
healthKitSource: 'HKQuantityTypeIdentifierHeartRate',
|
|
246
|
+
availableFrom: { watch: true, manual: false },
|
|
247
|
+
},
|
|
248
|
+
lapStrokeCounts: {
|
|
249
|
+
key: 'lapStrokeCounts',
|
|
250
|
+
label: 'Lap Strokes',
|
|
251
|
+
unit: '',
|
|
252
|
+
category: MetricCategory.TECHNIQUE,
|
|
253
|
+
description: 'Strokes per lap',
|
|
254
|
+
icon: 'hand-left-outline',
|
|
255
|
+
isPerLap: true,
|
|
256
|
+
source: 'Apple Watch accelerometer stroke detection per lap',
|
|
257
|
+
availableFrom: { watch: true, manual: false },
|
|
258
|
+
},
|
|
259
|
+
id: {
|
|
260
|
+
key: 'id',
|
|
261
|
+
label: 'ID',
|
|
262
|
+
category: MetricCategory.PERFORMANCE,
|
|
263
|
+
description: 'Session identifier',
|
|
264
|
+
isPerSession: true,
|
|
265
|
+
availableFrom: { watch: false, manual: false },
|
|
266
|
+
},
|
|
267
|
+
watchSessionId: {
|
|
268
|
+
key: 'watchSessionId',
|
|
269
|
+
label: 'Watch Session ID',
|
|
270
|
+
category: MetricCategory.PERFORMANCE,
|
|
271
|
+
description: 'Watch session identifier',
|
|
272
|
+
isPerSession: true,
|
|
273
|
+
availableFrom: { watch: true, manual: false },
|
|
274
|
+
},
|
|
275
|
+
date: {
|
|
276
|
+
key: 'date',
|
|
277
|
+
label: 'Date',
|
|
278
|
+
category: MetricCategory.PERFORMANCE,
|
|
279
|
+
description: 'Session date',
|
|
280
|
+
isPerSession: true,
|
|
281
|
+
availableFrom: { watch: true, manual: true },
|
|
282
|
+
},
|
|
283
|
+
source: {
|
|
284
|
+
key: 'source',
|
|
285
|
+
label: 'Source',
|
|
286
|
+
category: MetricCategory.PERFORMANCE,
|
|
287
|
+
description: 'Data source',
|
|
288
|
+
isPerSession: true,
|
|
289
|
+
availableFrom: { watch: false, manual: false },
|
|
290
|
+
},
|
|
291
|
+
lapTurnTimes: {
|
|
292
|
+
key: 'lapTurnTimes',
|
|
293
|
+
label: 'Turn Times',
|
|
294
|
+
unit: 's',
|
|
295
|
+
category: MetricCategory.TECHNIQUE,
|
|
296
|
+
description: 'Turn times per lap',
|
|
297
|
+
isPerLap: true,
|
|
298
|
+
availableFrom: { watch: true, manual: false },
|
|
299
|
+
},
|
|
300
|
+
lapStrokeTypes: {
|
|
301
|
+
key: 'lapStrokeTypes',
|
|
302
|
+
label: 'Stroke Types',
|
|
303
|
+
category: MetricCategory.TECHNIQUE,
|
|
304
|
+
description: 'Stroke types per lap',
|
|
305
|
+
isPerLap: true,
|
|
306
|
+
availableFrom: { watch: true, manual: false },
|
|
307
|
+
},
|
|
308
|
+
lapPaces: {
|
|
309
|
+
key: 'lapPaces',
|
|
310
|
+
label: 'Lap Paces',
|
|
311
|
+
unit: '/100m',
|
|
312
|
+
category: MetricCategory.PERFORMANCE,
|
|
313
|
+
description: 'Pace per lap',
|
|
314
|
+
isPerLap: true,
|
|
315
|
+
availableFrom: { watch: true, manual: false },
|
|
316
|
+
},
|
|
317
|
+
lapCalories: {
|
|
318
|
+
key: 'lapCalories',
|
|
319
|
+
label: 'Lap Calories',
|
|
320
|
+
unit: 'kcal',
|
|
321
|
+
category: MetricCategory.ENERGY,
|
|
322
|
+
description: 'Calories per lap',
|
|
323
|
+
isPerLap: true,
|
|
324
|
+
availableFrom: { watch: true, manual: false },
|
|
325
|
+
},
|
|
326
|
+
lapDistances: {
|
|
327
|
+
key: 'lapDistances',
|
|
328
|
+
label: 'Lap Distances',
|
|
329
|
+
unit: 'm',
|
|
330
|
+
category: MetricCategory.PERFORMANCE,
|
|
331
|
+
description: 'Distance per lap',
|
|
332
|
+
isPerLap: true,
|
|
333
|
+
availableFrom: { watch: true, manual: false },
|
|
334
|
+
},
|
|
335
|
+
lapSwolfScores: {
|
|
336
|
+
key: 'lapSwolfScores',
|
|
337
|
+
label: 'Lap SWOLF',
|
|
338
|
+
category: MetricCategory.TECHNIQUE,
|
|
339
|
+
description: 'SWOLF per lap',
|
|
340
|
+
isPerLap: true,
|
|
341
|
+
availableFrom: { watch: true, manual: false },
|
|
342
|
+
},
|
|
343
|
+
lapStrokeRates: {
|
|
344
|
+
key: 'lapStrokeRates',
|
|
345
|
+
label: 'Lap Stroke Rate',
|
|
346
|
+
unit: 'SPM',
|
|
347
|
+
category: MetricCategory.TECHNIQUE,
|
|
348
|
+
description: 'Strokes per minute per lap',
|
|
349
|
+
icon: 'repeat-outline',
|
|
350
|
+
isPerLap: true,
|
|
351
|
+
formula: 'Lap Strokes ÷ (Lap Time in Minutes)',
|
|
352
|
+
availableFrom: { watch: true, manual: false },
|
|
353
|
+
},
|
|
354
|
+
lapDistancesPerStroke: {
|
|
355
|
+
key: 'lapDistancesPerStroke',
|
|
356
|
+
label: 'Lap Distance/Stroke',
|
|
357
|
+
unit: 'm',
|
|
358
|
+
category: MetricCategory.TECHNIQUE,
|
|
359
|
+
description: 'Distance covered per stroke per lap',
|
|
360
|
+
icon: 'arrow-forward-outline',
|
|
361
|
+
isPerLap: true,
|
|
362
|
+
formula: 'Lap Distance ÷ Lap Strokes',
|
|
363
|
+
availableFrom: { watch: true, manual: false },
|
|
364
|
+
},
|
|
365
|
+
poolLocation: {
|
|
366
|
+
key: 'poolLocation',
|
|
367
|
+
label: 'Pool Location',
|
|
368
|
+
category: MetricCategory.PERFORMANCE,
|
|
369
|
+
description: 'Pool location details',
|
|
370
|
+
isPerSession: true,
|
|
371
|
+
availableFrom: { watch: false, manual: true },
|
|
372
|
+
},
|
|
373
|
+
notes: {
|
|
374
|
+
key: 'notes',
|
|
375
|
+
label: 'Notes',
|
|
376
|
+
category: MetricCategory.PERFORMANCE,
|
|
377
|
+
description: 'Session notes',
|
|
378
|
+
isPerSession: true,
|
|
379
|
+
availableFrom: { watch: false, manual: true },
|
|
380
|
+
},
|
|
381
|
+
trainingTypes: {
|
|
382
|
+
key: 'trainingTypes',
|
|
383
|
+
label: 'Training Types',
|
|
384
|
+
category: MetricCategory.PERFORMANCE,
|
|
385
|
+
description: 'Training types',
|
|
386
|
+
isPerSession: true,
|
|
387
|
+
availableFrom: { watch: false, manual: true },
|
|
388
|
+
},
|
|
389
|
+
perceivedEffort: {
|
|
390
|
+
key: 'perceivedEffort',
|
|
391
|
+
label: 'Perceived Effort',
|
|
392
|
+
category: MetricCategory.PERFORMANCE,
|
|
393
|
+
description: 'Effort rating',
|
|
394
|
+
isPerSession: true,
|
|
395
|
+
availableFrom: { watch: false, manual: true },
|
|
396
|
+
},
|
|
397
|
+
equipment: {
|
|
398
|
+
key: 'equipment',
|
|
399
|
+
label: 'Equipment',
|
|
400
|
+
category: MetricCategory.PERFORMANCE,
|
|
401
|
+
description: 'Equipment used',
|
|
402
|
+
isPerSession: true,
|
|
403
|
+
availableFrom: { watch: false, manual: true },
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
exports.MetricsHelper = {
|
|
407
|
+
getMetric: (key) => exports.METRICS_REGISTRY[key],
|
|
408
|
+
getSessionMetrics: () => Object.values(exports.METRICS_REGISTRY).filter((m) => m.isPerSession),
|
|
409
|
+
getLapMetrics: () => Object.values(exports.METRICS_REGISTRY).filter((m) => m.isPerLap),
|
|
410
|
+
getMetricsByCategory: (category) => Object.values(exports.METRICS_REGISTRY).filter((m) => m.category === category),
|
|
411
|
+
getAllMetricKeys: () => Object.keys(exports.METRICS_REGISTRY),
|
|
412
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@swimedge/metrics",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared metrics registry for SwimEdge (frontend, backend, watch)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"watch": "tsc --watch",
|
|
19
|
+
"export-json": "node scripts/export-json.js"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"metrics",
|
|
23
|
+
"swimming",
|
|
24
|
+
"shared"
|
|
25
|
+
],
|
|
26
|
+
"author": "SwimEdge",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"esbuild-register": "^3.6.0",
|
|
33
|
+
"typescript": "^5.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|