[OCaml] High Intensity Training Online
refactor Move the Ideal Routine into a Routines module
Nearly half of prescription.ml was HD1's Ideal Routine: 27 catalog lookups and three day definitions living inside the module that defines plan types. Move the fixture, unchanged, to Routines.ideal so Prescription is plan types only and the book's routine has its own home. The module is named for what it holds rather than for its one member, so a second authored routine — HD2 consolidation is on the deferred list — has a place without a sibling file.
Changed files
ARCHITECTURE.org
@@ -159,7 +159,7 @@
159
159
hito.app Trainee · Repository · Password_hash (ports) · Service · Catalog · Workout_import
160
160
| identity, the ports, recovery policy, Lwt — no database
161
161
v
162
Removed:
hito.core Exercise · Recovery · Prescription · Evidence · Progress
162
Added:
hito.core Exercise · Recovery · Prescription · Routines · Evidence · Progress
163
163
pure domain: no framework, no database, no serialization
164
164
#+end_src
165
165
@@ -173,7 +173,7 @@
173
173
composition root, the one file that names both an adapter and the use case it
174
174
plugs into.
175
175
176
Removed:
The core is five modules, unwrapped (=wrapped false=), so they are referred to
176
Added:
The core is six modules, unwrapped (=wrapped false=), so they are referred to
177
177
bare: =Prescription.Stimulus=, =Evidence.Workout=, =Evidence.Log=. It has no
178
178
dependency on Lwt, Caqti, Dream, or any serializer, and gaining one would be a
179
179
design failure rather than a convenience.
@@ -204,16 +204,25 @@
204
204
205
205
** Prescription — the plan
206
206
207
Removed:
=Rep_range= validates authored calibration bands against HD1's 6–12 limits.
207
Added:
=Rep_range= validates authored calibration bands against HD1's 6–12 envelope.
208
208
=Stimulus= is one prescribed drive: a delivery, its rep window, and its allowed
209
209
substitutes. =Workout= is an ordered, non-empty sequence of stimuli — Day 1, and
210
Removed:
so on. =Routine= is the cycle: its workouts, =workout_after= advancing by
211
Removed:
identity so rotation follows from what was last performed, and the recovery owed
212
Removed:
after each (=training_interval= 48h within the cycle, =cycle_rest= 72h after its
213
Removed:
final workout). It ships HD1's Ideal Routine as =ideal=.
210
Added:
so on — that refuses more than three sets on one muscle. =Routine= is the cycle:
211
Added:
its workouts, =workout_after= advancing by identity so rotation follows from
212
Added:
what was last performed, and the recovery owed after each (=training_interval=
213
Added:
48h within the cycle, =cycle_rest= 72h after its final workout). These are plan
214
Added:
types only; the plans themselves live in =Routines=.
214
215
215
216
Authoring errors raise. There is no =result= here because a malformed
216
217
prescription is a bug in a hand-written plan, not user input.
218
Added:
219
Added:
** Routines — the authored plans
220
Added:
221
Added:
HD1 supplies one routine, and =Routines.ideal= reproduces it exactly: three
222
Added:
days, each superset a pre-exhaust pair, each of the book's "or" lists a
223
Added:
permitted substitute, 6–10 reps throughout. It is doctrine content, so it stays
224
Added:
in the core, but it is data rather than a type, so it is kept apart from
225
Added:
=Prescription=. The application's =Catalog= maps a stable id to it.
217
226
218
227
** Evidence — the record
219
228
bin/seed.ml
@@ -15,7 +15,7 @@
15
15
Repo.history repo trainee_id >|= List.length >>= fun count ->
16
16
if count > 0 then Lwt.return_unit
17
17
else
18
Removed:
let routine = Prescription.Routine.ideal in
18
Added:
let routine = Routines.ideal in
19
19
let workouts = Prescription.Routine.workouts routine in
20
20
let clearance = Recovery.override Recovery.Ready in
21
21
let get id =
lib/app/catalog.ml
@@ -1,4 +1,4 @@
1
Removed:
let routines = [ (Repository.routine_id "ideal", Prescription.Routine.ideal) ]
1
Added:
let routines = [ (Repository.routine_id "ideal", Routines.ideal) ]
2
2
let find id = List.assoc_opt id routines
3
3
4
4
let find_by_name name =
lib/core/dune
@@ -1,8 +1,9 @@
1
1
; Two namespaces mirror the plan/record split: Prescription states what to do,
2
Removed:
; Evidence records what was done, and Progress is the only judge.
2
Added:
; Evidence records what was done, and Progress is the only judge. Routines
3
Added:
; holds the authored plans the book supplies.
3
4
4
5
(library
5
6
(name hito_core)
6
7
(public_name hito.core)
7
Removed:
(modules exercise recovery prescription evidence progress)
8
Added:
(modules exercise recovery prescription routines evidence progress)
8
9
(wrapped false))
lib/core/prescription.ml
@@ -177,165 +177,4 @@
177
177
match List.rev t.workouts with
178
178
| last :: _ when last == performed -> cycle_rest
179
179
| _ -> training_interval
180
Removed:
181
Removed:
(* {1 The Ideal Routine} *)
182
Removed:
183
Removed:
open Exercise
184
Removed:
185
Removed:
let exercise key =
186
Removed:
match Exercise.find key with
187
Removed:
| Some exercise -> exercise
188
Removed:
| None -> invalid_arg "Ideal Routine references an absent catalog key"
189
Removed:
190
Removed:
let dumbbell_flyes =
191
Removed:
exercise { equipment = Dumbbell; movement = Fly; variation = Standard }
192
Removed:
193
Removed:
let cable_crossovers =
194
Removed:
exercise { equipment = Cable; movement = Crossover; variation = Standard }
195
Removed:
196
Removed:
let pec_deck =
197
Removed:
exercise { equipment = Machine; movement = Pec_deck; variation = Standard }
198
Removed:
199
Removed:
let incline_presses =
200
Removed:
exercise { equipment = Unspecified; movement = Press; variation = Incline }
201
Removed:
202
Removed:
let laterals =
203
Removed:
exercise
204
Removed:
{
205
Removed:
equipment = Unspecified;
206
Removed:
movement = Lateral_raise;
207
Removed:
variation = Standard;
208
Removed:
}
209
Removed:
210
Removed:
let bent_over_laterals =
211
Removed:
exercise
212
Removed:
{ equipment = Dumbbell; movement = Lateral_raise; variation = Bent_over }
213
Removed:
214
Removed:
let reverse_pec_deck =
215
Removed:
exercise { equipment = Machine; movement = Pec_deck; variation = Rear_delt }
216
Removed:
217
Removed:
let lying_french_presses =
218
Removed:
exercise
219
Removed:
{ equipment = Unspecified; movement = French_press; variation = Lying }
220
Removed:
221
Removed:
let pressdowns =
222
Removed:
exercise { equipment = Cable; movement = Pressdown; variation = Standard }
223
Removed:
224
Removed:
let triceps_machine =
225
Removed:
exercise { equipment = Machine; movement = Press; variation = Triceps }
226
Removed:
227
Removed:
let dips =
228
Removed:
exercise { equipment = Bodyweight; movement = Dip; variation = Standard }
229
Removed:
230
Removed:
let pullovers =
231
Removed:
exercise
232
Removed:
{ equipment = Unspecified; movement = Pullover; variation = Standard }
233
Removed:
234
Removed:
let straight_arm_pulldowns =
235
Removed:
exercise
236
Removed:
{ equipment = Cable; movement = Pulldown; variation = Straight_arm }
237
Removed:
238
Removed:
let close_grip_pulldowns =
239
Removed:
exercise
240
Removed:
{
241
Removed:
equipment = Cable;
242
Removed:
movement = Pulldown;
243
Removed:
variation = Close_grip_palms_up;
244
Removed:
}
245
Removed:
246
Removed:
let bent_over_rows =
247
Removed:
exercise { equipment = Barbell; movement = Row; variation = Bent_over }
248
Removed:
249
Removed:
let shrugs =
250
Removed:
exercise { equipment = Unspecified; movement = Shrug; variation = Standard }
251
Removed:
252
Removed:
let hyperextensions =
253
Removed:
exercise
254
Removed:
{
255
Removed:
equipment = Bodyweight;
256
Removed:
movement = Hyperextension;
257
Removed:
variation = Standard;
258
Removed:
}
259
Removed:
260
Removed:
let deadlifts =
261
Removed:
exercise { equipment = Barbell; movement = Deadlift; variation = Standard }
262
Removed:
263
Removed:
let curls =
264
Removed:
exercise { equipment = Barbell; movement = Curl; variation = Standard }
265
Removed:
266
Removed:
let preacher_curls =
267
Removed:
exercise { equipment = Barbell; movement = Curl; variation = Preacher }
268
Removed:
269
Removed:
let leg_extensions =
270
Removed:
exercise
271
Removed:
{ equipment = Machine; movement = Leg_extension; variation = Standard }
272
Removed:
273
Removed:
let leg_presses =
274
Removed:
exercise { equipment = Machine; movement = Leg_press; variation = Standard }
275
Removed:
276
Removed:
let squats =
277
Removed:
exercise { equipment = Barbell; movement = Squat; variation = Standard }
278
Removed:
279
Removed:
let leg_curls =
280
Removed:
exercise { equipment = Machine; movement = Leg_curl; variation = Standard }
281
Removed:
282
Removed:
let calf_raises =
283
Removed:
exercise
284
Removed:
{ equipment = Unspecified; movement = Calf_raise; variation = Standard }
285
Removed:
286
Removed:
let sit_ups =
287
Removed:
exercise { equipment = Bodyweight; movement = Sit_up; variation = Standard }
288
Removed:
289
Removed:
(* HD1's guideline window for every listed exercise. *)
290
Removed:
let six_to_ten = Rep_range.make ~min:6 ~max:10
291
Removed:
292
Removed:
let prescribe ?(substitutes = []) delivery =
293
Removed:
Stimulus.make ~delivery ~rep_range:six_to_ten
294
Removed:
~allowed_substitutes:substitutes
295
Removed:
296
Removed:
let single ?substitutes exercise =
297
Removed:
prescribe ?substitutes (Stimulus.Single exercise)
298
Removed:
299
Removed:
let pre_exhaust ?substitutes ~isolation ~compound () =
300
Removed:
prescribe ?substitutes (Stimulus.Pre_exhaust { isolation; compound })
301
Removed:
302
Removed:
let day ~name stimuli = Workout.make ~name ~stimuli
303
Removed:
304
Removed:
let ideal_day_one =
305
Removed:
day ~name:"Day 1"
306
Removed:
[
307
Removed:
pre_exhaust
308
Removed:
~substitutes:[ cable_crossovers; pec_deck ]
309
Removed:
~isolation:dumbbell_flyes ~compound:incline_presses ();
310
Removed:
single laterals;
311
Removed:
single ~substitutes:[ reverse_pec_deck ] bent_over_laterals;
312
Removed:
pre_exhaust
313
Removed:
~substitutes:[ pressdowns; triceps_machine ]
314
Removed:
~isolation:lying_french_presses ~compound:dips ();
315
Removed:
]
316
Removed:
317
Removed:
let ideal_day_two =
318
Removed:
day ~name:"Day 2"
319
Removed:
[
320
Removed:
pre_exhaust ~substitutes:[ straight_arm_pulldowns ] ~isolation:pullovers
321
Removed:
~compound:close_grip_pulldowns ();
322
Removed:
single bent_over_rows;
323
Removed:
single shrugs;
324
Removed:
single ~substitutes:[ deadlifts ] hyperextensions;
325
Removed:
single ~substitutes:[ preacher_curls ] curls;
326
Removed:
]
327
Removed:
328
Removed:
let ideal_day_three =
329
Removed:
day ~name:"Day 3"
330
Removed:
[
331
Removed:
pre_exhaust ~substitutes:[ squats ] ~isolation:leg_extensions
332
Removed:
~compound:leg_presses ();
333
Removed:
single leg_curls;
334
Removed:
single calf_raises;
335
Removed:
single sit_ups;
336
Removed:
]
337
Removed:
338
Removed:
let[@warning "-32"] ideal =
339
Removed:
make ~name:"Ideal Routine"
340
Removed:
~workouts:[ ideal_day_one; ideal_day_two; ideal_day_three ]
341
180
end
lib/core/prescription.mli
@@ -109,7 +109,4 @@
109
109
(** 72h after the final workout. *)
110
110
111
111
val recovery_after : t -> Workout.t -> Recovery.duration
112
Removed:
113
Removed:
val ideal : t
114
Removed:
(** The supplied Heavy Duty I routine. *)
115
112
end
lib/core/routines.ml
@@ -0,0 +1,150 @@
1
Added:
(* Authored routines. HD1 supplies one, the Ideal Routine, reproduced here
2
Added:
exactly: its supersets as pre-exhaust pairs, its own "or" lists as permitted
3
Added:
substitutes, the 6-10 band on every exercise. This is doctrine content, kept
4
Added:
apart from the plan types in [Prescription] that it is built from. *)
5
Added:
6
Added:
open Prescription
7
Added:
open Exercise
8
Added:
9
Added:
let exercise key =
10
Added:
match Exercise.find key with
11
Added:
| Some exercise -> exercise
12
Added:
| None -> invalid_arg "Ideal Routine references an absent catalog key"
13
Added:
14
Added:
let dumbbell_flyes =
15
Added:
exercise { equipment = Dumbbell; movement = Fly; variation = Standard }
16
Added:
17
Added:
let cable_crossovers =
18
Added:
exercise { equipment = Cable; movement = Crossover; variation = Standard }
19
Added:
20
Added:
let pec_deck =
21
Added:
exercise { equipment = Machine; movement = Pec_deck; variation = Standard }
22
Added:
23
Added:
let incline_presses =
24
Added:
exercise { equipment = Unspecified; movement = Press; variation = Incline }
25
Added:
26
Added:
let laterals =
27
Added:
exercise
28
Added:
{ equipment = Unspecified; movement = Lateral_raise; variation = Standard }
29
Added:
30
Added:
let bent_over_laterals =
31
Added:
exercise
32
Added:
{ equipment = Dumbbell; movement = Lateral_raise; variation = Bent_over }
33
Added:
34
Added:
let reverse_pec_deck =
35
Added:
exercise { equipment = Machine; movement = Pec_deck; variation = Rear_delt }
36
Added:
37
Added:
let lying_french_presses =
38
Added:
exercise
39
Added:
{ equipment = Unspecified; movement = French_press; variation = Lying }
40
Added:
41
Added:
let pressdowns =
42
Added:
exercise { equipment = Cable; movement = Pressdown; variation = Standard }
43
Added:
44
Added:
let triceps_machine =
45
Added:
exercise { equipment = Machine; movement = Press; variation = Triceps }
46
Added:
47
Added:
let dips =
48
Added:
exercise { equipment = Bodyweight; movement = Dip; variation = Standard }
49
Added:
50
Added:
let pullovers =
51
Added:
exercise
52
Added:
{ equipment = Unspecified; movement = Pullover; variation = Standard }
53
Added:
54
Added:
let straight_arm_pulldowns =
55
Added:
exercise { equipment = Cable; movement = Pulldown; variation = Straight_arm }
56
Added:
57
Added:
let close_grip_pulldowns =
58
Added:
exercise
59
Added:
{ equipment = Cable; movement = Pulldown; variation = Close_grip_palms_up }
60
Added:
61
Added:
let bent_over_rows =
62
Added:
exercise { equipment = Barbell; movement = Row; variation = Bent_over }
63
Added:
64
Added:
let shrugs =
65
Added:
exercise { equipment = Unspecified; movement = Shrug; variation = Standard }
66
Added:
67
Added:
let hyperextensions =
68
Added:
exercise
69
Added:
{ equipment = Bodyweight; movement = Hyperextension; variation = Standard }
70
Added:
71
Added:
let deadlifts =
72
Added:
exercise { equipment = Barbell; movement = Deadlift; variation = Standard }
73
Added:
74
Added:
let curls =
75
Added:
exercise { equipment = Barbell; movement = Curl; variation = Standard }
76
Added:
77
Added:
let preacher_curls =
78
Added:
exercise { equipment = Barbell; movement = Curl; variation = Preacher }
79
Added:
80
Added:
let leg_extensions =
81
Added:
exercise
82
Added:
{ equipment = Machine; movement = Leg_extension; variation = Standard }
83
Added:
84
Added:
let leg_presses =
85
Added:
exercise { equipment = Machine; movement = Leg_press; variation = Standard }
86
Added:
87
Added:
let squats =
88
Added:
exercise { equipment = Barbell; movement = Squat; variation = Standard }
89
Added:
90
Added:
let leg_curls =
91
Added:
exercise { equipment = Machine; movement = Leg_curl; variation = Standard }
92
Added:
93
Added:
let calf_raises =
94
Added:
exercise
95
Added:
{ equipment = Unspecified; movement = Calf_raise; variation = Standard }
96
Added:
97
Added:
let sit_ups =
98
Added:
exercise { equipment = Bodyweight; movement = Sit_up; variation = Standard }
99
Added:
100
Added:
(* HD1's guideline window for every listed exercise. *)
101
Added:
let six_to_ten = Rep_range.make ~min:6 ~max:10
102
Added:
103
Added:
let prescribe ?(substitutes = []) delivery =
104
Added:
Stimulus.make ~delivery ~rep_range:six_to_ten ~allowed_substitutes:substitutes
105
Added:
106
Added:
let single ?substitutes exercise =
107
Added:
prescribe ?substitutes (Stimulus.Single exercise)
108
Added:
109
Added:
let pre_exhaust ?substitutes ~isolation ~compound () =
110
Added:
prescribe ?substitutes (Stimulus.Pre_exhaust { isolation; compound })
111
Added:
112
Added:
let day ~name stimuli = Workout.make ~name ~stimuli
113
Added:
114
Added:
let ideal_day_one =
115
Added:
day ~name:"Day 1"
116
Added:
[
117
Added:
pre_exhaust
118
Added:
~substitutes:[ cable_crossovers; pec_deck ]
119
Added:
~isolation:dumbbell_flyes ~compound:incline_presses ();
120
Added:
single laterals;
121
Added:
single ~substitutes:[ reverse_pec_deck ] bent_over_laterals;
122
Added:
pre_exhaust
123
Added:
~substitutes:[ pressdowns; triceps_machine ]
124
Added:
~isolation:lying_french_presses ~compound:dips ();
125
Added:
]
126
Added:
127
Added:
let ideal_day_two =
128
Added:
day ~name:"Day 2"
129
Added:
[
130
Added:
pre_exhaust ~substitutes:[ straight_arm_pulldowns ] ~isolation:pullovers
131
Added:
~compound:close_grip_pulldowns ();
132
Added:
single bent_over_rows;
133
Added:
single shrugs;
134
Added:
single ~substitutes:[ deadlifts ] hyperextensions;
135
Added:
single ~substitutes:[ preacher_curls ] curls;
136
Added:
]
137
Added:
138
Added:
let ideal_day_three =
139
Added:
day ~name:"Day 3"
140
Added:
[
141
Added:
pre_exhaust ~substitutes:[ squats ] ~isolation:leg_extensions
142
Added:
~compound:leg_presses ();
143
Added:
single leg_curls;
144
Added:
single calf_raises;
145
Added:
single sit_ups;
146
Added:
]
147
Added:
148
Added:
let ideal =
149
Added:
Routine.make ~name:"Ideal Routine"
150
Added:
~workouts:[ ideal_day_one; ideal_day_two; ideal_day_three ]
lib/core/routines.mli
@@ -0,0 +1,6 @@
1
Added:
(** Authored routines. HD1 supplies one. *)
2
Added:
3
Added:
val ideal : Prescription.Routine.t
4
Added:
(** HD1's Ideal Routine, reproduced from "The Routine": three days, each
5
Added:
superset a pre-exhaust pair, each "or" list a permitted substitute, 6-10
6
Added:
reps throughout. *)
test/test_codec.ml
@@ -24,9 +24,7 @@
24
24
(* A performed Day 1, including an extension, to exercise every branch. *)
25
25
let sample_workout ?(finished = true) () =
26
26
let clearance = Option.get (Recovery.clear Recovery.Ready) in
27
Removed:
let day_one =
28
Removed:
List.hd (Prescription.Routine.workouts Prescription.Routine.ideal)
29
Removed:
in
27
Added:
let day_one = List.hd (Prescription.Routine.workouts Routines.ideal) in
30
28
let w = Workout.start day_one ~clearance ~started_at:(at 100) in
31
29
let w =
32
30
Workout.add_stimulus w
@@ -118,9 +116,7 @@
118
116
{ rested = Recovery.hours 10; recommended = Recovery.hours 48 }
119
117
in
120
118
let clearance = Recovery.override readiness in
121
Removed:
let day_one =
122
Removed:
List.hd (Prescription.Routine.workouts Prescription.Routine.ideal)
123
Removed:
in
119
Added:
let day_one = List.hd (Prescription.Routine.workouts Routines.ideal) in
124
120
let w = Workout.start day_one ~clearance ~started_at:(at 100) in
125
121
let d = roundtrip w in
126
122
match Recovery.basis (Workout.clearance d) with
@@ -165,9 +161,7 @@
165
161
fun () ->
166
162
(* Fill slot 1, then correct it: replace, not append. The encoding
167
163
carries the slot, so replay reproduces one filled slot, not two. *)
168
Removed:
let day_one =
169
Removed:
List.hd (Prescription.Routine.workouts Prescription.Routine.ideal)
170
Removed:
in
164
Added:
let day_one = List.hd (Prescription.Routine.workouts Routines.ideal) in
171
165
let clearance = Option.get (Recovery.clear Recovery.Ready) in
172
166
let w = Workout.start day_one ~clearance ~started_at:(at 100) in
173
167
let w = Workout.add_stimulus w (single "laterals" 12. 8) in
test/test_evidence.ml
@@ -114,7 +114,7 @@
114
114
115
115
(* {1 One performed workout} *)
116
116
117
Removed:
let routine = Prescription.Routine.ideal
117
Added:
let routine = Routines.ideal
118
118
let prescribed n = List.nth (Prescription.Routine.workouts routine) n
119
119
let day_one = prescribed 0
120
120
let cleared = Option.get (Recovery.clear Recovery.Ready)
test/test_progression.ml
@@ -200,9 +200,7 @@
200
200
201
201
(* Performed workouts, for the diagnostics. *)
202
202
let cleared = Option.get (Recovery.clear Recovery.Ready)
203
Removed:
204
Removed:
let prescribed =
205
Removed:
List.hd (Prescription.Routine.workouts Prescription.Routine.ideal)
203
Added:
let prescribed = List.hd (Prescription.Routine.workouts Routines.ideal)
206
204
207
205
let performed ~clearance ~stimuli =
208
206
List.fold_left