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

146 lines (145 loc) 5.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StatsUtilities = void 0; class StatsUtilities { static serializeStats(stats) { const statsJSON = {}; stats.forEach((value) => { Object.assign(statsJSON, value.toJSON()); }); return statsJSON; } static getFiniteStatValue(source, statType) { var _a; const value = (_a = source.getStat(statType)) === null || _a === void 0 ? void 0 : _a.getValue(); return typeof value === 'number' && Number.isFinite(value) ? value : null; } static getFiniteNumericValue(value) { if (typeof value !== 'number' && typeof value !== 'string') { return null; } if (typeof value === 'string' && value.trim() === '') { return null; } const numericValue = Number(value); return Number.isFinite(numericValue) ? numericValue : null; } static getFiniteNumericRecordValue(source, keys) { if (!source || typeof source !== 'object') { return null; } const record = source; for (const key of keys) { const value = this.getFiniteNumericValue(record[key]); if (value !== null) { return value; } } return null; } static aggregateNumericRecords(records, aggregations) { const sourceRecords = records.filter(record => record && typeof record === 'object'); return aggregations.reduce((summary, aggregation) => { const valuesWithWeights = sourceRecords.reduce((accu, record) => { const value = this.getFiniteNumericRecordValue(record, aggregation.keys); if (value === null) { return accu; } accu.push({ value, weight: aggregation.weightKeys ? this.getFiniteNumericRecordValue(record, aggregation.weightKeys) : null }); return accu; }, []); if (aggregation.requireCompleteCoverage && valuesWithWeights.length !== sourceRecords.length) { return summary; } const aggregateValue = this.aggregateValues(valuesWithWeights, aggregation.reducer); if (aggregateValue !== null) { summary[aggregation.outputKey] = aggregateValue; } return summary; }, {}); } static sum(sources, statType) { let sum = 0; let hasValue = false; sources.forEach(source => { const value = this.getFiniteStatValue(source, statType); if (value === null) { return; } sum += value; hasValue = true; }); return hasValue ? sum : null; } static min(sources, statType) { const values = this.getFiniteStatValues(sources, statType); return values.length ? Math.min(...values) : null; } static max(sources, statType) { const values = this.getFiniteStatValues(sources, statType); return values.length ? Math.max(...values) : null; } static average(sources, statType) { const values = this.getFiniteStatValues(sources, statType); return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null; } static weightedAverage(sources, valueStatType, weightStatType) { const valuesWithWeights = sources.reduce((accu, source) => { const value = this.getFiniteStatValue(source, valueStatType); if (value === null) { return accu; } accu.push({ value, weight: this.getFiniteStatValue(source, weightStatType) }); return accu; }, []); if (!valuesWithWeights.length) { return null; } const canWeight = valuesWithWeights.every(({ weight }) => weight !== null && weight > 0); if (!canWeight) { return valuesWithWeights.reduce((sum, { value }) => sum + value, 0) / valuesWithWeights.length; } const totalWeight = valuesWithWeights.reduce((sum, { weight }) => sum + weight, 0); return valuesWithWeights.reduce((sum, { value, weight }) => sum + value * weight, 0) / totalWeight; } static getFiniteStatValues(sources, statType) { return sources.reduce((values, source) => { const value = this.getFiniteStatValue(source, statType); if (value !== null) { values.push(value); } return values; }, []); } static aggregateValues(valuesWithWeights, reducer) { if (!valuesWithWeights.length) { return null; } const values = valuesWithWeights.map(({ value }) => value); switch (reducer) { case 'sum': return values.reduce((sum, value) => sum + value, 0); case 'min': return Math.min(...values); case 'max': return Math.max(...values); case 'average': return values.reduce((sum, value) => sum + value, 0) / values.length; case 'weightedAverage': { const canWeight = valuesWithWeights.every(({ weight }) => weight !== null && weight > 0); if (!canWeight) { return values.reduce((sum, value) => sum + value, 0) / values.length; } const totalWeight = valuesWithWeights.reduce((sum, { weight }) => sum + weight, 0); return valuesWithWeights.reduce((sum, { value, weight }) => sum + value * weight, 0) / totalWeight; } } } } exports.StatsUtilities = StatsUtilities;