refactor collapse to one library and dissolve the set/group ontology

Begin the HD1-sourced rebuild. The four core sub-libraries are merged back into a single hito.core: enforced boundaries cost churn while the ontology is still moving, so re-split only once it settles. Delete the modules the new ontology dissolves: - Set — the working set becomes the Stimulus, one drive to failure. - Set_group / Set_group_prescription — "set group" is not an entity. HD1 has no grouping concept; pre-exhaustion is a property of how a stimulus is delivered, not a container holding sets. Their suites go with them. The library's (modules ...) field is scoped to what the rebuild has landed and grows task by task, so the build stays green throughout. Same for the test stanza. lib/app and lib/web are parked (dune.disabled): their stubs are written against the old ontology and will be reinstated once core is stable. BREAKING CHANGE: hito.core.vocabulary, hito.core.assessment, hito.core.prescription and hito.core.logbook no longer exist; depend on hito.core instead.

Commit
c606e3ee9d5f1c95b3371dfe9330dbe272ce6fc1
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
bin/dune
index f619b43d..a6c87c01 100644..100644
@@ -1,4 +1,4 @@
1 1 (executable
2 2 (public_name hito)
3 3 (name main)
4 Removed: (libraries hito.core.vocabulary))
4 Added: (libraries hito.core))
lib/app/dune
index 3533be22..00000000 100644..000000
@@ -1,8 +0,0 @@
1 Removed: (library
2 Removed: (name hito_app)
3 Removed: (public_name hito.app)
4 Removed: (libraries
5 Removed: hito.core.vocabulary
6 Removed: hito.core.assessment
7 Removed: hito.core.prescription
8 Removed: hito.core.logbook))
lib/app/dune.disabled
index 00000000..84d5f5a0 000000..100644
@@ -0,0 +1,4 @@
1 Added: (library
2 Added: (name hito_app)
3 Added: (public_name hito.app)
4 Added: (libraries hito.core))
lib/core/assessment/dune
index dc13fafd..00000000 100644..000000
@@ -1,5 +0,0 @@
1 Removed: (library
2 Removed: (name hito_assessment)
3 Removed: (public_name hito.core.assessment)
4 Removed: (libraries hito.core.vocabulary)
5 Removed: (wrapped false))
lib/core/assessment/progression.ml
index 059799f5..00000000 100644..000000
@@ -1,30 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (progression.mli) is the design surface. *)
2 Removed:
3 Removed: type t = Progressing | Stalled
4 Removed:
5 Removed: let equal _ _ = failwith "TODO"
6 Removed: let pp _ _ = failwith "TODO"
7 Removed:
8 Removed: type sample = Set.Working.performance
9 Removed: type error = Insufficient_data
10 Removed:
11 Removed: let evaluate ~history:_ = failwith "TODO"
12 Removed: let beats ~previous:_ ~current:_ = failwith "TODO"
13 Removed:
14 Removed: type band = Below_range | In_range | Above_range
15 Removed:
16 Removed: let classify ~target_reps:_ _ = failwith "TODO"
17 Removed:
18 Removed: type target =
19 Removed: | Add_reps of { load : Units.Weight.t; min_reps : Units.Reps.t }
20 Removed: | Add_load of { min_load : Units.Weight.t; reps : Units.Reps.t }
21 Removed:
22 Removed: type guidance = { band : band; next : target } [@@warning "-69"]
23 Removed:
24 Removed: let guide ~target_reps:_ _ = failwith "TODO"
25 Removed:
26 Removed: type 'a prescribed = { value : 'a; evidence : sample list; status : t }
27 Removed: [@@warning "-69"]
28 Removed:
29 Removed: let prescribe ~target_reps:_ ~evidence:_ = failwith "TODO"
30 Removed: let volume _ = failwith "TODO"
lib/core/assessment/progression.mli
index e46e82a7..00000000 100644..000000
@@ -1,56 +0,0 @@
1 Removed: (** Assessment: what the logged evidence says, and what it prescribes next.
2 Removed:
3 Removed: This is the only module that relates performance to plan. Prescriptions do
4 Removed: not judge, and the logbook does not interpret. *)
5 Removed:
6 Removed: type t = Progressing | Stalled
7 Removed:
8 Removed: val equal : t -> t -> bool
9 Removed: val pp : Format.formatter -> t -> unit
10 Removed:
11 Removed: type sample = Set.Working.performance
12 Removed: (** One working set's performance — the unit of evidence. *)
13 Removed:
14 Removed: type error = Insufficient_data
15 Removed:
16 Removed: val evaluate : history:sample list -> (t, error) result
17 Removed: (** [history] oldest-first. *)
18 Removed:
19 Removed: val beats : previous:sample -> current:sample -> bool
20 Removed: (** Progressive overload: heavier, or equal load for more reps. *)
21 Removed:
22 Removed: (** {1 Judging a performance against a band} *)
23 Removed:
24 Removed: type band =
25 Removed: | Below_range (** Load too heavy. *)
26 Removed: | In_range
27 Removed: | Above_range (** Load too light. *)
28 Removed:
29 Removed: val classify : target_reps:Units.Rep_range.t -> sample -> band
30 Removed:
31 Removed: (** What to aim for next. *)
32 Removed: type target =
33 Removed: | Add_reps of { load : Units.Weight.t; min_reps : Units.Reps.t }
34 Removed: | Add_load of { min_load : Units.Weight.t; reps : Units.Reps.t }
35 Removed: (** Band exceeded: raise load, reset to the band's bottom. *)
36 Removed:
37 Removed: type guidance = { band : band; next : target }
38 Removed: (** How one performance landed, and what follows from it. *)
39 Removed:
40 Removed: val guide : target_reps:Units.Rep_range.t -> sample -> guidance
41 Removed:
42 Removed: (** {1 Prescribing from evidence}
43 Removed:
44 Removed: Anything prescribed carries the evidence it came from and the status that
45 Removed: evidence showed. *)
46 Removed:
47 Removed: type 'a prescribed = { value : 'a; evidence : sample list; status : t }
48 Removed:
49 Removed: val prescribe :
50 Removed: target_reps:Units.Rep_range.t -> evidence:sample list -> target prescribed
51 Removed:
52 Removed: (** {1 Metrics} *)
53 Removed:
54 Removed: val volume : sample list -> float
55 Removed: (** Σ (load × reps), in kilogram-reps. A diagnostic, never a target: under Heavy
56 Removed: Duty rising volume is a warning, not an achievement. *)
lib/core/assessment/recovery.ml
index 101ca57b..00000000 100644..000000
@@ -1,20 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (recovery.mli) is the design surface. *)
2 Removed:
3 Removed: type timestamp = int
4 Removed:
5 Removed: let timestamp_of_unix_seconds _ = failwith "TODO"
6 Removed: let timestamp_to_unix_seconds _ = failwith "TODO"
7 Removed:
8 Removed: type duration = int
9 Removed:
10 Removed: let hours _ = failwith "TODO"
11 Removed: let days _ = failwith "TODO"
12 Removed: let duration_to_seconds _ = failwith "TODO"
13 Removed: let elapsed ~since:_ ~now:_ = failwith "TODO"
14 Removed:
15 Removed: type readiness =
16 Removed: | Ready
17 Removed: | Recovering of { rested : duration; recommended : duration }
18 Removed:
19 Removed: let evaluate_readiness ~elapsed:_ ~recommended:_ = failwith "TODO"
20 Removed: let is_ready _ = failwith "TODO"
lib/core/assessment/recovery.mli
index 023ae8b8..00000000 100644..000000
@@ -1,24 +0,0 @@
1 Removed: (** Recovery is read off the logbook, not managed here: the elapsed time between
2 Removed: two workouts, judged against a recommended window. There is no session,
3 Removed: override, or evidence-based prescription — {!Logbook} owns that context and
4 Removed: decides what to do with a {!readiness} reading. *)
5 Removed:
6 Removed: type timestamp = private int
7 Removed:
8 Removed: val timestamp_of_unix_seconds : int -> timestamp
9 Removed: val timestamp_to_unix_seconds : timestamp -> int
10 Removed:
11 Removed: type duration = private int
12 Removed:
13 Removed: val hours : int -> duration
14 Removed: val days : int -> duration
15 Removed: val duration_to_seconds : duration -> int
16 Removed: val elapsed : since:timestamp -> now:timestamp -> duration
17 Removed:
18 Removed: type readiness =
19 Removed: | Ready
20 Removed: | Recovering of { rested : duration; recommended : duration }
21 Removed: (** [rested] of [recommended] has passed. *)
22 Removed:
23 Removed: val evaluate_readiness : elapsed:duration -> recommended:duration -> readiness
24 Removed: val is_ready : readiness -> bool
lib/core/dune
index 00000000..cf99e042 000000..100644
@@ -0,0 +1,9 @@
1 Added: ; Modules are added here as the HD1 rebuild lands them, so the build stays
2 Added: ; green at every step. Target set: units muscle exercise prescription
3 Added: ; workout_prescription routine stimulus recovery entry logbook progression.
4 Added:
5 Added: (library
6 Added: (name hito_core)
7 Added: (public_name hito.core)
8 Added: (modules units exercise recovery prescription)
9 Added: (wrapped false))
lib/core/exercise.ml
index 00000000..3e5db58e 000000..100644
@@ -0,0 +1,17 @@
1 Added: (* Blank slate: stub only. Interface (exercise.mli) is the design surface. *)
2 Added:
3 Added: type id = string
4 Added: type t = unit
5 Added:
6 Added: let id _ = failwith "TODO"
7 Added: let name _ = failwith "TODO"
8 Added: let equal _ _ = failwith "TODO"
9 Added: let pp _ _ = failwith "TODO"
10 Added: let catalog = []
11 Added: let find _ = failwith "TODO"
12 Added:
13 Added: type error = Not_permitted of { original : id; candidate : id }
14 Added:
15 Added: let permitted_substitutes _ = failwith "TODO"
16 Added: let may_substitute ~original:_ ~candidate:_ = failwith "TODO"
17 Added: let substitute ~original:_ ~candidate:_ = failwith "TODO"
lib/core/exercise.mli
index 00000000..e3fc5206 000000..100644
@@ -0,0 +1,31 @@
1 Added: (** Curated exercise catalog with author-specified substitution whitelists.
2 Added:
3 Added: Users do not log arbitrary movements: every exercise comes from {!catalog}.
4 Added: Substitutions are limited to each exercise's author-specified whitelist. *)
5 Added:
6 Added: type t
7 Added: (** Abstract catalog exercise; only obtainable via {!catalog} / {!find}. *)
8 Added:
9 Added: type id = private string
10 Added:
11 Added: val id : t -> id
12 Added: val name : t -> string
13 Added: val equal : t -> t -> bool
14 Added: val pp : Format.formatter -> t -> unit
15 Added:
16 Added: (** {1 Catalog} *)
17 Added:
18 Added: val catalog : t list
19 Added: (** The complete curated catalog; the only source of {!t} values. *)
20 Added:
21 Added: val find : id -> t option
22 Added:
23 Added: (** {1 Substitutions} *)
24 Added:
25 Added: type error = Not_permitted of { original : id; candidate : id }
26 Added:
27 Added: val permitted_substitutes : t -> t list
28 Added: val may_substitute : original:t -> candidate:t -> bool
29 Added:
30 Added: val substitute : original:t -> candidate:t -> (t, error) result
31 Added: (** [Ok candidate] iff [candidate] is on [original]'s whitelist. *)
lib/core/logbook.ml
index 00000000..58d1c9b4 000000..100644
@@ -0,0 +1,32 @@
1 Added: (* Blank slate: stub only. Interface (logbook.mli) is the design surface. *)
2 Added:
3 Added: module Entry = struct
4 Added: type t = unit
5 Added: type error = Set_exercise_not_prescribed of Exercise.id | Already_finished
6 Added:
7 Added: let start _ ~started_at:_ = failwith "TODO"
8 Added: let add_group _ _ = failwith "TODO"
9 Added: let finish _ ~ended_at:_ = failwith "TODO"
10 Added: let is_finished _ = failwith "TODO"
11 Added: let prescription _ = failwith "TODO"
12 Added: let started_at _ = failwith "TODO"
13 Added: let ended_at _ = failwith "TODO"
14 Added: let duration _ = failwith "TODO"
15 Added: let groups _ = failwith "TODO"
16 Added: let working_sets _ = failwith "TODO"
17 Added: let performances _ = failwith "TODO"
18 Added: let guidance _ = failwith "TODO"
19 Added: let unperformed _ = failwith "TODO"
20 Added: let volume _ = failwith "TODO"
21 Added: let pp _ _ = failwith "TODO"
22 Added: end
23 Added:
24 Added: type t = unit
25 Added:
26 Added: let empty = ()
27 Added: let add _ _ = failwith "TODO"
28 Added: let entries _ = failwith "TODO"
29 Added: let evidence _ _ = failwith "TODO"
30 Added: let last_prescription _ = failwith "TODO"
31 Added: let readiness _ ~now:_ ~recommended:_ = failwith "TODO"
32 Added: let pp _ _ = failwith "TODO"
lib/core/logbook.mli
index 00000000..f25dbaec 000000..100644
@@ -0,0 +1,76 @@
1 Added: (** The training log: what was actually performed.
2 Added:
3 Added: Each entry wraps the {!Workout_prescription.t} it was performed against — a
4 Added: log without its prescription is meaningless — and its start/end timestamps.
5 Added: The logbook is also the source of the evidence future prescriptions are
6 Added: calculated from, and of the elapsed time {!Recovery} judges. *)
7 Added:
8 Added: (** One performed workout. *)
9 Added: module Entry : sig
10 Added: type t
11 Added:
12 Added: type error =
13 Added: | Set_exercise_not_prescribed of Exercise.id
14 Added: (** A set's exercise matches no slot in the prescription. *)
15 Added: | Already_finished
16 Added:
17 Added: val start : Workout_prescription.t -> started_at:Recovery.timestamp -> t
18 Added:
19 Added: val add_group : t -> Set_group.t -> (t, error) result
20 Added: (** Append a performed group. Each working set's exercise must match a slot in
21 Added: the prescription; a group may span exercises, so each set is checked. *)
22 Added:
23 Added: val finish : t -> ended_at:Recovery.timestamp -> t
24 Added: val is_finished : t -> bool
25 Added: val prescription : t -> Workout_prescription.t
26 Added: val started_at : t -> Recovery.timestamp
27 Added: val ended_at : t -> Recovery.timestamp option
28 Added: val duration : t -> Recovery.duration option
29 Added: val groups : t -> Set_group.t list
30 Added:
31 Added: val working_sets : t -> Set.Working.t list
32 Added: (** Every working set performed, in order. *)
33 Added:
34 Added: val performances : t -> (Exercise.id * Progression.sample) list
35 Added: (** Each working set's performance, keyed by its own exercise. *)
36 Added:
37 Added: val guidance : t -> (Exercise.id * Progression.guidance) list
38 Added: (** What was performed, judged against the prescription it was logged against.
39 Added: *)
40 Added:
41 Added: val unperformed : t -> Exercise.t list
42 Added: (** Prescribed exercises with no logged working set. *)
43 Added:
44 Added: val volume : t -> float
45 Added: (** Σ (load × reps). Diagnostic, not a target. *)
46 Added:
47 Added: val pp : Format.formatter -> t -> unit
48 Added: end
49 Added:
50 Added: type t
51 Added: (** A chronological log of finished entries. *)
52 Added:
53 Added: val empty : t
54 Added: val add : t -> Entry.t -> t
55 Added:
56 Added: val entries : t -> Entry.t list
57 Added: (** Most recent first. *)
58 Added:
59 Added: val evidence : t -> Exercise.id -> Progression.sample list
60 Added: (** An exercise's past performances, oldest-first — the input to
61 Added: {!Progression.prescribe}. *)
62 Added:
63 Added: val last_prescription : t -> Workout_prescription.t option
64 Added: (** The prescription of the most recent entry; feeds {!Routine.workout_after}.
65 Added: *)
66 Added:
67 Added: val readiness :
68 Added: t ->
69 Added: now:Recovery.timestamp ->
70 Added: recommended:Recovery.duration ->
71 Added: Recovery.readiness
72 Added: (** Elapsed time since the most recent finished entry, judged against
73 Added: [recommended]. Informational: nothing here prevents starting the next
74 Added: workout regardless. *)
75 Added:
76 Added: val pp : Format.formatter -> t -> unit
lib/core/logbook/dune
index fc2dc8a9..00000000 100644..000000
@@ -1,5 +0,0 @@
1 Removed: (library
2 Removed: (name hito_logbook)
3 Removed: (public_name hito.core.logbook)
4 Removed: (libraries hito.core.vocabulary hito.core.assessment hito.core.prescription)
5 Removed: (wrapped false))
lib/core/logbook/logbook.ml
index 58d1c9b4..00000000 100644..000000
@@ -1,32 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (logbook.mli) is the design surface. *)
2 Removed:
3 Removed: module Entry = struct
4 Removed: type t = unit
5 Removed: type error = Set_exercise_not_prescribed of Exercise.id | Already_finished
6 Removed:
7 Removed: let start _ ~started_at:_ = failwith "TODO"
8 Removed: let add_group _ _ = failwith "TODO"
9 Removed: let finish _ ~ended_at:_ = failwith "TODO"
10 Removed: let is_finished _ = failwith "TODO"
11 Removed: let prescription _ = failwith "TODO"
12 Removed: let started_at _ = failwith "TODO"
13 Removed: let ended_at _ = failwith "TODO"
14 Removed: let duration _ = failwith "TODO"
15 Removed: let groups _ = failwith "TODO"
16 Removed: let working_sets _ = failwith "TODO"
17 Removed: let performances _ = failwith "TODO"
18 Removed: let guidance _ = failwith "TODO"
19 Removed: let unperformed _ = failwith "TODO"
20 Removed: let volume _ = failwith "TODO"
21 Removed: let pp _ _ = failwith "TODO"
22 Removed: end
23 Removed:
24 Removed: type t = unit
25 Removed:
26 Removed: let empty = ()
27 Removed: let add _ _ = failwith "TODO"
28 Removed: let entries _ = failwith "TODO"
29 Removed: let evidence _ _ = failwith "TODO"
30 Removed: let last_prescription _ = failwith "TODO"
31 Removed: let readiness _ ~now:_ ~recommended:_ = failwith "TODO"
32 Removed: let pp _ _ = failwith "TODO"
lib/core/logbook/logbook.mli
index f25dbaec..00000000 100644..000000
@@ -1,76 +0,0 @@
1 Removed: (** The training log: what was actually performed.
2 Removed:
3 Removed: Each entry wraps the {!Workout_prescription.t} it was performed against — a
4 Removed: log without its prescription is meaningless — and its start/end timestamps.
5 Removed: The logbook is also the source of the evidence future prescriptions are
6 Removed: calculated from, and of the elapsed time {!Recovery} judges. *)
7 Removed:
8 Removed: (** One performed workout. *)
9 Removed: module Entry : sig
10 Removed: type t
11 Removed:
12 Removed: type error =
13 Removed: | Set_exercise_not_prescribed of Exercise.id
14 Removed: (** A set's exercise matches no slot in the prescription. *)
15 Removed: | Already_finished
16 Removed:
17 Removed: val start : Workout_prescription.t -> started_at:Recovery.timestamp -> t
18 Removed:
19 Removed: val add_group : t -> Set_group.t -> (t, error) result
20 Removed: (** Append a performed group. Each working set's exercise must match a slot in
21 Removed: the prescription; a group may span exercises, so each set is checked. *)
22 Removed:
23 Removed: val finish : t -> ended_at:Recovery.timestamp -> t
24 Removed: val is_finished : t -> bool
25 Removed: val prescription : t -> Workout_prescription.t
26 Removed: val started_at : t -> Recovery.timestamp
27 Removed: val ended_at : t -> Recovery.timestamp option
28 Removed: val duration : t -> Recovery.duration option
29 Removed: val groups : t -> Set_group.t list
30 Removed:
31 Removed: val working_sets : t -> Set.Working.t list
32 Removed: (** Every working set performed, in order. *)
33 Removed:
34 Removed: val performances : t -> (Exercise.id * Progression.sample) list
35 Removed: (** Each working set's performance, keyed by its own exercise. *)
36 Removed:
37 Removed: val guidance : t -> (Exercise.id * Progression.guidance) list
38 Removed: (** What was performed, judged against the prescription it was logged against.
39 Removed: *)
40 Removed:
41 Removed: val unperformed : t -> Exercise.t list
42 Removed: (** Prescribed exercises with no logged working set. *)
43 Removed:
44 Removed: val volume : t -> float
45 Removed: (** Σ (load × reps). Diagnostic, not a target. *)
46 Removed:
47 Removed: val pp : Format.formatter -> t -> unit
48 Removed: end
49 Removed:
50 Removed: type t
51 Removed: (** A chronological log of finished entries. *)
52 Removed:
53 Removed: val empty : t
54 Removed: val add : t -> Entry.t -> t
55 Removed:
56 Removed: val entries : t -> Entry.t list
57 Removed: (** Most recent first. *)
58 Removed:
59 Removed: val evidence : t -> Exercise.id -> Progression.sample list
60 Removed: (** An exercise's past performances, oldest-first — the input to
61 Removed: {!Progression.prescribe}. *)
62 Removed:
63 Removed: val last_prescription : t -> Workout_prescription.t option
64 Removed: (** The prescription of the most recent entry; feeds {!Routine.workout_after}.
65 Removed: *)
66 Removed:
67 Removed: val readiness :
68 Removed: t ->
69 Removed: now:Recovery.timestamp ->
70 Removed: recommended:Recovery.duration ->
71 Removed: Recovery.readiness
72 Removed: (** Elapsed time since the most recent finished entry, judged against
73 Removed: [recommended]. Informational: nothing here prevents starting the next
74 Removed: workout regardless. *)
75 Removed:
76 Removed: val pp : Format.formatter -> t -> unit
lib/core/logbook/set_group.ml
index 1673b1c6..00000000 100644..000000
@@ -1,25 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (set_group.mli) is the design surface. *)
2 Removed:
3 Removed: type t = unit
4 Removed:
5 Removed: let straight ?warm_ups:_ ~working:_ () = failwith "TODO"
6 Removed: let superset ?warm_ups:_ ~first:_ ~second:_ () = failwith "TODO"
7 Removed: let pre_exhaust ?warm_ups:_ ~isolation:_ ~compound:_ () = failwith "TODO"
8 Removed:
9 Removed: type view =
10 Removed: | Straight of { warm_ups : Set.Warm_up.t list; working : Set.Working.t }
11 Removed: | Superset of {
12 Removed: warm_ups : Set.Warm_up.t list;
13 Removed: first : Set.Working.t;
14 Removed: second : Set.Working.t;
15 Removed: }
16 Removed: | Pre_exhaust of {
17 Removed: warm_ups : Set.Warm_up.t list;
18 Removed: isolation : Set.Working.t;
19 Removed: compound : Set.Working.t;
20 Removed: }
21 Removed:
22 Removed: let view _ = failwith "TODO"
23 Removed: let working_sets _ = failwith "TODO"
24 Removed: let warm_ups _ = failwith "TODO"
25 Removed: let pp _ _ = failwith "TODO"
lib/core/logbook/set_group.mli
index 7613d119..00000000 100644..000000
@@ -1,52 +0,0 @@
1 Removed: (** How working sets are grouped and sequenced within a workout.
2 Removed:
3 Removed: A group may span exercises (superset, pre-exhaust), so the exercise belongs
4 Removed: to each individual set, never to the group. Heavy Duty prescribes exactly
5 Removed: one working set per exercise and this model admits no exceptions — extra
6 Removed: volume is unrepresentable. *)
7 Removed:
8 Removed: type t
9 Removed:
10 Removed: val straight :
11 Removed: ?warm_ups:Set.Warm_up.t list -> working:Set.Working.t -> unit -> t
12 Removed: (** One exercise: the working set, optionally preceded by a warm-up ramp. *)
13 Removed:
14 Removed: val superset :
15 Removed: ?warm_ups:Set.Warm_up.t list ->
16 Removed: first:Set.Working.t ->
17 Removed: second:Set.Working.t ->
18 Removed: unit ->
19 Removed: t
20 Removed: (** Two exercises' working sets, back-to-back. *)
21 Removed:
22 Removed: val pre_exhaust :
23 Removed: ?warm_ups:Set.Warm_up.t list ->
24 Removed: isolation:Set.Working.t ->
25 Removed: compound:Set.Working.t ->
26 Removed: unit ->
27 Removed: t
28 Removed: (** Isolation immediately followed by a compound — the signature HD technique.
29 Removed: *)
30 Removed:
31 Removed: (** {1 Inspection} *)
32 Removed:
33 Removed: type view =
34 Removed: | Straight of { warm_ups : Set.Warm_up.t list; working : Set.Working.t }
35 Removed: | Superset of {
36 Removed: warm_ups : Set.Warm_up.t list;
37 Removed: first : Set.Working.t;
38 Removed: second : Set.Working.t;
39 Removed: }
40 Removed: | Pre_exhaust of {
41 Removed: warm_ups : Set.Warm_up.t list;
42 Removed: isolation : Set.Working.t;
43 Removed: compound : Set.Working.t;
44 Removed: }
45 Removed:
46 Removed: val view : t -> view
47 Removed:
48 Removed: val working_sets : t -> Set.Working.t list
49 Removed: (** Every working set, in order. Each carries its own exercise. *)
50 Removed:
51 Removed: val warm_ups : t -> Set.Warm_up.t list
52 Removed: val pp : Format.formatter -> t -> unit
lib/core/prescription.ml
index 00000000..2a243fc4 000000..100644
@@ -0,0 +1,9 @@
1 Added: (* Blank slate: stub only. Interface (prescription.mli) is the design surface. *)
2 Added:
3 Added: type t = unit
4 Added:
5 Added: let make ~exercise:_ ~target_reps:_ ~allowed_substitutes:_ = failwith "TODO"
6 Added: let exercise _ = failwith "TODO"
7 Added: let target_reps _ = failwith "TODO"
8 Added: let allowed_substitutes _ = failwith "TODO"
9 Added: let pp _ _ = failwith "TODO"
lib/core/prescription.mli
index 00000000..b90dbf8f 000000..100644
@@ -0,0 +1,21 @@
1 Added: (** One exercise's plan: the movement, the target rep band, and which
2 Added: substitutes are permitted. Heavy Duty prescribes a single working set, so
3 Added: that is implicit and not configurable.
4 Added:
5 Added: Purely a plan — it knows nothing of what was performed. Judging performance
6 Added: against a prescription is {!Progression}'s job. *)
7 Added:
8 Added: type t
9 Added:
10 Added: val make :
11 Added: exercise:Exercise.t ->
12 Added: target_reps:Units.Rep_range.t ->
13 Added: allowed_substitutes:Exercise.t list ->
14 Added: (t, Exercise.error) result
15 Added: (** Substitutes must be on the exercise's catalog whitelist, so a prescription
16 Added: may only narrow the curated set. *)
17 Added:
18 Added: val exercise : t -> Exercise.t
19 Added: val target_reps : t -> Units.Rep_range.t
20 Added: val allowed_substitutes : t -> Exercise.t list
21 Added: val pp : Format.formatter -> t -> unit
lib/core/prescription/dune
index 8fda6d66..00000000 100644..000000
@@ -1,5 +0,0 @@
1 Removed: (library
2 Removed: (name hito_prescription)
3 Removed: (public_name hito.core.prescription)
4 Removed: (libraries hito.core.vocabulary hito.core.assessment)
5 Removed: (wrapped false))
lib/core/prescription/prescription.ml
index 2a243fc4..00000000 100644..000000
@@ -1,9 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (prescription.mli) is the design surface. *)
2 Removed:
3 Removed: type t = unit
4 Removed:
5 Removed: let make ~exercise:_ ~target_reps:_ ~allowed_substitutes:_ = failwith "TODO"
6 Removed: let exercise _ = failwith "TODO"
7 Removed: let target_reps _ = failwith "TODO"
8 Removed: let allowed_substitutes _ = failwith "TODO"
9 Removed: let pp _ _ = failwith "TODO"
lib/core/prescription/prescription.mli
index b90dbf8f..00000000 100644..000000
@@ -1,21 +0,0 @@
1 Removed: (** One exercise's plan: the movement, the target rep band, and which
2 Removed: substitutes are permitted. Heavy Duty prescribes a single working set, so
3 Removed: that is implicit and not configurable.
4 Removed:
5 Removed: Purely a plan — it knows nothing of what was performed. Judging performance
6 Removed: against a prescription is {!Progression}'s job. *)
7 Removed:
8 Removed: type t
9 Removed:
10 Removed: val make :
11 Removed: exercise:Exercise.t ->
12 Removed: target_reps:Units.Rep_range.t ->
13 Removed: allowed_substitutes:Exercise.t list ->
14 Removed: (t, Exercise.error) result
15 Removed: (** Substitutes must be on the exercise's catalog whitelist, so a prescription
16 Removed: may only narrow the curated set. *)
17 Removed:
18 Removed: val exercise : t -> Exercise.t
19 Removed: val target_reps : t -> Units.Rep_range.t
20 Removed: val allowed_substitutes : t -> Exercise.t list
21 Removed: val pp : Format.formatter -> t -> unit
lib/core/prescription/routine.ml
index 588eefd2..00000000 100644..000000
@@ -1,16 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (routine.mli) is the design surface. *)
2 Removed:
3 Removed: type t = unit
4 Removed: type error = Empty_routine
5 Removed:
6 Removed: let make ~name:_ ~workouts:_ = failwith "TODO"
7 Removed: let name _ = failwith "TODO"
8 Removed: let workouts _ = failwith "TODO"
9 Removed: let workout_after _ _ = failwith "TODO"
10 Removed: let recovery_base _ = failwith "TODO"
11 Removed: let pp _ _ = failwith "TODO"
12 Removed:
13 Removed: (* Presets are values of type [t = unit]; kept as unit placeholders so the
14 Removed: module loads without evaluating a [failwith]. *)
15 Removed: let ideal_routine = ()
16 Removed: let consolidation_routine = ()
lib/core/prescription/routine.mli
index 970fc75c..00000000 100644..000000
@@ -1,26 +0,0 @@
1 Removed: (** A routine is a sequence of prescribed workouts cycled through over time —
2 Removed: the Ideal Routine is a three-way split, Consolidation a two-way one. Purely
3 Removed: a template: no targets, no history. *)
4 Removed:
5 Removed: type t
6 Removed: type error = Empty_routine
7 Removed:
8 Removed: val make :
9 Removed: name:string -> workouts:Workout_prescription.t list -> (t, error) result
10 Removed:
11 Removed: val name : t -> string
12 Removed: val workouts : t -> Workout_prescription.t list
13 Removed:
14 Removed: val workout_after : t -> Workout_prescription.t -> Workout_prescription.t
15 Removed: (** The next workout in the cycle after the given one, wrapping around. Falls
16 Removed: back to the first workout if the argument is not part of this routine. *)
17 Removed:
18 Removed: val recovery_base : t -> Recovery.duration
19 Removed: (** Nominal rest between workouts; Consolidation rests longer than Ideal. *)
20 Removed:
21 Removed: val pp : Format.formatter -> t -> unit
22 Removed:
23 Removed: (** {1 Heavy Duty presets} *)
24 Removed:
25 Removed: val ideal_routine : t
26 Removed: val consolidation_routine : t
lib/core/prescription/set_group_prescription.ml
index bb7dfa9e..00000000 100644..000000
@@ -1,11 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (set_group_prescription.mli) is the design
2 Removed: surface. *)
3 Removed:
4 Removed: type t =
5 Removed: | Straight of Prescription.t
6 Removed: | Superset of { first : Prescription.t; second : Prescription.t }
7 Removed: | Pre_exhaust of { isolation : Prescription.t; compound : Prescription.t }
8 Removed:
9 Removed: let prescriptions _ = failwith "TODO"
10 Removed: let exercises _ = failwith "TODO"
11 Removed: let pp _ _ = failwith "TODO"
lib/core/prescription/set_group_prescription.mli
index 91f0792c..00000000 100644..000000
@@ -1,16 +0,0 @@
1 Removed: (** The prescribed counterpart to {!Set_group}: which exercises, in which
2 Removed: structure, with no performance data. A routine's workouts
3 Removed: ({!Workout_prescription}) are built from these. *)
4 Removed:
5 Removed: type t =
6 Removed: | Straight of Prescription.t
7 Removed: | Superset of { first : Prescription.t; second : Prescription.t }
8 Removed: | Pre_exhaust of { isolation : Prescription.t; compound : Prescription.t }
9 Removed:
10 Removed: val prescriptions : t -> Prescription.t list
11 Removed: (** Every slot's prescription, in order. *)
12 Removed:
13 Removed: val exercises : t -> Exercise.t list
14 Removed: (** The prescribed movements, in order. *)
15 Removed:
16 Removed: val pp : Format.formatter -> t -> unit
lib/core/prescription/workout_prescription.ml
index 7c72f0a3..00000000 100644..000000
@@ -1,13 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (workout_prescription.mli) is the design
2 Removed: surface. *)
3 Removed:
4 Removed: type id = string
5 Removed: type t = unit
6 Removed: type error = Empty_workout
7 Removed:
8 Removed: let make ~id:_ ~name:_ ~set_groups:_ = failwith "TODO"
9 Removed: let id _ = failwith "TODO"
10 Removed: let name _ = failwith "TODO"
11 Removed: let set_groups _ = failwith "TODO"
12 Removed: let equal _ _ = failwith "TODO"
13 Removed: let pp _ _ = failwith "TODO"
lib/core/prescription/workout_prescription.mli
index 3abccf44..00000000 100644..000000
@@ -1,25 +0,0 @@
1 Removed: (** A prescribed workout — one workout within a routine, e.g. HD1's "Workout A":
2 Removed: an ordered sequence of set-group prescriptions. Static: no targets, no
3 Removed: history. *)
4 Removed:
5 Removed: type t
6 Removed: type id = private string
7 Removed: type error = Empty_workout
8 Removed:
9 Removed: val make :
10 Removed: id:string ->
11 Removed: name:string ->
12 Removed: set_groups:Set_group_prescription.t list ->
13 Removed: (t, error) result
14 Removed: (** [Error Empty_workout] if [set_groups] is empty. *)
15 Removed:
16 Removed: val id : t -> id
17 Removed: val name : t -> string
18 Removed:
19 Removed: val set_groups : t -> Set_group_prescription.t list
20 Removed: (** In order; sequence matters for pre-exhaust pairings. *)
21 Removed:
22 Removed: val equal : t -> t -> bool
23 Removed: (** By {!id}. *)
24 Removed:
25 Removed: val pp : Format.formatter -> t -> unit
lib/core/progression.ml
index 00000000..059799f5 000000..100644
@@ -0,0 +1,30 @@
1 Added: (* Blank slate: stub only. Interface (progression.mli) is the design surface. *)
2 Added:
3 Added: type t = Progressing | Stalled
4 Added:
5 Added: let equal _ _ = failwith "TODO"
6 Added: let pp _ _ = failwith "TODO"
7 Added:
8 Added: type sample = Set.Working.performance
9 Added: type error = Insufficient_data
10 Added:
11 Added: let evaluate ~history:_ = failwith "TODO"
12 Added: let beats ~previous:_ ~current:_ = failwith "TODO"
13 Added:
14 Added: type band = Below_range | In_range | Above_range
15 Added:
16 Added: let classify ~target_reps:_ _ = failwith "TODO"
17 Added:
18 Added: type target =
19 Added: | Add_reps of { load : Units.Weight.t; min_reps : Units.Reps.t }
20 Added: | Add_load of { min_load : Units.Weight.t; reps : Units.Reps.t }
21 Added:
22 Added: type guidance = { band : band; next : target } [@@warning "-69"]
23 Added:
24 Added: let guide ~target_reps:_ _ = failwith "TODO"
25 Added:
26 Added: type 'a prescribed = { value : 'a; evidence : sample list; status : t }
27 Added: [@@warning "-69"]
28 Added:
29 Added: let prescribe ~target_reps:_ ~evidence:_ = failwith "TODO"
30 Added: let volume _ = failwith "TODO"
lib/core/progression.mli
index 00000000..e46e82a7 000000..100644
@@ -0,0 +1,56 @@
1 Added: (** Assessment: what the logged evidence says, and what it prescribes next.
2 Added:
3 Added: This is the only module that relates performance to plan. Prescriptions do
4 Added: not judge, and the logbook does not interpret. *)
5 Added:
6 Added: type t = Progressing | Stalled
7 Added:
8 Added: val equal : t -> t -> bool
9 Added: val pp : Format.formatter -> t -> unit
10 Added:
11 Added: type sample = Set.Working.performance
12 Added: (** One working set's performance — the unit of evidence. *)
13 Added:
14 Added: type error = Insufficient_data
15 Added:
16 Added: val evaluate : history:sample list -> (t, error) result
17 Added: (** [history] oldest-first. *)
18 Added:
19 Added: val beats : previous:sample -> current:sample -> bool
20 Added: (** Progressive overload: heavier, or equal load for more reps. *)
21 Added:
22 Added: (** {1 Judging a performance against a band} *)
23 Added:
24 Added: type band =
25 Added: | Below_range (** Load too heavy. *)
26 Added: | In_range
27 Added: | Above_range (** Load too light. *)
28 Added:
29 Added: val classify : target_reps:Units.Rep_range.t -> sample -> band
30 Added:
31 Added: (** What to aim for next. *)
32 Added: type target =
33 Added: | Add_reps of { load : Units.Weight.t; min_reps : Units.Reps.t }
34 Added: | Add_load of { min_load : Units.Weight.t; reps : Units.Reps.t }
35 Added: (** Band exceeded: raise load, reset to the band's bottom. *)
36 Added:
37 Added: type guidance = { band : band; next : target }
38 Added: (** How one performance landed, and what follows from it. *)
39 Added:
40 Added: val guide : target_reps:Units.Rep_range.t -> sample -> guidance
41 Added:
42 Added: (** {1 Prescribing from evidence}
43 Added:
44 Added: Anything prescribed carries the evidence it came from and the status that
45 Added: evidence showed. *)
46 Added:
47 Added: type 'a prescribed = { value : 'a; evidence : sample list; status : t }
48 Added:
49 Added: val prescribe :
50 Added: target_reps:Units.Rep_range.t -> evidence:sample list -> target prescribed
51 Added:
52 Added: (** {1 Metrics} *)
53 Added:
54 Added: val volume : sample list -> float
55 Added: (** Σ (load × reps), in kilogram-reps. A diagnostic, never a target: under Heavy
56 Added: Duty rising volume is a warning, not an achievement. *)
lib/core/recovery.ml
index 00000000..101ca57b 000000..100644
@@ -0,0 +1,20 @@
1 Added: (* Blank slate: stub only. Interface (recovery.mli) is the design surface. *)
2 Added:
3 Added: type timestamp = int
4 Added:
5 Added: let timestamp_of_unix_seconds _ = failwith "TODO"
6 Added: let timestamp_to_unix_seconds _ = failwith "TODO"
7 Added:
8 Added: type duration = int
9 Added:
10 Added: let hours _ = failwith "TODO"
11 Added: let days _ = failwith "TODO"
12 Added: let duration_to_seconds _ = failwith "TODO"
13 Added: let elapsed ~since:_ ~now:_ = failwith "TODO"
14 Added:
15 Added: type readiness =
16 Added: | Ready
17 Added: | Recovering of { rested : duration; recommended : duration }
18 Added:
19 Added: let evaluate_readiness ~elapsed:_ ~recommended:_ = failwith "TODO"
20 Added: let is_ready _ = failwith "TODO"
lib/core/recovery.mli
index 00000000..023ae8b8 000000..100644
@@ -0,0 +1,24 @@
1 Added: (** Recovery is read off the logbook, not managed here: the elapsed time between
2 Added: two workouts, judged against a recommended window. There is no session,
3 Added: override, or evidence-based prescription — {!Logbook} owns that context and
4 Added: decides what to do with a {!readiness} reading. *)
5 Added:
6 Added: type timestamp = private int
7 Added:
8 Added: val timestamp_of_unix_seconds : int -> timestamp
9 Added: val timestamp_to_unix_seconds : timestamp -> int
10 Added:
11 Added: type duration = private int
12 Added:
13 Added: val hours : int -> duration
14 Added: val days : int -> duration
15 Added: val duration_to_seconds : duration -> int
16 Added: val elapsed : since:timestamp -> now:timestamp -> duration
17 Added:
18 Added: type readiness =
19 Added: | Ready
20 Added: | Recovering of { rested : duration; recommended : duration }
21 Added: (** [rested] of [recommended] has passed. *)
22 Added:
23 Added: val evaluate_readiness : elapsed:duration -> recommended:duration -> readiness
24 Added: val is_ready : readiness -> bool
lib/core/routine.ml
index 00000000..588eefd2 000000..100644
@@ -0,0 +1,16 @@
1 Added: (* Blank slate: stub only. Interface (routine.mli) is the design surface. *)
2 Added:
3 Added: type t = unit
4 Added: type error = Empty_routine
5 Added:
6 Added: let make ~name:_ ~workouts:_ = failwith "TODO"
7 Added: let name _ = failwith "TODO"
8 Added: let workouts _ = failwith "TODO"
9 Added: let workout_after _ _ = failwith "TODO"
10 Added: let recovery_base _ = failwith "TODO"
11 Added: let pp _ _ = failwith "TODO"
12 Added:
13 Added: (* Presets are values of type [t = unit]; kept as unit placeholders so the
14 Added: module loads without evaluating a [failwith]. *)
15 Added: let ideal_routine = ()
16 Added: let consolidation_routine = ()
lib/core/routine.mli
index 00000000..970fc75c 000000..100644
@@ -0,0 +1,26 @@
1 Added: (** A routine is a sequence of prescribed workouts cycled through over time —
2 Added: the Ideal Routine is a three-way split, Consolidation a two-way one. Purely
3 Added: a template: no targets, no history. *)
4 Added:
5 Added: type t
6 Added: type error = Empty_routine
7 Added:
8 Added: val make :
9 Added: name:string -> workouts:Workout_prescription.t list -> (t, error) result
10 Added:
11 Added: val name : t -> string
12 Added: val workouts : t -> Workout_prescription.t list
13 Added:
14 Added: val workout_after : t -> Workout_prescription.t -> Workout_prescription.t
15 Added: (** The next workout in the cycle after the given one, wrapping around. Falls
16 Added: back to the first workout if the argument is not part of this routine. *)
17 Added:
18 Added: val recovery_base : t -> Recovery.duration
19 Added: (** Nominal rest between workouts; Consolidation rests longer than Ideal. *)
20 Added:
21 Added: val pp : Format.formatter -> t -> unit
22 Added:
23 Added: (** {1 Heavy Duty presets} *)
24 Added:
25 Added: val ideal_routine : t
26 Added: val consolidation_routine : t
lib/core/units.ml
index 00000000..4da6ef7b 000000..100644
@@ -0,0 +1,37 @@
1 Added: (* Blank slate: stub only. Interface (units.mli) is the design surface. *)
2 Added:
3 Added: type error = Negative | Not_positive | Inverted_range
4 Added:
5 Added: let pp_error _ _ = failwith "TODO"
6 Added:
7 Added: module Weight = struct
8 Added: type t = float
9 Added:
10 Added: let of_kg _ = failwith "TODO"
11 Added: let to_kg _ = failwith "TODO"
12 Added: let zero = 0.0
13 Added: let compare _ _ = failwith "TODO"
14 Added: let equal _ _ = failwith "TODO"
15 Added: let pp _ _ = failwith "TODO"
16 Added: end
17 Added:
18 Added: module Reps = struct
19 Added: type t = int
20 Added:
21 Added: let of_int _ = failwith "TODO"
22 Added: let to_int _ = failwith "TODO"
23 Added: let compare _ _ = failwith "TODO"
24 Added: let equal _ _ = failwith "TODO"
25 Added: let pp _ _ = failwith "TODO"
26 Added: end
27 Added:
28 Added: module Rep_range = struct
29 Added: type t = Reps.t * Reps.t
30 Added:
31 Added: let make ~min:_ ~max:_ = failwith "TODO"
32 Added: let min _ = failwith "TODO"
33 Added: let max _ = failwith "TODO"
34 Added: let contains _ _ = failwith "TODO"
35 Added: let equal _ _ = failwith "TODO"
36 Added: let pp _ _ = failwith "TODO"
37 Added: end
lib/core/units.mli
index 00000000..ac26c979 000000..100644
@@ -0,0 +1,46 @@
1 Added: (** Physical quantities recorded during training.
2 Added:
3 Added: Every type here is abstract and constructible only through a smart
4 Added: constructor, so an invalid measurement cannot exist. *)
5 Added:
6 Added: type error =
7 Added: | Negative (** A quantity that must be >= 0 was negative, or not finite. *)
8 Added: | Not_positive (** A quantity that must be > 0 was <= 0. *)
9 Added: | Inverted_range (** A range's lower bound exceeded its upper bound. *)
10 Added:
11 Added: val pp_error : Format.formatter -> error -> unit
12 Added:
13 Added: (** Load in kilograms. Nonnegative: body-weight movements are [zero]. *)
14 Added: module Weight : sig
15 Added: type t
16 Added:
17 Added: val of_kg : float -> (t, error) result
18 Added: val to_kg : t -> float
19 Added: val zero : t
20 Added: val compare : t -> t -> int
21 Added: val equal : t -> t -> bool
22 Added: val pp : Format.formatter -> t -> unit
23 Added: end
24 Added:
25 Added: (** A completed repetition count. Strictly positive. *)
26 Added: module Reps : sig
27 Added: type t
28 Added:
29 Added: val of_int : int -> (t, error) result
30 Added: val to_int : t -> int
31 Added: val compare : t -> t -> int
32 Added: val equal : t -> t -> bool
33 Added: val pp : Format.formatter -> t -> unit
34 Added: end
35 Added:
36 Added: (** An inclusive target rep band, such as Mentzer's canonical 6-8. *)
37 Added: module Rep_range : sig
38 Added: type t
39 Added:
40 Added: val make : min:Reps.t -> max:Reps.t -> (t, error) result
41 Added: val min : t -> Reps.t
42 Added: val max : t -> Reps.t
43 Added: val contains : t -> Reps.t -> bool
44 Added: val equal : t -> t -> bool
45 Added: val pp : Format.formatter -> t -> unit
46 Added: end
lib/core/vocabulary/dune
index 69170a46..00000000 100644..000000
@@ -1,4 +0,0 @@
1 Removed: (library
2 Removed: (name hito_vocabulary)
3 Removed: (public_name hito.core.vocabulary)
4 Removed: (wrapped false))
lib/core/vocabulary/exercise.ml
index 3e5db58e..00000000 100644..000000
@@ -1,17 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (exercise.mli) is the design surface. *)
2 Removed:
3 Removed: type id = string
4 Removed: type t = unit
5 Removed:
6 Removed: let id _ = failwith "TODO"
7 Removed: let name _ = failwith "TODO"
8 Removed: let equal _ _ = failwith "TODO"
9 Removed: let pp _ _ = failwith "TODO"
10 Removed: let catalog = []
11 Removed: let find _ = failwith "TODO"
12 Removed:
13 Removed: type error = Not_permitted of { original : id; candidate : id }
14 Removed:
15 Removed: let permitted_substitutes _ = failwith "TODO"
16 Removed: let may_substitute ~original:_ ~candidate:_ = failwith "TODO"
17 Removed: let substitute ~original:_ ~candidate:_ = failwith "TODO"
lib/core/vocabulary/exercise.mli
index e3fc5206..00000000 100644..000000
@@ -1,31 +0,0 @@
1 Removed: (** Curated exercise catalog with author-specified substitution whitelists.
2 Removed:
3 Removed: Users do not log arbitrary movements: every exercise comes from {!catalog}.
4 Removed: Substitutions are limited to each exercise's author-specified whitelist. *)
5 Removed:
6 Removed: type t
7 Removed: (** Abstract catalog exercise; only obtainable via {!catalog} / {!find}. *)
8 Removed:
9 Removed: type id = private string
10 Removed:
11 Removed: val id : t -> id
12 Removed: val name : t -> string
13 Removed: val equal : t -> t -> bool
14 Removed: val pp : Format.formatter -> t -> unit
15 Removed:
16 Removed: (** {1 Catalog} *)
17 Removed:
18 Removed: val catalog : t list
19 Removed: (** The complete curated catalog; the only source of {!t} values. *)
20 Removed:
21 Removed: val find : id -> t option
22 Removed:
23 Removed: (** {1 Substitutions} *)
24 Removed:
25 Removed: type error = Not_permitted of { original : id; candidate : id }
26 Removed:
27 Removed: val permitted_substitutes : t -> t list
28 Removed: val may_substitute : original:t -> candidate:t -> bool
29 Removed:
30 Removed: val substitute : original:t -> candidate:t -> (t, error) result
31 Removed: (** [Ok candidate] iff [candidate] is on [original]'s whitelist. *)
lib/core/vocabulary/set.ml
index dc55079e..00000000 100644..000000
@@ -1,28 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (set.mli) is the design surface. *)
2 Removed:
3 Removed: module Warm_up = struct
4 Removed: type t = unit
5 Removed:
6 Removed: let make ~exercise:_ ~load:_ ~reps:_ = failwith "TODO"
7 Removed: let exercise _ = failwith "TODO"
8 Removed: let load _ = failwith "TODO"
9 Removed: let reps _ = failwith "TODO"
10 Removed: let pp _ _ = failwith "TODO"
11 Removed: end
12 Removed:
13 Removed: module Working = struct
14 Removed: type extension = Forced_reps | Negatives | Rest_pause | Static_hold
15 Removed: type outcome = Positive_failure | Beyond_failure of extension
16 Removed: type t = unit
17 Removed:
18 Removed: let make ~exercise:_ ~load:_ ~reps:_ ~outcome:_ = failwith "TODO"
19 Removed: let exercise _ = failwith "TODO"
20 Removed: let load _ = failwith "TODO"
21 Removed: let reps _ = failwith "TODO"
22 Removed: let outcome _ = failwith "TODO"
23 Removed: let pp _ _ = failwith "TODO"
24 Removed:
25 Removed: type performance = { load : Units.Weight.t; reps : Units.Reps.t }
26 Removed:
27 Removed: let performance _ = failwith "TODO"
28 Removed: end
lib/core/vocabulary/set.mli
index e7db23fd..00000000 100644..000000
@@ -1,55 +0,0 @@
1 Removed: (** The individual set (invariant #1).
2 Removed:
3 Removed: Warm-up and working sets are distinct, incompatible types. A working set
4 Removed: always reaches failure — that is its definition — so the type records only
5 Removed: {e how} failure was reached, never whether.
6 Removed:
7 Removed: Intensity is therefore categorical, not a scalar: at failure a set is by
8 Removed: definition maximal, and going further is expressed qualitatively as
9 Removed: {!Working.Beyond_failure}. There is deliberately no numeric intensity
10 Removed: metric. *)
11 Removed:
12 Removed: (** A warm-up set: preparation, never taken to failure. *)
13 Removed: module Warm_up : sig
14 Removed: type t
15 Removed:
16 Removed: val make :
17 Removed: exercise:Exercise.t -> load:Units.Weight.t -> reps:Units.Reps.t -> t
18 Removed:
19 Removed: val exercise : t -> Exercise.t
20 Removed: val load : t -> Units.Weight.t
21 Removed: val reps : t -> Units.Reps.t
22 Removed: val pp : Format.formatter -> t -> unit
23 Removed: end
24 Removed:
25 Removed: (** A working set: always taken to failure. *)
26 Removed: module Working : sig
27 Removed: (** How failure was extended past positive failure, per HD1/HD2. *)
28 Removed: type extension = Forced_reps | Negatives | Rest_pause | Static_hold
29 Removed:
30 Removed: (** The failure mode of the set. *)
31 Removed: type outcome =
32 Removed: | Positive_failure (** Momentary positive failure: no further full rep. *)
33 Removed: | Beyond_failure of extension
34 Removed: (** Taken past failure via an {!extension}. *)
35 Removed:
36 Removed: type t
37 Removed:
38 Removed: val make :
39 Removed: exercise:Exercise.t ->
40 Removed: load:Units.Weight.t ->
41 Removed: reps:Units.Reps.t ->
42 Removed: outcome:outcome ->
43 Removed: t
44 Removed:
45 Removed: val exercise : t -> Exercise.t
46 Removed: val load : t -> Units.Weight.t
47 Removed: val reps : t -> Units.Reps.t
48 Removed: val outcome : t -> outcome
49 Removed: val pp : Format.formatter -> t -> unit
50 Removed:
51 Removed: type performance = { load : Units.Weight.t; reps : Units.Reps.t }
52 Removed:
53 Removed: val performance : t -> performance
54 Removed: (** Figures {!Progression} compares. *)
55 Removed: end
lib/core/vocabulary/units.ml
index 4da6ef7b..00000000 100644..000000
@@ -1,37 +0,0 @@
1 Removed: (* Blank slate: stub only. Interface (units.mli) is the design surface. *)
2 Removed:
3 Removed: type error = Negative | Not_positive | Inverted_range
4 Removed:
5 Removed: let pp_error _ _ = failwith "TODO"
6 Removed:
7 Removed: module Weight = struct
8 Removed: type t = float
9 Removed:
10 Removed: let of_kg _ = failwith "TODO"
11 Removed: let to_kg _ = failwith "TODO"
12 Removed: let zero = 0.0
13 Removed: let compare _ _ = failwith "TODO"
14 Removed: let equal _ _ = failwith "TODO"
15 Removed: let pp _ _ = failwith "TODO"
16 Removed: end
17 Removed:
18 Removed: module Reps = struct
19 Removed: type t = int
20 Removed:
21 Removed: let of_int _ = failwith "TODO"
22 Removed: let to_int _ = failwith "TODO"
23 Removed: let compare _ _ = failwith "TODO"
24 Removed: let equal _ _ = failwith "TODO"
25 Removed: let pp _ _ = failwith "TODO"
26 Removed: end
27 Removed:
28 Removed: module Rep_range = struct
29 Removed: type t = Reps.t * Reps.t
30 Removed:
31 Removed: let make ~min:_ ~max:_ = failwith "TODO"
32 Removed: let min _ = failwith "TODO"
33 Removed: let max _ = failwith "TODO"
34 Removed: let contains _ _ = failwith "TODO"
35 Removed: let equal _ _ = failwith "TODO"
36 Removed: let pp _ _ = failwith "TODO"
37 Removed: end
lib/core/vocabulary/units.mli
index ac26c979..00000000 100644..000000
@@ -1,46 +0,0 @@
1 Removed: (** Physical quantities recorded during training.
2 Removed:
3 Removed: Every type here is abstract and constructible only through a smart
4 Removed: constructor, so an invalid measurement cannot exist. *)
5 Removed:
6 Removed: type error =
7 Removed: | Negative (** A quantity that must be >= 0 was negative, or not finite. *)
8 Removed: | Not_positive (** A quantity that must be > 0 was <= 0. *)
9 Removed: | Inverted_range (** A range's lower bound exceeded its upper bound. *)
10 Removed:
11 Removed: val pp_error : Format.formatter -> error -> unit
12 Removed:
13 Removed: (** Load in kilograms. Nonnegative: body-weight movements are [zero]. *)
14 Removed: module Weight : sig
15 Removed: type t
16 Removed:
17 Removed: val of_kg : float -> (t, error) result
18 Removed: val to_kg : t -> float
19 Removed: val zero : t
20 Removed: val compare : t -> t -> int
21 Removed: val equal : t -> t -> bool
22 Removed: val pp : Format.formatter -> t -> unit
23 Removed: end
24 Removed:
25 Removed: (** A completed repetition count. Strictly positive. *)
26 Removed: module Reps : sig
27 Removed: type t
28 Removed:
29 Removed: val of_int : int -> (t, error) result
30 Removed: val to_int : t -> int
31 Removed: val compare : t -> t -> int
32 Removed: val equal : t -> t -> bool
33 Removed: val pp : Format.formatter -> t -> unit
34 Removed: end
35 Removed:
36 Removed: (** An inclusive target rep band, such as Mentzer's canonical 6-8. *)
37 Removed: module Rep_range : sig
38 Removed: type t
39 Removed:
40 Removed: val make : min:Reps.t -> max:Reps.t -> (t, error) result
41 Removed: val min : t -> Reps.t
42 Removed: val max : t -> Reps.t
43 Removed: val contains : t -> Reps.t -> bool
44 Removed: val equal : t -> t -> bool
45 Removed: val pp : Format.formatter -> t -> unit
46 Removed: end
lib/core/workout_prescription.ml
index 00000000..7c72f0a3 000000..100644
@@ -0,0 +1,13 @@
1 Added: (* Blank slate: stub only. Interface (workout_prescription.mli) is the design
2 Added: surface. *)
3 Added:
4 Added: type id = string
5 Added: type t = unit
6 Added: type error = Empty_workout
7 Added:
8 Added: let make ~id:_ ~name:_ ~set_groups:_ = failwith "TODO"
9 Added: let id _ = failwith "TODO"
10 Added: let name _ = failwith "TODO"
11 Added: let set_groups _ = failwith "TODO"
12 Added: let equal _ _ = failwith "TODO"
13 Added: let pp _ _ = failwith "TODO"
lib/core/workout_prescription.mli
index 00000000..3abccf44 000000..100644
@@ -0,0 +1,25 @@
1 Added: (** A prescribed workout — one workout within a routine, e.g. HD1's "Workout A":
2 Added: an ordered sequence of set-group prescriptions. Static: no targets, no
3 Added: history. *)
4 Added:
5 Added: type t
6 Added: type id = private string
7 Added: type error = Empty_workout
8 Added:
9 Added: val make :
10 Added: id:string ->
11 Added: name:string ->
12 Added: set_groups:Set_group_prescription.t list ->
13 Added: (t, error) result
14 Added: (** [Error Empty_workout] if [set_groups] is empty. *)
15 Added:
16 Added: val id : t -> id
17 Added: val name : t -> string
18 Added:
19 Added: val set_groups : t -> Set_group_prescription.t list
20 Added: (** In order; sequence matters for pre-exhaust pairings. *)
21 Added:
22 Added: val equal : t -> t -> bool
23 Added: (** By {!id}. *)
24 Added:
25 Added: val pp : Format.formatter -> t -> unit
lib/web/dune
index 9f4af84f..00000000 100644..000000
@@ -1,12 +0,0 @@
1 Removed: (library
2 Removed: (name hito_web)
3 Removed: (public_name hito.web)
4 Removed: (libraries
5 Removed: hito.core.vocabulary
6 Removed: hito.core.assessment
7 Removed: hito.core.prescription
8 Removed: hito.core.logbook
9 Removed: hito.app
10 Removed: eliom.server)
11 Removed: (preprocess
12 Removed: (pps eliom.ppx.server)))
lib/web/dune.disabled
index 00000000..e4507ab7 000000..100644
@@ -0,0 +1,6 @@
1 Added: (library
2 Added: (name hito_web)
3 Added: (public_name hito.web)
4 Added: (libraries hito.core hito.app eliom.server)
5 Added: (preprocess
6 Added: (pps eliom.ppx.server)))
test/dune
index 221ce962..7017e4c8 100644..100644
@@ -1,8 +1,7 @@
1 Added: ; Suites are added here as the HD1 rebuild lands each module (Task 12 completes
2 Added: ; the re-registration).
3 Added:
1 4 (test
2 5 (name test_hito)
3 Removed: (libraries
4 Removed: hito.core.vocabulary
5 Removed: hito.core.assessment
6 Removed: hito.core.prescription
7 Removed: hito.core.logbook
8 Removed: alcotest))
6 Added: (modules test_hito)
7 Added: (libraries hito.core alcotest))
test/test_set.ml
index 8ccd50ec..00000000 100644..000000
@@ -1,57 +0,0 @@
1 Removed: (** Unit tests for {!Set}, authored against set.mli.
2 Removed:
3 Removed: NOTE: Not yet registered in the main runner; wired in during implementation
4 Removed: (Task 8). set.mli is the source of truth over these assertions.
5 Removed:
6 Removed: Warm-up and working sets are distinct types (mismatch is a compile error,
7 Removed: not a runtime assertion). A working set always reaches failure — the type
8 Removed: only records how. *)
9 Removed:
10 Removed: let ok = function Ok v -> v | Error _ -> Alcotest.fail "expected Ok"
11 Removed: let mk_w kg = ok (Units.Weight.of_kg kg)
12 Removed: let mk_r n = ok (Units.Reps.of_int n)
13 Removed:
14 Removed: let some_exercise () =
15 Removed: match Exercise.catalog with
16 Removed: | ex :: _ -> ex
17 Removed: | [] -> Alcotest.fail "catalog is empty"
18 Removed:
19 Removed: let set_tests =
20 Removed: [
21 Removed: ( "warm-up records load and reps",
22 Removed: `Quick,
23 Removed: fun () ->
24 Removed: let w =
25 Removed: Set.Warm_up.make ~exercise:(some_exercise ()) ~load:(mk_w 40.0)
26 Removed: ~reps:(mk_r 10)
27 Removed: in
28 Removed: Alcotest.(check (float 0.0001))
29 Removed: "load" 40.0
30 Removed: (Units.Weight.to_kg (Set.Warm_up.load w)) );
31 Removed: ( "working set exposes performance",
32 Removed: `Quick,
33 Removed: fun () ->
34 Removed: let s =
35 Removed: Set.Working.make ~exercise:(some_exercise ()) ~load:(mk_w 80.0)
36 Removed: ~reps:(mk_r 7) ~outcome:Set.Working.Positive_failure
37 Removed: in
38 Removed: let p = Set.Working.performance s in
39 Removed: Alcotest.(check (float 0.0001))
40 Removed: "perf load" 80.0
41 Removed: (Units.Weight.to_kg p.load) );
42 Removed: ( "beyond-failure outcome is recorded",
43 Removed: `Quick,
44 Removed: fun () ->
45 Removed: let s =
46 Removed: Set.Working.make ~exercise:(some_exercise ()) ~load:(mk_w 80.0)
47 Removed: ~reps:(mk_r 6)
48 Removed: ~outcome:(Set.Working.Beyond_failure Set.Working.Rest_pause)
49 Removed: in
50 Removed: Alcotest.(check bool)
51 Removed: "outcome is beyond-failure" true
52 Removed: (match Set.Working.outcome s with
53 Removed: | Set.Working.Beyond_failure _ -> true
54 Removed: | _ -> false) );
55 Removed: ]
56 Removed:
57 Removed: let suite = [ ("set", set_tests) ]
test/test_set_group.ml
index 2e059109..00000000 100644..000000
@@ -1,110 +0,0 @@
1 Removed: (** Unit tests for {!Set_group}, authored against set_group.mli.
2 Removed:
3 Removed: Under test: each shape retains its working sets and warm-ups; the shape
4 Removed: admits no more or fewer working sets than Heavy Duty prescribes. *)
5 Removed:
6 Removed: let ok = function Ok v -> v | Error _ -> Alcotest.fail "expected Ok"
7 Removed: let mk_w kg = ok (Units.Weight.of_kg kg)
8 Removed: let mk_r n = ok (Units.Reps.of_int n)
9 Removed:
10 Removed: let exercises =
11 Removed: match Exercise.catalog with
12 Removed: | a :: b :: _ -> (a, b)
13 Removed: | _ -> Alcotest.fail "catalog needs at least two exercises"
14 Removed:
15 Removed: let working exercise load reps =
16 Removed: Set.Working.make ~exercise ~load:(mk_w load) ~reps:(mk_r reps)
17 Removed: ~outcome:Set.Working.Positive_failure
18 Removed:
19 Removed: let warm_up exercise load reps =
20 Removed: Set.Warm_up.make ~exercise ~load:(mk_w load) ~reps:(mk_r reps)
21 Removed:
22 Removed: let straight_tests =
23 Removed: [
24 Removed: ( "straight retains exactly one working set",
25 Removed: `Quick,
26 Removed: fun () ->
27 Removed: let ex, _ = exercises in
28 Removed: let g = Set_group.straight ~working:(working ex 80.0 6) () in
29 Removed: Alcotest.(check int)
30 Removed: "one working set" 1
31 Removed: (List.length (Set_group.working_sets g)) );
32 Removed: ( "straight retains its warm-up ramp",
33 Removed: `Quick,
34 Removed: fun () ->
35 Removed: let ex, _ = exercises in
36 Removed: let ramp = [ warm_up ex 40.0 10; warm_up ex 60.0 5 ] in
37 Removed: let g =
38 Removed: Set_group.straight ~warm_ups:ramp ~working:(working ex 80.0 6) ()
39 Removed: in
40 Removed: Alcotest.(check int)
41 Removed: "two warm-ups" 2
42 Removed: (List.length (Set_group.warm_ups g)) );
43 Removed: ( "straight defaults to no warm-up",
44 Removed: `Quick,
45 Removed: fun () ->
46 Removed: let ex, _ = exercises in
47 Removed: let g = Set_group.straight ~working:(working ex 80.0 6) () in
48 Removed: Alcotest.(check int)
49 Removed: "no warm-ups" 0
50 Removed: (List.length (Set_group.warm_ups g)) );
51 Removed: ]
52 Removed:
53 Removed: let superset_tests =
54 Removed: [
55 Removed: ( "superset retains both working sets in order",
56 Removed: `Quick,
57 Removed: fun () ->
58 Removed: let a, b = exercises in
59 Removed: let first = working a 80.0 6 and second = working b 20.0 12 in
60 Removed: let g = Set_group.superset ~first ~second () in
61 Removed: match Set_group.working_sets g with
62 Removed: | [ s1; s2 ] ->
63 Removed: Alcotest.(check bool)
64 Removed: "first matches" true
65 Removed: (Exercise.equal (Set.Working.exercise s1) a);
66 Removed: Alcotest.(check bool)
67 Removed: "second matches" true
68 Removed: (Exercise.equal (Set.Working.exercise s2) b)
69 Removed: | _ -> Alcotest.fail "expected exactly two working sets" );
70 Removed: ]
71 Removed:
72 Removed: let pre_exhaust_tests =
73 Removed: [
74 Removed: ( "pre-exhaust orders isolation before compound",
75 Removed: `Quick,
76 Removed: fun () ->
77 Removed: let isolation_ex, compound_ex = exercises in
78 Removed: let isolation = working isolation_ex 20.0 12 in
79 Removed: let compound = working compound_ex 80.0 6 in
80 Removed: let g = Set_group.pre_exhaust ~isolation ~compound () in
81 Removed: match Set_group.working_sets g with
82 Removed: | [ s1; s2 ] ->
83 Removed: Alcotest.(check bool)
84 Removed: "isolation first" true
85 Removed: (Exercise.equal (Set.Working.exercise s1) isolation_ex);
86 Removed: Alcotest.(check bool)
87 Removed: "compound second" true
88 Removed: (Exercise.equal (Set.Working.exercise s2) compound_ex)
89 Removed: | _ -> Alcotest.fail "expected exactly two working sets" );
90 Removed: ]
91 Removed:
92 Removed: let view_tests =
93 Removed: [
94 Removed: ( "view exposes the straight shape",
95 Removed: `Quick,
96 Removed: fun () ->
97 Removed: let ex, _ = exercises in
98 Removed: let g = Set_group.straight ~working:(working ex 80.0 6) () in
99 Removed: match Set_group.view g with
100 Removed: | Set_group.Straight _ -> ()
101 Removed: | _ -> Alcotest.fail "expected Straight" );
102 Removed: ]
103 Removed:
104 Removed: let suite =
105 Removed: [
106 Removed: ("set_group.straight", straight_tests);
107 Removed: ("set_group.superset", superset_tests);
108 Removed: ("set_group.pre_exhaust", pre_exhaust_tests);
109 Removed: ("set_group.view", view_tests);
110 Removed: ]
test/test_set_group_prescription.ml
index 14725331..00000000 100644..000000
@@ -1,66 +0,0 @@
1 Removed: (** Unit tests for {!Set_group_prescription}, authored against
2 Removed: set_group_prescription.mli.
3 Removed:
4 Removed: Under test: each shape exposes its slots' exercises in order — the
5 Removed: prescribed counterpart to {!Set_group}. *)
6 Removed:
7 Removed: let ok = function Ok v -> v | Error _ -> Alcotest.fail "expected Ok"
8 Removed:
9 Removed: let by_name n =
10 Removed: match
11 Removed: List.find_opt (fun ex -> String.equal (Exercise.name ex) n) Exercise.catalog
12 Removed: with
13 Removed: | Some ex -> ex
14 Removed: | None -> Alcotest.failf "no exercise named %s" n
15 Removed:
16 Removed: let band lo hi =
17 Removed: ok
18 Removed: (Units.Rep_range.make
19 Removed: ~min:(ok (Units.Reps.of_int lo))
20 Removed: ~max:(ok (Units.Reps.of_int hi)))
21 Removed:
22 Removed: let p name =
23 Removed: ok
24 Removed: (Prescription.make ~exercise:(by_name name) ~target_reps:(band 6 8)
25 Removed: ~allowed_substitutes:[])
26 Removed:
27 Removed: let tests =
28 Removed: [
29 Removed: ( "straight has one exercise",
30 Removed: `Quick,
31 Removed: fun () ->
32 Removed: let g = Set_group_prescription.Straight (p "Barbell Bench Press") in
33 Removed: Alcotest.(check int)
34 Removed: "one" 1
35 Removed: (List.length (Set_group_prescription.exercises g)) );
36 Removed: ( "superset has two exercises in order",
37 Removed: `Quick,
38 Removed: fun () ->
39 Removed: let g =
40 Removed: Set_group_prescription.Superset
41 Removed: { first = p "Pulldown"; second = p "Barbell Row" }
42 Removed: in
43 Removed: let names =
44 Removed: List.map Exercise.name (Set_group_prescription.exercises g)
45 Removed: in
46 Removed: Alcotest.(check (list string))
47 Removed: "order"
48 Removed: [ "Pulldown"; "Barbell Row" ]
49 Removed: names );
50 Removed: ( "pre_exhaust orders isolation before compound",
51 Removed: `Quick,
52 Removed: fun () ->
53 Removed: let g =
54 Removed: Set_group_prescription.Pre_exhaust
55 Removed: { isolation = p "Leg Extension"; compound = p "Back Squat" }
56 Removed: in
57 Removed: let names =
58 Removed: List.map Exercise.name (Set_group_prescription.exercises g)
59 Removed: in
60 Removed: Alcotest.(check (list string))
61 Removed: "order"
62 Removed: [ "Leg Extension"; "Back Squat" ]
63 Removed: names );
64 Removed: ]
65 Removed:
66 Removed: let suite = [ ("set_group_prescription", tests) ]