@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
JavaScript
"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();
});
});