In [19]:
from datetime import date, timedelta
import types
from collections import namedtuple
In [20]:
def determine_next_weekday(now, weekday):
'''
datetime, int -> datetime
'''
days_ahead = weekday - now.weekday() + 7
return now + timedelta(days_ahead)
In [21]:
def plan_range(start_date, num_weeks, start_week, step):
'''
Generator function to return week type, i.e. progression, rest or race.
'''
for wk in range(start_week, num_weeks, step):
date = start_date + timedelta(weeks=wk)
if wk == (num_weeks - 1):
week_type = 'race'
elif rest_week(wk, num_weeks):
week_type = 'rest'
else:
week_type = 'prog'
yield (wk, date, week_type)
In [22]:
def week_type(week, length):
if wk == (length - 1):
return 'race'
elif rest_week(week, length):
return 'rest'
else:
return 'prog'
In [23]:
def rest_week(week, plan_length):
'''
int -> boolean
Determine if current week is a rest week.
Plans work on a 4 week block, with every 4th week being an easier week.
Runner has at least 2 weeks, and a maximum of 5 weeks before they get an
easier week. So if they were on a 6 week plan they would only have an
easier week on race week.
Returns True if rest week and False if progression week.
'''
build_up = plan_length % 4
if week <= build_up and build_up < 3:
return False
elif (week - build_up + 1) % 4 == 0:
return True
else:
return False
In [24]:
def rest_pc_or_abs(week_cut, dur):
'''
Return rest week duration based on whether rest duration reduction is applied as an absolute
or % value
'''
if isinstance(week_cut, str):
return (float(week_cut.strip('%')) / 100) * dur
else:
return dur - week_cut
In [25]:
class Exercise:
def __init__(self, description, duration):
self.description = description
self.duration = duration
def __repr__(self):
'''
Return a more human-readable representation
'''
return '{0}({1})'.format(self.description, self.duration)
@staticmethod
def mins_to_seconds_formatter(dur_in_mins):
'''
Return duration nicely formatted minutes based on value in seconds
'''
return "{}s".format(int(dur_in_mins * 60))
In [26]:
class WorkoutSet:
def __init__(self, reps):
self.reps = reps
self.exercises = []
self._duration = 0
def __repr__(self):
'''
Return a more human-readable representation
'''
ex = ', '.join(str(exercise) for exercise in self.exercises)
return '{0}x ({1})'.format(self.reps, ex)
@property
def duration(self):
return self.reps * self._duration
def add_exercise(self, exercise):
self.exercises.append(exercise)
self._duration += exercise.duration
In [85]:
class Workout:
'''
Represents a workout session
'''
formatting_dict = {
'Event Day': {'color': '#001F3F',
'textColor': 'hsla(210, 100%, 75%, 1.0)'},
'RunEasy': {'color': '#2ECC40',
'textColor': 'hsla(127, 63%, 15%, 1.0)'},
'Intervals': {'color': '#FF4136',
'textColor': 'hsla(3, 100%, 25%, 1.0)'},
'Hillsprint': {'color': '#FFDC00',
'textColor': 'hsla(52, 100%, 20%, 1.0)'},
'Tempo': {'color': '#0074D9',
'textColor': 'hsla(208, 100%, 85%, 1.0)'}
}
def __init__(self, date, title):
self.date = date
self.title = title
self.duration = 0
self.workoutsets = []
def __repr__(self):
'''
Return a more human-readable representation
'''
return '{0} - {1}'.format(self.date.strftime('%d %b %Y'),
self.title)
def __str__(self):
'''
Return a more human-readable representation
'''
# TODO: add if for EventDay
ws = '\n'.join('{0}x {1}'.format(workoutset.reps,
workoutset.exercises) for workoutset in self.workoutsets)
return '{0}\n{1}'.format(self.title, ws)
@property
def color(self):
return self.formatting_dict[self.title]['color']
@property
def textColor(self):
return self.formatting_dict[self.title]['textColor']
def add_workoutset(self, workoutset):
self.workoutsets.append(workoutset)
self.duration += workoutset.duration
In [86]:
RuneasySettings = namedtuple('RuneasySettings', 'init_dur prog_freq rest race max_dur')
IntervalSettings = namedtuple('IntervalSettings', 'init_dur prog_freq rest race max_dur')
In [87]:
class Progression:
def __init__(self, start_date, length, progressions):
self.start_date = start_date
self.length = length
self.sessions = []
progress_dict = {
"runeasy": self.runeasy,
"interval": self.interval
}
start = 0
step = len(progressions)
for progression in progressions:
self.sessions += [wk for wk in progress_dict[progression[0]](start, step, progression[1])]
start += 1
def runeasy(self, start, step, settings):
'''
'''
wk = start
dur = settings.init_dur
while wk < self.length:
if week_type(wk, self.length) == 'prog':
if (wk + 1) % settings.prog_freq == 0 and dur < settings.max_dur:
dur += 5
wk_dur = dur
elif week_type(wk, self.length) == 'rest':
wk_dur = rest_pc_or_abs(settings.rest, dur)
else:
wk_dur = rest_pc_or_abs(settings.race, dur)
# Build workout
date = self.start_date + timedelta(weeks=wk)
w = Workout(date, 'RunEasy')
ws = WorkoutSet(1)
e = Exercise('Easy', wk_dur)
ws.add_exercise(e)
w.add_workoutset(ws)
yield w
wk += step
def interval(self, start, step, settings):
'''
'''
wk = start
dur = settings.init_dur
while wk < self.length:
if week_type(wk, self.length) == 'prog':
if (wk + 1) % settings.prog_freq == 0 and dur < settings.max_dur:
dur += 5
wk_dur = dur
elif week_type(wk, self.length) == 'rest':
wk_dur = rest_pc_or_abs(settings.rest, dur)
else:
wk_dur = rest_pc_or_abs(settings.race, dur)
# Build workout
date = self.start_date + timedelta(weeks=wk)
w = Workout(date, 'Interval')
ws = WorkoutSet(1)
e = Exercise('Easy', wk_dur)
ws.add_exercise(e)
w.add_workoutset(ws)
yield w
wk += step
In [88]:
class Plan:
'''
Represents running training plan for prescribed event and level.
'''
beginner = [
[("runeasy", RuneasySettings(5, 1, 5, 5, 35))],
[("runeasy", RuneasySettings(10, 1, 5, 5, 35)),
("interval", RuneasySettings(25, 1, 5, 5, 35))],
[("runeasy", RuneasySettings(30, 1, 5, 5, 35))]
]
def __init__(self, start_date, event_date, event_title, level):
self.start_date = start_date
self._event_date = event_date
self.event_title = event_title
self.level = level
# Populate schedule with event
self.schedule = [Workout(self._event_date, 'Event Day')]
@property
def length(self):
'''
Length of the training plan in weeks
'''
return self.weeks_between_dates(self.start_date, self._event_date)
def create(self, days):
'''
Creates schedule based on ability level and training days
'''
def builder_dict(level, days):
level_dict = {
'Beginner': self.beginner_plan,
'Intermediate': self.intermediate_plan,
'Advanced': self.advanced_plan
}.get(level, None)
return level_dict(days)
self.schedule += builder_dict(self.level, days)
def __repr__(self):
'''
Return a more human-readable representation
'''
return "{0} week {1} Plan for the {2}".format(self.length,
self.level,
self.event_title)
@property
def event_date(self):
'''
Event date property, formatted as a string
'''
return self._event_date.strftime('%d %b %Y')
@staticmethod
def weeks_between_dates(start_date, end_date):
'''
Return the number of weeks between two dates
'''
return int((determine_next_weekday(end_date, 0) -
determine_next_weekday(start_date, 0)).days / 7)
def beginner_plan(self, days):
raise NotImplementedError
def intermediate_plan(self, days):
raise NotImplementedError
def advanced_plan(self, days):
raise NotImplementedError
In [89]:
class Plan5k(Plan):
def beginner_plan(self, days):
'''
Return beginner plan schedule based on number of training days a week.
'''
details = [
[("runeasy", RuneasySettings(5, 1, 5, 5, 35))],
[("runeasy", RuneasySettings(10, 1, 5, 5, 35)),
("interval", RuneasySettings(25, 1, 5, 5, 35))],
[("runeasy", RuneasySettings(30, 1, 5, 5, 35))]
]
schedule = []
for day, detail in zip(days, details):
session_start = determine_next_weekday(self.start_date, day)
p = Progression(session_start, self.length, detail)
schedule += p.sessions
return schedule
def intermediate_plan(self, days):
'''
'''
pass
def advanced_plan(self, days):
pass
In [90]:
start_date = date(2017, 8, 25)
event_date = start_date + timedelta(weeks=8)
p = Plan5k(start_date, event_date, 'RACE DAY', 'Beginner')
days = [0, 2, 4]
p.create(days)
In [91]:
for wo in p.schedule:
print('{wo.date}:\n{wo}\n'.format(wo=wo))
In [92]:
# def gen_1(maxi):
# i = 0
# dur = 5
# while True:
# yield "Runeasy ({})".format(dur)
# dur += 5
# i += 1
# def gen_2(maxi):
# i = 0
# dur = 5
# while True:
# yield "Intervals ({})".format(dur)
# dur += 5
# i += 1
def gen_1(start, step, length):
i = start
while i < length:
yield "Runeasy ({})".format(i)
i += step
def gen_2(start, step, length):
i = start
while i < length:
yield "Intervals ({})".format(i)
i += step
In [3]:
from itertools import cycle
In [4]:
length = 10
schedule = []
gens = [gen_1, gen_2]
wk = 0
start = 0
step = len(gens)
for gen in gens:
schedule += [wk for wk in gen(start, step, length)]
start += 1
In [5]:
schedule
Out[5]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]: