UNPKG

@sports-alliance/sports-lib

Version:

A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc

263 lines (262 loc) 16 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const importer_fit_1 = require("./importer.fit"); const data_moving_time_1 = require("../../../../data/data.moving-time"); const data_effort_pace_avg_1 = require("../../../../data/data.effort-pace-avg"); const data_effort_pace_min_1 = require("../../../../data/data.effort-pace-min"); const data_effort_pace_max_1 = require("../../../../data/data.effort-pace-max"); const data_effort_pace_1 = require("../../../../data/data.effort-pace"); const data_depth_max_1 = require("../../../../data/data.depth-max"); const data_avg_stroke_distance_1 = require("../../../../data/data.avg-stroke-distance"); const data_avg_stroke_count_1 = require("../../../../data/data.avg-stroke-count"); const activity_1 = require("../../../../activities/activity"); const activity_types_1 = require("../../../../activities/activity.types"); const creator_1 = require("../../../../creators/creator"); const data_vertical_oscillation_avg_1 = require("../../../../data/data.vertical-oscillation-avg"); const data_vertical_ratio_avg_1 = require("../../../../data/data.vertical-ratio-avg"); const data_ground_contact_time_avg_1 = require("../../../../data/data.ground-contact-time-avg"); const data_grade_avg_1 = require("../../../../data/data.grade-avg"); const data_grade_min_1 = require("../../../../data/data.grade-min"); const data_grade_max_1 = require("../../../../data/data.grade-max"); const data_recovery_time_1 = require("../../../../data/data.recovery-time"); const data_stamina_1 = require("../../../../data/data.stamina"); const data_vo2_max_1 = require("../../../../data/data.vo2-max"); const helpers_1 = require("../../../utilities/helpers"); const activity_parsing_options_1 = require("../../../../activities/activity-parsing-options"); describe('EventImporterFIT session stats mapping', () => { const toArrayBuffer = (filePath) => { const fileContent = fs.readFileSync(filePath); return fileContent.buffer.slice(fileContent.byteOffset, fileContent.byteOffset + fileContent.byteLength); }; it('should use session total_moving_time when available', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../specs/fixtures/rides/fit/971150603.fit'); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const movingTime = activity.getStat(data_moving_time_1.DataMovingTime.type); expect(movingTime).toBeDefined(); expect(movingTime.getValue()).toBe(3802); })); it('should map session Effort Pace summary stat', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../../samples/coros/step-effort.fit'); if (!fs.existsSync(fitFilePath)) { console.warn(`Sample file not found at ${fitFilePath}. Skipping test.`); return; } const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const effortPace = activity.getStat(data_effort_pace_avg_1.DataEffortPaceAvg.type); expect(effortPace).toBeDefined(); expect(effortPace.getValue()).toBeCloseTo((0, helpers_1.convertSpeedToPace)(3.412), 3); })); it('should parse Effort Pace stream and stats for 2026-01-31_10-51_2.fit', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../../samples/fit/2026-01-31_10-51_2.fit'); if (!fs.existsSync(fitFilePath)) { console.warn(`Sample file not found at ${fitFilePath}. Skipping test.`); return; } const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const effortPaceStream = activity .getStreamData(data_effort_pace_1.DataEffortPace.type) .filter((value) => Number.isFinite(value)); const effortPaceAvg = activity.getStat(data_effort_pace_avg_1.DataEffortPaceAvg.type); const effortPaceMin = activity.getStat(data_effort_pace_min_1.DataEffortPaceMin.type); const effortPaceMax = activity.getStat(data_effort_pace_max_1.DataEffortPaceMax.type); expect(effortPaceStream.length).toBeGreaterThan(3000); expect(effortPaceAvg).toBeDefined(); expect(effortPaceMin).toBeDefined(); expect(effortPaceMax).toBeDefined(); const minValue = Number(effortPaceMin.getValue()); const avgValue = Number(effortPaceAvg.getValue()); const maxValue = Number(effortPaceMax.getValue()); expect(Number.isFinite(minValue)).toBe(true); expect(Number.isFinite(avgValue)).toBe(true); expect(Number.isFinite(maxValue)).toBe(true); expect(minValue).toBeLessThanOrEqual(avgValue); expect(avgValue).toBeLessThanOrEqual(maxValue); })); it('should map session max_depth to Maximum Depth stat', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../../samples/fit/2025-08-27_10-52.fit'); if (!fs.existsSync(fitFilePath)) { console.warn(`Sample file not found at ${fitFilePath}. Skipping test.`); return; } const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const maxDepth = activity.getStat(data_depth_max_1.DataDepthMax.type); expect(maxDepth).toBeDefined(); expect(maxDepth.getValue()).toBeCloseTo(3.86, 2); })); it('should ignore zero VO2 max and recovery time activity metrics', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../../samples/fit/2025-10-17_12-26.fit'); if (!fs.existsSync(fitFilePath)) { console.warn(`Sample file not found at ${fitFilePath}. Skipping test.`); return; } const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const vo2Max = activity.getStat(data_vo2_max_1.DataVO2Max.type); const recoveryTime = activity.getStat(data_recovery_time_1.DataRecoveryTime.type); expect(vo2Max).toBeUndefined(); expect(recoveryTime).toBeUndefined(); })); it('should prefer Garmin activity metrics VO2 max over user metrics VO2 max', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../../samples/fit/2026-05-04_16-22.fit'); if (!fs.existsSync(fitFilePath)) { console.warn(`Sample file not found at ${fitFilePath}. Skipping test.`); return; } const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const vo2Max = activity.getStat(data_vo2_max_1.DataVO2Max.type); expect(vo2Max).toBeDefined(); expect(vo2Max.getValue()).toBeCloseTo(56.58073425292969, 6); })); it('should fall back to Garmin user metrics VO2 max when activity metrics VO2 max is invalid', () => { const startTime = new Date('2026-05-04T13:22:31.000Z'); const endTime = new Date('2026-05-04T13:32:31.000Z'); const activity = importer_fit_1.EventImporterFIT.getActivityFromSessionObject({ start_time: startTime, timestamp: endTime, total_elapsed_time: 600, total_timer_time: 600, sport: 'cycling', laps: [] }, { file_ids: [{ manufacturer: 'garmin', product: 4655 }], records: [], events: [], laps: [], activity_metrics: [{ sport: 'cycling', vo2_max: 114688, first_vo2_max: 0, recovery_time: 0 }], user_metrics: [{ start_of_activity: startTime, vo2_max: 53.18017578125, first_vo2_max: 53.18 }] }, activity_parsing_options_1.ActivityParsingOptions.DEFAULT); const vo2Max = activity.getStat(data_vo2_max_1.DataVO2Max.type); expect(vo2Max).toBeDefined(); expect(vo2Max.getValue()).toBeCloseTo(53.18017578125, 6); }); it('should ignore irrational Garmin VO2 max values', () => { const startTime = new Date('2026-05-04T13:22:31.000Z'); const endTime = new Date('2026-05-04T13:32:31.000Z'); const activity = importer_fit_1.EventImporterFIT.getActivityFromSessionObject({ start_time: startTime, timestamp: endTime, total_elapsed_time: 600, total_timer_time: 600, sport: 'cycling', laps: [] }, { file_ids: [{ manufacturer: 'garmin', product: 4655 }], records: [], events: [], laps: [], activity_metrics: [{ sport: 'cycling', vo2_max: 114688, first_vo2_max: 0, recovery_time: 0 }], user_metrics: [{ start_of_activity: startTime, vo2_max: 500, first_vo2_max: -1 }] }, activity_parsing_options_1.ActivityParsingOptions.DEFAULT); expect(activity.getStat(data_vo2_max_1.DataVO2Max.type)).toBeUndefined(); }); it('should map swim session stroke summary stats', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../specs/fixtures/swim/fit/7617306288.fit'); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const avgStrokeDistance = activity.getStat(data_avg_stroke_distance_1.DataAvgStrokeDistance.type); const avgStrokeCount = activity.getStat(data_avg_stroke_count_1.DataAvgStrokeCount.type); expect(avgStrokeDistance).toBeDefined(); expect(avgStrokeDistance.getValue()).toBeCloseTo(2.11, 2); expect(avgStrokeCount).toBeDefined(); expect(avgStrokeCount.getValue()).toBeCloseTo(11.8, 1); })); it('should map running dynamics and grade summary from session fields when available', () => { var _a, _b, _c, _d, _e, _f; const activity = new activity_1.Activity(new Date(0), new Date(10000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test')); const stats = importer_fit_1.EventImporterFIT.getStatsFromObject({ total_elapsed_time: 10, total_timer_time: 10, avg_stance_time: 271.3, avg_vertical_oscillation: 97.2, avg_vertical_ratio: 9.57, avg_grade: 0.13, max_pos_grade: 4.47, max_neg_grade: -3.84 }, activity, false); const getStat = (type) => stats.find(stat => stat.getType() === type); expect((_a = getStat(data_ground_contact_time_avg_1.DataGroundContactTimeAvg.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(271.3); expect((_b = getStat(data_vertical_oscillation_avg_1.DataVerticalOscillationAvg.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBe(97.2); expect((_c = getStat(data_vertical_ratio_avg_1.DataVerticalRatioAvg.type)) === null || _c === void 0 ? void 0 : _c.getValue()).toBe(9.57); expect((_d = getStat(data_grade_avg_1.DataGradeAvg.type)) === null || _d === void 0 ? void 0 : _d.getValue()).toBe(0.13); expect((_e = getStat(data_grade_min_1.DataGradeMin.type)) === null || _e === void 0 ? void 0 : _e.getValue()).toBe(-3.84); expect((_f = getStat(data_grade_max_1.DataGradeMax.type)) === null || _f === void 0 ? void 0 : _f.getValue()).toBe(4.47); }); it('should map grade min/max/avg from FIT session summary when fields exist', () => __awaiter(void 0, void 0, void 0, function* () { const fitFilePath = path.join(__dirname, '../../../../specs/fixtures/rides/fit/7739869618.fit'); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(toArrayBuffer(fitFilePath)); const activity = event.getFirstActivity(); const avgGrade = activity.getStat(data_grade_avg_1.DataGradeAvg.type); const minGrade = activity.getStat(data_grade_min_1.DataGradeMin.type); const maxGrade = activity.getStat(data_grade_max_1.DataGradeMax.type); expect(avgGrade).toBeDefined(); expect(minGrade).toBeDefined(); expect(maxGrade).toBeDefined(); expect(avgGrade.getValue()).toBeCloseTo(0.13, 2); expect(minGrade.getValue()).toBeCloseTo(-3.84, 2); expect(maxGrade.getValue()).toBeCloseTo(4.47, 2); })); it('should map Garmin stamina session summary fields', () => { var _a, _b, _c; const activity = new activity_1.Activity(new Date(0), new Date(10000), activity_types_1.ActivityTypes.Running, new creator_1.Creator('test')); const stats = importer_fit_1.EventImporterFIT.getStatsFromObject({ total_elapsed_time: 10, total_timer_time: 10, beginning_potential_stamina: 95, ending_potential_stamina: 66, min_stamina: 34 }, activity, false); const getStat = (type) => stats.find(stat => stat.getType() === type); expect((_a = getStat(data_stamina_1.DataBeginningPotentialStamina.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(95); expect((_b = getStat(data_stamina_1.DataEndingPotentialStamina.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBe(66); expect((_c = getStat(data_stamina_1.DataStaminaMin.type)) === null || _c === void 0 ? void 0 : _c.getValue()).toBe(34); }); });