[OCaml] High Intensity Training Online
1
(** View models: the simple data structures the presenter renders.
2
3
The Clean Architecture rule for crossing a boundary is that the data passed
4
is a plain structure, never an entity or a database row. These DTOs are that
5
structure. A handler (the controller) maps a repository record or a core
6
entity into a view model, and the presenter ({!Pages}) renders the view
7
model without ever naming an inner-circle type.
8
9
Every page now crosses the boundary as a DTO. No page names a core entity.
10
Add a page DTO here before the presenter renders it. *)
11
12
module Viewer = struct
13
type t = { username : string }
14
(** The signed-in trainee, as the shell needs them: a name to show, nothing
15
more. Replaces passing the [Trainee.t] entity into the presenter. *)
16
end
17
18
module App_feedback = struct
19
type t = {
20
id : string; (** Opaque feedback identity, already stringified. *)
21
author : string;
22
contributions : int;
23
submitted_at : string; (** Pre-formatted "YYYY-MM-DD HH:MM" in UTC. *)
24
message : string;
25
upvotes : int;
26
viewer_upvoted : bool;
27
viewer_owns : bool;
28
}
29
(** One application-feedback entry, ready to render. Carries no timestamp
30
type, no opaque id, no repository record — only what the view shows. *)
31
end
32
33
module Feedback = struct
34
type report = {
35
at_unix : int; (** Report time in Unix seconds, for chronological order. *)
36
signals : string list; (** Each signal already described, for the list. *)
37
factor_scores : (string * int option) list;
38
(** Score per leveled factor field, aligned with the factor order the
39
presenter graphs. [None] where the report gave that factor no level.
40
Flags such as pain carry no score and are absent here. *)
41
}
42
(** One subjective-feedback report, ready to render. Carries no
43
[Evidence.Feedback] variant and no timestamp type. *)
44
end
45
46
module Routine = struct
47
type exercise = { name : string; reps : string }
48
(** One prescribed exercise line, its name and rep band already phrased. *)
49
50
type workout = { name : string; exercises : exercise list }
51
52
type t = { name : string; workouts : workout list }
53
(** A routine as the accordion descriptions render it: names, numeric rep
54
bands, and workout grouping only. No [Prescription] entity. *)
55
end
56
57
module Routine_choice = struct
58
type t = { id : string; name : string; workout_count : int }
59
(** One selectable routine on the chooser: its id for the select form, its
60
name, and how many workouts the cycle holds. *)
61
end
62
63
module Home = struct
64
type gate =
65
| Ready
66
| Recovering of { status : string }
67
(** Whether the next workout may begin, and the recovery status line to
68
show while it may not. *)
69
70
type t = {
71
routine_id : string; (** For the begin/override forms. *)
72
routine_name : string;
73
next_workout : string;
74
gate : gate;
75
}
76
(** The overview while no workout is in progress. The recovery policy has
77
already decided [gate] in the use case. The page only renders it. *)
78
end
79
80
module Workout = struct
81
type movement = {
82
name : string; (** The exercise name shown for this input row. *)
83
load_field : string; (** Form field name for the load input. *)
84
reps_field : string; (** Form field name for the reps input. *)
85
reps_label : string;
86
load_value : string option;
87
(** Pre-fill when correcting a recorded slot. *)
88
reps_value : string option;
89
}
90
(** One input row of a slot's record/correction form. The controller emits one
91
movement for a single-exercise slot and two for a pre-exhaust pair, so the
92
presenter renders whatever movements it is given and never inspects the
93
prescription's [delivery] shape itself. *)
94
95
type slot = {
96
index : int;
97
label : string; (** Exercise label for the selector option and heading. *)
98
movements : movement list;
99
recorded_summary : string option;
100
(** [Some description] once the slot is filled. [None] while
101
outstanding. Drives the recorded/outstanding branch without an
102
entity. *)
103
selected_extension : string;
104
(** The ending code to pre-select when correcting a recorded slot ("",
105
"forced", "negatives", "rest-pause", "static"). "" for an
106
outstanding slot. *)
107
}
108
109
type t = {
110
name : string; (** The workout (prescription) name, for the page title. *)
111
slots : slot list;
112
active_slot : int;
113
started_at_unix : int; (** Workout start, for the elapsed timer. *)
114
overridden : bool; (** Begun before recovery finished. *)
115
all_recorded : bool; (** Every prescribed slot has a record. *)
116
}
117
(** A workout view: the slot the trainee is on, the record/correction state of
118
each slot, and the timer and override facts. Carries no Evidence or
119
Prescription entity. Extension/ending selection stays in the presenter
120
form, since its option set is fixed UI vocabulary, not domain data. *)
121
end
122
123
module Logbook_entry = struct
124
type t = {
125
id : string; (** Record id for the link. *)
126
workout_name : string;
127
stimuli_count : int;
128
complete : bool;
129
}
130
(** One saved workout as the logbook list shows it: a link, a name, how many
131
stimuli it holds, and whether it is complete. No Repository record and no
132
Evidence.Workout entity. *)
133
end
134
135
module Import_warning = struct
136
type t = {
137
row : int; (** The source row that the adapter dropped. *)
138
detail : string; (** Why the row was dropped, already phrased. *)
139
}
140
(** One dropped import row, ready to render. The controller phrases the
141
reason, so no adapter warning variant crosses the boundary. *)
142
end
143
144
module Import_set = struct
145
type t = {
146
row : int; (** The source row, for provenance. *)
147
exercise_name : string; (** The export's exercise name, before mapping. *)
148
set_type : string; (** The set type, already phrased. *)
149
weight : string; (** The load, already formatted. *)
150
reps : string; (** The rep count, already formatted. *)
151
mapping : string option;
152
(** The catalog exercise name this set maps to, when confirmed. *)
153
}
154
(** One provisional import set, ready to render. Carries only strings, no
155
source-set entity. *)
156
end
157
158
module Import_workout = struct
159
type t = {
160
id : string; (** The workout id, for the per-workout forms. *)
161
title : string;
162
sets : Import_set.t list;
163
blockers : string list;
164
(** Why the workout cannot promote yet, each already phrased. Empty when
165
the workout can promote. *)
166
acknowledgement : string option;
167
(** The retained normal-set acknowledgement, when present. *)
168
revision : int; (** The workout revision, for provenance display. *)
169
}
170
(** One provisional import workout, ready to render: its sets, its blockers,
171
and its acknowledgement state. No Workout_import entity. *)
172
end
173
174
module Import_review = struct
175
type t = {
176
batch_id : string;
177
warnings : Import_warning.t list;
178
workouts : Import_workout.t list;
179
}
180
(** A provisional import batch under review: the rows dropped on parse and the
181
workouts awaiting cleaning and promotion. No batch entity crosses the
182
boundary. *)
183
end
184