Adopt use of Seconds data-type
This commit is contained in:
parent
78a1bed1f2
commit
45467a434f
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 } : {}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()}%
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export class Seconds {
|
||||
constructor(readonly value: number) {}
|
||||
}
|
||||
Loading…
Reference in New Issue