From 212df4e7480ee7d675f415578f2eb0b8bf202105 Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Fri, 25 Sep 2020 14:28:52 +0300 Subject: [PATCH] Introduce Intensity & IntensityRange classes --- src/Intensity.ts | 31 +++++++ src/ast.ts | 3 +- src/detectRepeats.test.ts | 137 ++++++++++++++-------------- src/detectRepeats.ts | 3 +- src/generateZwo.ts | 14 +-- src/parser/parser.test.ts | 77 +++++++--------- src/parser/parser.ts | 5 +- src/stats/averageIntensity.ts | 5 +- src/stats/index.ts | 4 +- src/stats/intervalsToIntensities.ts | 8 +- src/stats/normalizedIntensity.ts | 19 ++-- src/stats/tss.ts | 6 +- src/stats/tss2.ts | 5 +- 13 files changed, 172 insertions(+), 145 deletions(-) create mode 100644 src/Intensity.ts diff --git a/src/Intensity.ts b/src/Intensity.ts new file mode 100644 index 0000000..f209763 --- /dev/null +++ b/src/Intensity.ts @@ -0,0 +1,31 @@ +export class Intensity { + constructor(private _value: number) {} + + get value() { + return this._value; + } + + get start() { + return this._value; + } + + get end() { + return this._value; + } +} + +export class IntensityRange { + constructor(private _start: number, private _end: number) {} + + get value() { + return this._start; + } + + get start() { + return this._start; + } + + get end() { + return this._end; + } +} diff --git a/src/ast.ts b/src/ast.ts index 9862dad..654ff87 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,5 +1,6 @@ import { IntervalType } from "./parser/tokenizer"; import { Duration } from "./Duration"; +import { Intensity, IntensityRange } from "./Intensity"; export type Workout = { name: string; @@ -11,7 +12,7 @@ export type Workout = { export type Interval = { type: IntervalType; duration: Duration; - intensity: { from: number; to: number }; + intensity: Intensity | IntensityRange; cadence?: number; comments: Comment[]; }; diff --git a/src/detectRepeats.test.ts b/src/detectRepeats.test.ts index 176e93d..ad07515 100644 --- a/src/detectRepeats.test.ts +++ b/src/detectRepeats.test.ts @@ -1,6 +1,7 @@ import { Interval } from "./ast"; import { detectRepeats } from "./detectRepeats"; import { Duration } from "./Duration"; +import { Intensity, IntensityRange } from "./Intensity"; describe("detectRepeats()", () => { it("does nothing with empty array", () => { @@ -9,32 +10,32 @@ describe("detectRepeats()", () => { it("does nothing when no interval repeats", () => { const intervals: Interval[] = [ - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Interval", duration: new Duration(30), intensity: { from: 1.2, to: 1.2 }, comments: [] }, - { type: "Cooldown", duration: new Duration(60), intensity: { from: 1, to: 0.5 }, comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Interval", duration: new Duration(30), intensity: new Intensity(1.2), comments: [] }, + { type: "Cooldown", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, ]; expect(detectRepeats(intervals)).toEqual(intervals); }); it("detects whole workout consisting of repetitions", () => { const intervals: Interval[] = [ - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, ]; expect(detectRepeats(intervals)).toEqual([ { type: "repeat", times: 4, intervals: [ - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, ], comments: [], }, @@ -43,54 +44,54 @@ describe("detectRepeats()", () => { it("detects repetitions in the middle of workout", () => { const intervals: Interval[] = [ - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.5, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(120), intensity: { from: 0.2, to: 0.2 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Rest", duration: new Duration(120), intensity: { from: 0.2, to: 0.2 }, comments: [] }, - { type: "Cooldown", duration: new Duration(60), intensity: { from: 1, to: 0.5 }, comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.5, 1), comments: [] }, + { type: "Rest", duration: new Duration(120), intensity: new Intensity(0.2), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Rest", duration: new Duration(120), intensity: new Intensity(0.2), comments: [] }, + { type: "Cooldown", duration: new Duration(60), intensity: new IntensityRange(1, 0.5), comments: [] }, ]; expect(detectRepeats(intervals)).toEqual([ - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.5, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(120), intensity: { from: 0.2, to: 0.2 }, comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.5, 1), comments: [] }, + { type: "Rest", duration: new Duration(120), intensity: new Intensity(0.2), comments: [] }, { type: "repeat", times: 4, intervals: [ - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, ], comments: [], }, - { type: "Rest", duration: new Duration(120), intensity: { from: 0.2, to: 0.2 }, comments: [] }, - { type: "Cooldown", duration: new Duration(60), intensity: { from: 1, to: 0.5 }, comments: [] }, + { type: "Rest", duration: new Duration(120), intensity: new Intensity(0.2), comments: [] }, + { type: "Cooldown", duration: new Duration(60), intensity: new IntensityRange(1, 0.5), comments: [] }, ]); }); it("detects multiple repetitions", () => { const intervals: Interval[] = [ - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(100), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(100), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(100), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(100), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(100), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(100), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(100), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(100), intensity: new Intensity(0.5), comments: [] }, ]; expect(detectRepeats(intervals)).toEqual([ { type: "repeat", times: 2, intervals: [ - { type: "Interval", duration: new Duration(60), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(60), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, ], comments: [], }, @@ -98,8 +99,8 @@ describe("detectRepeats()", () => { type: "repeat", times: 2, intervals: [ - { type: "Interval", duration: new Duration(100), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(100), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(100), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(100), intensity: new Intensity(0.5), comments: [] }, ], comments: [], }, @@ -108,22 +109,22 @@ describe("detectRepeats()", () => { it("takes cadence differences into account", () => { const intervals: Interval[] = [ - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, cadence: 100, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, cadence: 80, comments: [] }, - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, cadence: 100, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, cadence: 80, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), cadence: 100, comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), cadence: 80, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), cadence: 100, comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), cadence: 80, comments: [] }, ]; expect(detectRepeats(intervals)).toEqual([ - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), comments: [] }, { type: "repeat", times: 2, intervals: [ - { type: "Interval", duration: new Duration(120), intensity: { from: 1, to: 1 }, cadence: 100, comments: [] }, - { type: "Rest", duration: new Duration(60), intensity: { from: 0.5, to: 0.5 }, cadence: 80, comments: [] }, + { type: "Interval", duration: new Duration(120), intensity: new Intensity(1), cadence: 100, comments: [] }, + { type: "Rest", duration: new Duration(60), intensity: new Intensity(0.5), cadence: 80, comments: [] }, ], comments: [], }, @@ -132,14 +133,14 @@ describe("detectRepeats()", () => { it("does not consider warmup/cooldown-range intervals to be repeatable", () => { const intervals: Interval[] = [ - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.1, to: 1 }, comments: [] }, - { type: "Cooldown", duration: new Duration(120), intensity: { from: 1, to: 0.5 }, comments: [] }, - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.1, to: 1 }, comments: [] }, - { type: "Cooldown", duration: new Duration(120), intensity: { from: 1, to: 0.5 }, comments: [] }, - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.1, to: 1 }, comments: [] }, - { type: "Cooldown", duration: new Duration(120), intensity: { from: 1, to: 0.5 }, comments: [] }, - { type: "Warmup", duration: new Duration(60), intensity: { from: 0.1, to: 1 }, comments: [] }, - { type: "Cooldown", duration: new Duration(120), intensity: { from: 1, to: 0.5 }, comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.1, 1), comments: [] }, + { type: "Cooldown", duration: new Duration(120), intensity: new IntensityRange(1, 0.5), comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.1, 1), comments: [] }, + { type: "Cooldown", duration: new Duration(120), intensity: new IntensityRange(1, 0.5), comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.1, 1), comments: [] }, + { type: "Cooldown", duration: new Duration(120), intensity: new IntensityRange(1, 0.5), comments: [] }, + { type: "Warmup", duration: new Duration(60), intensity: new IntensityRange(0.1, 1), comments: [] }, + { type: "Cooldown", duration: new Duration(120), intensity: new IntensityRange(1, 0.5), comments: [] }, ]; expect(detectRepeats(intervals)).toEqual(intervals); }); @@ -149,7 +150,7 @@ describe("detectRepeats()", () => { { type: "Interval", duration: new Duration(100), - intensity: { from: 1, to: 1 }, + intensity: new Intensity(1), comments: [ { offset: new Duration(0), text: "Let's start" }, { offset: new Duration(20), text: "Stay strong!" }, @@ -159,7 +160,7 @@ describe("detectRepeats()", () => { { type: "Rest", duration: new Duration(100), - intensity: { from: 0.5, to: 0.5 }, + intensity: new Intensity(0.5), comments: [ { offset: new Duration(0), text: "Huh... have a rest" }, { offset: new Duration(80), text: "Ready for next?" }, @@ -168,7 +169,7 @@ describe("detectRepeats()", () => { { type: "Interval", duration: new Duration(100), - intensity: { from: 1, to: 1 }, + intensity: new Intensity(1), comments: [ { offset: new Duration(0), text: "Bring it on again!" }, { offset: new Duration(50), text: "Half way" }, @@ -178,7 +179,7 @@ describe("detectRepeats()", () => { { type: "Rest", duration: new Duration(100), - intensity: { from: 0.5, to: 0.5 }, + intensity: new Intensity(0.5), comments: [ { offset: new Duration(30), text: "Wow... you did it!" }, { offset: new Duration(40), text: "Nice job." }, @@ -191,8 +192,8 @@ describe("detectRepeats()", () => { type: "repeat", times: 2, intervals: [ - { type: "Interval", duration: new Duration(100), intensity: { from: 1, to: 1 }, comments: [] }, - { type: "Rest", duration: new Duration(100), intensity: { from: 0.5, to: 0.5 }, comments: [] }, + { type: "Interval", duration: new Duration(100), intensity: new Intensity(1), comments: [] }, + { type: "Rest", duration: new Duration(100), intensity: new Intensity(0.5), comments: [] }, ], comments: [ { offset: new Duration(0), text: "Let's start" }, diff --git a/src/detectRepeats.ts b/src/detectRepeats.ts index 3a0376a..0c46ed7 100644 --- a/src/detectRepeats.ts +++ b/src/detectRepeats.ts @@ -1,6 +1,7 @@ import { eqProps, flatten, zip } from "ramda"; import { Interval, Comment } from "./ast"; import { Duration } from "./Duration"; +import { IntensityRange } from "./Intensity"; export type RepeatedInterval = { type: "repeat"; @@ -54,7 +55,7 @@ const stripComments = (intervals: Interval[]): Interval[] => { return intervals.map(({ comments, ...rest }) => ({ comments: [], ...rest })); }; -const isRangeInterval = (interval: Interval): boolean => interval.intensity.from !== interval.intensity.to; +const isRangeInterval = (interval: Interval): boolean => interval.intensity instanceof IntensityRange; export const detectRepeats = (intervals: Interval[]): (Interval | RepeatedInterval)[] => { if (intervals.length < windowSize) { diff --git a/src/generateZwo.ts b/src/generateZwo.ts index aaf3dca..1d42d51 100644 --- a/src/generateZwo.ts +++ b/src/generateZwo.ts @@ -19,8 +19,8 @@ const generateRangeInterval = ( { _attr: { Duration: duration.seconds, - PowerLow: intensity.from, - PowerHigh: intensity.to, + PowerLow: intensity.start, + PowerHigh: intensity.end, ...(cadence ? { Cadence: cadence } : {}), }, }, @@ -35,7 +35,7 @@ const generateSteadyStateInterval = ({ duration, intensity, cadence, comments }: { _attr: { Duration: duration.seconds, - Power: intensity.from, + Power: intensity.value, ...(cadence ? { Cadence: cadence } : {}), }, }, @@ -53,11 +53,11 @@ const generateRepeatInterval = (repInterval: RepeatedInterval): xml.XmlObject => Repeat: repInterval.times, OnDuration: on.duration.seconds, - OnPower: on.intensity.from, + OnPower: on.intensity.start, ...(on.cadence ? { Cadence: on.cadence } : {}), OffDuration: off.duration.seconds, - OffPower: off.intensity.from, + OffPower: off.intensity.end, ...(off.cadence ? { CadenceResting: off.cadence } : {}), }, }, @@ -72,9 +72,9 @@ const generateInterval = (interval: Interval | RepeatedInterval): xml.XmlObject } const { intensity } = interval; - if (intensity.from < intensity.to) { + if (intensity.start < intensity.end) { return generateRangeInterval("Warmup", interval); - } else if (intensity.from > intensity.to) { + } else if (intensity.start > intensity.end) { return generateRangeInterval("Cooldown", interval); } else { return generateSteadyStateInterval(interval); diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts index 652131f..bc20dab 100644 --- a/src/parser/parser.test.ts +++ b/src/parser/parser.test.ts @@ -74,9 +74,8 @@ Rest: 5:00 45% "duration": Duration { "seconds": 300, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Rest", }, @@ -86,9 +85,8 @@ Rest: 5:00 45% "duration": Duration { "seconds": 600, }, - "intensity": Object { - "from": 0.8, - "to": 0.8, + "intensity": Intensity { + "_value": 0.8, }, "type": "Interval", }, @@ -98,9 +96,8 @@ Rest: 5:00 45% "duration": Duration { "seconds": 300, }, - "intensity": Object { - "from": 0.45, - "to": 0.45, + "intensity": Intensity { + "_value": 0.45, }, "type": "Rest", }, @@ -139,9 +136,8 @@ Interval: 5:00 50% "duration": Duration { "seconds": 300, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", }, @@ -151,9 +147,8 @@ Interval: 5:00 50% "duration": Duration { "seconds": 600, }, - "intensity": Object { - "from": 1, - "to": 1, + "intensity": Intensity { + "_value": 1, }, "type": "Interval", }, @@ -163,9 +158,8 @@ Interval: 5:00 50% "duration": Duration { "seconds": 300, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", }, @@ -191,9 +185,9 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 330, }, - "intensity": Object { - "from": 0.5, - "to": 0.8, + "intensity": IntensityRange { + "_end": 0.8, + "_start": 0.5, }, "type": "Warmup", }, @@ -203,9 +197,9 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 330, }, - "intensity": Object { - "from": 0.7, - "to": 0.45, + "intensity": IntensityRange { + "_end": 0.45, + "_start": 0.7, }, "type": "Cooldown", }, @@ -235,9 +229,8 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 10, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", } @@ -249,9 +242,8 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 10, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", } @@ -263,9 +255,8 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 10, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", } @@ -280,9 +271,8 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 10, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", } @@ -294,9 +284,8 @@ Cooldown: 5:30 70%..45% "duration": Duration { "seconds": 10, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Interval", } @@ -406,9 +395,8 @@ Rest: 5:00 50% "duration": Duration { "seconds": 600, }, - "intensity": Object { - "from": 0.9, - "to": 0.9, + "intensity": Intensity { + "_value": 0.9, }, "type": "Interval", }, @@ -431,9 +419,8 @@ Rest: 5:00 50% "duration": Duration { "seconds": 300, }, - "intensity": Object { - "from": 0.5, - "to": 0.5, + "intensity": Intensity { + "_value": 0.5, }, "type": "Rest", }, diff --git a/src/parser/parser.ts b/src/parser/parser.ts index a9a7a2a..29f6c42 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,5 +1,6 @@ import { Interval, Workout, Comment } from "../ast"; import { Duration } from "../Duration"; +import { Intensity, IntensityRange } from "../Intensity"; import { ParseError } from "./ParseError"; import { SourceLocation, Token } from "./tokenizer"; @@ -91,10 +92,10 @@ const parseIntervalParams = (tokens: Token[], loc: SourceLocation): [IntervalDat data.cadence = token.value; tokens.shift(); } else if (token.type === "intensity") { - data.intensity = { from: token.value, to: token.value }; + data.intensity = new Intensity(token.value); tokens.shift(); } else if (token.type === "intensity-range") { - data.intensity = { from: token.value[0], to: token.value[1] }; + data.intensity = new IntensityRange(token.value[0], token.value[1]); tokens.shift(); } else { break; diff --git a/src/stats/averageIntensity.ts b/src/stats/averageIntensity.ts index 3ed16cf..cd07f61 100644 --- a/src/stats/averageIntensity.ts +++ b/src/stats/averageIntensity.ts @@ -1,8 +1,9 @@ import { pipe } from "ramda"; import { Interval } from "../ast"; +import { Intensity } from "../Intensity"; import { average } from "./average"; import { intervalsToIntensities } from "./intervalsToIntensities"; -export const averageIntensity = (intervals: Interval[]): number => { - return pipe(intervalsToIntensities, average)(intervals); +export const averageIntensity = (intervals: Interval[]): Intensity => { + return new Intensity(pipe(intervalsToIntensities, average)(intervals)); }; diff --git a/src/stats/index.ts b/src/stats/index.ts index ca65caa..b85803e 100644 --- a/src/stats/index.ts +++ b/src/stats/index.ts @@ -14,8 +14,8 @@ export const stats = ({ intervals }: Workout): string => { return ` Total duration: ${(duration.seconds / 60).toFixed()} minutes -Average intensity: ${(avgIntensity * 100).toFixed()}% -Normalized intensity: ${(normIntensity * 100).toFixed()}% +Average intensity: ${(avgIntensity.value * 100).toFixed()}% +Normalized intensity: ${(normIntensity.value * 100).toFixed()}% TSS #1: ${tss(intervals).toFixed()} TSS #2: ${tss2(duration, normIntensity).toFixed()} diff --git a/src/stats/intervalsToIntensities.ts b/src/stats/intervalsToIntensities.ts index 6ae3dc4..88bd20d 100644 --- a/src/stats/intervalsToIntensities.ts +++ b/src/stats/intervalsToIntensities.ts @@ -3,13 +3,13 @@ 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; + const intensities: number[] = []; + const [from, to] = [intensity.start, intensity.end]; for (let i = 0; i < duration.seconds; i++) { // Intensity in a single second - seconds.push(from + (to - from) * (i / duration.seconds)); + intensities.push(from + (to - from) * (i / duration.seconds)); } - return seconds; + return intensities; }; export const intervalsToIntensities = chain(intervalToIntensities); diff --git a/src/stats/normalizedIntensity.ts b/src/stats/normalizedIntensity.ts index aa27fa7..899b5ef 100644 --- a/src/stats/normalizedIntensity.ts +++ b/src/stats/normalizedIntensity.ts @@ -1,5 +1,6 @@ import { pipe, sum } from "ramda"; import { Interval } from "../ast"; +import { Intensity } from "../Intensity"; import { average } from "./average"; import { intervalsToIntensities } from "./intervalsToIntensities"; @@ -24,12 +25,14 @@ const fourthPower = (x: number) => Math.pow(x, 4); const fourthRoot = (x: number) => Math.pow(x, 1 / 4); -export const normalizedIntensity = (intervals: Interval[]): number => { - return pipe( - intervalsToIntensities, - rollingAverages, - (averages) => averages.map(fourthPower), - average, - fourthRoot, - )(intervals); +export const normalizedIntensity = (intervals: Interval[]): Intensity => { + return new Intensity( + pipe( + intervalsToIntensities, + rollingAverages, + (averages) => averages.map(fourthPower), + average, + fourthRoot, + )(intervals), + ); }; diff --git a/src/stats/tss.ts b/src/stats/tss.ts index be7b4d5..2c95478 100644 --- a/src/stats/tss.ts +++ b/src/stats/tss.ts @@ -24,10 +24,10 @@ const rangeTss = (duration: Duration, from: number, to: number): number => { }; const intervalTss = ({ duration, intensity }: Interval): number => { - if (intensity.from === intensity.to) { - return steadyTss(duration, intensity.from); + if (intensity.start === intensity.end) { + return steadyTss(duration, intensity.value); } else { - return rangeTss(duration, intensity.from, intensity.to); + return rangeTss(duration, intensity.start, intensity.end); } }; diff --git a/src/stats/tss2.ts b/src/stats/tss2.ts index 7073ae4..747df35 100644 --- a/src/stats/tss2.ts +++ b/src/stats/tss2.ts @@ -1,4 +1,5 @@ import { Duration } from "../Duration"; +import { Intensity } from "../Intensity"; // Training Stress Score formula from Training and Racing with a Power Meter: // @@ -13,6 +14,6 @@ import { Duration } from "../Duration"; // TSS = (s * (FTP * IF) * IF) / (FTP * 3600) * 100 // TSS = (s * IF * IF) / 3600 * 100 -export const tss2 = (duration: Duration, intensity: number): number => { - return ((duration.seconds * intensity * intensity) / 3600) * 100; +export const tss2 = (duration: Duration, intensity: Intensity): number => { + return ((duration.seconds * Math.pow(intensity.value, 2)) / 3600) * 100; };