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.

Commit
30569f45df72feee02246a5e8330501d1fa8a722
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
ARCHITECTURE.org
index 70ae3011..7ddefeab 100644..100644
@@ -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
index 72070994..b951d771 100644..100644
@@ -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
index e741cc7d..47db5655 100644..100644
@@ -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
index 8d852a3e..e0a0f578 100644..100644
@@ -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
index 95a65ab9..4f9c754e 100644..100644
@@ -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
index 819e53e7..222e5b22 100644..100644
@@ -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
index 00000000..c6c960c2 000000..100644
@@ -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
index 00000000..1c470b59 000000..100644
@@ -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
index 9aaf7a1f..44804bfd 100644..100644
@@ -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
index 797628d0..34075a97 100644..100644
@@ -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
index 7ad113bf..4e174506 100644..100644
@@ -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