@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
JavaScript
;
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;