diff --git a/src/average-intensity.ts b/src/average-intensity.ts new file mode 100644 index 0000000..b74f0c6 --- /dev/null +++ b/src/average-intensity.ts @@ -0,0 +1,19 @@ +import { chain, pipe, sum } from "ramda"; +import { Interval } from "./ast"; + +// Converts interval to array of intensity values for each second +const intervalToIntensities = ({ duration, intensity }: Interval): number[] => { + const seconds = []; + const { from, to } = intensity; + for (let i = 0; i < duration; i++) { + // Intensity in a single second + seconds.push(from + (to - from) * (i / duration)); + } + return seconds; +}; + +const average = (arr: number[]) => sum(arr) / arr.length; + +export const averageIntensity = (intervals: Interval[]): number => { + return pipe(chain(intervalToIntensities), average)(intervals); +}; diff --git a/src/index.ts b/src/index.ts index ae63ec2..7a3cf52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import * as fs from "fs"; +import { averageIntensity } from "./average-intensity"; +import { normalizedIntensity } from "./normalized-intensity"; import { parse } from "./parser"; import { tokenize } from "./tokenizer"; import { tss } from "./tss"; @@ -23,3 +25,7 @@ const duration = workout.intervals.map(({ duration }) => duration).reduce((a, b) => a + b, 0) / 60; console.log(`Total duration: ${duration} minutes`); + +console.log(); +console.log(`Average intensity: ${averageIntensity(workout.intervals)}`); +console.log(`Normalized intensity: ${normalizedIntensity(workout.intervals)}`); diff --git a/src/normalized-intensity.ts b/src/normalized-intensity.ts new file mode 100644 index 0000000..0dc185f --- /dev/null +++ b/src/normalized-intensity.ts @@ -0,0 +1,46 @@ +import { chain, pipe, sum } from "ramda"; +import { Interval } from "./ast"; + +// Converts interval to array of intensity values for each second +const intervalToIntensities = ({ duration, intensity }: Interval): number[] => { + const seconds = []; + const { from, to } = intensity; + for (let i = 0; i < duration; i++) { + // Intensity in a single second + seconds.push(from + (to - from) * (i / duration)); + } + return seconds; +}; + +// Starting at the beginning of the data, calculate 30-second rolling average +const windowSize = 30; +const rollingAverages = (intensities: number[]): number[] => { + if (intensities.length < windowSize) { + throw new Error(`Workout must be at least ${windowSize} seconds long`); + } + const averages: number[] = []; + let rollingSum: number = sum(intensities.slice(0, windowSize)); + averages.push(rollingSum / windowSize); + for (let i = 0; i < intensities.length - windowSize; i++) { + rollingSum -= intensities[i]; + rollingSum += intensities[i + windowSize]; + averages.push(rollingSum / windowSize); + } + return averages; +}; + +const fourthPower = (x: number) => Math.pow(x, 4); + +const fourthRoot = (x: number) => Math.pow(x, 1 / 4); + +const average = (arr: number[]) => sum(arr) / arr.length; + +export const normalizedIntensity = (intervals: Interval[]): number => { + return pipe( + chain(intervalToIntensities), + rollingAverages, + (averages) => averages.map(fourthPower), + average, + fourthRoot + )(intervals); +};