@sports-alliance/sports-lib 7.1.2 → 7.2.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 (70) hide show
  1. package/.agent/README.md +16 -0
  2. package/inspect_jump_raw.js +24 -0
  3. package/lib/cjs/data/data-ground-contact-time-balance-left.d.ts +4 -0
  4. package/lib/cjs/data/data-ground-contact-time-balance-left.js +8 -0
  5. package/lib/cjs/data/data-ground-contact-time-balance-right.d.ts +4 -0
  6. package/lib/cjs/data/data-ground-contact-time-balance-right.js +8 -0
  7. package/lib/cjs/data/data-ground-contact-time-balance.d.ts +3 -0
  8. package/lib/cjs/data/data-ground-contact-time-balance.js +7 -0
  9. package/lib/cjs/data/data-stance-time-balance-left.d.ts +1 -0
  10. package/lib/cjs/data/data-stance-time-balance-left.js +1 -0
  11. package/lib/cjs/data/data-stance-time-balance-right.d.ts +1 -0
  12. package/lib/cjs/data/data-stance-time-balance-right.js +1 -0
  13. package/lib/cjs/data/data-stance-time-balance.d.ts +1 -0
  14. package/lib/cjs/data/data-stance-time-balance.js +1 -0
  15. package/lib/cjs/data/data.depth-max.d.ts +5 -0
  16. package/lib/cjs/data/data.depth-max.js +9 -0
  17. package/lib/cjs/data/data.depth.d.ts +5 -0
  18. package/lib/cjs/data/data.depth.js +9 -0
  19. package/lib/cjs/data/data.fitness-age.d.ts +5 -0
  20. package/lib/cjs/data/data.fitness-age.js +9 -0
  21. package/lib/cjs/data/data.ground-contact-time-avg.d.ts +5 -0
  22. package/lib/cjs/data/data.ground-contact-time-avg.js +9 -0
  23. package/lib/cjs/data/data.ground-contact-time-max.d.ts +5 -0
  24. package/lib/cjs/data/data.ground-contact-time-max.js +9 -0
  25. package/lib/cjs/data/data.ground-contact-time-min.d.ts +5 -0
  26. package/lib/cjs/data/data.ground-contact-time-min.js +9 -0
  27. package/lib/cjs/data/data.ground-contact-time.d.ts +5 -0
  28. package/lib/cjs/data/data.ground-contact-time.js +9 -0
  29. package/lib/cjs/data/data.jump-event.d.ts +6 -1
  30. package/lib/cjs/data/data.jump-stats.d.ts +91 -0
  31. package/lib/cjs/data/data.jump-stats.js +112 -0
  32. package/lib/cjs/data/data.max-hr-setting.d.ts +5 -0
  33. package/lib/cjs/data/data.max-hr-setting.js +9 -0
  34. package/lib/cjs/data/data.stance-time.d.ts +1 -0
  35. package/lib/cjs/data/data.stance-time.js +1 -0
  36. package/lib/cjs/data/data.store.js +58 -1
  37. package/lib/cjs/data/data.vertical-oscillation-avg.d.ts +5 -0
  38. package/lib/cjs/data/data.vertical-oscillation-avg.js +9 -0
  39. package/lib/cjs/data/data.vertical-oscillation-max.d.ts +5 -0
  40. package/lib/cjs/data/data.vertical-oscillation-max.js +9 -0
  41. package/lib/cjs/data/data.vertical-oscillation-min.d.ts +5 -0
  42. package/lib/cjs/data/data.vertical-oscillation-min.js +9 -0
  43. package/lib/cjs/events/adapters/importers/fit/importer.fit.js +112 -9
  44. package/lib/cjs/events/adapters/importers/fit/importer.fit.mapper.js +23 -0
  45. package/lib/cjs/events/adapters/importers/fit/importer.fit.mtb.spec.js +34 -6
  46. package/lib/cjs/events/adapters/importers/suunto/importer.suunto.integration.spec.js +87 -3
  47. package/lib/cjs/events/adapters/importers/suunto/importer.suunto.json.js +86 -6
  48. package/lib/cjs/events/utilities/activity.utilities.js +38 -2
  49. package/lib/esm/data/data-ground-contact-time-balance-left.d.ts +4 -0
  50. package/lib/esm/data/data-ground-contact-time-balance-right.d.ts +4 -0
  51. package/lib/esm/data/data-ground-contact-time-balance.d.ts +3 -0
  52. package/lib/esm/data/data-stance-time-balance-left.d.ts +1 -0
  53. package/lib/esm/data/data-stance-time-balance-right.d.ts +1 -0
  54. package/lib/esm/data/data-stance-time-balance.d.ts +1 -0
  55. package/lib/esm/data/data.depth-max.d.ts +5 -0
  56. package/lib/esm/data/data.depth.d.ts +5 -0
  57. package/lib/esm/data/data.fitness-age.d.ts +5 -0
  58. package/lib/esm/data/data.ground-contact-time-avg.d.ts +5 -0
  59. package/lib/esm/data/data.ground-contact-time-max.d.ts +5 -0
  60. package/lib/esm/data/data.ground-contact-time-min.d.ts +5 -0
  61. package/lib/esm/data/data.ground-contact-time.d.ts +5 -0
  62. package/lib/esm/data/data.jump-event.d.ts +6 -1
  63. package/lib/esm/data/data.jump-stats.d.ts +91 -0
  64. package/lib/esm/data/data.max-hr-setting.d.ts +5 -0
  65. package/lib/esm/data/data.stance-time.d.ts +1 -0
  66. package/lib/esm/data/data.vertical-oscillation-avg.d.ts +5 -0
  67. package/lib/esm/data/data.vertical-oscillation-max.d.ts +5 -0
  68. package/lib/esm/data/data.vertical-oscillation-min.d.ts +5 -0
  69. package/lib/esm/index.js +555 -12
  70. package/package.json +2 -2
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DataVerticalOscillationMin = void 0;
4
+ const data_number_1 = require("./data.number");
5
+ class DataVerticalOscillationMin extends data_number_1.DataNumber {
6
+ }
7
+ exports.DataVerticalOscillationMin = DataVerticalOscillationMin;
8
+ DataVerticalOscillationMin.type = 'Minimum Vertical Oscillation';
9
+ DataVerticalOscillationMin.unit = 'mm';
@@ -129,6 +129,7 @@ const data_cycling_standing_time_1 = require("../../../../data/data.cycling-stan
129
129
  const data_cycling_seated_time_1 = require("../../../../data/data.cycling-seated-time");
130
130
  const data_cycling_position_1 = require("../../../../data/data.cycling-position");
131
131
  const data_rider_position_change_event_1 = require("../../../../data/data.rider-position-change-event");
132
+ const data_ground_contact_time_avg_1 = require("../../../../data/data.ground-contact-time-avg");
132
133
  const data_stance_time_1 = require("../../../../data/data.stance-time");
133
134
  const data_vertical_oscillation_1 = require("../../../../data/data.vertical-oscillation");
134
135
  const data_vertical_ratio_1 = require("../../../../data/data.vertical-ratio");
@@ -166,6 +167,7 @@ const data_age_1 = require("../../../../data/data.age");
166
167
  const data_gender_1 = require("../../../../data/data.gender");
167
168
  const data_avg_grit_1 = require("../../../../data/data.avg-grit");
168
169
  const data_jump_event_1 = require("../../../../data/data.jump-event");
170
+ const data_jump_stats_1 = require("../../../../data/data.jump-stats");
169
171
  // Threshold to detect that session.timestamp are not trustable (when exceeding 15% of session.total_elapsed_time)
170
172
  const INVALID_DATES_ELAPSED_TIME_RATIO_THRESHOLD = 1.15;
171
173
  class EventImporterFIT {
@@ -183,7 +185,7 @@ class EventImporterFIT {
183
185
  mode: 'both'
184
186
  });
185
187
  fitFileParser.parse(arrayBuffer, (error, fitDataObject) => {
186
- var _a;
188
+ var _a, _b;
187
189
  if (error) {
188
190
  // For now, assume any error from parser on this file means it's broken/empty in a way we treat as EmptyEventLibError
189
191
  // to satisfy existing tests. Or ideally we wrap in a generic EventLibError.
@@ -216,6 +218,17 @@ class EventImporterFIT {
216
218
  });
217
219
  });
218
220
  }
221
+ // Check for jumps data at the top level
222
+ if (fitDataObject.jumps && fitDataObject.jumps.length > 0) {
223
+ (_b = fitDataObject.sessions) === null || _b === void 0 ? void 0 : _b.forEach((session) => {
224
+ const sessionStartTime = new Date(session.start_time).getTime();
225
+ const sessionEndTime = sessionStartTime + (session.total_elapsed_time || 0) * 1000;
226
+ session.jumps = fitDataObject.jumps.filter((jump) => {
227
+ const jumpTime = new Date(jump.timestamp).getTime();
228
+ return jumpTime >= sessionStartTime && jumpTime < sessionEndTime;
229
+ });
230
+ });
231
+ }
219
232
  // Iterate over the sessions and create their activities
220
233
  const activities = fitDataObject.sessions.map((sessionObject) => {
221
234
  // Get the activity from the sessionObject
@@ -350,7 +363,12 @@ class EventImporterFIT {
350
363
  activity.addEvent(new data_jump_event_1.DataJumpEvent(activity.getDateIndex(jump.timestamp), {
351
364
  distance: jump.distance,
352
365
  height: jump.height,
353
- score: jump.score
366
+ score: jump.score,
367
+ hang_time: jump.hang_time,
368
+ position_lat: jump.position_lat,
369
+ position_long: jump.position_long,
370
+ speed: jump.speed,
371
+ rotations: jump.rotations
354
372
  }));
355
373
  });
356
374
  }
@@ -625,13 +643,6 @@ class EventImporterFIT {
625
643
  const activity = new activity_1.Activity(startDate, endDate, this.getActivityTypeFromSessionObject(sessionObject), this.getCreatorFromFitDataObject(fitDataObject), options);
626
644
  // Set the activity stats
627
645
  this.getStatsFromObject(sessionObject, activity, false).forEach(stat => activity.addStat(stat));
628
- // Check for VO2 Max in jumps
629
- if (fitDataObject.jumps && fitDataObject.jumps.length) {
630
- const jumpWithMets = fitDataObject.jumps.find((j) => j.enhanced_mets);
631
- if (jumpWithMets) {
632
- activity.addStat(new data_vo2_max_1.DataVO2Max(jumpWithMets.enhanced_mets * 3.5));
633
- }
634
- }
635
646
  // Check for User Profile
636
647
  if (fitDataObject.user_profile) {
637
648
  const userProfile = fitDataObject.user_profile;
@@ -1008,7 +1019,11 @@ class EventImporterFIT {
1008
1019
  stats.push(new data_cycling_seated_time_1.DataCyclingSeatedTime(seatedTime));
1009
1020
  }
1010
1021
  // Running dynamics
1022
+ // Stance Time
1011
1023
  if (Number.isFinite(object.avg_stance_time)) {
1024
+ stats.push(new data_ground_contact_time_avg_1.DataGroundContactTimeAvg(object.avg_stance_time));
1025
+ // Keep DataStanceTime for backward compatibility (if needed, though logically it's an Avg)
1026
+ // The original code mapped avg_stance_time to DataStanceTime, which was arguably incorrect naming or type usage
1012
1027
  stats.push(new data_stance_time_1.DataStanceTime(object.avg_stance_time));
1013
1028
  }
1014
1029
  if (Number.isFinite(object.avg_vertical_oscillation)) {
@@ -1080,6 +1095,94 @@ class EventImporterFIT {
1080
1095
  if ((0, helpers_1.isNumberOrString)(object.avg_vam)) {
1081
1096
  stats.push(new data_avg_vam_1.DataAvgVAM(object.avg_vam));
1082
1097
  }
1098
+ // Jump Statistics
1099
+ if (object.jumps && object.jumps.length > 0) {
1100
+ const jumps = object.jumps;
1101
+ let count = 0;
1102
+ let hangTimeSum = 0, hangTimeMin = Number.MAX_VALUE, hangTimeMax = -Number.MAX_VALUE;
1103
+ let distanceSum = 0, distanceMin = Number.MAX_VALUE, distanceMax = -Number.MAX_VALUE;
1104
+ let speedSum = 0, speedMin = Number.MAX_VALUE, speedMax = -Number.MAX_VALUE;
1105
+ let rotationsSum = 0, rotationsMin = Number.MAX_VALUE, rotationsMax = -Number.MAX_VALUE;
1106
+ let scoreSum = 0, scoreMin = Number.MAX_VALUE, scoreMax = -Number.MAX_VALUE;
1107
+ let heightSum = 0, heightMin = Number.MAX_VALUE, heightMax = -Number.MAX_VALUE;
1108
+ jumps.forEach((j) => {
1109
+ count++;
1110
+ // Hangtime
1111
+ if (Number.isFinite(j.hang_time)) {
1112
+ hangTimeSum += j.hang_time;
1113
+ hangTimeMin = Math.min(hangTimeMin, j.hang_time);
1114
+ hangTimeMax = Math.max(hangTimeMax, j.hang_time);
1115
+ }
1116
+ // Distance
1117
+ if (Number.isFinite(j.distance)) {
1118
+ distanceSum += j.distance;
1119
+ distanceMin = Math.min(distanceMin, j.distance);
1120
+ distanceMax = Math.max(distanceMax, j.distance);
1121
+ }
1122
+ // Speed
1123
+ if (Number.isFinite(j.speed)) {
1124
+ speedSum += j.speed;
1125
+ speedMin = Math.min(speedMin, j.speed);
1126
+ speedMax = Math.max(speedMax, j.speed);
1127
+ }
1128
+ // Rotations
1129
+ if (Number.isFinite(j.rotations)) {
1130
+ rotationsSum += j.rotations;
1131
+ rotationsMin = Math.min(rotationsMin, j.rotations);
1132
+ rotationsMax = Math.max(rotationsMax, j.rotations);
1133
+ }
1134
+ // Score
1135
+ if (Number.isFinite(j.score)) {
1136
+ scoreSum += j.score;
1137
+ scoreMin = Math.min(scoreMin, j.score);
1138
+ scoreMax = Math.max(scoreMax, j.score);
1139
+ }
1140
+ // Height
1141
+ if (Number.isFinite(j.height)) {
1142
+ heightSum += j.height;
1143
+ heightMin = Math.min(heightMin, j.height);
1144
+ heightMax = Math.max(heightMax, j.height);
1145
+ }
1146
+ });
1147
+ if (count > 0) {
1148
+ if (hangTimeMin !== Number.MAX_VALUE)
1149
+ stats.push(new data_jump_stats_1.DataJumpHangTimeMin(hangTimeMin));
1150
+ if (hangTimeMax !== -Number.MAX_VALUE)
1151
+ stats.push(new data_jump_stats_1.DataJumpHangTimeMax(hangTimeMax));
1152
+ if (hangTimeSum > 0)
1153
+ stats.push(new data_jump_stats_1.DataJumpHangTimeAvg(hangTimeSum / count)); // Assuming count is correct divisor for existing values
1154
+ if (distanceMin !== Number.MAX_VALUE)
1155
+ stats.push(new data_jump_stats_1.DataJumpDistanceMin(distanceMin));
1156
+ if (distanceMax !== -Number.MAX_VALUE)
1157
+ stats.push(new data_jump_stats_1.DataJumpDistanceMax(distanceMax));
1158
+ if (distanceSum > 0)
1159
+ stats.push(new data_jump_stats_1.DataJumpDistanceAvg(distanceSum / count));
1160
+ if (speedMin !== Number.MAX_VALUE)
1161
+ stats.push(new data_jump_stats_1.DataJumpSpeedMin(speedMin));
1162
+ if (speedMax !== -Number.MAX_VALUE)
1163
+ stats.push(new data_jump_stats_1.DataJumpSpeedMax(speedMax));
1164
+ if (speedSum > 0)
1165
+ stats.push(new data_jump_stats_1.DataJumpSpeedAvg(speedSum / count));
1166
+ if (rotationsMin !== Number.MAX_VALUE)
1167
+ stats.push(new data_jump_stats_1.DataJumpRotationsMin(rotationsMin));
1168
+ if (rotationsMax !== -Number.MAX_VALUE)
1169
+ stats.push(new data_jump_stats_1.DataJumpRotationsMax(rotationsMax));
1170
+ if (rotationsSum > 0)
1171
+ stats.push(new data_jump_stats_1.DataJumpRotationsAvg(rotationsSum / count));
1172
+ if (scoreMin !== Number.MAX_VALUE)
1173
+ stats.push(new data_jump_stats_1.DataJumpScoreMin(scoreMin));
1174
+ if (scoreMax !== -Number.MAX_VALUE)
1175
+ stats.push(new data_jump_stats_1.DataJumpScoreMax(scoreMax));
1176
+ if (scoreSum > 0)
1177
+ stats.push(new data_jump_stats_1.DataJumpScoreAvg(scoreSum / count));
1178
+ if (heightMin !== Number.MAX_VALUE)
1179
+ stats.push(new data_jump_stats_1.DataJumpHeightMin(heightMin));
1180
+ if (heightMax !== -Number.MAX_VALUE)
1181
+ stats.push(new data_jump_stats_1.DataJumpHeightMax(heightMax));
1182
+ if (heightSum > 0)
1183
+ stats.push(new data_jump_stats_1.DataJumpHeightAvg(heightSum / count));
1184
+ }
1185
+ }
1083
1186
  return stats;
1084
1187
  }
1085
1188
  static getCreatorFromFitDataObject(fitDataObject) {
@@ -21,6 +21,9 @@ const data_stryd_distance_1 = require("../../../../data/data.stryd-distance");
21
21
  const data_stryd_speed_1 = require("../../../../data/data.stryd-speed");
22
22
  const data_right_balance_1 = require("../../../../data/data.right-balance");
23
23
  const data_left_balance_1 = require("../../../../data/data.left-balance");
24
+ const data_ground_contact_time_1 = require("../../../../data/data.ground-contact-time");
25
+ const data_ground_contact_time_balance_left_1 = require("../../../../data/data-ground-contact-time-balance-left");
26
+ const data_ground_contact_time_balance_right_1 = require("../../../../data/data-ground-contact-time-balance-right");
24
27
  const data_stance_time_1 = require("../../../../data/data.stance-time");
25
28
  const data_stance_time_balance_left_1 = require("../../../../data/data-stance-time-balance-left");
26
29
  const data_step_length_1 = require("../../../../data/data.step-length");
@@ -213,12 +216,32 @@ exports.FITSampleMapper = [
213
216
  : 100 - sample.left_right_balance.value;
214
217
  }
215
218
  },
219
+ {
220
+ dataType: data_ground_contact_time_1.DataGroundContactTime.type,
221
+ getSampleValue: (sample) => {
222
+ return sample.stance_time;
223
+ }
224
+ },
225
+ // Keep DataStanceTime for backward compatibility
216
226
  {
217
227
  dataType: data_stance_time_1.DataStanceTime.type,
218
228
  getSampleValue: (sample) => {
219
229
  return sample.stance_time;
220
230
  }
221
231
  },
232
+ {
233
+ dataType: data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft.type,
234
+ getSampleValue: (sample) => {
235
+ return sample.stance_time_balance; // The field sample refers to the balance on left leg
236
+ }
237
+ },
238
+ {
239
+ dataType: data_ground_contact_time_balance_right_1.DataGroundContactTimeBalanceRight.type,
240
+ getSampleValue: (sample) => {
241
+ return (0, helpers_1.isNumber)(sample.stance_time_balance) ? 100 - sample.stance_time_balance : null;
242
+ }
243
+ },
244
+ // Keep DataStanceTimeBalanceLeft for backward compatibility
222
245
  {
223
246
  dataType: data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type,
224
247
  getSampleValue: (sample) => {
@@ -47,7 +47,6 @@ const path = __importStar(require("path"));
47
47
  const importer_fit_1 = require("./importer.fit");
48
48
  const data_aerobic_training_effect_1 = require("../../../../data/data-aerobic-training-effect");
49
49
  const data_anaerobic_training_effect_1 = require("../../../../data/data-anaerobic-training-effect");
50
- const data_vo2_max_1 = require("../../../../data/data.vo2-max");
51
50
  const data_recovery_time_1 = require("../../../../data/data.recovery-time");
52
51
  const data_avg_respiration_rate_1 = require("../../../../data/data.avg-respiration-rate");
53
52
  const data_heart_rate_zone_five_duration_1 = require("../../../../data/data.heart-rate-zone-five-duration");
@@ -146,10 +145,6 @@ describe('EventImporterFIT MTB Jumps', () => {
146
145
  const anaerobic = activity.getStat(data_anaerobic_training_effect_1.DataAnaerobicTrainingEffect.type);
147
146
  expect(anaerobic).toBeDefined();
148
147
  expect(anaerobic.getValue()).toBe(2);
149
- // VO2 Max
150
- const vo2Max = activity.getStat(data_vo2_max_1.DataVO2Max.type);
151
- expect(vo2Max).toBeDefined();
152
- expect(vo2Max.getValue()).toBeCloseTo(57.0633, 4);
153
148
  expect(activity.getStat(data_temperature_max_1.DataTemperatureMax.type).getValue()).toBe(19);
154
149
  expect(activity.getStat(data_temperature_min_1.DataTemperatureMin.type).getValue()).toBe(7);
155
150
  // Positions
@@ -200,13 +195,46 @@ describe('EventImporterFIT MTB Jumps', () => {
200
195
  // Check Jumps
201
196
  const jumpEvents = activity.getAllEvents().filter((e) => e.getType() === data_jump_event_1.DataJumpEvent.type);
202
197
  expect(jumpEvents.length).toBeGreaterThan(0);
198
+ expect(jumpEvents.length).toBe(11);
203
199
  const jump = jumpEvents[0];
204
200
  expect(jump.jumpData).toBeDefined();
205
201
  expect((0, helpers_1.isNumber)(jump.jumpData.distance)).toBeTruthy();
206
- expect((0, helpers_1.isNumber)(jump.jumpData.height)).toBeTruthy();
207
202
  expect((0, helpers_1.isNumber)(jump.jumpData.score)).toBeTruthy();
203
+ // Verify new jump fields with expected values
204
+ expect(jump.jumpData.distance).toBeCloseTo(2.069, 2);
205
+ expect(jump.jumpData.hang_time).toBeCloseTo(0.36, 2);
206
+ expect(jump.jumpData.score).toBeCloseTo(62.44, 1);
207
+ expect(jump.jumpData.position_lat).toBeCloseTo(39.6679, 3);
208
+ expect(jump.jumpData.position_long).toBeCloseTo(20.8382, 3);
209
+ expect(jump.jumpData.speed).toBeCloseTo(5.748, 2);
208
210
  console.log(`Found ${jumpEvents.length} jumps.`);
209
211
  console.log('First jump:', jump.jumpData);
212
+ // Verify Jump Statistics (Min, Max, Avg)
213
+ const { DataJumpDistanceAvg, DataJumpDistanceMax, DataJumpDistanceMin, DataJumpHangTimeAvg, DataJumpHangTimeMax, DataJumpHangTimeMin, DataJumpHeightAvg, DataJumpHeightMax, DataJumpHeightMin, DataJumpRotationsAvg, DataJumpRotationsMax, DataJumpRotationsMin, DataJumpScoreAvg, DataJumpScoreMax, DataJumpScoreMin, DataJumpSpeedAvg, DataJumpSpeedMax, DataJumpSpeedMin } = yield Promise.resolve().then(() => __importStar(require('../../../../data/data.jump-stats')));
214
+ // Hangtime
215
+ expect(activity.getStat(DataJumpHangTimeMin.type).getValue()).toBeCloseTo(0.36, 2);
216
+ expect(activity.getStat(DataJumpHangTimeMax.type).getValue()).toBeCloseTo(0.696, 3);
217
+ expect(activity.getStat(DataJumpHangTimeAvg.type).getValue()).toBeCloseTo(0.45, 2);
218
+ // Distance
219
+ expect(activity.getStat(DataJumpDistanceMin.type).getValue()).toBeCloseTo(1.40, 2);
220
+ expect(activity.getStat(DataJumpDistanceMax.type).getValue()).toBeCloseTo(4.68, 2);
221
+ expect(activity.getStat(DataJumpDistanceAvg.type).getValue()).toBeCloseTo(3.02, 2);
222
+ // Speed
223
+ expect(activity.getStat(DataJumpSpeedMin.type).getValue()).toBeCloseTo(3.88, 2);
224
+ expect(activity.getStat(DataJumpSpeedMax.type).getValue()).toBeCloseTo(8.995, 3);
225
+ expect(activity.getStat(DataJumpSpeedAvg.type).getValue()).toBeCloseTo(6.55, 2);
226
+ // Score
227
+ expect(activity.getStat(DataJumpScoreMin.type).getValue()).toBeCloseTo(53.9, 1);
228
+ expect(activity.getStat(DataJumpScoreMax.type).getValue()).toBeCloseTo(122.6, 1);
229
+ expect(activity.getStat(DataJumpScoreAvg.type).getValue()).toBeCloseTo(81.8, 1);
230
+ // Rotations (Should be undefined for this file)
231
+ expect(activity.getStat(DataJumpRotationsMin.type)).toBeUndefined();
232
+ expect(activity.getStat(DataJumpRotationsMax.type)).toBeUndefined();
233
+ expect(activity.getStat(DataJumpRotationsAvg.type)).toBeUndefined();
234
+ // Height (Should be undefined for this file)
235
+ expect(activity.getStat(DataJumpHeightMin.type)).toBeUndefined();
236
+ expect(activity.getStat(DataJumpHeightMax.type)).toBeUndefined();
237
+ expect(activity.getStat(DataJumpHeightAvg.type)).toBeUndefined();
210
238
  // We can check if we can find a sample with Grit.
211
239
  // In sports-lib, samples are often accessed via activity.getStream(type) or similar, BUT
212
240
  // importer creates `DataPoint`s? Or `DataSample`s?
@@ -45,6 +45,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
45
45
  const fs = __importStar(require("fs"));
46
46
  const path = __importStar(require("path"));
47
47
  const importer_suunto_json_1 = require("./importer.suunto.json");
48
+ const data_ground_contact_time_1 = require("../../../../data/data.ground-contact-time");
49
+ const data_ground_contact_time_avg_1 = require("../../../../data/data.ground-contact-time-avg");
50
+ const data_ground_contact_time_max_1 = require("../../../../data/data.ground-contact-time-max");
51
+ const data_ground_contact_time_min_1 = require("../../../../data/data.ground-contact-time-min");
52
+ const data_vertical_oscillation_1 = require("../../../../data/data.vertical-oscillation");
53
+ const data_vertical_oscillation_avg_1 = require("../../../../data/data.vertical-oscillation-avg");
54
+ const data_vertical_oscillation_max_1 = require("../../../../data/data.vertical-oscillation-max");
55
+ const data_vertical_oscillation_min_1 = require("../../../../data/data.vertical-oscillation-min");
56
+ const data_fitness_age_1 = require("../../../../data/data.fitness-age");
57
+ const data_max_hr_setting_1 = require("../../../../data/data.max-hr-setting");
48
58
  describe('EventImporterSuuntoJSON Integration', () => {
49
59
  // Go up 5 levels from src/events/adapters/importers/suunto -> sports-lib root
50
60
  const samplesDir = path.resolve(__dirname, '../../../../../samples/suunto');
@@ -58,7 +68,6 @@ describe('EventImporterSuuntoJSON Integration', () => {
58
68
  console.warn('No .json files found in samples directory.');
59
69
  return;
60
70
  }
61
- console.log(`Found ${files.length} .json files to test:`, files);
62
71
  for (const file of files) {
63
72
  const filePath = path.join(samplesDir, file);
64
73
  const fileString = fs.readFileSync(filePath, 'utf-8');
@@ -67,7 +76,6 @@ describe('EventImporterSuuntoJSON Integration', () => {
67
76
  const event = yield importer_suunto_json_1.EventImporterSuuntoJSON.getFromJSONString(fileString);
68
77
  expect(event).toBeDefined();
69
78
  expect(event.getActivities().length).toBeGreaterThan(0);
70
- console.log(`✅ Successfully parsed ${file}`);
71
79
  }
72
80
  catch (error) {
73
81
  console.error(`❌ Failed to parse ${file}:`, error);
@@ -75,5 +83,81 @@ describe('EventImporterSuuntoJSON Integration', () => {
75
83
  }
76
84
  }
77
85
  }));
78
- // Note: If SML/XML files existed, we would test EventImporterSuuntoSML here too.
86
+ describe('running-with-extra-data.json', () => {
87
+ let event;
88
+ let activity;
89
+ beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
90
+ const filePath = path.join(samplesDir, 'running-with-extra-data.json');
91
+ if (!fs.existsSync(filePath)) {
92
+ console.warn('running-with-extra-data.json not found. Skipping detailed tests.');
93
+ return;
94
+ }
95
+ const fileString = fs.readFileSync(filePath, 'utf-8');
96
+ event = yield importer_suunto_json_1.EventImporterSuuntoJSON.getFromJSONString(fileString);
97
+ // DEBUG: print one sample's date from the source file just blindly
98
+ // (we can't easily access json here again without parsing, but we can infer from activity)
99
+ // Find the main running activity (longest duration)
100
+ activity = event.getActivities().reduce((prev, current) => (prev.getDuration().getValue() > current.getDuration().getValue()) ? prev : current);
101
+ }));
102
+ it('should parse Ground Contact Time stream', () => {
103
+ if (!activity)
104
+ return;
105
+ // Depending on the file structure, GCT might be in the first or second activity.
106
+ // We selected the longest one.
107
+ const hasStream = activity.hasStreamData(data_ground_contact_time_1.DataGroundContactTime.type);
108
+ expect(hasStream).toBe(true);
109
+ if (hasStream) {
110
+ const stream = activity.getStreamData(data_ground_contact_time_1.DataGroundContactTime.type);
111
+ expect(stream.length).toBeGreaterThan(0);
112
+ }
113
+ });
114
+ it('should parse Ground Contact Time stats (avg, max, min)', () => {
115
+ if (!activity)
116
+ return;
117
+ const avgStat = activity.getStat(data_ground_contact_time_avg_1.DataGroundContactTimeAvg.type);
118
+ const maxStat = activity.getStat(data_ground_contact_time_max_1.DataGroundContactTimeMax.type);
119
+ const minStat = activity.getStat(data_ground_contact_time_min_1.DataGroundContactTimeMin.type);
120
+ expect(avgStat).toBeDefined();
121
+ expect(avgStat === null || avgStat === void 0 ? void 0 : avgStat.getValue()).toBeCloseTo(255.969, 3);
122
+ expect(maxStat).toBeDefined();
123
+ expect(maxStat === null || maxStat === void 0 ? void 0 : maxStat.getValue()).toBe(339);
124
+ expect(minStat).toBeDefined();
125
+ expect(minStat === null || minStat === void 0 ? void 0 : minStat.getValue()).toBe(219);
126
+ });
127
+ it('should parse Vertical Oscillation stream', () => {
128
+ if (!activity)
129
+ return;
130
+ const hasStream = activity.hasStreamData(data_vertical_oscillation_1.DataVerticalOscillation.type);
131
+ expect(hasStream).toBe(true);
132
+ });
133
+ it('should parse Vertical Oscillation stats (avg, max, min)', () => {
134
+ if (!activity)
135
+ return;
136
+ const avgStat = activity.getStat(data_vertical_oscillation_avg_1.DataVerticalOscillationAvg.type);
137
+ const maxStat = activity.getStat(data_vertical_oscillation_max_1.DataVerticalOscillationMax.type);
138
+ const minStat = activity.getStat(data_vertical_oscillation_min_1.DataVerticalOscillationMin.type);
139
+ expect(avgStat).toBeDefined();
140
+ expect(avgStat === null || avgStat === void 0 ? void 0 : avgStat.getValue()).toBeCloseTo(74.359, 3);
141
+ expect(maxStat).toBeDefined();
142
+ expect(maxStat === null || maxStat === void 0 ? void 0 : maxStat.getValue()).toBe(87);
143
+ expect(minStat).toBeDefined();
144
+ expect(minStat === null || minStat === void 0 ? void 0 : minStat.getValue()).toBe(42);
145
+ });
146
+ it('should parse Fitness Age from header (Event or Activity)', () => {
147
+ let stat = event.getStat(data_fitness_age_1.DataFitnessAge.type);
148
+ if (!stat && activity) {
149
+ stat = activity.getStat(data_fitness_age_1.DataFitnessAge.type);
150
+ }
151
+ expect(stat).toBeDefined();
152
+ expect(stat === null || stat === void 0 ? void 0 : stat.getValue()).toBe(25);
153
+ });
154
+ it('should parse Personal MaxHR from header (Event or Activity)', () => {
155
+ let stat = event.getStat(data_max_hr_setting_1.DataMaxHRSetting.type);
156
+ if (!stat && activity) {
157
+ stat = activity.getStat(data_max_hr_setting_1.DataMaxHRSetting.type);
158
+ }
159
+ expect(stat).toBeDefined();
160
+ expect(stat === null || stat === void 0 ? void 0 : stat.getValue()).toBe(171);
161
+ });
162
+ });
79
163
  });
@@ -100,6 +100,18 @@ const data_speed_zone_two_duration_1 = require("../../../../data/data.speed-zone
100
100
  const data_speed_zone_three_duration_1 = require("../../../../data/data.speed-zone-three-duration");
101
101
  const data_speed_zone_four_duration_1 = require("../../../../data/data.speed-zone-four-duration");
102
102
  const data_speed_zone_five_duration_1 = require("../../../../data/data.speed-zone-five-duration");
103
+ const data_ground_contact_time_1 = require("../../../../data/data.ground-contact-time");
104
+ const data_ground_contact_time_avg_1 = require("../../../../data/data.ground-contact-time-avg");
105
+ const data_ground_contact_time_max_1 = require("../../../../data/data.ground-contact-time-max");
106
+ const data_ground_contact_time_min_1 = require("../../../../data/data.ground-contact-time-min");
107
+ const data_vertical_oscillation_1 = require("../../../../data/data.vertical-oscillation");
108
+ const data_vertical_oscillation_avg_1 = require("../../../../data/data.vertical-oscillation-avg");
109
+ const data_vertical_oscillation_max_1 = require("../../../../data/data.vertical-oscillation-max");
110
+ const data_vertical_oscillation_min_1 = require("../../../../data/data.vertical-oscillation-min");
111
+ const data_fitness_age_1 = require("../../../../data/data.fitness-age");
112
+ const data_max_hr_setting_1 = require("../../../../data/data.max-hr-setting");
113
+ const data_depth_1 = require("../../../../data/data.depth");
114
+ const data_depth_max_1 = require("../../../../data/data.depth-max");
103
115
  const file_type_enum_1 = require("../../file-type.enum");
104
116
  const activity_parsing_options_1 = require("../../../../activities/activity-parsing-options");
105
117
  class EventImporterSuuntoJSON {
@@ -152,8 +164,11 @@ class EventImporterSuuntoJSON {
152
164
  : [];
153
165
  // Create the activities
154
166
  const activities = activityStartEventSamples.map((activityStartEventSample, index) => {
167
+ // If no stop event, use the last sample time as end date
168
+ const lastSampleTime = eventJSONObject.DeviceLog.Samples[eventJSONObject.DeviceLog.Samples.length - 1].TimeISO8601;
169
+ const fallbackEndTime = stopEventSample ? stopEventSample.TimeISO8601 : lastSampleTime;
155
170
  const activity = new activity_1.Activity(new Date(activityStartEventSample.TimeISO8601), activityStartEventSamples.length - 1 === index
156
- ? new Date(stopEventSample ? stopEventSample.TimeISO8601 : (eventJSONObject.DeviceLog.Header.TimeISO8601 || eventJSONObject.DeviceLog.Header.DateTime))
171
+ ? new Date(fallbackEndTime)
157
172
  : new Date(activityStartEventSamples[index + 1].TimeISO8601), activity_types_1.ActivityTypes[(importer_suunto_activity_ids_1.ImporterSuuntoActivityIds[activityStartEventSample.Events[0].Activity.ActivityType])], creator, options);
158
173
  // Set the end date to the stop event time if the activity is the last or the only one else set it on the next itery time
159
174
  // Create the stats these are a 1:1 ref arrays
@@ -268,10 +283,6 @@ class EventImporterSuuntoJSON {
268
283
  // @todo check if start and end date can derive from the json
269
284
  const event = new event_1.Event('', activities[0].startDate, activities[activities.length - 1].endDate, file_type_enum_1.FileType.SUUNTO);
270
285
  activities.forEach(activity => event.addActivity(activity));
271
- // Populate the event stats from the Header Object // @todo maybe remove
272
- this.getStats(eventJSONObject.DeviceLog.Header).forEach(stat => {
273
- event.addStat(stat);
274
- });
275
286
  // Get the settings and add it to all activities as it's logical
276
287
  if (eventJSONObject.DeviceLog.Header.Settings) {
277
288
  this.getSettings(eventJSONObject.DeviceLog.Header.Settings).forEach(stat => {
@@ -285,9 +296,12 @@ class EventImporterSuuntoJSON {
285
296
  stat instanceof data_pool_length_1.DataPoolLength);
286
297
  stats.forEach(stat => activities[0].addStat(stat));
287
298
  }
288
- // @todo see how we can have those event stats persisted as the below generation wipes those off.
289
299
  // Generate stats
290
300
  event_utilities_1.EventUtilities.generateStatsForAll(event);
301
+ // Populate the event stats from the Header Object
302
+ this.getStats(eventJSONObject.DeviceLog.Header).forEach(stat => {
303
+ event.addStat(stat);
304
+ });
291
305
  resolve(event);
292
306
  });
293
307
  }
@@ -355,8 +369,12 @@ class EventImporterSuuntoJSON {
355
369
  return samples;
356
370
  }
357
371
  static setStreamsForActivity(activity, samples) {
372
+ // console.log(`Setting streams for activity with ${samples.length} samples`);
358
373
  exports.SuuntoSampleMapper.forEach(sampleMapping => {
359
374
  const subjectSamples = samples.filter(sample => (0, helpers_1.isNumberOrString)(sample[sampleMapping.sampleField]));
375
+ // if (sampleMapping.sampleField === 'GroundContactTime') {
376
+ // console.log(`GCT Samples found: ${subjectSamples.length}`);
377
+ // }
360
378
  if (subjectSamples.length) {
361
379
  activity.addStream(activity.createStream(sampleMapping.dataType));
362
380
  subjectSamples.forEach(subjectSample => {
@@ -525,6 +543,53 @@ class EventImporterSuuntoJSON {
525
543
  }
526
544
  }
527
545
  }
546
+ // Ground Contact Time (Running Dynamics)
547
+ if (object.hasOwnProperty('GroundContactTime')) {
548
+ if (Array.isArray(object.GroundContactTime)) {
549
+ if ((0, helpers_1.isNumber)(object.GroundContactTime[0].Avg)) {
550
+ stats.push(new data_ground_contact_time_avg_1.DataGroundContactTimeAvg(object.GroundContactTime[0].Avg * 1000)); // Convert s to ms
551
+ }
552
+ if ((0, helpers_1.isNumber)(object.GroundContactTime[0].Max)) {
553
+ stats.push(new data_ground_contact_time_max_1.DataGroundContactTimeMax(object.GroundContactTime[0].Max * 1000));
554
+ }
555
+ if ((0, helpers_1.isNumber)(object.GroundContactTime[0].Min)) {
556
+ stats.push(new data_ground_contact_time_min_1.DataGroundContactTimeMin(object.GroundContactTime[0].Min * 1000));
557
+ }
558
+ }
559
+ }
560
+ // Vertical Oscillation (Running Dynamics)
561
+ if (object.hasOwnProperty('VerticalOscillation')) {
562
+ if (Array.isArray(object.VerticalOscillation)) {
563
+ if ((0, helpers_1.isNumber)(object.VerticalOscillation[0].Avg)) {
564
+ stats.push(new data_vertical_oscillation_avg_1.DataVerticalOscillationAvg(object.VerticalOscillation[0].Avg * 1000)); // Convert m to mm
565
+ }
566
+ if ((0, helpers_1.isNumber)(object.VerticalOscillation[0].Max)) {
567
+ stats.push(new data_vertical_oscillation_max_1.DataVerticalOscillationMax(object.VerticalOscillation[0].Max * 1000));
568
+ }
569
+ if ((0, helpers_1.isNumber)(object.VerticalOscillation[0].Min)) {
570
+ stats.push(new data_vertical_oscillation_min_1.DataVerticalOscillationMin(object.VerticalOscillation[0].Min * 1000));
571
+ }
572
+ }
573
+ }
574
+ // Fitness Age
575
+ if ((0, helpers_1.isNumber)(object.FitnessAge)) {
576
+ stats.push(new data_fitness_age_1.DataFitnessAge(object.FitnessAge));
577
+ }
578
+ // Personal MaxHR
579
+ if (object.Personal && (0, helpers_1.isNumber)(object.Personal.MaxHR)) {
580
+ stats.push(new data_max_hr_setting_1.DataMaxHRSetting(object.Personal.MaxHR * 60)); // Convert from Hz to bpm
581
+ }
582
+ // Depth (Diving)
583
+ if (object.hasOwnProperty('Depth')) {
584
+ if (Array.isArray(object.Depth)) {
585
+ if ((0, helpers_1.isNumber)(object.Depth[0].Max) && object.Depth[0].Max > 0) {
586
+ stats.push(new data_depth_max_1.DataDepthMax(object.Depth[0].Max));
587
+ }
588
+ }
589
+ else if ((0, helpers_1.isNumber)(object.Depth.Max) && object.Depth.Max > 0) {
590
+ stats.push(new data_depth_max_1.DataDepthMax(object.Depth.Max));
591
+ }
592
+ }
528
593
  return stats;
529
594
  }
530
595
  }
@@ -717,5 +782,20 @@ exports.SuuntoSampleMapper = [
717
782
  dataType: data_battery_voltage_1.DataBatteryVoltage.type,
718
783
  sampleField: 'BatteryVoltage',
719
784
  convertSampleValue: (value) => Number(value)
785
+ },
786
+ {
787
+ dataType: data_vertical_oscillation_1.DataVerticalOscillation.type,
788
+ sampleField: 'VerticalOscillation',
789
+ convertSampleValue: (value) => Number(value * 1000) // Convert m to mm
790
+ },
791
+ {
792
+ dataType: data_ground_contact_time_1.DataGroundContactTime.type,
793
+ sampleField: 'GroundContactTime',
794
+ convertSampleValue: (value) => Number(value * 1000) // Convert s to ms
795
+ },
796
+ {
797
+ dataType: data_depth_1.DataDepth.type,
798
+ sampleField: 'Depth',
799
+ convertSampleValue: (value) => Number(value)
720
800
  }
721
801
  ];
@@ -82,6 +82,18 @@ const data_speed_zone_two_duration_1 = require("../../data/data.speed-zone-two-d
82
82
  const data_speed_zone_three_duration_1 = require("../../data/data.speed-zone-three-duration");
83
83
  const data_speed_zone_four_duration_1 = require("../../data/data.speed-zone-four-duration");
84
84
  const data_speed_zone_five_duration_1 = require("../../data/data.speed-zone-five-duration");
85
+ const data_ground_contact_time_1 = require("../../data/data.ground-contact-time");
86
+ const data_ground_contact_time_avg_1 = require("../../data/data.ground-contact-time-avg");
87
+ const data_ground_contact_time_max_1 = require("../../data/data.ground-contact-time-max");
88
+ const data_ground_contact_time_min_1 = require("../../data/data.ground-contact-time-min");
89
+ const data_ground_contact_time_balance_left_1 = require("../../data/data-ground-contact-time-balance-left");
90
+ const data_ground_contact_time_balance_right_1 = require("../../data/data-ground-contact-time-balance-right");
91
+ const data_vertical_oscillation_1 = require("../../data/data.vertical-oscillation");
92
+ const data_vertical_oscillation_avg_1 = require("../../data/data.vertical-oscillation-avg");
93
+ const data_vertical_oscillation_max_1 = require("../../data/data.vertical-oscillation-max");
94
+ const data_vertical_oscillation_min_1 = require("../../data/data.vertical-oscillation-min");
95
+ const data_stance_time_balance_left_1 = require("../../data/data-stance-time-balance-left");
96
+ const data_stance_time_balance_right_1 = require("../../data/data-stance-time-balance-right");
85
97
  const data_store_1 = require("../../data/data.store");
86
98
  const data_start_position_1 = require("../../data/data.start-position");
87
99
  const data_end_position_1 = require("../../data/data.end-position");
@@ -101,8 +113,6 @@ const data_altitude_smooth_1 = require("../../data/data.altitude-smooth");
101
113
  const data_grade_smooth_1 = require("../../data/data.grade-smooth");
102
114
  const data_swolf_25m_1 = require("../../data/data.swolf-25m");
103
115
  const data_swolf_50m_1 = require("../../data/data.swolf-50m");
104
- const data_stance_time_balance_left_1 = require("../../data/data-stance-time-balance-left");
105
- const data_stance_time_balance_right_1 = require("../../data/data-stance-time-balance-right");
106
116
  const low_pass_filter_1 = require("./grade-calculator/low-pass-filter");
107
117
  const data_power_normalized_1 = require("../../data/data.power-normalized");
108
118
  const data_power_work_1 = require("../../data/data.power-work");
@@ -1310,11 +1320,37 @@ class ActivityUtilities {
1310
1320
  activity.addStat(new data_left_balance_1.DataLeftBalance(100 - avgRightBalance));
1311
1321
  }
1312
1322
  // Assign L/R balance stance time from streams if exists
1323
+ if (!activity.getStat(data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft.type) && activity.hasStreamData(data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft.type)) {
1324
+ const avgStanceTimeLeftBalance = this.round(this.getDataTypeAvg(activity, data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft.type), 2);
1325
+ activity.addStat(new data_ground_contact_time_balance_left_1.DataGroundContactTimeBalanceLeft(avgStanceTimeLeftBalance));
1326
+ activity.addStat(new data_ground_contact_time_balance_right_1.DataGroundContactTimeBalanceRight(100 - avgStanceTimeLeftBalance));
1327
+ }
1328
+ // Backward compatibility for Stance Time Balance
1313
1329
  if (!activity.getStat(data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type) && activity.hasStreamData(data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type)) {
1314
1330
  const avgStanceTimeLeftBalance = this.round(this.getDataTypeAvg(activity, data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type), 2);
1315
1331
  activity.addStat(new data_stance_time_balance_left_1.DataStanceTimeBalanceLeft(avgStanceTimeLeftBalance));
1316
1332
  activity.addStat(new data_stance_time_balance_right_1.DataStanceTimeBalanceRight(100 - avgStanceTimeLeftBalance));
1317
1333
  }
1334
+ // Ground Contact Time
1335
+ if (!activity.getStat(data_ground_contact_time_max_1.DataGroundContactTimeMax.type) && activity.hasStreamData(data_ground_contact_time_1.DataGroundContactTime.type)) {
1336
+ activity.addStat(new data_ground_contact_time_max_1.DataGroundContactTimeMax(this.getDataTypeMax(activity, data_ground_contact_time_1.DataGroundContactTime.type)));
1337
+ }
1338
+ if (!activity.getStat(data_ground_contact_time_min_1.DataGroundContactTimeMin.type) && activity.hasStreamData(data_ground_contact_time_1.DataGroundContactTime.type)) {
1339
+ activity.addStat(new data_ground_contact_time_min_1.DataGroundContactTimeMin(this.getDataTypeMin(activity, data_ground_contact_time_1.DataGroundContactTime.type)));
1340
+ }
1341
+ if (!activity.getStat(data_ground_contact_time_avg_1.DataGroundContactTimeAvg.type) && activity.hasStreamData(data_ground_contact_time_1.DataGroundContactTime.type)) {
1342
+ activity.addStat(new data_ground_contact_time_avg_1.DataGroundContactTimeAvg(this.getDataTypeAvg(activity, data_ground_contact_time_1.DataGroundContactTime.type)));
1343
+ }
1344
+ // Vertical Oscillation
1345
+ if (!activity.getStat(data_vertical_oscillation_max_1.DataVerticalOscillationMax.type) && activity.hasStreamData(data_vertical_oscillation_1.DataVerticalOscillation.type)) {
1346
+ activity.addStat(new data_vertical_oscillation_max_1.DataVerticalOscillationMax(this.getDataTypeMax(activity, data_vertical_oscillation_1.DataVerticalOscillation.type)));
1347
+ }
1348
+ if (!activity.getStat(data_vertical_oscillation_min_1.DataVerticalOscillationMin.type) && activity.hasStreamData(data_vertical_oscillation_1.DataVerticalOscillation.type)) {
1349
+ activity.addStat(new data_vertical_oscillation_min_1.DataVerticalOscillationMin(this.getDataTypeMin(activity, data_vertical_oscillation_1.DataVerticalOscillation.type)));
1350
+ }
1351
+ if (!activity.getStat(data_vertical_oscillation_avg_1.DataVerticalOscillationAvg.type) && activity.hasStreamData(data_vertical_oscillation_1.DataVerticalOscillation.type)) {
1352
+ activity.addStat(new data_vertical_oscillation_avg_1.DataVerticalOscillationAvg(this.getDataTypeAvg(activity, data_vertical_oscillation_1.DataVerticalOscillation.type)));
1353
+ }
1318
1354
  }
1319
1355
  static generateMissingSpeedDerivedStatsForActivity(activity) {
1320
1356
  // Pace
@@ -0,0 +1,4 @@
1
+ import { DataGroundContactTimeBalance } from './data-ground-contact-time-balance';
2
+ export declare class DataGroundContactTimeBalanceLeft extends DataGroundContactTimeBalance {
3
+ static type: string;
4
+ }
@@ -0,0 +1,4 @@
1
+ import { DataGroundContactTimeBalance } from './data-ground-contact-time-balance';
2
+ export declare class DataGroundContactTimeBalanceRight extends DataGroundContactTimeBalance {
3
+ static type: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ import { DataPercent } from './data.percent';
2
+ export declare abstract class DataGroundContactTimeBalance extends DataPercent {
3
+ }