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

384 lines (383 loc) 28 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 activity_1 = require("../../activities/activity"); const activity_parsing_options_1 = require("../../activities/activity-parsing-options"); const activity_types_1 = require("../../activities/activity.types"); const creator_1 = require("../../creators/creator"); const data_energy_1 = require("../../data/data.energy"); const data_ftp_1 = require("../../data/data.ftp"); const data_grade_smooth_1 = require("../../data/data.grade-smooth"); const data_heart_rate_1 = require("../../data/data.heart-rate"); const data_power_1 = require("../../data/data.power"); const data_power_intensity_factor_1 = require("../../data/data.power-intensity-factor"); const data_power_normalized_1 = require("../../data/data.power-normalized"); const data_training_stress_score_1 = require("../../data/data.training-stress-score"); const data_speed_1 = require("../../data/data.speed"); const data_swim_pace_1 = require("../../data/data.swim-pace"); const data_training_stress_score_method_1 = require("../../data/data.training-stress-score-method"); const data_vertical_speed_1 = require("../../data/data.vertical-speed"); const data_weight_1 = require("../../data/data.weight"); const intensity_zones_1 = require("../../intensity-zones/intensity-zones"); const activity_utilities_1 = require("./activity.utilities"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const importer_fit_1 = require("../adapters/importers/fit/importer.fit"); const creator = new creator_1.Creator('test'); function createActivity(type, durationSeconds, options) { const start = new Date('2026-01-01T00:00:00.000Z'); const end = new Date(start.getTime() + durationSeconds * 1000); return new activity_1.Activity(start, end, type, creator, options); } function addNumericStream(activity, type, data) { const stream = activity.createStream(type); stream.setData(data); activity.addStream(stream); } function addZoneFiveThreshold(activity, type, zone5LowerLimit) { const zones = new intensity_zones_1.IntensityZones(type); zones.zone1Duration = 0; zones.zone2Duration = 0; zones.zone3Duration = 0; zones.zone4Duration = 0; zones.zone5Duration = 0; zones.zone5LowerLimit = zone5LowerLimit; activity.intensityZones.push(zones); } function computeLegacyPowerTssFromSamples(samples, ftp) { if (!Number.isFinite(ftp) || ftp <= 0) { return null; } const orderedSamples = samples .filter(sample => Number.isFinite(sample.duration) && Number.isFinite(sample.power)) .sort((left, right) => left.duration - right.duration); if (!orderedSamples.length) { return null; } const power30sList = orderedSamples.map((sample, index) => { if (sample.duration < 30) { return null; } const index30SecAgo = Math.max(0, orderedSamples.slice(0, index).findIndex(previous => sample.duration - previous.duration < 30)); const window = orderedSamples.slice(index30SecAgo, index + 1).map(item => item.power); const mean = window.reduce((sum, value) => sum + value, 0) / window.length; return Number.isNaN(mean) ? null : mean; }); let tss = 0; let intensityFactor = 0; let normalizedPower = 0; let rollPower4Sum = 0; let rollPower4Count = 0; orderedSamples.forEach((sample, index) => { const power30s = power30sList[index]; if (power30s !== null) { rollPower4Sum += Math.pow(power30s, 4); rollPower4Count += 1; } if (rollPower4Count > 0) { normalizedPower = Math.pow(rollPower4Sum / rollPower4Count, 0.25); } intensityFactor = normalizedPower / ftp; tss = (100 * (sample.duration - 29) * normalizedPower * intensityFactor) / (ftp * 3600); }); return Number.isFinite(tss) ? tss : null; } describe('Training Stress Score integration', () => { it('preserves imported TSS by default and sets method to IMPORTED', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Cycling, 1200); activity.addStat(new data_training_stress_score_1.DataTrainingStressScore(42.5)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(42.5); expect((_b = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.IMPORTED); }); it('recomputes imported TSS when preserveImportedTss is disabled', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Cycling, 3600, new activity_parsing_options_1.ActivityParsingOptions({ tss: { preserveImportedTss: false, overrides: { functionalThresholdPower: 250 } } })); activity.addStat(new data_training_stress_score_1.DataTrainingStressScore(42.5)); addNumericStream(activity, data_power_1.DataPower.type, new Array(3600).fill(250)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.POWER); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).not.toBe(42.5); }); it('uses POWER first when all methods could be computed', () => { var _a; const activity = createActivity(activity_types_1.ActivityTypes.Running, 600, new activity_parsing_options_1.ActivityParsingOptions({ tss: { overrides: { functionalThresholdPower: 200 } } })); addNumericStream(activity, data_power_1.DataPower.type, new Array(600).fill(200)); addNumericStream(activity, data_heart_rate_1.DataHeartRate.type, new Array(600).fill(160)); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(600).fill(3)); addNumericStream(activity, data_vertical_speed_1.DataVerticalSpeed.type, new Array(600).fill(0.1)); addZoneFiveThreshold(activity, data_heart_rate_1.DataHeartRate.type, 170); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 3); activity.addStat(new data_energy_1.DataEnergy(200)); activity.addStat(new data_weight_1.DataWeight(70)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.POWER); }); it('keeps POWER IF and TSS aligned to the same NP/FTP source', () => { var _a, _b, _c, _d, _e; const ftpOverride = 280; const activity = createActivity(activity_types_1.ActivityTypes.Cycling, 3600, new activity_parsing_options_1.ActivityParsingOptions({ tss: { overrides: { functionalThresholdPower: ftpOverride } } })); addNumericStream(activity, data_power_1.DataPower.type, new Array(3600).fill(300)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); const method = (_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue(); const normalizedPower = (_b = activity.getStat(data_power_normalized_1.DataPowerNormalized.type)) === null || _b === void 0 ? void 0 : _b.getValue(); const intensityFactor = (_c = activity.getStat(data_power_intensity_factor_1.DataPowerIntensityFactor.type)) === null || _c === void 0 ? void 0 : _c.getValue(); const tss = (_d = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _d === void 0 ? void 0 : _d.getValue(); const derivedFtp = (_e = activity.getStat(data_ftp_1.DataFTP.type)) === null || _e === void 0 ? void 0 : _e.getValue(); const expectedIf = Math.round((normalizedPower / ftpOverride) * 1000) / 1000; const expectedTss = Math.round(((100 * Math.max(3600 - 29, 0) * normalizedPower * (normalizedPower / ftpOverride)) / (ftpOverride * 3600)) * 10) / 10; expect(method).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.POWER); expect(derivedFtp).toBe(ftpOverride); expect(intensityFactor).toBeCloseTo(expectedIf, 3); expect(tss).toBeCloseTo(expectedTss, 1); expect(intensityFactor).toBeCloseTo(normalizedPower / derivedFtp, 3); }); it('falls back to HR when POWER is unavailable', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Running, 300); addNumericStream(activity, data_heart_rate_1.DataHeartRate.type, new Array(300).fill(160)); addZoneFiveThreshold(activity, data_heart_rate_1.DataHeartRate.type, 170); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.HR); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeGreaterThan(0); }); it('uses Banister when resting HR is provided and Edwards when it is missing', () => { var _a, _b, _c, _d; const banisterActivity = createActivity(activity_types_1.ActivityTypes.Running, 3600, new activity_parsing_options_1.ActivityParsingOptions({ tss: { overrides: { maxHeartRate: 190, restingHeartRate: 50, lactateThresholdHR: 170 } } })); addNumericStream(banisterActivity, data_heart_rate_1.DataHeartRate.type, new Array(3600).fill(160)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(banisterActivity); const edwardsActivity = createActivity(activity_types_1.ActivityTypes.Running, 3600, new activity_parsing_options_1.ActivityParsingOptions({ tss: { overrides: { maxHeartRate: 190, lactateThresholdHR: 170 } } })); addNumericStream(edwardsActivity, data_heart_rate_1.DataHeartRate.type, new Array(3600).fill(160)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(edwardsActivity); const banisterTss = (_a = banisterActivity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _a === void 0 ? void 0 : _a.getValue(); const edwardsTss = (_b = edwardsActivity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue(); expect((_c = banisterActivity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _c === void 0 ? void 0 : _c.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.HR); expect((_d = edwardsActivity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _d === void 0 ? void 0 : _d.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.HR); expect(banisterTss).toBeDefined(); expect(edwardsTss).toBeDefined(); expect(banisterTss).not.toBe(edwardsTss); }); it('falls back to PACE for pace-enabled activities when HR is unavailable', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Running, 300); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(300).fill(3)); addNumericStream(activity, data_vertical_speed_1.DataVerticalSpeed.type, new Array(300).fill(0.1)); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 3.2); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.PACE); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeGreaterThan(0); }); it('treats grade stream values as percent for PACE TSS inputs', () => { var _a, _b, _c, _d; const runningWithPercentGrade = createActivity(activity_types_1.ActivityTypes.Running, 1200); addNumericStream(runningWithPercentGrade, data_speed_1.DataSpeed.type, new Array(1200).fill(3)); addNumericStream(runningWithPercentGrade, data_grade_smooth_1.DataGradeSmooth.type, new Array(1200).fill(10)); addZoneFiveThreshold(runningWithPercentGrade, data_speed_1.DataSpeed.type, 3); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(runningWithPercentGrade); const runningWithDerivedDecimalGrade = createActivity(activity_types_1.ActivityTypes.Running, 1200); addNumericStream(runningWithDerivedDecimalGrade, data_speed_1.DataSpeed.type, new Array(1200).fill(3)); addNumericStream(runningWithDerivedDecimalGrade, data_vertical_speed_1.DataVerticalSpeed.type, new Array(1200).fill(0.3)); addZoneFiveThreshold(runningWithDerivedDecimalGrade, data_speed_1.DataSpeed.type, 3); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(runningWithDerivedDecimalGrade); const percentMethod = (_a = runningWithPercentGrade.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue(); const decimalMethod = (_b = runningWithDerivedDecimalGrade.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _b === void 0 ? void 0 : _b.getValue(); const percentTss = (_c = runningWithPercentGrade.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _c === void 0 ? void 0 : _c.getValue(); const decimalTss = (_d = runningWithDerivedDecimalGrade.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _d === void 0 ? void 0 : _d.getValue(); expect(percentMethod).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.PACE); expect(decimalMethod).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.PACE); expect(Math.abs(percentTss - decimalTss)).toBeLessThanOrEqual(0.2); }); it('uses PACE for Track and Field (TSS pace-enabled)', () => { var _a; const activity = createActivity(activity_types_1.ActivityTypes.TrackAndField, 300); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(300).fill(3)); addNumericStream(activity, data_vertical_speed_1.DataVerticalSpeed.type, new Array(300).fill(0.1)); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 3.2); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.PACE); }); it('skips PACE for non-pace sports and falls back to MET', () => { var _a; const activity = createActivity(activity_types_1.ActivityTypes.Cycling, 1200); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(1200).fill(8)); addNumericStream(activity, data_vertical_speed_1.DataVerticalSpeed.type, new Array(1200).fill(0.2)); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 8.5); activity.addStat(new data_energy_1.DataEnergy(220)); activity.addStat(new data_weight_1.DataWeight(70)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.MET); }); it('uses SWIM_PACE for swimming activities before MET', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Swimming, 1800); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(1800).fill(1.05)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.SWIM_PACE); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeGreaterThan(0); }); it('converts swim-pace zone threshold (sec/100m) to speed for SWIM_PACE TSS', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Swimming, 1800); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(1800).fill(1)); addZoneFiveThreshold(activity, data_swim_pace_1.DataSwimPace.type, 100); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.SWIM_PACE); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeCloseTo(50, 1); }); it('continues SWIM_PACE fallback chain when swim-pace zone threshold is invalid', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Swimming, 1800); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(1800).fill(1)); addZoneFiveThreshold(activity, data_swim_pace_1.DataSwimPace.type, 0); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 1.25); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.SWIM_PACE); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeCloseTo(25.6, 1); }); it('falls back to MET as last resort', () => { var _a, _b; const activity = createActivity(activity_types_1.ActivityTypes.Running, 1800); activity.addStat(new data_energy_1.DataEnergy(320)); activity.addStat(new data_weight_1.DataWeight(70)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.MET); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBeGreaterThan(0); }); it('skips pace when vertical speed is missing and heuristic fallbacks are disabled', () => { var _a; const activity = createActivity(activity_types_1.ActivityTypes.Running, 1200, new activity_parsing_options_1.ActivityParsingOptions({ tss: { enableHeuristicFallbacks: false } })); addNumericStream(activity, data_speed_1.DataSpeed.type, new Array(1200).fill(3)); addZoneFiveThreshold(activity, data_speed_1.DataSpeed.type, 3.2); activity.addStat(new data_energy_1.DataEnergy(220)); activity.addStat(new data_weight_1.DataWeight(70)); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_a = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.MET); }); it('keeps imported fixture TSS untouched and marks it as IMPORTED', () => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c; const fixturePath = path.join(__dirname, '../../specs/fixtures/rides/fit/7386755164.fit'); const buffer = fs.readFileSync(fixturePath); const event = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)); const activity = event.getFirstActivity(); expect((_a = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _a === void 0 ? void 0 : _a.getValue()).toBe(105.4); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect((_b = activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _b === void 0 ? void 0 : _b.getValue()).toBe(105.4); expect((_c = activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _c === void 0 ? void 0 : _c.getValue()).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.IMPORTED); })); it.each(['2026-03-30_11-20.fit', 'laps-issue.fit'])('improves POWER delta vs legacy rolling baseline for %s', (fixtureName) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d; const fixturePath = path.join(__dirname, '../../../samples/fit', fixtureName); const fixtureBuffer = fs.readFileSync(fixturePath); const fixtureArrayBuffer = fixtureBuffer.buffer.slice(fixtureBuffer.byteOffset, fixtureBuffer.byteOffset + fixtureBuffer.byteLength); const importedEvent = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(fixtureArrayBuffer); const importedActivity = importedEvent.getFirstActivity(); const reportedTss = (_a = importedActivity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _a === void 0 ? void 0 : _a.getValue(); expect(reportedTss).toBeGreaterThan(0); const recomputedEvent = yield importer_fit_1.EventImporterFIT.getFromArrayBuffer(fixtureArrayBuffer, new activity_parsing_options_1.ActivityParsingOptions({ tss: { preserveImportedTss: false } }), fixtureName); const recomputedActivity = recomputedEvent.getFirstActivity(); const method = (_b = recomputedActivity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)) === null || _b === void 0 ? void 0 : _b.getValue(); const computedTss = (_c = recomputedActivity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)) === null || _c === void 0 ? void 0 : _c.getValue(); const ftp = (_d = recomputedActivity.getStat(data_ftp_1.DataFTP.type)) === null || _d === void 0 ? void 0 : _d.getValue(); const powerSamples = recomputedActivity .getStreamDataByDuration(data_power_1.DataPower.type, true, true) .map(sample => ({ duration: sample.time / 1000, power: sample.value })); const baselineLegacyTssRaw = computeLegacyPowerTssFromSamples(powerSamples, ftp); const baselineLegacyTss = baselineLegacyTssRaw === null ? null : Math.round(baselineLegacyTssRaw * 10) / 10; const deltaCurrent = Math.abs(computedTss - reportedTss); const deltaLegacy = baselineLegacyTss === null ? Infinity : Math.abs(baselineLegacyTss - reportedTss); expect(method).toBe(data_training_stress_score_method_1.TrainingStressScoreMethod.POWER); expect(deltaCurrent).toBeLessThan(deltaLegacy); })); it('keeps TSS unset when no method inputs are available', () => { const activity = createActivity(activity_types_1.ActivityTypes.Cycling, 1200); expect(activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)).toBeUndefined(); activity_utilities_1.ActivityUtilities.generateMissingStreamsAndStatsForActivity(activity); expect(activity.getStat(data_training_stress_score_1.DataTrainingStressScore.type)).toBeUndefined(); expect(activity.getStat(data_training_stress_score_method_1.DataTrainingStressScoreMethod.type)).toBeUndefined(); }); });