Supported cycling workouts #16

master
mgifos 8 years ago
parent 86ce6f7041
commit b2aaefaf56
  1. 22
      README.md
  2. BIN
      images/15k-wo.png
  3. 4
      src/main/scala/com.github.mgifos.workouts/model/Step.scala
  4. 21
      src/main/scala/com.github.mgifos.workouts/model/Target.scala
  5. 25
      src/main/scala/com.github.mgifos.workouts/model/Workout.scala
  6. 52
      src/test/resources/ultra-80k-runnersworld.csv
  7. 4
      src/test/scala/com/github/mgifos/workouts/model/StepSpec.scala
  8. 29
      src/test/scala/com/github/mgifos/workouts/model/WorkoutSpec.scala

@ -3,7 +3,7 @@ is a command line tool to define, import, schedule and share GarminConnect worko
An example of a workout definition notation: An example of a workout definition notation:
```sh ```sh
workout: 15k, 3x3.2k @HMP running: 15k, 3x3.2k @HMP
- warmup: 2km @z2 - warmup: 2km @z2
- repeat: 3 - repeat: 3
- run: 3200m @ 5:05-4:50 - run: 3200m @ 5:05-4:50
@ -12,7 +12,7 @@ workout: 15k, 3x3.2k @HMP
- cooldown: lap-button - cooldown: lap-button
``` ```
and the tool's job is actually to translate it to this: and the tool's job is actually to translate it to this:
![15k workout](https://i.imgur.com/vxXNV7w.png) ![15k workout](https://raw.githubusercontent.com/mgifos/quick-plan/master/images/15k-wo.png)
## File format ## File format
@ -22,10 +22,10 @@ An example of 2-weeks training plan, containing 2 workout definitions, 4 referen
| Week | Mon | Tue | Wed | Thu | Fri | Sat | Sun | | Week | Mon | Tue | Wed | Thu | Fri | Sat | Sun |
| ----:| --- | --- | --- | --- | --- | --- | --- | | ----:| --- | --- | --- | --- | --- | --- | --- |
| 1 | ``workout: run-fast``<br>``- warmup: 10:00 @ z2``<br>``- repeat: 3``<br>&nbsp;&nbsp;``- run: 1.5km @ 5:10-4:40``<br>&nbsp;&nbsp;``- recover: 500m @ z2``<br>``- cooldown: 05:00``|rest|rest|run-fast|rest|rest|rest| | 1 | ``running: run-fast``<br>``- warmup: 10:00 @ z2``<br>``- repeat: 3``<br>&nbsp;&nbsp;``- run: 1.5km @ 5:10-4:40``<br>&nbsp;&nbsp;``- recover: 500m @ z2``<br>``- cooldown: 05:00``|rest|rest|run-fast|rest|rest|rest|
| 2 | run-fast| ``workout: long-15`` <br> ``- run: 15 km @ z2``|rest|run-fast|rest|rest|long-15| | 2 | run-fast| ``cycling: cycle-wo`` <br> ``- bike: 15 km @ 20.0-30kph``|rest|run-fast|rest|rest|cycle-wo|
Checkout a [complete training plan for 80K ultra](https://docs.google.com/spreadsheets/d/1b1ZzrAFrjd-kvPq11zlbE2bWn2IQmUy0lBqIOFjqbwk/edit?usp=sharing). It was originally published in an article of Runner's world website - here's [the link](https://www.runnersworld.com/ultrarunning/the-ultimate-ultramarathon-training-plan). Checkout a [complete training plan for 80K ultra](https://docs.google.com/spreadsheets/d/1b1ZzrAFrjd-kvPq11zlbE2bWn2IQmUy0lBqIOFjqbwk/edit?usp=sharing). It was originally published in an article on Runner's world website - here's [the link](https://www.runnersworld.com/ultrarunning/the-ultimate-ultramarathon-training-plan).
## Installation ## Installation
@ -67,11 +67,13 @@ quick-plan schedule -n 2018-04-29 -x -e your-mail-address@example.com ultra-80k-
``` ```
## Workout notation ## Workout notation
The reserved keywords of the notation are: workout, warmup, cooldown, run, repeat, recover and lap-button. The reserved keywords of the notation are: workout, warmup, cooldown, run, bike, repeat, recover and lap-button.
**`<workout>`** := `<header><step>+` **`<workout>`** := `<header><step>+`
**`<header>`** := `workout: <name>` **`<header>`** := `<sport>: <name>`
**`<sport>`** := (running | cycling)
**`<name>`** := `[\u0020-\u007F]+` (printable ascii characters) **`<name>`** := `[\u0020-\u007F]+` (printable ascii characters)
@ -79,7 +81,7 @@ The reserved keywords of the notation are: workout, warmup, cooldown, run, repea
**`<step-def>`** := `<simple-step> | <repetition-step>` **`<step-def>`** := `<simple-step> | <repetition-step>`
**`<simple-step>`** := `(warmup | cooldown | run | recover): <duration> [@ <target>]` **`<simple-step>`** := `(warmup | cooldown | run | bike | recover): <duration> [@ <target>]`
**`<repetition-step>`** := `repeat: <count>(<newline> - <simple-step>)+` **`<repetition-step>`** := `repeat: <count>(<newline> - <simple-step>)+`
@ -95,8 +97,12 @@ The reserved keywords of the notation are: workout, warmup, cooldown, run, repea
**`<pace-target>`** := `<pace> - <pace>` **`<pace-target>`** := `<pace> - <pace>`
**`<speed-target>`** := `<kph-speed> - <kph-speed> kph`
**`<pace>`** := `<minutes>:<seconds>` **`<pace>`** := `<minutes>:<seconds>`
**`<kph-speed>`** := `\d{1,3}(\.\d)?`
**`<minutes>`** := `\d{1,2}` **`<minutes>`** := `\d{1,2}`
**`<seconds>`** := `\d{2}` **`<seconds>`** := `\d{2}`

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@ -29,7 +29,7 @@ case class WarmupStep(duration: Duration, target: Option[Target] = None) extends
case class CooldownStep(duration: Duration, target: Option[Target] = None) extends DurationStep("cooldown", 2) case class CooldownStep(duration: Duration, target: Option[Target] = None) extends DurationStep("cooldown", 2)
case class RunStep(duration: Duration, target: Option[Target] = None) extends DurationStep("interval", 3) case class IntervalStep(duration: Duration, target: Option[Target] = None) extends DurationStep("interval", 3)
case class RecoverStep(duration: Duration, target: Option[Target] = None) extends DurationStep("recovery", 4) case class RecoverStep(duration: Duration, target: Option[Target] = None) extends DurationStep("recovery", 4)
@ -70,7 +70,7 @@ object Step {
private def parseDurationStep(x: String): DurationStep = x match { private def parseDurationStep(x: String): DurationStep = x match {
case StepHeader(name, params) => name match { case StepHeader(name, params) => name match {
case "warmup" => WarmupStep.tupled(expect(params)) case "warmup" => WarmupStep.tupled(expect(params))
case "run" => RunStep.tupled(expect(params)) case "run" | "bike" => IntervalStep.tupled(expect(params))
case "recover" => RecoverStep.tupled(expect(params)) case "recover" => RecoverStep.tupled(expect(params))
case "cooldown" => CooldownStep.tupled(expect(params)) case "cooldown" => CooldownStep.tupled(expect(params))
case _ => throw new IllegalArgumentException(s"Duration step type was expected, $name") case _ => throw new IllegalArgumentException(s"Duration step type was expected, $name")

@ -15,6 +15,7 @@ case class HrZoneTarget(zone: Int) extends Target {
"targetValueTwo" -> "", "targetValueTwo" -> "",
"zoneNumber" -> zone.toString) "zoneNumber" -> zone.toString)
} }
case class PaceTarget(from: Pace, to: Pace) extends Target { case class PaceTarget(from: Pace, to: Pace) extends Target {
override def json = Json.obj( override def json = Json.obj(
"targetType" -> Json.obj( "targetType" -> Json.obj(
@ -25,6 +26,16 @@ case class PaceTarget(from: Pace, to: Pace) extends Target {
"zoneNumber" -> JsNull) "zoneNumber" -> JsNull)
} }
case class SpeedTarget(from: KphSpeed, to: KphSpeed) extends Target {
override def json = Json.obj(
"targetType" -> Json.obj(
"workoutTargetTypeId" -> 5,
"workoutTargetTypeKey" -> "speed.zone"),
"targetValueOne" -> from.speed,
"targetValueTwo" -> to.speed,
"zoneNumber" -> JsNull)
}
object NoTarget extends Target { object NoTarget extends Target {
override def json = Json.obj( override def json = Json.obj(
"targetType" -> Json.obj( "targetType" -> Json.obj(
@ -45,12 +56,22 @@ case class Pace(exp: String) {
def speed: Double = 1000D / (minutes * 60 + seconds) def speed: Double = 1000D / (minutes * 60 + seconds)
} }
case class KphSpeed(exp: String) {
/**
* @return Speed in m/s
*/
def speed: Double = exp.toDouble * 10 / 36
}
object Target { object Target {
private val HrZoneRx = """^z(\d)$""".r private val HrZoneRx = """^z(\d)$""".r
private val PaceRangeRx = """^(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})$""".r private val PaceRangeRx = """^(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})$""".r
private val SpeedRangeRx = """^(\d{1,3}(\.\d{1})?)\s*-\s*(\d{1,3}(\.\d{1})?)\s*kph$""".r
def parse(x: String): Target = x.trim match { def parse(x: String): Target = x.trim match {
case HrZoneRx(zone) => HrZoneTarget(zone.toInt) case HrZoneRx(zone) => HrZoneTarget(zone.toInt)
case SpeedRangeRx(from, _, to, _) => SpeedTarget(KphSpeed(from), KphSpeed(to))
case PaceRangeRx(from, to) => PaceTarget(Pace(from), Pace(to)) case PaceRangeRx(from, to) => PaceTarget(Pace(from), Pace(to))
case _ => throw new IllegalArgumentException(s"Unknown target specification: $x") case _ => throw new IllegalArgumentException(s"Unknown target specification: $x")
} }

@ -1,25 +1,26 @@
package com.github.mgifos.workouts.model package com.github.mgifos.workouts.model
import play.api.libs.json.{ JsValue, Json } import play.api.libs.json.{ JsValue, Json }
import Workout._
trait Workout { trait Workout {
def json: JsValue def json: JsValue
} }
case class WorkoutDef(name: String, steps: Seq[Step] = Nil) extends Workout { case class WorkoutDef(sport: String, name: String, steps: Seq[Step] = Nil) extends Workout {
def toRef: WorkoutRef = WorkoutRef(name) def toRef: WorkoutRef = WorkoutRef(name)
def withStep(step: Step): WorkoutDef = WorkoutDef(name, steps :+ step) def withStep(step: Step): WorkoutDef = WorkoutDef(sport, name, steps :+ step)
def json: JsValue = Json.obj( def json: JsValue = Json.obj(
"sportType" -> Json.obj( "sportType" -> Json.obj(
"sportTypeId" -> 1, "sportTypeId" -> sportId(sport),
"sportTypeKey" -> "running"), "sportTypeKey" -> sport),
"workoutName" -> name, "workoutName" -> name,
"workoutSegments" -> Json.arr( "workoutSegments" -> Json.arr(
Json.obj( Json.obj(
"segmentOrder" -> 1, "segmentOrder" -> 1,
"sportType" -> Json.obj( "sportType" -> Json.obj(
"sportTypeId" -> 1, "sportTypeId" -> sportId(sport),
"sportTypeKey" -> "running"), "sportTypeKey" -> sport),
"workoutSteps" -> steps.zipWithIndex.map { case (s, i) => s.json(i + 1) }))) "workoutSteps" -> steps.zipWithIndex.map { case (s, i) => s.json(i + 1) })))
} }
@ -33,7 +34,7 @@ case class WorkoutNote(note: String) extends Workout {
object Workout { object Workout {
private val WorkoutName = """^workout:\s([\u0020-\u007F]+)((\n\s*\-\s[a-z]+:.*)*)$""".r private val WorkoutHeader = """^(running|cycling):\s([\u0020-\u007F]+)((\n\s*\-\s[a-z]+:.*)*)$""".r
private val NextStepRx = """^((-\s\w*:\s.*)((\n\s{1,}-\s.*)*))(([\s].*)*)$""".r private val NextStepRx = """^((-\s\w*:\s.*)((\n\s{1,}-\s.*)*))(([\s].*)*)$""".r
def parseDef(x: String): Either[String, WorkoutDef] = { def parseDef(x: String): Either[String, WorkoutDef] = {
@ -45,13 +46,19 @@ object Workout {
case _ => Left(s"Input string cannot be parsed to Workout: $steps") case _ => Left(s"Input string cannot be parsed to Workout: $steps")
} }
x match { x match {
case WorkoutName(name, steps, _) => loop(WorkoutDef(name), steps.trim) case WorkoutHeader(sport, name, steps, _) => loop(WorkoutDef(sport, name), steps.trim)
case _ => Left(s"Input string cannot be parsed to Workout: $x") case _ => Left(s"Input string cannot be parsed to Workout: $x")
} }
} }
def parseRef(x: String): WorkoutRef = x match { def parseRef(x: String): WorkoutRef = x match {
case WorkoutName(name, _, _) => WorkoutRef(name) case WorkoutHeader(_, name, _, _) => WorkoutRef(name)
case _ => WorkoutRef(x.trim) case _ => WorkoutRef(x.trim)
} }
def sportId(sport: String) = sport match {
case "running" => 1
case "cycling" => 2
case _ => throw new IllegalArgumentException("Only running and cycling workouts are supported.")
}
} }

@ -1,114 +1,114 @@
WEEK,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,Estimated km,,Duration,Avg km,Miles WEEK,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday,Estimated km,,Duration,Avg km,Miles
1,,"workout: 14k, 4x 1.6k @TMP 1,,"running: 14k, 4x 1.6k @TMP
- warmup: 2km @z2 - warmup: 2km @z2
- repeat: 4 - repeat: 4
- run: 1600m @ 5:00-4:30 - run: 1600m @ 5:00-4:30
- recover: 900m @z2 - recover: 900m @z2
- run: 2km - run: 2km
- cooldown: lap-button","workout: 8k jog - cooldown: lap-button","running: 8k jog
- run: 8km @z2 - run: 8km @z2
- cooldown: lap-button","workout: 11-15k, middle 5k @MP - cooldown: lap-button","running: 11-15k, middle 5k @MP
- warmup: 4km @z2 - warmup: 4km @z2
- run: 5km @ 5:40-5:30 - run: 5km @ 5:40-5:30
- run: 6km @z2 - run: 6km @z2
- cooldown: lap-button",,"workout: 1.5h run - cooldown: lap-button",,"running: 1.5h run
- run: 90:00 - run: 90:00
- cooldown: lap-button","workout: 3h run - cooldown: lap-button","running: 3h run
- run: 180:00 - run: 180:00
- cooldown: lap-button",78.0,,0:10:00,1.7,1.0 - cooldown: lap-button",78.0,,0:10:00,1.7,1.0
2,,"14k, 4x 1.6k @TMP",8k jog,"11-15k, middle 5k @MP",,1.5h run,3h run,78.0,,0:15:00,2.5,1.6 2,,"14k, 4x 1.6k @TMP",8k jog,"11-15k, middle 5k @MP",,1.5h run,3h run,78.0,,0:15:00,2.5,1.6
3,,"workout: 14k, 2x1.6k @HMP 3,,"running: 14k, 2x1.6k @HMP
- warmup: 4km @z2 - warmup: 4km @z2
- repeat: 2 - repeat: 2
- run: 1.6km @ 5:05-4:50 - run: 1.6km @ 5:05-4:50
- recover: 1.4km @z2 - recover: 1.4km @z2
- run: 4km @z2 - run: 4km @z2
- cooldown: lap-button",8k jog,"11-15k, middle 5k @MP",,"workout: 2h run - cooldown: lap-button",8k jog,"11-15k, middle 5k @MP",,"running: 2h run
- run: 120:00 - run: 120:00
- cooldown: lap-button","workout: 3.5h run - cooldown: lap-button","running: 3.5h run
- run: 210:00 - run: 210:00
- cooldown: lap-button",90.0,,0:30:00,5.0,3.1 - cooldown: lap-button",90.0,,0:30:00,5.0,3.1
4,,"workout: 10k, 3x1.6k @TMP 4,,"running: 10k, 3x1.6k @TMP
- warmup: 1500m @z2 - warmup: 1500m @z2
- repeat: 3 - repeat: 3
- run: 1600m @ 5:00-4:30 - run: 1600m @ 5:00-4:30
- recover: 900m @z2 - recover: 900m @z2
- run: 1km @z2 - run: 1km @z2
- cooldown: lap-button",8k jog,"workout: 10k, middle 3.2k @MP - cooldown: lap-button",8k jog,"running: 10k, middle 3.2k @MP
- warmup: 4km @z2 - warmup: 4km @z2
- run: 3200m @ 5:40-5:30 - run: 3200m @ 5:40-5:30
- run: 2800m @z2 - run: 2800m @z2
- cooldown: lap-button",,1.5h run,2h run,63.0,,0:40:00,6.7,4.1 - cooldown: lap-button",,1.5h run,2h run,63.0,,0:40:00,6.7,4.1
5,,"workout: 15k, 6x1.6k @TMP 5,,"running: 15k, 6x1.6k @TMP
- warmup: 2km @z2 - warmup: 2km @z2
- repeat: 6 - repeat: 6
- run: 1600m @ 5:00-4:30 - run: 1600m @ 5:00-4:30
- recover: 400m @z2 - recover: 400m @z2
- run: 1km - run: 1km
- cooldown: lap-button",8k jog,"workout: 15k, middle 5k @MP - cooldown: lap-button",8k jog,"running: 15k, middle 5k @MP
- warmup: 5km @z2 - warmup: 5km @z2
- run: 5km @ 5:40-5:30 - run: 5km @ 5:40-5:30
- run: 5km @z2 - run: 5km @z2
- cooldown: lap-button",,"workout: 3.5-4h run - cooldown: lap-button",,"running: 3.5-4h run
- run 225:00 - run 225:00
- cooldown: lap-button",3h run,104.7,,0:45:00,7.5,4.7 - cooldown: lap-button",3h run,104.7,,0:45:00,7.5,4.7
6,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,3.5-4h run,3h run,104.7,,0:50:00,8.3,5.2 6,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,3.5-4h run,3h run,104.7,,0:50:00,8.3,5.2
7,,"workout: 15k, 6x1.6k @HMP 7,,"running: 15k, 6x1.6k @HMP
- warmup: 2km @z2 - warmup: 2km @z2
- repeat: 6 - repeat: 6
- run: 1600m @ 5:05-4:50 - run: 1600m @ 5:05-4:50
- recover: 400m @z2 - recover: 400m @z2
- run: 1km - run: 1km
- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,3.5-4h run,"workout: 3h run, last @MP - cooldown: lap-button",8k jog,"15k, middle 5k @MP",,3.5-4h run,"running: 3h run, last @MP
- run: 120:00 - run: 120:00
- run: 60:00 @ 5:40-5:30 - run: 60:00 @ 5:40-5:30
- cooldown: lap-button",104.7,,1:00:00,10.0,6.2 - cooldown: lap-button",104.7,,1:00:00,10.0,6.2
8,,"workout: 15k, 3x3.2k @HMP 8,,"running: 15k, 3x3.2k @HMP
- warmup: 2km @z2 - warmup: 2km @z2
- repeat: 3 - repeat: 3
- run: 3200m @ 5:05-4:50 - run: 3200m @ 5:05-4:50
- recover: 800m @z2 - recover: 800m @z2
- run: 1km @z2 - run: 1km @z2
- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,"workout: 2.5h run - cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,"running: 2.5h run
- run: 150:00 - run: 150:00
- cooldown: lap-button",83.0,,1:20:00,13.3,8.3 - cooldown: lap-button",83.0,,1:20:00,13.3,8.3
9,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,"workout: 4h run 9,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,"running: 4h run
- run: 240:00 - run: 240:00
- cooldown: lap-button","workout: 3.5h run, last @MP - cooldown: lap-button","running: 3.5h run, last @MP
- run: 150:00 - run: 150:00
- run: 60:00 @ 5:40-5:30 - run: 60:00 @ 5:40-5:30
- cooldown: lap-button",113.0,,1:30:00,15.0,9.3 - cooldown: lap-button",113.0,,1:30:00,15.0,9.3
10,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4-hour run,"3.5h run, last @MP",113.0,,1:45:00,17.5,10.9 10,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4-hour run,"3.5h run, last @MP",113.0,,1:45:00,17.5,10.9
11,,"15k, 3x3.2k @HMP",8k jog,"15k, middle 5k @MP",,2.5h run,3h run,93.0,,2:00:00,20.0,12.4 11,,"15k, 3x3.2k @HMP",8k jog,"15k, middle 5k @MP",,2.5h run,3h run,93.0,,2:00:00,20.0,12.4
12,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,"workout: 5h run 12,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,"running: 5h run
- run: 300:00 - run: 300:00
- cooldown: lap-button",128.0,,2:30:00,25.0,15.5 - cooldown: lap-button",128.0,,2:30:00,25.0,15.5
13,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,5h run,128.0,,3:00:00,30.0,18.6 13,,"15k, 6x1.6k @TMP",8k jog,"15k, middle 5k @MP",,4h run,5h run,128.0,,3:00:00,30.0,18.6
14,,"workout: 15k, 4x1.6k @TMP 14,,"running: 15k, 4x1.6k @TMP
- warmup: 3km @z2 - warmup: 3km @z2
- repeat: 4 - repeat: 4
- run: 1600m @ 5:00-4:30 - run: 1600m @ 5:00-4:30
- recover: 1400m @z2 - recover: 1400m @z2
- run: 1km @z2 - run: 1km @z2
- cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,2h run,78.0,,3:30:00,35.0,21.7 - cooldown: lap-button",8k jog,"15k, middle 5k @MP",,2h run,2h run,78.0,,3:30:00,35.0,21.7
15,,"workout: 11k, 3x1.6k @MP 15,,"running: 11k, 3x1.6k @MP
- warmup: 1.5km @z2 - warmup: 1.5km @z2
- repeat: 3 - repeat: 3
- run: 1600m @ 5:40-5:30 - run: 1600m @ 5:40-5:30
- recover: 1400m @z2 - recover: 1400m @z2
- run: 500m @z2 - run: 500m @z2
- cooldown: lap-button",8k jog,"workout: 11k, middle 5k @MP - cooldown: lap-button",8k jog,"running: 11k, middle 5k @MP
- warmup: 3km @z2 - warmup: 3km @z2
- run: 5km @ 5:40-5:30 - run: 5km @ 5:40-5:30
- run: 3km @z2 - run: 3km @z2
- cooldown: lap-button",,1.5h run,"workout: easy 1h jog - cooldown: lap-button",,1.5h run,"running: easy 1h jog
- run: 60:00 - run: 60:00
- cooldown: lap-button",55.0,,4:00:00,40.0,24.8 - cooldown: lap-button",55.0,,4:00:00,40.0,24.8
16,,"workout: 10k, middle 5k @HMP 16,,"running: 10k, middle 5k @HMP
- warmup: 3km @z2 - warmup: 3km @z2
- run: 5km @ 5:05-4:30 - run: 5km @ 5:05-4:30
- run: 2km @z2 - run: 2km @z2
- cooldown: lap-button",8k jog,"workout: easy 5k jog - cooldown: lap-button",8k jog,"running: easy 5k jog
- run: 5km - run: 5km
- cooldown: lap-button",,Race day!,,103.0,,5:00:00,50.0,31.1 - cooldown: lap-button",,Race day!,,103.0,,5:00:00,50.0,31.1
,,,,,,,,"1,517.0",,,, ,,,,,,,,"1,517.0",,,,

1 WEEK Monday Tuesday Wednesday Thursday Friday Saturday Sunday Estimated km Duration Avg km Miles
2 1 workout: 14k, 4x 1.6k @TMP - warmup: 2km @z2 - repeat: 4 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 - run: 2km - cooldown: lap-button running: 14k, 4x 1.6k @TMP - warmup: 2km @z2 - repeat: 4 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 - run: 2km - cooldown: lap-button workout: 8k jog - run: 8km @z2 - cooldown: lap-button running: 8k jog - run: 8km @z2 - cooldown: lap-button workout: 11-15k, middle 5k @MP - warmup: 4km @z2 - run: 5km @ 5:40-5:30 - run: 6km @z2 - cooldown: lap-button running: 11-15k, middle 5k @MP - warmup: 4km @z2 - run: 5km @ 5:40-5:30 - run: 6km @z2 - cooldown: lap-button workout: 1.5h run - run: 90:00 - cooldown: lap-button running: 1.5h run - run: 90:00 - cooldown: lap-button workout: 3h run - run: 180:00 - cooldown: lap-button running: 3h run - run: 180:00 - cooldown: lap-button 78.0 0:10:00 1.7 1.0
3 2 14k, 4x 1.6k @TMP 8k jog 11-15k, middle 5k @MP 1.5h run 3h run 78.0 0:15:00 2.5 1.6
4 3 workout: 14k, 2x1.6k @HMP - warmup: 4km @z2 - repeat: 2 - run: 1.6km @ 5:05-4:50 - recover: 1.4km @z2 - run: 4km @z2 - cooldown: lap-button running: 14k, 2x1.6k @HMP - warmup: 4km @z2 - repeat: 2 - run: 1.6km @ 5:05-4:50 - recover: 1.4km @z2 - run: 4km @z2 - cooldown: lap-button 8k jog 11-15k, middle 5k @MP workout: 2h run - run: 120:00 - cooldown: lap-button running: 2h run - run: 120:00 - cooldown: lap-button workout: 3.5h run - run: 210:00 - cooldown: lap-button running: 3.5h run - run: 210:00 - cooldown: lap-button 90.0 0:30:00 5.0 3.1
5 4 workout: 10k, 3x1.6k @TMP - warmup: 1500m @z2 - repeat: 3 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 - run: 1km @z2 - cooldown: lap-button running: 10k, 3x1.6k @TMP - warmup: 1500m @z2 - repeat: 3 - run: 1600m @ 5:00-4:30 - recover: 900m @z2 - run: 1km @z2 - cooldown: lap-button 8k jog workout: 10k, middle 3.2k @MP - warmup: 4km @z2 - run: 3200m @ 5:40-5:30 - run: 2800m @z2 - cooldown: lap-button running: 10k, middle 3.2k @MP - warmup: 4km @z2 - run: 3200m @ 5:40-5:30 - run: 2800m @z2 - cooldown: lap-button 1.5h run 2h run 63.0 0:40:00 6.7 4.1
6 5 workout: 15k, 6x1.6k @TMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:00-4:30 - recover: 400m @z2 - run: 1km - cooldown: lap-button running: 15k, 6x1.6k @TMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:00-4:30 - recover: 400m @z2 - run: 1km - cooldown: lap-button 8k jog workout: 15k, middle 5k @MP - warmup: 5km @z2 - run: 5km @ 5:40-5:30 - run: 5km @z2 - cooldown: lap-button running: 15k, middle 5k @MP - warmup: 5km @z2 - run: 5km @ 5:40-5:30 - run: 5km @z2 - cooldown: lap-button workout: 3.5-4h run - run 225:00 - cooldown: lap-button running: 3.5-4h run - run 225:00 - cooldown: lap-button 3h run 104.7 0:45:00 7.5 4.7
7 6 15k, 6x1.6k @TMP 8k jog 15k, middle 5k @MP 3.5-4h run 3h run 104.7 0:50:00 8.3 5.2
8 7 workout: 15k, 6x1.6k @HMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:05-4:50 - recover: 400m @z2 - run: 1km - cooldown: lap-button running: 15k, 6x1.6k @HMP - warmup: 2km @z2 - repeat: 6 - run: 1600m @ 5:05-4:50 - recover: 400m @z2 - run: 1km - cooldown: lap-button 8k jog 15k, middle 5k @MP 3.5-4h run workout: 3h run, last @MP - run: 120:00 - run: 60:00 @ 5:40-5:30 - cooldown: lap-button running: 3h run, last @MP - run: 120:00 - run: 60:00 @ 5:40-5:30 - cooldown: lap-button 104.7 1:00:00 10.0 6.2
9 8 workout: 15k, 3x3.2k @HMP - warmup: 2km @z2 - repeat: 3 - run: 3200m @ 5:05-4:50 - recover: 800m @z2 - run: 1km @z2 - cooldown: lap-button running: 15k, 3x3.2k @HMP - warmup: 2km @z2 - repeat: 3 - run: 3200m @ 5:05-4:50 - recover: 800m @z2 - run: 1km @z2 - cooldown: lap-button 8k jog 15k, middle 5k @MP 2h run workout: 2.5h run - run: 150:00 - cooldown: lap-button running: 2.5h run - run: 150:00 - cooldown: lap-button 83.0 1:20:00 13.3 8.3
10 9 15k, 6x1.6k @TMP 8k jog 15k, middle 5k @MP workout: 4h run - run: 240:00 - cooldown: lap-button running: 4h run - run: 240:00 - cooldown: lap-button workout: 3.5h run, last @MP - run: 150:00 - run: 60:00 @ 5:40-5:30 - cooldown: lap-button running: 3.5h run, last @MP - run: 150:00 - run: 60:00 @ 5:40-5:30 - cooldown: lap-button 113.0 1:30:00 15.0 9.3
11 10 15k, 6x1.6k @TMP 8k jog 15k, middle 5k @MP 4-hour run 3.5h run, last @MP 113.0 1:45:00 17.5 10.9
12 11 15k, 3x3.2k @HMP 8k jog 15k, middle 5k @MP 2.5h run 3h run 93.0 2:00:00 20.0 12.4
13 12 15k, 6x1.6k @TMP 8k jog 15k, middle 5k @MP 4h run workout: 5h run - run: 300:00 - cooldown: lap-button running: 5h run - run: 300:00 - cooldown: lap-button 128.0 2:30:00 25.0 15.5
14 13 15k, 6x1.6k @TMP 8k jog 15k, middle 5k @MP 4h run 5h run 128.0 3:00:00 30.0 18.6
15 14 workout: 15k, 4x1.6k @TMP - warmup: 3km @z2 - repeat: 4 - run: 1600m @ 5:00-4:30 - recover: 1400m @z2 - run: 1km @z2 - cooldown: lap-button running: 15k, 4x1.6k @TMP - warmup: 3km @z2 - repeat: 4 - run: 1600m @ 5:00-4:30 - recover: 1400m @z2 - run: 1km @z2 - cooldown: lap-button 8k jog 15k, middle 5k @MP 2h run 2h run 78.0 3:30:00 35.0 21.7
16 15 workout: 11k, 3x1.6k @MP - warmup: 1.5km @z2 - repeat: 3 - run: 1600m @ 5:40-5:30 - recover: 1400m @z2 - run: 500m @z2 - cooldown: lap-button running: 11k, 3x1.6k @MP - warmup: 1.5km @z2 - repeat: 3 - run: 1600m @ 5:40-5:30 - recover: 1400m @z2 - run: 500m @z2 - cooldown: lap-button 8k jog workout: 11k, middle 5k @MP - warmup: 3km @z2 - run: 5km @ 5:40-5:30 - run: 3km @z2 - cooldown: lap-button running: 11k, middle 5k @MP - warmup: 3km @z2 - run: 5km @ 5:40-5:30 - run: 3km @z2 - cooldown: lap-button 1.5h run workout: easy 1h jog - run: 60:00 - cooldown: lap-button running: easy 1h jog - run: 60:00 - cooldown: lap-button 55.0 4:00:00 40.0 24.8
17 16 workout: 10k, middle 5k @HMP - warmup: 3km @z2 - run: 5km @ 5:05-4:30 - run: 2km @z2 - cooldown: lap-button running: 10k, middle 5k @HMP - warmup: 3km @z2 - run: 5km @ 5:05-4:30 - run: 2km @z2 - cooldown: lap-button 8k jog workout: easy 5k jog - run: 5km - cooldown: lap-button running: easy 5k jog - run: 5km - cooldown: lap-button Race day! 103.0 5:00:00 50.0 31.1
18 1,517.0
19 TMP 16k pace
20 HMP Half Marathon Pace Avg min/km 0:06:00
21 MP Marathon Pace
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

@ -11,11 +11,11 @@ class StepSpec extends FlatSpec with Matchers {
a[AssertionError] should be thrownBy Step.parse("- warmup: 5km\n - run: 10km\n - recover: 100m") a[AssertionError] should be thrownBy Step.parse("- warmup: 5km\n - run: 10km\n - recover: 100m")
Step.parse("- warmup: 5km") should be(WarmupStep(DistanceDuration(5, km))) Step.parse("- warmup: 5km") should be(WarmupStep(DistanceDuration(5, km)))
Step.parse("- run: 2km @ 5:00-4:50") should be(RunStep(DistanceDuration(2, km), Some(PaceTarget(Pace("5:00"), Pace("4:50"))))) Step.parse("- run: 2km @ 5:00-4:50") should be(IntervalStep(DistanceDuration(2, km), Some(PaceTarget(Pace("5:00"), Pace("4:50")))))
Step.parse("- recover: 500m @z2") should be(RecoverStep(DistanceDuration(500, m), Some(HrZoneTarget(2)))) Step.parse("- recover: 500m @z2") should be(RecoverStep(DistanceDuration(500, m), Some(HrZoneTarget(2))))
Step.parse("- cooldown: 05:00") should be(CooldownStep(TimeDuration(minutes = 5))) Step.parse("- cooldown: 05:00") should be(CooldownStep(TimeDuration(minutes = 5)))
Step.parse("- repeat: 3\n - run: 10km\n - recover: 100m") should be(RepeatStep(3, List( Step.parse("- repeat: 3\n - run: 10km\n - recover: 100m") should be(RepeatStep(3, List(
RunStep(DistanceDuration(10, km)), IntervalStep(DistanceDuration(10, km)),
RecoverStep(DistanceDuration(100, m))))) RecoverStep(DistanceDuration(100, m)))))
} }
} }

@ -7,29 +7,29 @@ import play.api.libs.json.Json
class WorkoutSpec extends FlatSpec with Matchers { class WorkoutSpec extends FlatSpec with Matchers {
/* /*
workout: run-fast running: run-fast
- warmup: 10:00 - warmup: 10:00
- repeat: 2 - repeat: 2
- run: 1500m @ 4:30-5:00 - run: 1500m @ 4:30-5:00
- recover: 01:30 @ z2 - recover: 01:30 @ z2
- cooldown: lap-button - cooldown: lap-button
*/ */
val testWO = "workout: run-fast\n- warmup: 10:00\n- repeat: 2\n - run: 1500m @ 4:30-5:00\n - recover: 01:30 @ z2\n- cooldown: lap-button" val testWO = "running: run-fast\n- warmup: 10:00\n- repeat: 2\n - run: 1500m @ 4:30-5:00\n - recover: 01:30 @ z2\n- cooldown: lap-button"
"Workout" should "parse correctly" in { "Workout" should "parse correctly" in {
Workout.parseDef("") should be('left) Workout.parseDef("") should be('left)
Workout.parseDef("workout") should be('left) Workout.parseDef("running") should be('left)
Workout.parseDef("workout:") should be('left) Workout.parseDef("running:") should be('left)
Workout.parseDef("workout: !") should be('left) Workout.parseDef("running !") should be('left)
Workout.parseDef("workout run-fast") should be('left) Workout.parseDef("running run-fast") should be('left)
Workout.parseDef(" workout: run-fast") should be('left) Workout.parseDef(" running: run-fast") should be('left)
Workout.parseDef(testWO) should be( Workout.parseDef(testWO) should be(
Right( Right(
WorkoutDef("run-fast", Seq( WorkoutDef("running", "run-fast", Seq(
WarmupStep(TimeDuration(minutes = 10)), WarmupStep(TimeDuration(minutes = 10)),
RepeatStep(2, Seq( RepeatStep(2, Seq(
RunStep(DistanceDuration(1500, m), Some(PaceTarget(Pace("4:30"), Pace("5:00")))), IntervalStep(DistanceDuration(1500, m), Some(PaceTarget(Pace("4:30"), Pace("5:00")))),
RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2))))), RecoverStep(TimeDuration(1, 30), Some(HrZoneTarget(2))))),
CooldownStep(LapButtonPressed))))) CooldownStep(LapButtonPressed)))))
} }
@ -45,10 +45,19 @@ class WorkoutSpec extends FlatSpec with Matchers {
} }
} }
"Workout" should "dump json correctly" in { "Workout" should "dump json correctly" in {
val is = getClass.getClassLoader.getResourceAsStream("run-fast.json") val is = getClass.getClassLoader.getResourceAsStream("run-fast.json")
val expectJson = Json.parse(is) val expectJson = Json.parse(is)
Workout.parseDef(testWO).map(_.json) should be(Right(expectJson)) Workout.parseDef(testWO).map(_.json) should be(Right(expectJson))
} }
"Workout" should "support cycling ws" in {
val testBike = "cycling: cycle-test\n- warmup: 5:00\n- bike: 20km @ 20.0-100kph\n- cooldown: lap-button"
Workout.parseDef(testBike) should be(
Right(
WorkoutDef("cycling", "cycle-test", Seq(
WarmupStep(TimeDuration(minutes = 5)),
IntervalStep(DistanceDuration(20, km), Some(SpeedTarget(KphSpeed("20.0"), KphSpeed("100")))),
CooldownStep(LapButtonPressed)))))
}
} }

Loading…
Cancel
Save