@sports-alliance/sports-lib 7.0.0 → 7.0.3

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 (112) hide show
  1. package/.github/workflows/test.yml +2 -2
  2. package/lib/cjs/activities/activity.types.d.ts +6 -3
  3. package/lib/cjs/activities/activity.types.js +9 -4
  4. package/lib/cjs/activities/devices/device.d.ts +1 -0
  5. package/lib/cjs/activities/devices/device.interface.d.ts +1 -0
  6. package/lib/cjs/activities/devices/device.js +1 -0
  7. package/lib/cjs/activities/devices/device.json.interface.d.ts +1 -0
  8. package/lib/cjs/data/data.cycling-position.js +1 -1
  9. package/lib/cjs/data/data.feeling.js +1 -1
  10. package/lib/cjs/data/data.interface.js +1 -1
  11. package/lib/cjs/data/data.rpe.js +1 -1
  12. package/lib/cjs/data/data.store.export.spec.js +28 -14
  13. package/lib/cjs/data/data.store.js +1 -1
  14. package/lib/cjs/events/adapters/file-type.enum.js +1 -1
  15. package/lib/cjs/events/adapters/importers/fit/importer.fit.coros.device.names.js +2 -1
  16. package/lib/cjs/events/adapters/importers/fit/importer.fit.d.ts +2 -1
  17. package/lib/cjs/events/adapters/importers/fit/importer.fit.garmin.device.names.js +61 -2
  18. package/lib/cjs/events/adapters/importers/fit/importer.fit.garmin.profile.data.js +867 -855
  19. package/lib/cjs/events/adapters/importers/fit/importer.fit.garmin.profile.mapper.spec.d.ts +1 -0
  20. package/lib/cjs/events/adapters/importers/fit/importer.fit.garmin.profile.mapper.spec.js +34 -0
  21. package/lib/cjs/events/adapters/importers/fit/importer.fit.integration.spec.js +22 -8
  22. package/lib/cjs/events/adapters/importers/fit/importer.fit.js +24 -2
  23. package/lib/cjs/events/adapters/importers/fit/importer.fit.suunto.device.names.js +1 -0
  24. package/lib/cjs/events/adapters/importers/fit/importer.fit.wahoo.device.names.js +5 -1
  25. package/lib/cjs/events/adapters/importers/gpx/importer.gpx.integration.spec.js +22 -8
  26. package/lib/cjs/events/adapters/importers/gpx/importer.gpx.js +1 -1
  27. package/lib/cjs/events/adapters/importers/gpx/importer.gpx.spec.js +50 -0
  28. package/lib/cjs/events/adapters/importers/suunto/importer.suunto.activity.ids.js +1 -1
  29. package/lib/cjs/events/adapters/importers/suunto/importer.suunto.device.names.js +1 -0
  30. package/lib/cjs/events/adapters/importers/suunto/importer.suunto.integration.spec.js +22 -8
  31. package/lib/cjs/events/adapters/importers/tcx/importer.tcx.integration.spec.js +22 -8
  32. package/lib/cjs/events/utilities/activity.utilities.js +7 -7
  33. package/lib/cjs/events/utilities/grade-calculator/low-pass-filter.js +3 -3
  34. package/lib/cjs/events/utilities/helpers.d.ts +1 -1
  35. package/lib/cjs/events/utilities/helpers.js +19 -19
  36. package/lib/cjs/index.d.ts +1 -1
  37. package/lib/cjs/index.js +5 -1
  38. package/lib/cjs/laps/lap.types.js +1 -1
  39. package/lib/cjs/meta-data/event-meta-data.interface.js +1 -1
  40. package/lib/cjs/privacy/privacy.class.interface.js +1 -1
  41. package/lib/cjs/specs/activities-parsing.integration.spec.js +75 -61
  42. package/lib/cjs/specs/strava-streams-compliance.spec.js +22 -8
  43. package/lib/cjs/streams/compressed.stream.interface.js +2 -2
  44. package/lib/cjs/tiles/tile.settings.interface.js +5 -5
  45. package/lib/cjs/users/settings/dashboard/user.dashboard.settings.interface.js +1 -1
  46. package/lib/cjs/users/settings/user.app.settings.interface.js +1 -1
  47. package/lib/cjs/users/settings/user.chart.settings.interface.js +3 -3
  48. package/lib/cjs/users/settings/user.map.settings.interface.js +2 -2
  49. package/lib/cjs/users/settings/user.unit.settings.interface.js +9 -9
  50. package/lib/esm/activities/activity.types.d.ts +6 -3
  51. package/lib/esm/activities/activity.types.js +7 -2
  52. package/lib/esm/activities/devices/device.d.ts +1 -0
  53. package/lib/esm/activities/devices/device.interface.d.ts +1 -0
  54. package/lib/esm/activities/devices/device.js +1 -0
  55. package/lib/esm/activities/devices/device.json.interface.d.ts +1 -0
  56. package/lib/esm/data/data.store.export.spec.js +6 -6
  57. package/lib/esm/data/data.store.js +1 -1
  58. package/lib/esm/events/adapters/importers/fit/importer.fit.coros.device.names.js +2 -1
  59. package/lib/esm/events/adapters/importers/fit/importer.fit.d.ts +2 -1
  60. package/lib/esm/events/adapters/importers/fit/importer.fit.garmin.device.names.js +61 -2
  61. package/lib/esm/events/adapters/importers/fit/importer.fit.garmin.profile.data.js +867 -855
  62. package/lib/esm/events/adapters/importers/fit/importer.fit.garmin.profile.mapper.spec.d.ts +1 -0
  63. package/lib/esm/events/adapters/importers/fit/importer.fit.garmin.profile.mapper.spec.js +32 -0
  64. package/lib/esm/events/adapters/importers/fit/importer.fit.js +24 -2
  65. package/lib/esm/events/adapters/importers/fit/importer.fit.suunto.device.names.js +1 -0
  66. package/lib/esm/events/adapters/importers/fit/importer.fit.wahoo.device.names.js +5 -1
  67. package/lib/esm/events/adapters/importers/gpx/importer.gpx.js +1 -1
  68. package/lib/esm/events/adapters/importers/gpx/importer.gpx.spec.js +17 -0
  69. package/lib/esm/events/adapters/importers/suunto/importer.suunto.device.names.js +1 -0
  70. package/lib/esm/events/utilities/activity.utilities.js +7 -7
  71. package/lib/esm/events/utilities/grade-calculator/low-pass-filter.js +3 -3
  72. package/lib/esm/events/utilities/helpers.d.ts +1 -1
  73. package/lib/esm/index.d.ts +1 -1
  74. package/lib/esm/specs/activities-parsing.integration.spec.js +53 -53
  75. package/package.json +21 -21
  76. package/.editorconfig +0 -14
  77. package/.eslintignore +0 -4
  78. package/.eslintrc.js +0 -9
  79. package/.prettierignore +0 -24
  80. package/.prettierrc.json +0 -12
  81. package/.vscode/launch.json +0 -49
  82. package/.vscode/tasks.json +0 -34
  83. package/CODE_OF_CONDUCT.md +0 -76
  84. package/debug-fit-import.js +0 -10
  85. package/jest.config.js +0 -23
  86. package/lib/cjs/package.json +0 -1
  87. package/lib/cjs/specs/fixtures/streams/strava/rides/3171472783.json +0 -52534
  88. package/lib/cjs/specs/fixtures/streams/strava/rides/3171487458.json +0 -78818
  89. package/lib/cjs/specs/fixtures/streams/strava/rides/343080886.json +0 -105090
  90. package/lib/cjs/specs/fixtures/streams/strava/rides/5910143591.json +0 -110711
  91. package/lib/cjs/specs/fixtures/streams/strava/runs/2451375851.json +0 -74846
  92. package/lib/cjs/specs/fixtures/streams/strava/runs/2709634581.json +0 -66817
  93. package/lib/cjs/specs/fixtures/streams/strava/runs/3156040843.json +0 -17594
  94. package/lib/cjs/specs/fixtures/streams/strava/runs/3182900697.json +0 -17322
  95. package/lib/cjs/specs/fixtures/streams/strava/runs/3183465494.json +0 -20463
  96. package/lib/cjs/specs/fixtures/streams/strava/runs/3183490558.json +0 -58202
  97. package/lib/esm/specs/fixtures/streams/strava/rides/3171472783.json +0 -52534
  98. package/lib/esm/specs/fixtures/streams/strava/rides/3171487458.json +0 -78818
  99. package/lib/esm/specs/fixtures/streams/strava/rides/343080886.json +0 -105090
  100. package/lib/esm/specs/fixtures/streams/strava/rides/5910143591.json +0 -110711
  101. package/lib/esm/specs/fixtures/streams/strava/runs/2451375851.json +0 -74846
  102. package/lib/esm/specs/fixtures/streams/strava/runs/2709634581.json +0 -66817
  103. package/lib/esm/specs/fixtures/streams/strava/runs/3156040843.json +0 -17594
  104. package/lib/esm/specs/fixtures/streams/strava/runs/3182900697.json +0 -17322
  105. package/lib/esm/specs/fixtures/streams/strava/runs/3183465494.json +0 -20463
  106. package/lib/esm/specs/fixtures/streams/strava/runs/3183490558.json +0 -58202
  107. package/test_output.log +0 -162
  108. package/test_output_2.log +0 -168
  109. package/test_output_3.log +0 -168
  110. package/tsconfig.cjs.json +0 -7
  111. package/tsconfig.esm.json +0 -8
  112. package/tsconfig.lib.json +0 -8
@@ -0,0 +1,32 @@
1
+ import { GarminProfileMapper } from './importer.fit.garmin.profile.mapper';
2
+ describe('GarminProfileMapper', () => {
3
+ describe('getSportName', () => {
4
+ it('should translate sport ID', () => {
5
+ // Assuming ID 1 is Running
6
+ expect(GarminProfileMapper.getSportName(1)).toBe('running');
7
+ });
8
+ it('should return null for unknown sport', () => {
9
+ expect(GarminProfileMapper.getSportName(99999)).toBeNull();
10
+ });
11
+ it('should return names in snake_case (regression check for extraction logic)', () => {
12
+ // Checking ID 4 which is 'fitness_equipment' (was 'fitnessEquipment' in raw SDK)
13
+ expect(GarminProfileMapper.getSportName(4)).toBe('fitness_equipment');
14
+ });
15
+ });
16
+ describe('getSubSportName', () => {
17
+ it('should translate sub sport ID', () => {
18
+ // Assuming ID 1 is Treadmill
19
+ expect(GarminProfileMapper.getSubSportName(1)).toBe('treadmill');
20
+ });
21
+ it('should return names in snake_case (regression check for extraction logic)', () => {
22
+ // Checking ID 6 which is 'indoor_cycling' (was 'indoorCycling' in raw SDK)
23
+ expect(GarminProfileMapper.getSubSportName(6)).toBe('indoor_cycling');
24
+ });
25
+ it('should correctly map Enduro and Downhill MTB (IDs 153/154)', () => {
26
+ // ID 153 = mountain_enduro
27
+ expect(GarminProfileMapper.getSubSportName(153)).toBe('mountain_enduro');
28
+ // ID 154 = mountain_downhill
29
+ expect(GarminProfileMapper.getSubSportName(154)).toBe('mountain_downhill');
30
+ });
31
+ });
32
+ });
@@ -116,8 +116,8 @@ import FitFileParser from 'fit-file-parser';
116
116
  // Threshold to detect that session.timestamp are not trustable (when exceeding 15% of session.total_elapsed_time)
117
117
  const INVALID_DATES_ELAPSED_TIME_RATIO_THRESHOLD = 1.15;
118
118
  export class EventImporterFIT {
119
- static getFromArrayBuffer(arrayBuffer, options = ActivityParsingOptions.DEFAULT, name = 'New Event') {
120
- return __awaiter(this, void 0, void 0, function* () {
119
+ static getFromArrayBuffer(arrayBuffer_1) {
120
+ return __awaiter(this, arguments, void 0, function* (arrayBuffer, options = ActivityParsingOptions.DEFAULT, name = 'New Event') {
121
121
  return new Promise((resolve, reject) => {
122
122
  const fitFileParser = new FitFileParser({
123
123
  force: true,
@@ -128,6 +128,7 @@ export class EventImporterFIT {
128
128
  mode: 'both'
129
129
  });
130
130
  fitFileParser.parse(arrayBuffer, (error, fitDataObject) => {
131
+ var _a;
131
132
  if (error) {
132
133
  // For now, assume any error from parser on this file means it's broken/empty in a way we treat as EmptyEventLibError
133
134
  // to satisfy existing tests. Or ideally we wrap in a generic EventLibError.
@@ -139,6 +140,27 @@ export class EventImporterFIT {
139
140
  reject(new EmptyEventLibError());
140
141
  return;
141
142
  }
143
+ // Check if we have length data at the top level (new parser behavior or missing mapping)
144
+ if (fitDataObject.lengths && fitDataObject.lengths.length > 0) {
145
+ (_a = fitDataObject.sessions) === null || _a === void 0 ? void 0 : _a.forEach((session) => {
146
+ var _a;
147
+ const sessionStartTime = new Date(session.start_time).getTime();
148
+ const sessionEndTime = sessionStartTime + (session.total_elapsed_time || 0) * 1000;
149
+ session.lengths = fitDataObject.lengths.filter((length) => {
150
+ const lengthTime = new Date(length.timestamp || length.start_time).getTime();
151
+ return lengthTime >= sessionStartTime && lengthTime < sessionEndTime;
152
+ });
153
+ // Also distribute to laps
154
+ (_a = session.laps) === null || _a === void 0 ? void 0 : _a.forEach((lap) => {
155
+ const lapStartTime = new Date(lap.start_time).getTime();
156
+ const lapEndTime = lapStartTime + (lap.total_elapsed_time || 0) * 1000;
157
+ lap.lengths = fitDataObject.lengths.filter((length) => {
158
+ const lengthTime = new Date(length.timestamp || length.start_time).getTime();
159
+ return lengthTime >= lapStartTime && lengthTime < lapEndTime;
160
+ });
161
+ });
162
+ });
163
+ }
142
164
  // Iterate over the sessions and create their activities
143
165
  const activities = fitDataObject.sessions.map((sessionObject) => {
144
166
  // Get the activity from the sessionObject
@@ -1,5 +1,6 @@
1
1
  import { ImporterSuuntoDeviceNames } from '../suunto/importer.suunto.device.names';
2
2
  export const ImporterFitSuuntoDeviceNames = {
3
+ 19: ImporterSuuntoDeviceNames.Ambit2,
3
4
  28: ImporterSuuntoDeviceNames.Brighton,
4
5
  29: ImporterSuuntoDeviceNames.Amsterdam,
5
6
  34: ImporterSuuntoDeviceNames.Ibiza,
@@ -1,6 +1,10 @@
1
1
  export const ImporterFitWahooDeviceNames = {
2
2
  28: 'ELEMNT',
3
+ 30: 'ELEMNT MINI',
3
4
  31: 'ELEMNT BOLT',
5
+ 33: 'ELEMNT RIVAL',
4
6
  37: 'ELEMNT ROAM',
5
- 43: 'ELEMNT BOLT'
7
+ 38: 'ELEMNT MINI',
8
+ 43: 'ELEMNT BOLT',
9
+ 47: 'ELEMNT ROAM'
6
10
  };
@@ -54,7 +54,7 @@ export class EventImporterGPX {
54
54
  // @todo for routes add a separate parser
55
55
  const endDate = isActivity
56
56
  ? new Date(samples[samples.length - 1].time[0])
57
- : new Date(startDate.getTime() + samples.length * 1000);
57
+ : new Date(startDate.getTime() + (samples.length > 0 ? samples.length - 1 : 0) * 1000);
58
58
  let activityType = isActivity ? ActivityTypes.unknown : ActivityTypes.route;
59
59
  if (trackOrRoute.type && ActivityTypes[trackOrRoute.type]) {
60
60
  activityType = ActivityTypes[trackOrRoute.type];
@@ -51,4 +51,21 @@ describe('importer.gpx', () => {
51
51
  const result = yield EventImporterGPX.getFromString(gpxString, xmldom.DOMParser);
52
52
  expect(result.getFirstActivity().name).toEqual('Meylan Road Cycling');
53
53
  }));
54
+ it('parses route.gpx from samples', () => __awaiter(void 0, void 0, void 0, function* () {
55
+ const fs = yield import('fs');
56
+ const path = yield import('path');
57
+ const samplesDir = path.resolve(__dirname, '../../../../../samples/gpx');
58
+ const filePath = path.join(samplesDir, 'route.gpx');
59
+ const fileString = fs.readFileSync(filePath, 'utf-8');
60
+ const result = yield EventImporterGPX.getFromString(fileString, xmldom.DOMParser);
61
+ expect(result.getActivities().length).toBeGreaterThan(0);
62
+ const activity = result.getFirstActivity();
63
+ expect(activity.type).toEqual('Route');
64
+ const distance = activity.getStat('Distance');
65
+ expect(distance).toBeDefined();
66
+ expect(distance.getValue()).toBeGreaterThan(0);
67
+ // Check if the number of samples matches the number of points in the GPX
68
+ const latStream = activity.getStream('Latitude');
69
+ expect(latStream.getData().length).toBe(2987); // I counted 2987 rtept earlier
70
+ }));
54
71
  });
@@ -18,6 +18,7 @@ export const ImporterSuuntoDeviceNames = {
18
18
  Gdansk: 'Spartan WHR Baro',
19
19
  'Spartan Sport Wrist HR Baro': 'Spartan WHR Baro',
20
20
  Helsinki: '3 Fitness',
21
+ Ambit2: 'Ambit2',
21
22
  'Ambit3 Sport': 'Ambit3 Sport',
22
23
  'Ambit3 Peak': 'Ambit3 Peak',
23
24
  'Ambit3 Run': 'Ambit3 Run',
@@ -108,8 +108,8 @@ const ALTITUDE_SPIKES_FILTER_WIN = 3;
108
108
  // Fix abnormal streams
109
109
  const SPEED_STREAM_STD_DEV_THRESHOLD_DEFAULT = 25 / 3.6; // Kph to mps
110
110
  const SPEED_STREAM_STD_DEV_THRESHOLD_MAP = new Map([
111
- [ActivityTypeGroups.Running, 15 / 3.6],
112
- [ActivityTypeGroups.Cycling, 27 / 3.6],
111
+ [ActivityTypeGroups.Running, 15 / 3.6], // kph to m/s
112
+ [ActivityTypeGroups.Cycling, 27 / 3.6], // kph to m/s
113
113
  [ActivityTypeGroups.Swimming, 5 / 3.6] // kph to m/s
114
114
  ]);
115
115
  export class ActivityUtilities {
@@ -275,7 +275,7 @@ export class ActivityUtilities {
275
275
  this.shapeStream(DataSpeed.type, activity, squashedSpeedData => {
276
276
  // Grade stream
277
277
  const SPEED_KALMAN_SMOOTHING = {
278
- R: 0.01,
278
+ R: 0.01, // Speed model calculation is something stable
279
279
  Q: speedStdDev * 2 // We intend to get a measurement error which can be under and over std dev (explaining the double factor)
280
280
  };
281
281
  // Apply kalman filter
@@ -291,7 +291,7 @@ export class ActivityUtilities {
291
291
  // Always include derived base streams (like Pace), but conditionally include unit variants
292
292
  const includeUnitVariants = !activity.parseOptions || activity.parseOptions.generateUnitStreams;
293
293
  activity.addStreams(this.createUnitStreamsFromStreams(activity.getAllStreams(), activity.type, undefined, {
294
- includeDerivedTypes: true,
294
+ includeDerivedTypes: true, // Always include derived base types (Pace etc)
295
295
  includeUnitVariants
296
296
  }));
297
297
  }
@@ -757,9 +757,9 @@ export class ActivityUtilities {
757
757
  return prevPosition;
758
758
  }
759
759
  if (prevPosition && position) {
760
- distance += this.round(this.geoLibAdapter.getDistance([prevPosition, position]), 2);
760
+ distance += this.geoLibAdapter.getDistance([prevPosition, position]);
761
761
  }
762
- streamData[index] = distance;
762
+ streamData[index] = this.round(distance, 2);
763
763
  return position;
764
764
  });
765
765
  if (!activity.hasStreamData(DataDistance.type)) {
@@ -812,7 +812,7 @@ export class ActivityUtilities {
812
812
  this.shapeStream(DataGradeSmooth.type, activity, squashedGradeData => {
813
813
  // Grade stream
814
814
  const GRADE_KALMAN_SMOOTHING = {
815
- R: 0.01,
815
+ R: 0.01, // Grade model is stable
816
816
  Q: 0.5 // Grade measurement error which can be expected
817
817
  };
818
818
  // Predict proper grade values
@@ -1,12 +1,12 @@
1
1
  export class LowPassFilter {
2
+ static smooth(values, smoothing = 0.5) {
3
+ return new LowPassFilter(smoothing).smoothArray(values);
4
+ }
2
5
  constructor(smoothing) {
3
6
  this._smoothing = smoothing || 0.5; // must be smaller than 1
4
7
  this._buffer = []; // FIFO queue
5
8
  this._bufferMaxSize = 10;
6
9
  }
7
- static smooth(values, smoothing = 0.5) {
8
- return new LowPassFilter(smoothing).smoothArray(values);
9
- }
10
10
  /**
11
11
  * Init buffer with array of values
12
12
  * @param {number[]} values
@@ -1,4 +1,4 @@
1
- export declare function isNumberOrString(property: any): boolean;
1
+ export declare function isNumberOrString(property: any): property is string | number;
2
2
  export declare function isNumber(property: any): boolean;
3
3
  /**
4
4
  * Converts speed from m/s to pace as of seconds per km
@@ -20,7 +20,7 @@ export declare class SportsLib {
20
20
  * @param arrayBuffer
21
21
  * @param options
22
22
  */
23
- static importFromFit(arrayBuffer: ArrayBuffer, options?: ActivityParsingOptions): Promise<EventInterface>;
23
+ static importFromFit(arrayBuffer: ArrayBuffer | Buffer<ArrayBuffer>, options?: ActivityParsingOptions): Promise<EventInterface>;
24
24
  /**
25
25
  * Parses and returns an event using Suunto format
26
26
  * @param jsonString