Your running season on one sheet of paper.
Write down your races and how much you run now. The generator lays out the weeks up to race day as a printable table: one row per week, a cell for every day. Put it on the wall and colour a cell after each run.
You need your race dates, one recent race time and how far you run now, and about ten minutes at a terminal. Make your own
The example sheet could not be shown here. It is built from this season file.
How the sheet works
Each row is a week, Monday to Sunday. The columns on the right say what the week holds:
ER, the easy runs (12+12 is two easy runs of 12 km),
QR, one quality run, and LR, the long run. The grey number at the end
is the week’s planned distance. You decide on which day each run happens.
The quality run is written as a short code:
- H: 6×1′
- Hill repeats: six one-minute hard efforts uphill, by effort, not pace; jog back down.
- I: 5×3′
- Intervals: five three-minute repeats at interval pace.
- T: 3×8′
- Threshold: repeats at threshold pace, comfortably hard.
- MP: 3×3 km
- Repeats at marathon pace.
- S
- Strides: a shorter easy run with six relaxed 20-second accelerations, never a sprint.
- RACE 21 km
- A race in place of the session.
- –
- No session this week.
Intervals, threshold and marathon-pace repeats have two minutes of jogging between them.
The paces
The paces are printed under the table, worked out from one race you ran recently with Jack Daniels’ formula. A 3:30:02 marathon, for example, gives easy runs at 5:37 per km, threshold at 4:40, intervals at 4:18 and marathon pace at 4:59. You can also set any of them yourself.
Colouring it in
When you’ve done a run, colour its day. The sheet itself is black and white and the colours are yours; these are the ones the author uses:
- easy run
- quality run
- long run
- something unexpected: illness, a trip
At the end of the week, colour one box for every kilometre you ran. A week counts as fully done at 80 % of its planned kilometres, so a missed run doesn’t spoil it. The colour says how the week went:
- 80 % or more: done
- 50 to 80 %
- under 50 %
The idea behind it
An outline, not a prescription. No season goes to plan. You get ill, work runs late, you travel. The sheet shows the shape of the season, and you adapt the details as you go. Two rules printed on every sheet say what to give up first:
Short on time: drop an easy run, then intensity, then the long run.
Tired or ill: drop intensity first.
Grown from practice, checked against coaching guidance. Three weeks of building, then a lighter week. Quality sessions that grow a little each time they come round. Before each important race, a block aimed at that race, then a taper; after it, recovery and an easier week back into training. These rules started as the author’s own plan for his 2026 season, and each one was then checked against published coaching guidance and changed where the two disagreed. His threshold pace, for example, was about 10 seconds per km too fast for his race result; the generator now works it out with Jack Daniels’ formula. Every rule and its source is listed in the training model.
Make your own
The generator runs on your own computer, from the terminal. You don’t need to know how to program, but you will type a few commands. It takes about ten minutes the first time.
-
Install it once
You need Python 3.11 or newer and Git. Then, in a terminal:
git clone https://github.com/f-hluchnik/training-table-generator.git cd training-table-generator python3 -m venv .venv . .venv/bin/activate pip install -e .On Windows, the fourth line is
.venv\Scripts\activate, andpy -3replacespython3. Run the fourth line again in every new terminal before using the generator. -
Describe your season
Create a text file, for example
my-season.toml, with your races, a recent race result and how far you run now:start = 2027-05-03 # the first week of the plan [[race]] name = "Běhej lesy Lednice" date = 2027-10-09 distance_km = 42.195 [paces] # a race you ran recently; all paces are worked out from it race_result = { distance_km = 42.195, time = "3:30:02" } [load] easy_run_km = 12 # the length of one easy run long_run_km = 15 # your long run nowThat is all the generator needs. Dates are written without quotes.
-
Build the sheet
ttg build my-season.tomlThe PDF appears in a folder
outnext to your file. The generator never refuses a plan: if two races are close together or the season starts late, it still builds the sheet, shortens what has to give, and prints a warning in the terminal. If the file has a mistake, the message names the field at fault and nothing is written. -
Print it
On A4, landscape, at 100 % (not “fit to page”), so the cells are big enough to colour. To change the plan later, edit your file and build again.
Common changes
Add any of these to your file:
-
More races. Add a
[[race]]section for each. Mark a tune-up race inside your training withpriority = "C", an important race that isn’t your main goal withpriority = "B". - A trail or mountain race. Add
hilly = trueto its section. - A goal marathon pace. Add
mp = "4:55"under[paces]. - More runs a week. Add
runs_per_week = 5under[load]; the default is 4. -
One week different. Add
[overrides]with, for example,14 = { long_run_km = 30 }.
Every field and its default is in the reference below.
Keep your paces current
Your paces come from one race result, so they don’t follow your fitness on their own. Check them after each important race and every two or three months: let your easy runs get faster at the same heart rate, but change threshold and marathon pace only after a race or a test run. Then build the sheet again.
Reference
Everything else the generator can do, for when you need it.
Every field of the season file
Only the fields marked required must be in the file. A misspelled field is reported as an error, so a typo is never silently ignored.
The plan
startdate- Week 1 is the Monday-to-Sunday week that contains this date.
weeksnumber-
Instead of
start: the plan’s length in weeks, ending with the week of the last race. Give one of the two, not both. With neither, the plan starts next Monday.
Races: [[race]]
One section per race, as many as you like, on any dates.
nametext, required- Printed under the table. Each race needs its own name.
datedate, required- The race day, or the first day of a race over several days.
enddate- The last day of a race over several days.
distance_kmnumber, required- Decides how the race is trained for: up to 10 km, half marathon, marathon, or longer (ultra).
priority"A", "B" or "C", default "A"- A is a main goal, with a full taper and recovery. B is important, with a taper one week shorter. C is a tune-up race inside your training, with no taper or recovery of its own: a C race up to 10 km takes the place of the quality run, a longer one the place of the long run.
taper_weeks,specific_weeks,recovery_weeksnumbers- Change the length of the race’s taper (including race week), its race-specific block and its recovery; see how long each phase is. Not for C races.
hillytrue or false, default false- For a course where climbs, not a steady pace, decide the effort, such as a trail or mountain race. In the weeks aimed at it, every other quality run is hill repeats, threshold work replaces marathon pace, and the long runs have no marathon-pace kilometres. Not for C races.
Paces: [paces]
race_resultrequired-
A race you ran recently, as
{ distance_km = 21.1, time = "1:39:30" }. The time is"h:mm:ss"or"m:ss". Every pace is worked out from it. easy,mp,threshold,interval"m:ss" per km-
Set a pace yourself instead of the worked-out one: easy runs, marathon pace, threshold, intervals.
mpis the place for a goal marathon pace. A threshold pace far from the worked-out one gets a warning.
Your running now: [load]
easy_run_kmnumber, required- The length of one easy run. It stays the same all season; in lighter weeks the last one is halved.
long_run_kmnumber, required- The long run of the first week. It grows by 2 km in each building week, up to a limit set by the race.
runs_per_week3 to 6, default 4- Always one quality run and one long run; the others are easy runs.
The sheet: [sheet]
titletext, default "training plan"- The title at the top of the sheet.
ruleslist of texts-
The rules printed under the table, such as
rules = ["Short on time: drop an easy run."]. Without it, the sheet prints its own.
Changing single weeks: [overrides]
Change any week by its number. The change applies to that week only; the weeks after it are still planned as before.
[overrides]
1 = { easy = "reduced" } # "regular" or "reduced": the last easy run halved
13 = { quality = { type = "H", reps = 10, minutes = 1 } } # any quality session
14 = { long_run_km = 30 } # 0 = no long run
20 = { note = "holiday" } # replaces the grey number at the end
A quality session is { type, reps, minutes } or { type, reps, km }, with
type one of "H", "I", "T", "MP".
Strides are { type = "S" }, no quality run is { type = "none" }. A race
can’t be replaced.
How long each phase is
The plan is laid out backwards from each A and B race. The lengths are in weeks; the long run never goes above its limit.
| Race | Taper, with race week | Race-specific block | Recovery | Longest long run |
|---|---|---|---|---|
| up to 10 km | 1 | 3 | 1 | 16 km |
| half marathon | 2 | 4 | 2 | 21 km |
| marathon | 2 | 6 | 2 | 32 km |
| ultra | 3 | 6 | 3 | 40 km |
When there isn’t room for all of it, the build weeks give way first, then the race-specific block, then the recovery after the previous race, then the taper. Each shortened block gets a warning.
What the warnings mean
Warnings never stop the build. Each starts with a code:
C1- Races are close together: a block was shortened because of the previous race.
C2- The plan starts too late for the first main race.
C3- The easy runs and the long run grow by more than 10 % from one week to the next.
C4- The long run is more than half of the week.
C5- The long run is above the race’s limit.
C6- More than 40 minutes at threshold pace in one session.
C7- More than 10 km, or 8 % of the week, at interval pace in one session.
C8- A threshold pace you set is more than 10 seconds per km away from the worked-out one.
C9- Hill repeats longer than 2 minutes, or more than 20 minutes of hill effort in one session.