From 45467a434fbb314ba94c747411e1a4ffa7c04b1f Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Tue, 22 Sep 2020 17:36:12 +0300 Subject: [PATCH] Adopt use of Seconds data-type --- src/ast.ts | 5 +- src/generateZwo.ts | 4 +- src/parser/parser.test.ts | 92 ++++++++++++++++++++--------- src/parser/parser.ts | 5 +- src/stats/index.ts | 2 +- src/stats/intervalsToIntensities.ts | 4 +- src/stats/normalizedIntensity.ts | 2 +- src/stats/totalDuration.ts | 3 +- src/stats/tss.ts | 13 ++-- src/stats/tss2.ts | 6 +- src/types.ts | 3 + 11 files changed, 93 insertions(+), 46 deletions(-) create mode 100644 src/types.ts diff --git a/src/ast.ts b/src/ast.ts index 5ae4a18..1731bff 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,4 +1,5 @@ import { IntervalLabelTokenValue } from "./parser/tokenizer"; +import { Seconds } from "./types"; export type Workout = { name: string; @@ -9,13 +10,13 @@ export type Workout = { export type Interval = { type: IntervalLabelTokenValue; - duration: number; + duration: Seconds; intensity: { from: number; to: number }; cadence?: number; comments: Comment[]; }; export type Comment = { - offset: number; + offset: Seconds; text: string; }; diff --git a/src/generateZwo.ts b/src/generateZwo.ts index 32724f0..7ebcdac 100644 --- a/src/generateZwo.ts +++ b/src/generateZwo.ts @@ -17,7 +17,7 @@ const generateRangeInterval = ( [tagName]: [ { _attr: { - Duration: duration, + Duration: duration.value, PowerLow: intensity.from, PowerHigh: intensity.from, ...(cadence ? { Cadence: cadence } : {}), @@ -33,7 +33,7 @@ const generateSteadyStateInterval = ({ duration, intensity, cadence, comments }: SteadyState: [ { _attr: { - Duration: duration, + Duration: duration.value, Power: intensity.from, ...(cadence ? { Cadence: cadence } : {}), }, diff --git a/src/parser/parser.test.ts b/src/parser/parser.test.ts index 7ac7408..a751bac 100644 --- a/src/parser/parser.test.ts +++ b/src/parser/parser.test.ts @@ -62,7 +62,9 @@ Rest: 5:00 45% Object { "cadence": undefined, "comments": Array [], - "duration": 300, + "duration": Seconds { + "value": 300, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -72,7 +74,9 @@ Rest: 5:00 45% Object { "cadence": 90, "comments": Array [], - "duration": 600, + "duration": Seconds { + "value": 600, + }, "intensity": Object { "from": 0.8, "to": 0.8, @@ -82,7 +86,9 @@ Rest: 5:00 45% Object { "cadence": undefined, "comments": Array [], - "duration": 300, + "duration": Seconds { + "value": 300, + }, "intensity": Object { "from": 0.45, "to": 0.45, @@ -106,7 +112,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": 100, "comments": Array [], - "duration": 330, + "duration": Seconds { + "value": 330, + }, "intensity": Object { "from": 0.5, "to": 0.8, @@ -116,7 +124,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": undefined, "comments": Array [], - "duration": 330, + "duration": Seconds { + "value": 330, + }, "intensity": Object { "from": 0.7, "to": 0.45, @@ -146,7 +156,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": undefined, "comments": Array [], - "duration": 10, + "duration": Seconds { + "value": 10, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -158,7 +170,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": 100, "comments": Array [], - "duration": 10, + "duration": Seconds { + "value": 10, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -170,7 +184,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": 100, "comments": Array [], - "duration": 10, + "duration": Seconds { + "value": 10, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -185,7 +201,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": 100, "comments": Array [], - "duration": 10, + "duration": Seconds { + "value": 10, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -197,7 +215,9 @@ Cooldown: 5:30 70%..45% Object { "cadence": 100, "comments": Array [], - "duration": 10, + "duration": Seconds { + "value": 10, + }, "intensity": Object { "from": 0.5, "to": 0.5, @@ -208,14 +228,14 @@ Cooldown: 5:30 70%..45% }); it("parses correct duration formats", () => { - expect(parseInterval("Interval: 0:10 50%").duration).toEqual(10); - expect(parseInterval("Interval: 00:10 50%").duration).toEqual(10); - expect(parseInterval("Interval: 0:00:10 50%").duration).toEqual(10); - expect(parseInterval("Interval: 0:02:05 50%").duration).toEqual(125); - expect(parseInterval("Interval: 1:00:00 50%").duration).toEqual(3600); - expect(parseInterval("Interval: 1:00:0 50%").duration).toEqual(3600); - expect(parseInterval("Interval: 1:0:0 50%").duration).toEqual(3600); - expect(parseInterval("Interval: 10:00:00 50%").duration).toEqual(36000); + expect(parseInterval("Interval: 0:10 50%").duration.value).toEqual(10); + expect(parseInterval("Interval: 00:10 50%").duration.value).toEqual(10); + expect(parseInterval("Interval: 0:00:10 50%").duration.value).toEqual(10); + expect(parseInterval("Interval: 0:02:05 50%").duration.value).toEqual(125); + expect(parseInterval("Interval: 1:00:00 50%").duration.value).toEqual(3600); + expect(parseInterval("Interval: 1:00:0 50%").duration.value).toEqual(3600); + expect(parseInterval("Interval: 1:0:0 50%").duration.value).toEqual(3600); + expect(parseInterval("Interval: 10:00:00 50%").duration.value).toEqual(36000); }); it("throws error for incorrect duration formats", () => { @@ -277,27 +297,39 @@ Rest: 5:00 50% "cadence": undefined, "comments": Array [ Object { - "offset": 0, + "offset": Seconds { + "value": 0, + }, "text": "Find your rythm.", }, Object { - "offset": 60, + "offset": Seconds { + "value": 60, + }, "text": "Try to settle in for the effort", }, Object { - "offset": 300, + "offset": Seconds { + "value": 300, + }, "text": "Half way through", }, Object { - "offset": 540, + "offset": Seconds { + "value": 540, + }, "text": "Almost there", }, Object { - "offset": 570, + "offset": Seconds { + "value": 570, + }, "text": "Final push. YOU GOT IT!", }, ], - "duration": 600, + "duration": Seconds { + "value": 600, + }, "intensity": Object { "from": 0.9, "to": 0.9, @@ -308,15 +340,21 @@ Rest: 5:00 50% "cadence": undefined, "comments": Array [ Object { - "offset": 0, + "offset": Seconds { + "value": 0, + }, "text": "Great effort!", }, Object { - "offset": 30, + "offset": Seconds { + "value": 30, + }, "text": "Cool down well after all of this.", }, ], - "duration": 300, + "duration": Seconds { + "value": 300, + }, "intensity": Object { "from": 0.5, "to": 0.5, diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 81bdb1c..1580edf 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,4 +1,5 @@ import { Interval, Workout, Comment } from "../ast"; +import { Seconds } from "../types"; import { ParseError } from "./ParseError"; import { isIntervalLabelTokenValue, SourceLocation, Token } from "./tokenizer"; @@ -68,7 +69,7 @@ const parseIntervalComments = (tokens: Token[]): [Comment[], Token[]] => { throw new ParseError(`Expected [comment text] instead got ${tokenToString(text)}`, text?.loc || offset.loc); } comments.push({ - offset: offset.value, + offset: new Seconds(offset.value), text: text.value, }); tokens = rest; @@ -90,7 +91,7 @@ const parseIntervalParams = (tokens: Token[], loc: SourceLocation): [IntervalDat while (tokens[0]) { const token = tokens[0]; if (token.type === "duration") { - data.duration = token.value; + data.duration = new Seconds(token.value); tokens.shift(); } else if (token.type === "cadence") { data.cadence = token.value; diff --git a/src/stats/index.ts b/src/stats/index.ts index d89b6f0..1d6909c 100644 --- a/src/stats/index.ts +++ b/src/stats/index.ts @@ -12,7 +12,7 @@ export const stats = ({ intervals }: Workout): string => { const normIntensity = normalizedIntensity(intervals); return ` -Total duration: ${(duration / 60).toFixed()} minutes +Total duration: ${(duration.value / 60).toFixed()} minutes Average intensity: ${(avgIntensity * 100).toFixed()}% Normalized intensity: ${(normIntensity * 100).toFixed()}% diff --git a/src/stats/intervalsToIntensities.ts b/src/stats/intervalsToIntensities.ts index e365eb1..ae6527c 100644 --- a/src/stats/intervalsToIntensities.ts +++ b/src/stats/intervalsToIntensities.ts @@ -5,9 +5,9 @@ import { Interval } from "../ast"; const intervalToIntensities = ({ duration, intensity }: Interval): number[] => { const seconds = []; const { from, to } = intensity; - for (let i = 0; i < duration; i++) { + for (let i = 0; i < duration.value; i++) { // Intensity in a single second - seconds.push(from + (to - from) * (i / duration)); + seconds.push(from + (to - from) * (i / duration.value)); } return seconds; }; diff --git a/src/stats/normalizedIntensity.ts b/src/stats/normalizedIntensity.ts index 5af676f..aa27fa7 100644 --- a/src/stats/normalizedIntensity.ts +++ b/src/stats/normalizedIntensity.ts @@ -4,7 +4,7 @@ import { average } from "./average"; import { intervalsToIntensities } from "./intervalsToIntensities"; // Starting at the beginning of the data, calculate 30-second rolling average -const windowSize = 30; +const windowSize = 30; // equals to nr of seconds, but also to nr of entries in intensities array const rollingAverages = (intensities: number[]): number[] => { if (intensities.length < windowSize) { throw new Error(`Workout must be at least ${windowSize} seconds long`); diff --git a/src/stats/totalDuration.ts b/src/stats/totalDuration.ts index 677a374..034ca5c 100644 --- a/src/stats/totalDuration.ts +++ b/src/stats/totalDuration.ts @@ -1,4 +1,5 @@ import { pluck, sum } from "ramda"; import { Interval } from "../ast"; +import { Seconds } from "../types"; -export const totalDuration = (intervals: Interval[]) => sum(pluck("duration", intervals)); +export const totalDuration = (intervals: Interval[]): Seconds => new Seconds(sum(pluck("duration.value", intervals))); diff --git a/src/stats/tss.ts b/src/stats/tss.ts index 97659bb..2f1fa99 100644 --- a/src/stats/tss.ts +++ b/src/stats/tss.ts @@ -1,4 +1,5 @@ import { Interval } from "../ast"; +import { Seconds } from "../types"; // Training Stress Score formula from Training and Racing with a Power Meter: // @@ -8,15 +9,15 @@ import { Interval } from "../ast"; // W - power in watts // IF - intensity factor (power / FTP) -const steadyTss = (duration: number, intensity: number): number => { - return ((duration * intensity * intensity) / 3600) * 100; +const steadyTss = (duration: Seconds, intensity: number): number => { + return ((duration.value * intensity * intensity) / 3600) * 100; }; -const rangeTss = (duration: number, from: number, to: number): number => { +const rangeTss = (duration: Seconds, from: number, to: number): number => { let score = 0; - const step = 1; - for (let i = 0; i < duration; i += step) { - const intensity = from + (to - from) * (i / duration); + const step = new Seconds(1); + for (let i = 0; i < duration.value; i += step.value) { + const intensity = from + (to - from) * (i / duration.value); score += steadyTss(step, intensity); } return score; diff --git a/src/stats/tss2.ts b/src/stats/tss2.ts index efddc76..c4ec722 100644 --- a/src/stats/tss2.ts +++ b/src/stats/tss2.ts @@ -1,3 +1,5 @@ +import { Seconds } from "../types"; + // Training Stress Score formula from Training and Racing with a Power Meter: // // TSS = (s * W * IF) / (FTP * 3600) * 100 @@ -11,6 +13,6 @@ // TSS = (s * (FTP * IF) * IF) / (FTP * 3600) * 100 // TSS = (s * IF * IF) / 3600 * 100 -export const tss2 = (duration: number, intensity: number): number => { - return ((duration * intensity * intensity) / 3600) * 100; +export const tss2 = (duration: Seconds, intensity: number): number => { + return ((duration.value * intensity * intensity) / 3600) * 100; }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..cd78fda --- /dev/null +++ b/src/types.ts @@ -0,0 +1,3 @@ +export class Seconds { + constructor(readonly value: number) {} +}