@sports-alliance/sports-lib 6.1.10 → 6.1.12
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/babel.config.js +1 -1
- package/jest.config.js +2 -1
- package/jest.setup.ts +9 -0
- package/lib/cjs/activities/activity.types.js +2 -0
- package/lib/cjs/data/data-store.spec.js +3 -1
- package/lib/cjs/events/adapters/importers/fit/importer.fit.js +27 -23
- package/lib/cjs/events/adapters/importers/fit/importer.fit.mapper.js +21 -9
- package/lib/cjs/events/utilities/activity.utilities.js +34 -27
- package/lib/cjs/events/utilities/activity.utilities.spec.js +69 -13
- package/lib/esm/activities/activity.types.js +2 -0
- package/lib/esm/data/data-store.spec.js +3 -1
- package/lib/esm/events/adapters/importers/fit/importer.fit.js +27 -23
- package/lib/esm/events/adapters/importers/fit/importer.fit.mapper.js +21 -9
- package/lib/esm/events/utilities/activity.utilities.js +34 -27
- package/lib/esm/events/utilities/activity.utilities.spec.js +70 -14
- package/package.json +2 -2
package/babel.config.js
CHANGED
package/jest.config.js
CHANGED
package/jest.setup.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
import { ActivityParsingOptions } from './src/activities/activity-parsing-options';
|
|
3
|
+
|
|
4
|
+
// Disable unit stream generation by default for tests to improve performance
|
|
5
|
+
// This creates separate arrays for km/h, mph, etc. which are expensive and rarely needed in tests
|
|
6
|
+
// unless specifically testing the charting logic or unit conversion.
|
|
7
|
+
ActivityParsingOptions.DEFAULT.generateUnitStreams = false;
|
|
8
|
+
|
|
9
|
+
console.log('Jest Setup: Disabled ActivityParsingOptions.generateUnitStreams for faster tests.');
|
|
@@ -48,6 +48,7 @@ class ActivityTypesHelper {
|
|
|
48
48
|
case ActivityTypeGroups.TrailRunning:
|
|
49
49
|
return [data_pace_avg_1.DataPaceAvg.type, data_grade_adjusted_pace_avg_1.DataGradeAdjustedPaceAvg.type, data_speed_avg_1.DataSpeedAvg.type, data_grade_adjusted_speed_avg_1.DataGradeAdjustedSpeedAvg.type];
|
|
50
50
|
case ActivityTypeGroups.WaterSports:
|
|
51
|
+
case ActivityTypeGroups.Swimming:
|
|
51
52
|
return [data_speed_avg_1.DataSpeedAvg.type, data_swim_pace_avg_1.DataSwimPaceAvg.type];
|
|
52
53
|
default:
|
|
53
54
|
return [data_speed_avg_1.DataSpeedAvg.type];
|
|
@@ -60,6 +61,7 @@ class ActivityTypesHelper {
|
|
|
60
61
|
case ActivityTypeGroups.TrailRunning:
|
|
61
62
|
return [data_pace_1.DataPace.type, data_speed_1.DataSpeed.type];
|
|
62
63
|
case ActivityTypeGroups.WaterSports:
|
|
64
|
+
case ActivityTypeGroups.Swimming:
|
|
63
65
|
return [data_speed_1.DataSpeed.type, data_swim_pace_1.DataSwimPace.type];
|
|
64
66
|
default:
|
|
65
67
|
return [data_speed_1.DataSpeed.type];
|
|
@@ -70,7 +70,9 @@ describe('DataStore', () => {
|
|
|
70
70
|
temperatureUnits: [],
|
|
71
71
|
weightUnits: []
|
|
72
72
|
};
|
|
73
|
-
const result = data_store_1.DynamicDataLoader.getUnitBasedDataTypesFromDataTypes([data_speed_1.DataSpeed.type], settings, {
|
|
73
|
+
const result = data_store_1.DynamicDataLoader.getUnitBasedDataTypesFromDataTypes([data_speed_1.DataSpeed.type], settings, {
|
|
74
|
+
includeDerivedTypes: false
|
|
75
|
+
});
|
|
74
76
|
expect(result).toContain(data_speed_1.DataSpeedKilometersPerHour.type);
|
|
75
77
|
expect(result).not.toContain(data_pace_1.DataPaceMinutesPerMile.type);
|
|
76
78
|
});
|
|
@@ -627,10 +627,20 @@ class EventImporterFIT {
|
|
|
627
627
|
// Pause TIME on Object (activity, lap...)
|
|
628
628
|
const pause = elapsedTime > movingTime && movingTime > 0 ? Math.round((elapsedTime - movingTime) * 100) / 100 : 0;
|
|
629
629
|
stats.push(new data_pause_1.DataPause(pause));
|
|
630
|
+
const getStatValue = (obj, keys) => {
|
|
631
|
+
for (const key of keys) {
|
|
632
|
+
if ((0, helpers_1.isNumberOrString)(obj[key])) {
|
|
633
|
+
return obj[key];
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return null;
|
|
637
|
+
};
|
|
638
|
+
const avgSpeed = getStatValue(object, ['enhanced_avg_speed', 'EnhancedAvgSpeed', 'avg_speed', 'AvgSpeed']);
|
|
639
|
+
const totalDistance = getStatValue(object, ['total_distance', 'TotalDistance']);
|
|
630
640
|
// Assign is active lap status
|
|
631
|
-
stats.push(new data_active_lap_1.DataActiveLap(!!(
|
|
632
|
-
if (
|
|
633
|
-
stats.push(new data_distance_1.DataDistance(
|
|
641
|
+
stats.push(new data_active_lap_1.DataActiveLap(!!(totalDistance || avgSpeed)));
|
|
642
|
+
if (totalDistance !== null) {
|
|
643
|
+
stats.push(new data_distance_1.DataDistance(totalDistance));
|
|
634
644
|
}
|
|
635
645
|
else {
|
|
636
646
|
stats.push(new data_distance_1.DataDistance(0));
|
|
@@ -690,24 +700,16 @@ class EventImporterFIT {
|
|
|
690
700
|
stats.push(new data_power_pedal_smoothness_right_1.DataPowerPedalSmoothnessRight(object.avg_right_pedal_smoothness));
|
|
691
701
|
}
|
|
692
702
|
// Speed
|
|
693
|
-
if (
|
|
694
|
-
stats.push(new data_speed_avg_1.DataSpeedAvg(
|
|
695
|
-
}
|
|
696
|
-
if ((0, helpers_1.isNumberOrString)(object.min_speed)) {
|
|
697
|
-
stats.push(new data_speed_min_1.DataSpeedMin(object.min_speed));
|
|
698
|
-
}
|
|
699
|
-
if ((0, helpers_1.isNumberOrString)(object.max_speed)) {
|
|
700
|
-
stats.push(new data_speed_max_1.DataSpeedMax(object.max_speed));
|
|
701
|
-
}
|
|
702
|
-
// Keep latest , enhanced @todo this can create a bug
|
|
703
|
-
if ((0, helpers_1.isNumberOrString)(object.enhanced_avg_speed)) {
|
|
704
|
-
stats.push(new data_speed_avg_1.DataSpeedAvg(object.enhanced_avg_speed));
|
|
703
|
+
if (avgSpeed !== null) {
|
|
704
|
+
stats.push(new data_speed_avg_1.DataSpeedAvg(avgSpeed));
|
|
705
705
|
}
|
|
706
|
-
|
|
707
|
-
|
|
706
|
+
const minSpeed = getStatValue(object, ['enhanced_min_speed', 'EnhancedMinSpeed', 'min_speed', 'MinSpeed']);
|
|
707
|
+
if (minSpeed !== null) {
|
|
708
|
+
stats.push(new data_speed_min_1.DataSpeedMin(minSpeed));
|
|
708
709
|
}
|
|
709
|
-
|
|
710
|
-
|
|
710
|
+
const maxSpeed = getStatValue(object, ['enhanced_max_speed', 'EnhancedMaxSpeed', 'max_speed', 'MaxSpeed']);
|
|
711
|
+
if (maxSpeed !== null) {
|
|
712
|
+
stats.push(new data_speed_max_1.DataSpeedMax(maxSpeed));
|
|
711
713
|
}
|
|
712
714
|
// Temperature
|
|
713
715
|
if ((0, helpers_1.isNumberOrString)(object.avg_temperature)) {
|
|
@@ -720,12 +722,14 @@ class EventImporterFIT {
|
|
|
720
722
|
stats.push(new data_temperature_max_1.DataTemperatureMax(object.max_temperature));
|
|
721
723
|
}
|
|
722
724
|
// Ascent
|
|
723
|
-
|
|
724
|
-
|
|
725
|
+
const ascent = getStatValue(object, ['total_ascent', 'TotalAscent']);
|
|
726
|
+
if (ascent !== null) {
|
|
727
|
+
stats.push(new data_ascent_1.DataAscent(ascent));
|
|
725
728
|
}
|
|
726
729
|
// Descent
|
|
727
|
-
|
|
728
|
-
|
|
730
|
+
const descent = getStatValue(object, ['total_descent', 'TotalDescent']);
|
|
731
|
+
if (descent !== null) {
|
|
732
|
+
stats.push(new data_descent_1.DataDescent(descent));
|
|
729
733
|
}
|
|
730
734
|
// Calories
|
|
731
735
|
if ((0, helpers_1.isNumberOrString)(object.total_calories)) {
|
|
@@ -50,7 +50,7 @@ exports.FITSampleMapper = [
|
|
|
50
50
|
{
|
|
51
51
|
dataType: data_distance_1.DataDistance.type,
|
|
52
52
|
getSampleValue: (sample) => {
|
|
53
|
-
return sample.distance;
|
|
53
|
+
return (0, helpers_1.isNumber)(sample.distance) ? sample.distance : sample.Distance;
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
{
|
|
@@ -62,13 +62,19 @@ exports.FITSampleMapper = [
|
|
|
62
62
|
{
|
|
63
63
|
dataType: data_altitude_1.DataAltitude.type,
|
|
64
64
|
getSampleValue: (sample) => {
|
|
65
|
-
|
|
66
|
-
?
|
|
65
|
+
const altitude = (0, helpers_1.isNumber)(sample.enhanced_altitude)
|
|
66
|
+
? sample.enhanced_altitude
|
|
67
|
+
: (0, helpers_1.isNumber)(sample.EnhancedAltitude)
|
|
68
|
+
? sample.EnhancedAltitude
|
|
69
|
+
: (0, helpers_1.isNumber)(sample.altitude)
|
|
70
|
+
? sample.altitude
|
|
71
|
+
: (0, helpers_1.isNumber)(sample.Altitude)
|
|
72
|
+
? sample.Altitude
|
|
73
|
+
: null;
|
|
74
|
+
return (0, helpers_1.isNumber)(altitude)
|
|
75
|
+
? Math.round(altitude * Math.pow(10, constants_1.ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)) /
|
|
67
76
|
Math.pow(10, constants_1.ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)
|
|
68
|
-
:
|
|
69
|
-
? Math.round(sample.altitude * Math.pow(10, constants_1.ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)) /
|
|
70
|
-
Math.pow(10, constants_1.ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)
|
|
71
|
-
: sample.altitude;
|
|
77
|
+
: altitude;
|
|
72
78
|
}
|
|
73
79
|
},
|
|
74
80
|
{
|
|
@@ -105,12 +111,18 @@ exports.FITSampleMapper = [
|
|
|
105
111
|
{
|
|
106
112
|
dataType: data_speed_1.DataSpeed.type,
|
|
107
113
|
getSampleValue: (sample) => {
|
|
108
|
-
if (
|
|
114
|
+
if ((0, helpers_1.isNumber)(sample.enhanced_speed)) {
|
|
109
115
|
return sample.enhanced_speed;
|
|
110
116
|
}
|
|
111
|
-
if (
|
|
117
|
+
if ((0, helpers_1.isNumber)(sample.EnhancedSpeed)) {
|
|
118
|
+
return sample.EnhancedSpeed;
|
|
119
|
+
}
|
|
120
|
+
if ((0, helpers_1.isNumber)(sample.speed)) {
|
|
112
121
|
return sample.speed;
|
|
113
122
|
}
|
|
123
|
+
if ((0, helpers_1.isNumber)(sample.Speed)) {
|
|
124
|
+
return sample.Speed;
|
|
125
|
+
}
|
|
114
126
|
return null;
|
|
115
127
|
}
|
|
116
128
|
},
|
|
@@ -291,9 +291,12 @@ class ActivityUtilities {
|
|
|
291
291
|
static generateMissingStreams(activity) {
|
|
292
292
|
// Compute missing streams
|
|
293
293
|
this.generateMissingStreamsForActivity(activity);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
// Always include derived base streams (like Pace), but conditionally include unit variants
|
|
295
|
+
const includeUnitVariants = !activity.parseOptions || activity.parseOptions.generateUnitStreams;
|
|
296
|
+
activity.addStreams(this.createUnitStreamsFromStreams(activity.getAllStreams(), activity.type, undefined, {
|
|
297
|
+
includeDerivedTypes: true,
|
|
298
|
+
includeUnitVariants
|
|
299
|
+
}));
|
|
297
300
|
}
|
|
298
301
|
static getSummaryStatsForActivities(activities) {
|
|
299
302
|
const stats = [];
|
|
@@ -680,34 +683,37 @@ class ActivityUtilities {
|
|
|
680
683
|
includeUnitVariants: true
|
|
681
684
|
}) {
|
|
682
685
|
// @todo perhaps check input to be unitStreamTypesStrictly
|
|
683
|
-
const unitStreamTypesToCreate = unitStreamTypes ||
|
|
686
|
+
const unitStreamTypesToCreate = unitStreamTypes || [
|
|
687
|
+
...data_store_1.DynamicDataLoader.allUnitDerivedDataTypes,
|
|
688
|
+
...data_store_1.DynamicDataLoader.speedDerivedDataTypes
|
|
689
|
+
];
|
|
684
690
|
let baseUnitStreams = [];
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
691
|
+
// Iterate over all possible base types that can have unit variants
|
|
692
|
+
// This allows us to dynamically include ALL base streams (like Distance, Power, etc.) that need unit conversion
|
|
693
|
+
Object.keys(data_store_1.DynamicDataLoader.dataTypeUnitGroups).forEach(baseDataType => {
|
|
694
|
+
const stream = streams.find(s => s.type === baseDataType);
|
|
695
|
+
if (!stream) {
|
|
696
|
+
return;
|
|
689
697
|
}
|
|
690
|
-
|
|
691
|
-
|
|
698
|
+
// Special handling for derived types (Pace from Speed, etc.)
|
|
699
|
+
if (baseDataType === data_speed_1.DataSpeed.type && options.includeDerivedTypes) {
|
|
700
|
+
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeSpeedBasedStreams(stream, activityType));
|
|
701
|
+
return;
|
|
692
702
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (options.includeDerivedTypes) {
|
|
697
|
-
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeAltiDistanceSpeedBasedStreams(gradeAdjustedSpeedStream, activityType));
|
|
703
|
+
if (baseDataType === data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type && options.includeDerivedTypes) {
|
|
704
|
+
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeAltiDistanceSpeedBasedStreams(stream, activityType));
|
|
705
|
+
return;
|
|
698
706
|
}
|
|
699
|
-
|
|
700
|
-
|
|
707
|
+
if (baseDataType === data_vertical_speed_1.DataVerticalSpeed.type) {
|
|
708
|
+
// Vertical speed handling
|
|
709
|
+
if (activity_types_1.ActivityTypesHelper.verticalSpeedDerivedDataTypesToUseForActivityType(activityType).length) {
|
|
710
|
+
baseUnitStreams.push(stream);
|
|
711
|
+
}
|
|
712
|
+
return;
|
|
701
713
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
// For vertical speed (yet) we dont need a seperate function so just add the base that is the "derived" one
|
|
706
|
-
baseUnitStreams = activity_types_1.ActivityTypesHelper.verticalSpeedDerivedDataTypesToUseForActivityType(activityType).length
|
|
707
|
-
? baseUnitStreams.concat(verticalSpeedStream)
|
|
708
|
-
: baseUnitStreams;
|
|
709
|
-
}
|
|
710
|
-
// @todo add distance ?
|
|
714
|
+
// For everything else (like Distance), just add the base stream so it can be used for unit generation
|
|
715
|
+
baseUnitStreams.push(stream);
|
|
716
|
+
});
|
|
711
717
|
const startWith = baseUnitStreams.filter(baseUnitStream => unitStreamTypesToCreate.indexOf(baseUnitStream.type) !== -1 && streams.indexOf(baseUnitStream) === -1);
|
|
712
718
|
if (options.includeUnitVariants === false) {
|
|
713
719
|
return startWith;
|
|
@@ -867,7 +873,8 @@ class ActivityUtilities {
|
|
|
867
873
|
activity.addStream(leftPowerStream);
|
|
868
874
|
}
|
|
869
875
|
// If left stance time stream available, then add the right balance stream too
|
|
870
|
-
if (activity.hasStreamData(data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type) &&
|
|
876
|
+
if (activity.hasStreamData(data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type) &&
|
|
877
|
+
!activity.hasStreamData(data_stance_time_balance_right_1.DataStanceTimeBalanceRight.type)) {
|
|
871
878
|
const rightStanceBalanceTimeStream = activity.createStream(data_stance_time_balance_right_1.DataStanceTimeBalanceRight.type);
|
|
872
879
|
const leftStanceBalanceTimeStream = activity.getStreamData(data_stance_time_balance_left_1.DataStanceTimeBalanceLeft.type);
|
|
873
880
|
const rightStanceBalanceTimeData = leftStanceBalanceTimeStream.map(leftBalance => {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const event_1 = require("../event");
|
|
4
4
|
const data_speed_max_1 = require("../../data/data.speed-max");
|
|
5
5
|
const activity_1 = require("../../activities/activity");
|
|
6
|
+
const activity_parsing_options_1 = require("../../activities/activity-parsing-options");
|
|
6
7
|
const data_heart_rate_1 = require("../../data/data.heart-rate");
|
|
7
8
|
const data_altitude_1 = require("../../data/data.altitude");
|
|
8
9
|
const data_distance_1 = require("../../data/data.distance");
|
|
@@ -20,6 +21,7 @@ const file_type_enum_1 = require("../adapters/file-type.enum");
|
|
|
20
21
|
const importer_json_1 = require("../adapters/importers/json/importer.json");
|
|
21
22
|
const data_pace_1 = require("../../data/data.pace");
|
|
22
23
|
const data_speed_2 = require("../../data/data.speed");
|
|
24
|
+
const data_swim_pace_1 = require("../../data/data.swim-pace");
|
|
23
25
|
describe('Activity Utilities', () => {
|
|
24
26
|
let event;
|
|
25
27
|
beforeEach(() => {
|
|
@@ -238,7 +240,10 @@ describe('Activity Utilities', () => {
|
|
|
238
240
|
});
|
|
239
241
|
it('should exclude derived types when includeDerivedTypes is false', () => {
|
|
240
242
|
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
|
|
241
|
-
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, undefined, {
|
|
243
|
+
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, undefined, {
|
|
244
|
+
includeDerivedTypes: false,
|
|
245
|
+
includeUnitVariants: true
|
|
246
|
+
});
|
|
242
247
|
const paceStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
|
|
243
248
|
const kmhStream = result.find(s => s.type === data_speed_2.DataSpeedKilometersPerHour.type);
|
|
244
249
|
expect(paceStream).toBeUndefined();
|
|
@@ -247,7 +252,10 @@ describe('Activity Utilities', () => {
|
|
|
247
252
|
it('should exclude unit variants when includeUnitVariants is false', () => {
|
|
248
253
|
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
|
|
249
254
|
// We pass DataPace.type in unitStreamTypes so we can check if the derived base stream is present
|
|
250
|
-
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
|
|
255
|
+
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
|
|
256
|
+
includeDerivedTypes: true,
|
|
257
|
+
includeUnitVariants: false
|
|
258
|
+
});
|
|
251
259
|
const paceStream = result.find(s => s.type === data_pace_1.DataPace.type);
|
|
252
260
|
const paceUnitStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
|
|
253
261
|
expect(paceStream).toBeDefined(); // Derived type should be there
|
|
@@ -255,7 +263,10 @@ describe('Activity Utilities', () => {
|
|
|
255
263
|
});
|
|
256
264
|
it('should exclude both derived types and unit variants', () => {
|
|
257
265
|
const streams = [new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20])];
|
|
258
|
-
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
|
|
266
|
+
const result = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams(streams, activity_types_1.ActivityTypes.Running, [data_pace_1.DataPace.type], {
|
|
267
|
+
includeDerivedTypes: false,
|
|
268
|
+
includeUnitVariants: false
|
|
269
|
+
});
|
|
259
270
|
const paceStream = result.find(s => s.type === data_pace_1.DataPace.type);
|
|
260
271
|
const paceUnitStream = result.find(s => s.type === data_pace_1.DataPaceMinutesPerMile.type);
|
|
261
272
|
expect(paceStream).toBeUndefined();
|
|
@@ -263,8 +274,8 @@ describe('Activity Utilities', () => {
|
|
|
263
274
|
});
|
|
264
275
|
});
|
|
265
276
|
describe('generateMissingStreams', () => {
|
|
266
|
-
it('should generate unit streams
|
|
267
|
-
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
|
|
277
|
+
it('should generate unit streams when generateUnitStreams = true', () => {
|
|
278
|
+
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: true }));
|
|
268
279
|
// Add a speed stream
|
|
269
280
|
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
|
|
270
281
|
activity_utilities_1.ActivityUtilities.generateMissingStreams(activity);
|
|
@@ -278,7 +289,7 @@ describe('Activity Utilities', () => {
|
|
|
278
289
|
activity.parseOptions = {
|
|
279
290
|
streams: { smooth: {}, fixAbnormal: {} },
|
|
280
291
|
maxActivityDurationDays: 14,
|
|
281
|
-
generateUnitStreams: false
|
|
292
|
+
generateUnitStreams: false
|
|
282
293
|
};
|
|
283
294
|
// Add a speed stream
|
|
284
295
|
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
|
|
@@ -296,24 +307,69 @@ describe('Activity Utilities', () => {
|
|
|
296
307
|
activity.parseOptions = {
|
|
297
308
|
streams: { smooth: {}, fixAbnormal: {} },
|
|
298
309
|
maxActivityDurationDays: 14,
|
|
299
|
-
generateUnitStreams: false
|
|
310
|
+
generateUnitStreams: false // DISABLE streams
|
|
300
311
|
};
|
|
301
312
|
// Add a speed stream [10 m/s, 20 m/s]
|
|
302
313
|
// Max speed = 20 m/s
|
|
303
314
|
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [10, 20]));
|
|
304
315
|
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
305
|
-
// 1. Verify Streams are missing (as requested)
|
|
306
|
-
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
|
|
307
|
-
// 1. Verify Streams are missing (as requested)
|
|
316
|
+
// 1. Verify Unit Streams are missing (as requested)
|
|
308
317
|
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
|
|
309
|
-
// 2. Verify
|
|
318
|
+
// 2. Verify Derived Base Streams are PRESENT (The fix)
|
|
319
|
+
expect(activity.hasStreamData(data_pace_1.DataPace.type)).toBe(true);
|
|
320
|
+
// 3. Verify Stats are PRESENT (Safety Check)
|
|
310
321
|
// 20 m/s = 72 km/h
|
|
311
|
-
// We check for the stat by its string type if we can't import the constant easily, or iterate.
|
|
312
|
-
// Based on activity.utilities.ts it uses: DataSpeedMaxKilometersPerHour
|
|
313
322
|
const allStats = Array.from(activity.getStats().values());
|
|
314
323
|
const speedMaxKmh = allStats.find(s => s.getType() === data_speed_max_1.DataSpeedMaxKilometersPerHour.type);
|
|
315
324
|
expect(speedMaxKmh).toBeDefined();
|
|
316
325
|
expect(speedMaxKmh === null || speedMaxKmh === void 0 ? void 0 : speedMaxKmh.getValue()).toBe(72);
|
|
317
326
|
});
|
|
327
|
+
it('should generate DataSwimPace when generateUnitStreams = false for swimming', () => {
|
|
328
|
+
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Swimming, new creator_1.Creator('test'));
|
|
329
|
+
activity.parseOptions = {
|
|
330
|
+
streams: { smooth: {}, fixAbnormal: {} },
|
|
331
|
+
maxActivityDurationDays: 14,
|
|
332
|
+
generateUnitStreams: false
|
|
333
|
+
};
|
|
334
|
+
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, [1, 2])); // m/s
|
|
335
|
+
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
336
|
+
// Verify Unit Streams missing
|
|
337
|
+
expect(activity.hasStreamData(data_speed_2.DataSpeedKilometersPerHour.type)).toBe(false);
|
|
338
|
+
// Verify Derived Base Stream (Swim Pace) IS present
|
|
339
|
+
expect(activity.hasStreamData(data_swim_pace_1.DataSwimPace.type)).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
it('should generate DataDistanceMiles when generateUnitStreams = true', () => {
|
|
342
|
+
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'), new activity_parsing_options_1.ActivityParsingOptions({ generateUnitStreams: true }));
|
|
343
|
+
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [1000, 2000]));
|
|
344
|
+
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
345
|
+
// Should generate miles
|
|
346
|
+
expect(activity.hasStreamData(data_distance_1.DataDistanceMiles.type)).toBe(true);
|
|
347
|
+
});
|
|
348
|
+
it('should NOT generate DataDistanceMiles when generateUnitStreams = false', () => {
|
|
349
|
+
const activity = new activity_1.Activity(new Date(), new Date(), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test'));
|
|
350
|
+
activity.parseOptions = {
|
|
351
|
+
streams: { smooth: {}, fixAbnormal: {} },
|
|
352
|
+
maxActivityDurationDays: 14,
|
|
353
|
+
generateUnitStreams: false
|
|
354
|
+
};
|
|
355
|
+
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, [1000, 2000]));
|
|
356
|
+
activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
357
|
+
// Should NOT generate miles
|
|
358
|
+
expect(activity.hasStreamData(data_distance_1.DataDistanceMiles.type)).toBe(false);
|
|
359
|
+
// But base Distance should still be there (it was added manually)
|
|
360
|
+
expect(activity.hasStreamData(data_distance_1.DataDistance.type)).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
it('should generate unit streams for Mountain Biking using DataSpeed', () => {
|
|
363
|
+
const speedData = [10, 20, 30]; // m/s
|
|
364
|
+
const speedStream = new stream_1.Stream(data_speed_1.DataSpeed.type, speedData);
|
|
365
|
+
// Mountain Biking (defaults to Cycling group)
|
|
366
|
+
const unitStreams = activity_utilities_1.ActivityUtilities.createUnitStreamsFromStreams([speedStream], activity_types_1.ActivityTypes.MountainBiking, undefined, // Auto-detect all known unit types
|
|
367
|
+
{ includeDerivedTypes: true, includeUnitVariants: true });
|
|
368
|
+
const kmhStream = unitStreams.find(s => s.type === 'Speed in kilometers per hour');
|
|
369
|
+
expect(kmhStream).toBeDefined();
|
|
370
|
+
if (kmhStream) {
|
|
371
|
+
expect(kmhStream.getData()[0]).toBeCloseTo(36, 1); // 10 m/s = 36 km/h
|
|
372
|
+
}
|
|
373
|
+
});
|
|
318
374
|
});
|
|
319
375
|
});
|
|
@@ -45,6 +45,7 @@ export class ActivityTypesHelper {
|
|
|
45
45
|
case ActivityTypeGroups.TrailRunning:
|
|
46
46
|
return [DataPaceAvg.type, DataGradeAdjustedPaceAvg.type, DataSpeedAvg.type, DataGradeAdjustedSpeedAvg.type];
|
|
47
47
|
case ActivityTypeGroups.WaterSports:
|
|
48
|
+
case ActivityTypeGroups.Swimming:
|
|
48
49
|
return [DataSpeedAvg.type, DataSwimPaceAvg.type];
|
|
49
50
|
default:
|
|
50
51
|
return [DataSpeedAvg.type];
|
|
@@ -57,6 +58,7 @@ export class ActivityTypesHelper {
|
|
|
57
58
|
case ActivityTypeGroups.TrailRunning:
|
|
58
59
|
return [DataPace.type, DataSpeed.type];
|
|
59
60
|
case ActivityTypeGroups.WaterSports:
|
|
61
|
+
case ActivityTypeGroups.Swimming:
|
|
60
62
|
return [DataSpeed.type, DataSwimPace.type];
|
|
61
63
|
default:
|
|
62
64
|
return [DataSpeed.type];
|
|
@@ -68,7 +68,9 @@ describe('DataStore', () => {
|
|
|
68
68
|
temperatureUnits: [],
|
|
69
69
|
weightUnits: []
|
|
70
70
|
};
|
|
71
|
-
const result = DynamicDataLoader.getUnitBasedDataTypesFromDataTypes([DataSpeed.type], settings, {
|
|
71
|
+
const result = DynamicDataLoader.getUnitBasedDataTypesFromDataTypes([DataSpeed.type], settings, {
|
|
72
|
+
includeDerivedTypes: false
|
|
73
|
+
});
|
|
72
74
|
expect(result).toContain(DataSpeedKilometersPerHour.type);
|
|
73
75
|
expect(result).not.toContain(DataPaceMinutesPerMile.type);
|
|
74
76
|
});
|
|
@@ -621,10 +621,20 @@ export class EventImporterFIT {
|
|
|
621
621
|
// Pause TIME on Object (activity, lap...)
|
|
622
622
|
const pause = elapsedTime > movingTime && movingTime > 0 ? Math.round((elapsedTime - movingTime) * 100) / 100 : 0;
|
|
623
623
|
stats.push(new DataPause(pause));
|
|
624
|
+
const getStatValue = (obj, keys) => {
|
|
625
|
+
for (const key of keys) {
|
|
626
|
+
if (isNumberOrString(obj[key])) {
|
|
627
|
+
return obj[key];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
};
|
|
632
|
+
const avgSpeed = getStatValue(object, ['enhanced_avg_speed', 'EnhancedAvgSpeed', 'avg_speed', 'AvgSpeed']);
|
|
633
|
+
const totalDistance = getStatValue(object, ['total_distance', 'TotalDistance']);
|
|
624
634
|
// Assign is active lap status
|
|
625
|
-
stats.push(new DataActiveLap(!!(
|
|
626
|
-
if (
|
|
627
|
-
stats.push(new DataDistance(
|
|
635
|
+
stats.push(new DataActiveLap(!!(totalDistance || avgSpeed)));
|
|
636
|
+
if (totalDistance !== null) {
|
|
637
|
+
stats.push(new DataDistance(totalDistance));
|
|
628
638
|
}
|
|
629
639
|
else {
|
|
630
640
|
stats.push(new DataDistance(0));
|
|
@@ -684,24 +694,16 @@ export class EventImporterFIT {
|
|
|
684
694
|
stats.push(new DataPowerPedalSmoothnessRight(object.avg_right_pedal_smoothness));
|
|
685
695
|
}
|
|
686
696
|
// Speed
|
|
687
|
-
if (
|
|
688
|
-
stats.push(new DataSpeedAvg(
|
|
689
|
-
}
|
|
690
|
-
if (isNumberOrString(object.min_speed)) {
|
|
691
|
-
stats.push(new DataSpeedMin(object.min_speed));
|
|
692
|
-
}
|
|
693
|
-
if (isNumberOrString(object.max_speed)) {
|
|
694
|
-
stats.push(new DataSpeedMax(object.max_speed));
|
|
695
|
-
}
|
|
696
|
-
// Keep latest , enhanced @todo this can create a bug
|
|
697
|
-
if (isNumberOrString(object.enhanced_avg_speed)) {
|
|
698
|
-
stats.push(new DataSpeedAvg(object.enhanced_avg_speed));
|
|
697
|
+
if (avgSpeed !== null) {
|
|
698
|
+
stats.push(new DataSpeedAvg(avgSpeed));
|
|
699
699
|
}
|
|
700
|
-
|
|
701
|
-
|
|
700
|
+
const minSpeed = getStatValue(object, ['enhanced_min_speed', 'EnhancedMinSpeed', 'min_speed', 'MinSpeed']);
|
|
701
|
+
if (minSpeed !== null) {
|
|
702
|
+
stats.push(new DataSpeedMin(minSpeed));
|
|
702
703
|
}
|
|
703
|
-
|
|
704
|
-
|
|
704
|
+
const maxSpeed = getStatValue(object, ['enhanced_max_speed', 'EnhancedMaxSpeed', 'max_speed', 'MaxSpeed']);
|
|
705
|
+
if (maxSpeed !== null) {
|
|
706
|
+
stats.push(new DataSpeedMax(maxSpeed));
|
|
705
707
|
}
|
|
706
708
|
// Temperature
|
|
707
709
|
if (isNumberOrString(object.avg_temperature)) {
|
|
@@ -714,12 +716,14 @@ export class EventImporterFIT {
|
|
|
714
716
|
stats.push(new DataTemperatureMax(object.max_temperature));
|
|
715
717
|
}
|
|
716
718
|
// Ascent
|
|
717
|
-
|
|
718
|
-
|
|
719
|
+
const ascent = getStatValue(object, ['total_ascent', 'TotalAscent']);
|
|
720
|
+
if (ascent !== null) {
|
|
721
|
+
stats.push(new DataAscent(ascent));
|
|
719
722
|
}
|
|
720
723
|
// Descent
|
|
721
|
-
|
|
722
|
-
|
|
724
|
+
const descent = getStatValue(object, ['total_descent', 'TotalDescent']);
|
|
725
|
+
if (descent !== null) {
|
|
726
|
+
stats.push(new DataDescent(descent));
|
|
723
727
|
}
|
|
724
728
|
// Calories
|
|
725
729
|
if (isNumberOrString(object.total_calories)) {
|
|
@@ -47,7 +47,7 @@ export const FITSampleMapper = [
|
|
|
47
47
|
{
|
|
48
48
|
dataType: DataDistance.type,
|
|
49
49
|
getSampleValue: (sample) => {
|
|
50
|
-
return sample.distance;
|
|
50
|
+
return isNumber(sample.distance) ? sample.distance : sample.Distance;
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
{
|
|
@@ -59,13 +59,19 @@ export const FITSampleMapper = [
|
|
|
59
59
|
{
|
|
60
60
|
dataType: DataAltitude.type,
|
|
61
61
|
getSampleValue: (sample) => {
|
|
62
|
-
|
|
63
|
-
?
|
|
62
|
+
const altitude = isNumber(sample.enhanced_altitude)
|
|
63
|
+
? sample.enhanced_altitude
|
|
64
|
+
: isNumber(sample.EnhancedAltitude)
|
|
65
|
+
? sample.EnhancedAltitude
|
|
66
|
+
: isNumber(sample.altitude)
|
|
67
|
+
? sample.altitude
|
|
68
|
+
: isNumber(sample.Altitude)
|
|
69
|
+
? sample.Altitude
|
|
70
|
+
: null;
|
|
71
|
+
return isNumber(altitude)
|
|
72
|
+
? Math.round(altitude * Math.pow(10, ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)) /
|
|
64
73
|
Math.pow(10, ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)
|
|
65
|
-
:
|
|
66
|
-
? Math.round(sample.altitude * Math.pow(10, ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)) /
|
|
67
|
-
Math.pow(10, ALTITUDE_PRECISION_NUMBER_OF_DECIMAL_PLACES)
|
|
68
|
-
: sample.altitude;
|
|
74
|
+
: altitude;
|
|
69
75
|
}
|
|
70
76
|
},
|
|
71
77
|
{
|
|
@@ -102,12 +108,18 @@ export const FITSampleMapper = [
|
|
|
102
108
|
{
|
|
103
109
|
dataType: DataSpeed.type,
|
|
104
110
|
getSampleValue: (sample) => {
|
|
105
|
-
if (
|
|
111
|
+
if (isNumber(sample.enhanced_speed)) {
|
|
106
112
|
return sample.enhanced_speed;
|
|
107
113
|
}
|
|
108
|
-
if (
|
|
114
|
+
if (isNumber(sample.EnhancedSpeed)) {
|
|
115
|
+
return sample.EnhancedSpeed;
|
|
116
|
+
}
|
|
117
|
+
if (isNumber(sample.speed)) {
|
|
109
118
|
return sample.speed;
|
|
110
119
|
}
|
|
120
|
+
if (isNumber(sample.Speed)) {
|
|
121
|
+
return sample.Speed;
|
|
122
|
+
}
|
|
111
123
|
return null;
|
|
112
124
|
}
|
|
113
125
|
},
|
|
@@ -288,9 +288,12 @@ export class ActivityUtilities {
|
|
|
288
288
|
static generateMissingStreams(activity) {
|
|
289
289
|
// Compute missing streams
|
|
290
290
|
this.generateMissingStreamsForActivity(activity);
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
// Always include derived base streams (like Pace), but conditionally include unit variants
|
|
292
|
+
const includeUnitVariants = !activity.parseOptions || activity.parseOptions.generateUnitStreams;
|
|
293
|
+
activity.addStreams(this.createUnitStreamsFromStreams(activity.getAllStreams(), activity.type, undefined, {
|
|
294
|
+
includeDerivedTypes: true,
|
|
295
|
+
includeUnitVariants
|
|
296
|
+
}));
|
|
294
297
|
}
|
|
295
298
|
static getSummaryStatsForActivities(activities) {
|
|
296
299
|
const stats = [];
|
|
@@ -677,34 +680,37 @@ export class ActivityUtilities {
|
|
|
677
680
|
includeUnitVariants: true
|
|
678
681
|
}) {
|
|
679
682
|
// @todo perhaps check input to be unitStreamTypesStrictly
|
|
680
|
-
const unitStreamTypesToCreate = unitStreamTypes ||
|
|
683
|
+
const unitStreamTypesToCreate = unitStreamTypes || [
|
|
684
|
+
...DynamicDataLoader.allUnitDerivedDataTypes,
|
|
685
|
+
...DynamicDataLoader.speedDerivedDataTypes
|
|
686
|
+
];
|
|
681
687
|
let baseUnitStreams = [];
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
688
|
+
// Iterate over all possible base types that can have unit variants
|
|
689
|
+
// This allows us to dynamically include ALL base streams (like Distance, Power, etc.) that need unit conversion
|
|
690
|
+
Object.keys(DynamicDataLoader.dataTypeUnitGroups).forEach(baseDataType => {
|
|
691
|
+
const stream = streams.find(s => s.type === baseDataType);
|
|
692
|
+
if (!stream) {
|
|
693
|
+
return;
|
|
686
694
|
}
|
|
687
|
-
|
|
688
|
-
|
|
695
|
+
// Special handling for derived types (Pace from Speed, etc.)
|
|
696
|
+
if (baseDataType === DataSpeed.type && options.includeDerivedTypes) {
|
|
697
|
+
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeSpeedBasedStreams(stream, activityType));
|
|
698
|
+
return;
|
|
689
699
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if (options.includeDerivedTypes) {
|
|
694
|
-
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeAltiDistanceSpeedBasedStreams(gradeAdjustedSpeedStream, activityType));
|
|
700
|
+
if (baseDataType === DataGradeAdjustedSpeed.type && options.includeDerivedTypes) {
|
|
701
|
+
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeAltiDistanceSpeedBasedStreams(stream, activityType));
|
|
702
|
+
return;
|
|
695
703
|
}
|
|
696
|
-
|
|
697
|
-
|
|
704
|
+
if (baseDataType === DataVerticalSpeed.type) {
|
|
705
|
+
// Vertical speed handling
|
|
706
|
+
if (ActivityTypesHelper.verticalSpeedDerivedDataTypesToUseForActivityType(activityType).length) {
|
|
707
|
+
baseUnitStreams.push(stream);
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
698
710
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
// For vertical speed (yet) we dont need a seperate function so just add the base that is the "derived" one
|
|
703
|
-
baseUnitStreams = ActivityTypesHelper.verticalSpeedDerivedDataTypesToUseForActivityType(activityType).length
|
|
704
|
-
? baseUnitStreams.concat(verticalSpeedStream)
|
|
705
|
-
: baseUnitStreams;
|
|
706
|
-
}
|
|
707
|
-
// @todo add distance ?
|
|
711
|
+
// For everything else (like Distance), just add the base stream so it can be used for unit generation
|
|
712
|
+
baseUnitStreams.push(stream);
|
|
713
|
+
});
|
|
708
714
|
const startWith = baseUnitStreams.filter(baseUnitStream => unitStreamTypesToCreate.indexOf(baseUnitStream.type) !== -1 && streams.indexOf(baseUnitStream) === -1);
|
|
709
715
|
if (options.includeUnitVariants === false) {
|
|
710
716
|
return startWith;
|
|
@@ -864,7 +870,8 @@ export class ActivityUtilities {
|
|
|
864
870
|
activity.addStream(leftPowerStream);
|
|
865
871
|
}
|
|
866
872
|
// If left stance time stream available, then add the right balance stream too
|
|
867
|
-
if (activity.hasStreamData(DataStanceTimeBalanceLeft.type) &&
|
|
873
|
+
if (activity.hasStreamData(DataStanceTimeBalanceLeft.type) &&
|
|
874
|
+
!activity.hasStreamData(DataStanceTimeBalanceRight.type)) {
|
|
868
875
|
const rightStanceBalanceTimeStream = activity.createStream(DataStanceTimeBalanceRight.type);
|
|
869
876
|
const leftStanceBalanceTimeStream = activity.getStreamData(DataStanceTimeBalanceLeft.type);
|
|
870
877
|
const rightStanceBalanceTimeData = leftStanceBalanceTimeStream.map(leftBalance => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Event } from '../event';
|
|
2
2
|
import { DataSpeedMaxKilometersPerHour } from '../../data/data.speed-max';
|
|
3
3
|
import { Activity } from '../../activities/activity';
|
|
4
|
+
import { ActivityParsingOptions } from '../../activities/activity-parsing-options';
|
|
4
5
|
import { DataHeartRate } from '../../data/data.heart-rate';
|
|
5
6
|
import { DataAltitude } from '../../data/data.altitude';
|
|
6
|
-
import { DataDistance } from '../../data/data.distance';
|
|
7
|
+
import { DataDistance, DataDistanceMiles } from '../../data/data.distance';
|
|
7
8
|
import { DataDuration } from '../../data/data.duration';
|
|
8
9
|
import { Creator } from '../../creators/creator';
|
|
9
10
|
import { ActivityTypes } from '../../activities/activity.types';
|
|
@@ -18,6 +19,7 @@ import { FileType } from '../adapters/file-type.enum';
|
|
|
18
19
|
import { EventImporterJSON } from '../adapters/importers/json/importer.json';
|
|
19
20
|
import { DataPace, DataPaceMinutesPerMile } from '../../data/data.pace';
|
|
20
21
|
import { DataSpeedKilometersPerHour } from '../../data/data.speed';
|
|
22
|
+
import { DataSwimPace } from '../../data/data.swim-pace';
|
|
21
23
|
describe('Activity Utilities', () => {
|
|
22
24
|
let event;
|
|
23
25
|
beforeEach(() => {
|
|
@@ -236,7 +238,10 @@ describe('Activity Utilities', () => {
|
|
|
236
238
|
});
|
|
237
239
|
it('should exclude derived types when includeDerivedTypes is false', () => {
|
|
238
240
|
const streams = [new Stream(DataSpeed.type, [10, 20])];
|
|
239
|
-
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, undefined, {
|
|
241
|
+
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, undefined, {
|
|
242
|
+
includeDerivedTypes: false,
|
|
243
|
+
includeUnitVariants: true
|
|
244
|
+
});
|
|
240
245
|
const paceStream = result.find(s => s.type === DataPaceMinutesPerMile.type);
|
|
241
246
|
const kmhStream = result.find(s => s.type === DataSpeedKilometersPerHour.type);
|
|
242
247
|
expect(paceStream).toBeUndefined();
|
|
@@ -245,7 +250,10 @@ describe('Activity Utilities', () => {
|
|
|
245
250
|
it('should exclude unit variants when includeUnitVariants is false', () => {
|
|
246
251
|
const streams = [new Stream(DataSpeed.type, [10, 20])];
|
|
247
252
|
// We pass DataPace.type in unitStreamTypes so we can check if the derived base stream is present
|
|
248
|
-
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, [DataPace.type], {
|
|
253
|
+
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, [DataPace.type], {
|
|
254
|
+
includeDerivedTypes: true,
|
|
255
|
+
includeUnitVariants: false
|
|
256
|
+
});
|
|
249
257
|
const paceStream = result.find(s => s.type === DataPace.type);
|
|
250
258
|
const paceUnitStream = result.find(s => s.type === DataPaceMinutesPerMile.type);
|
|
251
259
|
expect(paceStream).toBeDefined(); // Derived type should be there
|
|
@@ -253,7 +261,10 @@ describe('Activity Utilities', () => {
|
|
|
253
261
|
});
|
|
254
262
|
it('should exclude both derived types and unit variants', () => {
|
|
255
263
|
const streams = [new Stream(DataSpeed.type, [10, 20])];
|
|
256
|
-
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, [DataPace.type], {
|
|
264
|
+
const result = ActivityUtilities.createUnitStreamsFromStreams(streams, ActivityTypes.Running, [DataPace.type], {
|
|
265
|
+
includeDerivedTypes: false,
|
|
266
|
+
includeUnitVariants: false
|
|
267
|
+
});
|
|
257
268
|
const paceStream = result.find(s => s.type === DataPace.type);
|
|
258
269
|
const paceUnitStream = result.find(s => s.type === DataPaceMinutesPerMile.type);
|
|
259
270
|
expect(paceStream).toBeUndefined();
|
|
@@ -261,8 +272,8 @@ describe('Activity Utilities', () => {
|
|
|
261
272
|
});
|
|
262
273
|
});
|
|
263
274
|
describe('generateMissingStreams', () => {
|
|
264
|
-
it('should generate unit streams
|
|
265
|
-
const activity = new Activity(new Date(), new Date(), ActivityTypes.Running, new Creator('test'));
|
|
275
|
+
it('should generate unit streams when generateUnitStreams = true', () => {
|
|
276
|
+
const activity = new Activity(new Date(), new Date(), ActivityTypes.Running, new Creator('test'), new ActivityParsingOptions({ generateUnitStreams: true }));
|
|
266
277
|
// Add a speed stream
|
|
267
278
|
activity.addStream(new Stream(DataSpeed.type, [10, 20]));
|
|
268
279
|
ActivityUtilities.generateMissingStreams(activity);
|
|
@@ -276,7 +287,7 @@ describe('Activity Utilities', () => {
|
|
|
276
287
|
activity.parseOptions = {
|
|
277
288
|
streams: { smooth: {}, fixAbnormal: {} },
|
|
278
289
|
maxActivityDurationDays: 14,
|
|
279
|
-
generateUnitStreams: false
|
|
290
|
+
generateUnitStreams: false
|
|
280
291
|
};
|
|
281
292
|
// Add a speed stream
|
|
282
293
|
activity.addStream(new Stream(DataSpeed.type, [10, 20]));
|
|
@@ -294,24 +305,69 @@ describe('Activity Utilities', () => {
|
|
|
294
305
|
activity.parseOptions = {
|
|
295
306
|
streams: { smooth: {}, fixAbnormal: {} },
|
|
296
307
|
maxActivityDurationDays: 14,
|
|
297
|
-
generateUnitStreams: false
|
|
308
|
+
generateUnitStreams: false // DISABLE streams
|
|
298
309
|
};
|
|
299
310
|
// Add a speed stream [10 m/s, 20 m/s]
|
|
300
311
|
// Max speed = 20 m/s
|
|
301
312
|
activity.addStream(new Stream(DataSpeed.type, [10, 20]));
|
|
302
313
|
ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
303
|
-
// 1. Verify Streams are missing (as requested)
|
|
304
|
-
expect(activity.hasStreamData(DataSpeedKilometersPerHour.type)).toBe(false);
|
|
305
|
-
// 1. Verify Streams are missing (as requested)
|
|
314
|
+
// 1. Verify Unit Streams are missing (as requested)
|
|
306
315
|
expect(activity.hasStreamData(DataSpeedKilometersPerHour.type)).toBe(false);
|
|
307
|
-
// 2. Verify
|
|
316
|
+
// 2. Verify Derived Base Streams are PRESENT (The fix)
|
|
317
|
+
expect(activity.hasStreamData(DataPace.type)).toBe(true);
|
|
318
|
+
// 3. Verify Stats are PRESENT (Safety Check)
|
|
308
319
|
// 20 m/s = 72 km/h
|
|
309
|
-
// We check for the stat by its string type if we can't import the constant easily, or iterate.
|
|
310
|
-
// Based on activity.utilities.ts it uses: DataSpeedMaxKilometersPerHour
|
|
311
320
|
const allStats = Array.from(activity.getStats().values());
|
|
312
321
|
const speedMaxKmh = allStats.find(s => s.getType() === DataSpeedMaxKilometersPerHour.type);
|
|
313
322
|
expect(speedMaxKmh).toBeDefined();
|
|
314
323
|
expect(speedMaxKmh === null || speedMaxKmh === void 0 ? void 0 : speedMaxKmh.getValue()).toBe(72);
|
|
315
324
|
});
|
|
325
|
+
it('should generate DataSwimPace when generateUnitStreams = false for swimming', () => {
|
|
326
|
+
const activity = new Activity(new Date(), new Date(), ActivityTypes.Swimming, new Creator('test'));
|
|
327
|
+
activity.parseOptions = {
|
|
328
|
+
streams: { smooth: {}, fixAbnormal: {} },
|
|
329
|
+
maxActivityDurationDays: 14,
|
|
330
|
+
generateUnitStreams: false
|
|
331
|
+
};
|
|
332
|
+
activity.addStream(new Stream(DataSpeed.type, [1, 2])); // m/s
|
|
333
|
+
ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
334
|
+
// Verify Unit Streams missing
|
|
335
|
+
expect(activity.hasStreamData(DataSpeedKilometersPerHour.type)).toBe(false);
|
|
336
|
+
// Verify Derived Base Stream (Swim Pace) IS present
|
|
337
|
+
expect(activity.hasStreamData(DataSwimPace.type)).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
it('should generate DataDistanceMiles when generateUnitStreams = true', () => {
|
|
340
|
+
const activity = new Activity(new Date(), new Date(), ActivityTypes.Running, new Creator('test'), new ActivityParsingOptions({ generateUnitStreams: true }));
|
|
341
|
+
activity.addStream(new Stream(DataDistance.type, [1000, 2000]));
|
|
342
|
+
ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
343
|
+
// Should generate miles
|
|
344
|
+
expect(activity.hasStreamData(DataDistanceMiles.type)).toBe(true);
|
|
345
|
+
});
|
|
346
|
+
it('should NOT generate DataDistanceMiles when generateUnitStreams = false', () => {
|
|
347
|
+
const activity = new Activity(new Date(), new Date(), ActivityTypes.Running, new Creator('test'));
|
|
348
|
+
activity.parseOptions = {
|
|
349
|
+
streams: { smooth: {}, fixAbnormal: {} },
|
|
350
|
+
maxActivityDurationDays: 14,
|
|
351
|
+
generateUnitStreams: false
|
|
352
|
+
};
|
|
353
|
+
activity.addStream(new Stream(DataDistance.type, [1000, 2000]));
|
|
354
|
+
ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity);
|
|
355
|
+
// Should NOT generate miles
|
|
356
|
+
expect(activity.hasStreamData(DataDistanceMiles.type)).toBe(false);
|
|
357
|
+
// But base Distance should still be there (it was added manually)
|
|
358
|
+
expect(activity.hasStreamData(DataDistance.type)).toBe(true);
|
|
359
|
+
});
|
|
360
|
+
it('should generate unit streams for Mountain Biking using DataSpeed', () => {
|
|
361
|
+
const speedData = [10, 20, 30]; // m/s
|
|
362
|
+
const speedStream = new Stream(DataSpeed.type, speedData);
|
|
363
|
+
// Mountain Biking (defaults to Cycling group)
|
|
364
|
+
const unitStreams = ActivityUtilities.createUnitStreamsFromStreams([speedStream], ActivityTypes.MountainBiking, undefined, // Auto-detect all known unit types
|
|
365
|
+
{ includeDerivedTypes: true, includeUnitVariants: true });
|
|
366
|
+
const kmhStream = unitStreams.find(s => s.type === 'Speed in kilometers per hour');
|
|
367
|
+
expect(kmhStream).toBeDefined();
|
|
368
|
+
if (kmhStream) {
|
|
369
|
+
expect(kmhStream.getData()[0]).toBeCloseTo(36, 1); // 10 m/s = 36 km/h
|
|
370
|
+
}
|
|
371
|
+
});
|
|
316
372
|
});
|
|
317
373
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sports-alliance/sports-lib",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.12",
|
|
4
4
|
"description": "A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gpx",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"fast-xml-parser": "^5.3.3",
|
|
57
57
|
"fit-file-parser": "^2.1.0",
|
|
58
|
-
"geolib": "^3.3.
|
|
58
|
+
"geolib": "^3.3.4",
|
|
59
59
|
"gpx-builder": "^3.7.8",
|
|
60
60
|
"kalmanjs": "^1.1.0",
|
|
61
61
|
"lowpassf": "^0.5.0",
|