From 819629094e8ee89cd25e132febb0f13f97144910 Mon Sep 17 00:00:00 2001 From: Rene Saarsoo Date: Wed, 30 Sep 2020 20:49:04 +0300 Subject: [PATCH] Implement zone-distrubution stats --- README.md | 1 - src/stats/index.ts | 5 +++++ src/stats/zoneDistribution.ts | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/stats/zoneDistribution.ts diff --git a/README.md b/README.md index e6605e7..8ef10ab 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,5 @@ - FreeRide blocks - Repeats (and nested repeats) - Change # to @ -- Zone distribution stats - Unsupported params: message duration & y-position - More restricted syntax for text (with quotes) diff --git a/src/stats/index.ts b/src/stats/index.ts index b85803e..2e8c0fa 100644 --- a/src/stats/index.ts +++ b/src/stats/index.ts @@ -4,12 +4,14 @@ import { normalizedIntensity } from "./normalizedIntensity"; import { totalDuration } from "./totalDuration"; import { tss } from "./tss"; import { tss2 } from "./tss2"; +import { zoneDistribution } from "./zoneDistribution"; // Generates statistics export const stats = ({ intervals }: Workout): string => { const duration = totalDuration(intervals); const avgIntensity = averageIntensity(intervals); const normIntensity = normalizedIntensity(intervals); + const zones = zoneDistribution(intervals); return ` Total duration: ${(duration.seconds / 60).toFixed()} minutes @@ -19,5 +21,8 @@ Normalized intensity: ${(normIntensity.value * 100).toFixed()}% TSS #1: ${tss(intervals).toFixed()} TSS #2: ${tss2(duration, normIntensity).toFixed()} + +Zone Distribution: +${zones.map(({ name, duration }) => `${(duration.seconds / 60).toFixed().padStart(3)} min - ${name}`).join("\n")} `; }; diff --git a/src/stats/zoneDistribution.ts b/src/stats/zoneDistribution.ts new file mode 100644 index 0000000..7801a80 --- /dev/null +++ b/src/stats/zoneDistribution.ts @@ -0,0 +1,35 @@ +import { Interval } from "../ast"; +import { Duration } from "../Duration"; +import { intervalsToIntensityNumbers } from "./intervalsToIntensityNumbers"; + +type Zone = { + from: number; + to: number; + name: string; +}; + +type NumericZoneDuration = Zone & { duration: number }; +type ZoneDuration = Zone & { duration: Duration }; + +// Intensity ranges based on https://zwiftinsider.com/power-zone-colors/ +const emptyZones = (): NumericZoneDuration[] => [ + { from: 0.0, to: 0.6, name: "Z1: Recovery", duration: 0 }, + { from: 0.6, to: 0.75, name: "Z2: Endurance", duration: 0 }, + { from: 0.75, to: 0.9, name: "Z3: Tempo", duration: 0 }, + { from: 0.9, to: 1.05, name: "Z4: Threshold", duration: 0 }, + { from: 1.05, to: 1.18, name: "Z5: VO2 Max", duration: 0 }, + { from: 1.18, to: Infinity, name: "Z6: Anaerobic", duration: 0 }, +]; + +export const zoneDistribution = (intervals: Interval[]): ZoneDuration[] => { + const zones = emptyZones(); + + intervalsToIntensityNumbers(intervals).forEach((intensity) => { + const zone = zones.find((zone) => intensity >= zone.from && intensity < zone.to); + if (zone) { + zone.duration++; + } + }); + + return zones.map(({ duration, ...rest }) => ({ duration: new Duration(duration), ...rest })); +};