refactor Separate feedback from workouts

Feedback can be reported independently of workout completion. Keep it as\ntimestamped typed evidence instead of mutating a workout record.

Commit
f08404069248c159c18e922ccbc7150045b4d89b
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/app/service.ml
index d32fda8d..934ff0a4 100644..100644
@@ -124,16 +124,6 @@
124 124 | Error error -> Error (Rejected_edit error)
125 125 | Ok workout -> replace t { record with Repository.workout })
126 126
127 Removed: let set_feedback t id feedback =
128 Removed: match R.find t.repo id with
129 Removed: | None -> Error Unknown_workout
130 Removed: | Some record -> (
131 Removed: match
132 Removed: Evidence.Workout.set_feedback record.Repository.workout feedback
133 Removed: with
134 Removed: | Error error -> Error (Rejected_edit error)
135 Removed: | Ok workout -> replace t { record with Repository.workout })
136 Removed:
137 127 let history t = R.history t.repo
138 128
139 129 let progress t exercise =
lib/app/service.mli
index f471fa62..c50ce36e 100644..100644
@@ -77,12 +77,6 @@
77 77 Evidence.Stimulus.t ->
78 78 (Repository.record, edit_error) result
79 79
80 Removed: val set_feedback :
81 Removed: t ->
82 Removed: Repository.workout_id ->
83 Removed: Evidence.Workout.Feedback.t ->
84 Removed: (Repository.record, edit_error) result
85 Removed:
86 80 val history : t -> Repository.record list
87 81
88 82 val progress :
lib/core/evidence.ml
index 597bc077..845a198b 100644..100644
@@ -71,65 +71,53 @@
71 71 Format.fprintf ppf "%a then %a" Movement.pp first Movement.pp second
72 72 end
73 73
74 Removed: module Workout = struct
75 Removed: type shape = As_single | As_pair
76 Removed: type completeness = Complete | Incomplete
74 Added: module Feedback = struct
75 Added: type level = Below_usual | Usual | Above_usual
77 76
78 Removed: module Feedback = struct
79 Removed: type sleep = Sleep_poor | Sleep_ordinary | Sleep_good
80 Removed: type appetite = Appetite_reduced | Appetite_ordinary | Appetite_increased
77 Added: type signal =
78 Added: | Sleep of level
79 Added: | Appetite of level
80 Added: | Readiness of level
81 Added: | Motivation of level
82 Added: | Difficulty of level
83 Added: | Pain
84 Added: | Injury
85 Added: | Preparation_insufficient
81 86
82 Removed: type illness_stress =
83 Removed: | No_illness_or_stress
84 Removed: | Illness
85 Removed: | Stress
86 Removed: | Illness_and_stress
87 Added: type t = { reported_at : Recovery.timestamp; signals : signal list }
88 Added: type error = Duplicate_signal of signal
87 89
88 Removed: type motivation = Motivation_low | Motivation_ordinary | Motivation_high
89 Removed: type recovery = Recovery_poor | Recovery_ordinary | Recovery_good
90 Added: let same_category left right =
91 Added: match (left, right) with
92 Added: | Sleep _, Sleep _
93 Added: | Appetite _, Appetite _
94 Added: | Readiness _, Readiness _
95 Added: | Motivation _, Motivation _
96 Added: | Difficulty _, Difficulty _
97 Added: | Pain, Pain
98 Added: | Injury, Injury
99 Added: | Preparation_insufficient, Preparation_insufficient ->
100 Added: true
101 Added: | _ -> false
90 102
91 Removed: type difficulty =
92 Removed: | Difficulty_easier
93 Removed: | Difficulty_expected
94 Removed: | Difficulty_harder
103 Added: let make ~reported_at signals =
104 Added: let rec validate seen = function
105 Added: | [] -> Ok { reported_at; signals }
106 Added: | signal :: rest ->
107 Added: if List.exists (same_category signal) seen then
108 Added: Error (Duplicate_signal signal)
109 Added: else validate (signal :: seen) rest
110 Added: in
111 Added: validate [] signals
95 112
96 Removed: type t = {
97 Removed: sleep : sleep;
98 Removed: appetite : appetite;
99 Removed: illness_stress : illness_stress;
100 Removed: pain_injury : bool;
101 Removed: pain_detail : string option;
102 Removed: motivation : motivation;
103 Removed: recovery : recovery;
104 Removed: difficulty : difficulty;
105 Removed: note : string option;
106 Removed: }
113 Added: let reported_at t = t.reported_at
114 Added: let signals t = t.signals
115 Added: end
107 116
108 Removed: let make ~sleep ~appetite ~illness_stress ~pain_injury ?pain_detail
109 Removed: ~motivation ~recovery ~difficulty ?note () =
110 Removed: {
111 Removed: sleep;
112 Removed: appetite;
113 Removed: illness_stress;
114 Removed: pain_injury;
115 Removed: pain_detail;
116 Removed: motivation;
117 Removed: recovery;
118 Removed: difficulty;
119 Removed: note;
120 Removed: }
117 Added: module Workout = struct
118 Added: type shape = As_single | As_pair
119 Added: type completeness = Complete | Incomplete
121 120
122 Removed: let sleep t = t.sleep
123 Removed: let appetite t = t.appetite
124 Removed: let illness_stress t = t.illness_stress
125 Removed: let pain_injury t = t.pain_injury
126 Removed: let pain_detail t = t.pain_detail
127 Removed: let motivation t = t.motivation
128 Removed: let recovery t = t.recovery
129 Removed: let difficulty t = t.difficulty
130 Removed: let note t = t.note
131 Removed: end
132 Removed:
133 121 type error =
134 122 | Not_prescribed of Exercise.id
135 123 | Delivery_mismatch of {
@@ -144,7 +132,6 @@
144 132 clearance : Recovery.clearance;
145 133 started_at : Recovery.timestamp;
146 134 ended_at : Recovery.timestamp option;
147 Removed: feedback : Feedback.t option;
148 135 performed : (int * Stimulus.t) list;
149 136 }
150 137
@@ -163,21 +150,13 @@
163 150 | Not_finished -> Format.pp_print_string ppf "this workout is not finished"
164 151
165 152 let start prescription ~clearance ~started_at =
166 Removed: {
167 Removed: prescription;
168 Removed: clearance;
169 Removed: started_at;
170 Removed: ended_at = None;
171 Removed: feedback = None;
172 Removed: performed = [];
173 Removed: }
153 Added: { prescription; clearance; started_at; ended_at = None; performed = [] }
174 154
175 155 let prescription t = t.prescription
176 156 let clearance t = t.clearance
177 157 let started_at t = t.started_at
178 158 let ended_at t = t.ended_at
179 159 let is_finished t = Option.is_some t.ended_at
180 Removed: let feedback t = t.feedback
181 160 let stimuli t = List.map snd (List.rev t.performed)
182 161
183 162 let duration t =
@@ -252,11 +231,6 @@
252 231 match t.ended_at with
253 232 | None -> { t with ended_at = Some ended_at }
254 233 | Some _ -> t
255 Removed:
256 Removed: let set_feedback t feedback =
257 Removed: match t.ended_at with
258 Removed: | None -> Error Not_finished
259 Removed: | Some _ -> Ok { t with feedback = Some feedback }
260 234
261 235 let pp ppf t =
262 236 Format.fprintf ppf "%a (%d of %d)" Prescription.Workout.pp t.prescription
lib/core/evidence.mli
index a08e542c..c776ce63 100644..100644
@@ -51,56 +51,34 @@
51 51 val pp : Format.formatter -> t -> unit
52 52 end
53 53
54 Added: (** Typed signals reported at a point in time. *)
55 Added: module Feedback : sig
56 Added: type level = Below_usual | Usual | Above_usual
57 Added:
58 Added: type signal =
59 Added: | Sleep of level
60 Added: | Appetite of level
61 Added: | Readiness of level
62 Added: | Motivation of level
63 Added: | Difficulty of level
64 Added: | Pain
65 Added: | Injury
66 Added: | Preparation_insufficient
67 Added:
68 Added: type t
69 Added: type error = Duplicate_signal of signal
70 Added:
71 Added: val make : reported_at:Recovery.timestamp -> signal list -> (t, error) result
72 Added: val reported_at : t -> Recovery.timestamp
73 Added: val signals : t -> signal list
74 Added: end
75 Added:
54 76 (** A performed or in-progress prescribed workout. *)
55 77 module Workout : sig
56 78 type t
57 79 type shape = As_single | As_pair
58 80 type completeness = Complete | Incomplete
59 81
60 Removed: module Feedback : sig
61 Removed: type sleep = Sleep_poor | Sleep_ordinary | Sleep_good
62 Removed: type appetite = Appetite_reduced | Appetite_ordinary | Appetite_increased
63 Removed:
64 Removed: type illness_stress =
65 Removed: | No_illness_or_stress
66 Removed: | Illness
67 Removed: | Stress
68 Removed: | Illness_and_stress
69 Removed:
70 Removed: type motivation = Motivation_low | Motivation_ordinary | Motivation_high
71 Removed: type recovery = Recovery_poor | Recovery_ordinary | Recovery_good
72 Removed:
73 Removed: type difficulty =
74 Removed: | Difficulty_easier
75 Removed: | Difficulty_expected
76 Removed: | Difficulty_harder
77 Removed:
78 Removed: type t
79 Removed:
80 Removed: val make :
81 Removed: sleep:sleep ->
82 Removed: appetite:appetite ->
83 Removed: illness_stress:illness_stress ->
84 Removed: pain_injury:bool ->
85 Removed: ?pain_detail:string ->
86 Removed: motivation:motivation ->
87 Removed: recovery:recovery ->
88 Removed: difficulty:difficulty ->
89 Removed: ?note:string ->
90 Removed: unit ->
91 Removed: t
92 Removed:
93 Removed: val sleep : t -> sleep
94 Removed: val appetite : t -> appetite
95 Removed: val illness_stress : t -> illness_stress
96 Removed: val pain_injury : t -> bool
97 Removed: val pain_detail : t -> string option
98 Removed: val motivation : t -> motivation
99 Removed: val recovery : t -> recovery
100 Removed: val difficulty : t -> difficulty
101 Removed: val note : t -> string option
102 Removed: end
103 Removed:
104 82 type error =
105 83 | Not_prescribed of Exercise.id
106 84 | Delivery_mismatch of {
@@ -124,9 +102,6 @@
124 102 val finish : t -> ended_at:Recovery.timestamp -> t
125 103 (** Sets [ended_at] once; later calls retain the first value. *)
126 104
127 Removed: val set_feedback : t -> Feedback.t -> (t, error) result
128 Removed: (** Requires a finished workout. *)
129 Removed:
130 105 val prescription : t -> Prescription.Workout.t
131 106 val clearance : t -> Recovery.clearance
132 107 val started_at : t -> Recovery.timestamp
@@ -134,7 +109,6 @@
134 109 val is_finished : t -> bool
135 110 val duration : t -> Recovery.duration option
136 111 val completeness : t -> completeness
137 Removed: val feedback : t -> Feedback.t option
138 112
139 113 val stimuli : t -> Stimulus.t list
140 114 (** Performance order. *)
lib/web/pages.ml
index b7ac5c6c..6bc591b4 100644..100644
@@ -443,12 +443,7 @@
443 443 Printf.sprintf "ended %d"
444 444 (Recovery.timestamp_to_unix_seconds time)
445 445 in
446 Removed: let feedback =
447 Removed: if Option.is_some (Evidence.Workout.feedback w) then
448 Removed: "; feedback saved"
449 Removed: else "; no feedback"
450 Removed: in
451 Removed: ended ^ "; " ^ completeness ^ feedback
446 Added: ended ^ "; " ^ completeness
452 447 in
453 448 li
454 449 [
@@ -467,76 +462,4 @@
467 462 (Evidence.Workout.stimuli w));
468 463 ])
469 464 records));
470 Removed: ]
471 Removed:
472 Removed: let feedback ~record =
473 Removed: let option name values =
474 Removed: match
475 Removed: List.map
476 Removed: (fun (value, label) -> Form.Option ([], value, Some (txt label), false))
477 Removed: values
478 Removed: with
479 Removed: | first :: rest -> Form.select ~name Form.string first rest
480 Removed: | [] -> assert false
481 Removed: in
482 Removed: shell ~title:"Workout feedback"
483 Removed: [
484 Removed: h2 [ txt "Workout complete" ];
485 Removed: p
486 Removed: [
487 Removed: txt "Record any context; this feedback is not scored or interpreted.";
488 Removed: ];
489 Removed: Form.post_form ~service:Routes.feedback
490 Removed: (fun (workout, rest) ->
491 Removed: let sleep, rest = rest in
492 Removed: let appetite, rest = rest in
493 Removed: let illness_stress, rest = rest in
494 Removed: let pain, rest = rest in
495 Removed: let pain_detail, rest = rest in
496 Removed: let motivation, rest = rest in
497 Removed: let recovery, rest = rest in
498 Removed: let difficulty, note = rest in
499 Removed: [
500 Removed: Form.input ~input_type:`Hidden ~name:workout
501 Removed: ~value:(record.Repository.id :> string)
502 Removed: Form.string;
503 Removed: label [ txt "Sleep" ];
504 Removed: option sleep
505 Removed: [ ("poor", "poor"); ("ordinary", "ordinary"); ("good", "good") ];
506 Removed: label [ txt "Appetite" ];
507 Removed: option appetite
508 Removed: [
509 Removed: ("reduced", "reduced");
510 Removed: ("ordinary", "ordinary");
511 Removed: ("increased", "increased");
512 Removed: ];
513 Removed: label [ txt "Illness or stress" ];
514 Removed: option illness_stress
515 Removed: [
516 Removed: ("none", "none");
517 Removed: ("illness", "illness");
518 Removed: ("stress", "stress");
519 Removed: ("both", "both");
520 Removed: ];
521 Removed: label [ txt "Pain or injury" ];
522 Removed: Form.input ~input_type:`Checkbox ~name:pain Form.bool;
523 Removed: Form.input ~input_type:`Text ~name:pain_detail Form.string;
524 Removed: label [ txt "Motivation" ];
525 Removed: option motivation
526 Removed: [ ("low", "low"); ("ordinary", "ordinary"); ("high", "high") ];
527 Removed: label [ txt "Perceived recovery" ];
528 Removed: option recovery
529 Removed: [ ("poor", "poor"); ("ordinary", "ordinary"); ("good", "good") ];
530 Removed: label [ txt "Exercise difficulty" ];
531 Removed: option difficulty
532 Removed: [
533 Removed: ("easier", "easier");
534 Removed: ("expected", "expected");
535 Removed: ("harder", "harder");
536 Removed: ];
537 Removed: label [ txt "Overall note" ];
538 Removed: Form.input ~input_type:`Text ~name:note Form.string;
539 Removed: Form.input ~input_type:`Submit ~value:"Save feedback" Form.string;
540 Removed: ])
541 Removed: ();
542 465 ]
lib/web/pages.mli
index a0ee8780..4eef14e9 100644..100644
@@ -23,6 +23,5 @@
23 23 page
24 24
25 25 val log_workout : record_id:string option -> workout:Evidence.Workout.t -> page
26 Removed: val feedback : record:Repository.record -> page
27 26 val history : records:Repository.record list -> page
28 27 val problem : title:string -> detail:string -> page
lib/web/routes.ml
index f2e08ffb..8c6ac244 100644..100644
@@ -101,19 +101,6 @@
101 101 (Eliom_parameter.unit, Eliom_parameter.(string "routine")))
102 102 ()
103 103
104 Removed: (* POST /feedback — attach categorical feedback to a finished workout. *)
105 Removed: let feedback =
106 Removed: Eliom_service.create ~path:(Eliom_service.Path [ "feedback" ])
107 Removed: ~meth:
108 Removed: (Eliom_service.Post
109 Removed: ( Eliom_parameter.unit,
110 Removed: Eliom_parameter.(
111 Removed: string "workout" ** string "sleep" ** string "appetite"
112 Removed: ** string "illness_stress" ** bool "pain" ** string "pain_detail"
113 Removed: ** string "motivation" ** string "recovery" ** string "difficulty"
114 Removed: ** string "note") ))
115 Removed: ()
116 Removed:
117 104 (* GET /edit — add prescribed records to a saved workout without changing its end. *)
118 105 let edit =
119 106 Eliom_service.create ~path:(Eliom_service.Path [ "edit" ])
lib/web/services.ml
index d1e041b8..55144aa7 100644..100644
@@ -106,71 +106,6 @@
106 106 if text = "" then None else Some text
107 107 in
108 108
109 Removed: let feedback_of_strings ~sleep:sleep_text ~appetite:appetite_text
110 Removed: ~illness_stress:illness_stress_text ~pain ~pain_detail:pain_detail_text
111 Removed: ~motivation:motivation_text ~recovery:recovery_text
112 Removed: ~difficulty:difficulty_text ~note:note_text =
113 Removed: let open Evidence.Workout.Feedback in
114 Removed: let sleep =
115 Removed: match sleep_text with
116 Removed: | "poor" -> Some Sleep_poor
117 Removed: | "ordinary" -> Some Sleep_ordinary
118 Removed: | "good" -> Some Sleep_good
119 Removed: | _ -> None
120 Removed: in
121 Removed: let appetite =
122 Removed: match appetite_text with
123 Removed: | "reduced" -> Some Appetite_reduced
124 Removed: | "ordinary" -> Some Appetite_ordinary
125 Removed: | "increased" -> Some Appetite_increased
126 Removed: | _ -> None
127 Removed: in
128 Removed: let illness_stress =
129 Removed: match illness_stress_text with
130 Removed: | "none" -> Some No_illness_or_stress
131 Removed: | "illness" -> Some Illness
132 Removed: | "stress" -> Some Stress
133 Removed: | "both" -> Some Illness_and_stress
134 Removed: | _ -> None
135 Removed: in
136 Removed: let motivation =
137 Removed: match motivation_text with
138 Removed: | "low" -> Some Motivation_low
139 Removed: | "ordinary" -> Some Motivation_ordinary
140 Removed: | "high" -> Some Motivation_high
141 Removed: | _ -> None
142 Removed: in
143 Removed: let recovery =
144 Removed: match recovery_text with
145 Removed: | "poor" -> Some Recovery_poor
146 Removed: | "ordinary" -> Some Recovery_ordinary
147 Removed: | "good" -> Some Recovery_good
148 Removed: | _ -> None
149 Removed: in
150 Removed: let difficulty =
151 Removed: match difficulty_text with
152 Removed: | "easier" -> Some Difficulty_easier
153 Removed: | "expected" -> Some Difficulty_expected
154 Removed: | "harder" -> Some Difficulty_harder
155 Removed: | _ -> None
156 Removed: in
157 Removed: match
158 Removed: (sleep, appetite, illness_stress, motivation, recovery, difficulty)
159 Removed: with
160 Removed: | ( Some sleep,
161 Removed: Some appetite,
162 Removed: Some illness_stress,
163 Removed: Some motivation,
164 Removed: Some recovery,
165 Removed: Some difficulty ) ->
166 Removed: Some
167 Removed: (make ~sleep ~appetite ~illness_stress ~pain_injury:pain
168 Removed: ?pain_detail:(optional_text pain_detail_text)
169 Removed: ~motivation ~recovery ~difficulty ?note:(optional_text note_text)
170 Removed: ())
171 Removed: | _ -> None
172 Removed: in
173 Removed:
174 109 (* The slot names which prescribed stimulus is being answered. *)
175 110 let target_workout workout_id =
176 111 if workout_id = "" then
@@ -297,34 +232,8 @@
297 232
298 233 Eliom_registration.Html.register ~service:Routes.finish (fun () () ->
299 234 match Service.finish service ~ended_at:(now ()) with
300 Removed: | Some record -> Lwt.return (Pages.feedback ~record)
235 Added: | Some _ -> Lwt.return (Pages.history ~records:(Service.history service))
301 236 | None ->
302 237 Lwt.return
303 238 (Pages.problem ~title:"No workout in progress"
304 Removed: ~detail:"There was nothing to finish."));
305 Removed:
306 Removed: Eliom_registration.Html.register ~service:Routes.feedback
307 Removed: (fun () (workout, rest) ->
308 Removed: let sleep, rest = rest in
309 Removed: let appetite, rest = rest in
310 Removed: let illness_stress, rest = rest in
311 Removed: let pain, rest = rest in
312 Removed: let pain_detail, rest = rest in
313 Removed: let motivation, rest = rest in
314 Removed: let recovery, rest = rest in
315 Removed: let difficulty, note = rest in
316 Removed: let id = Repository.workout_id workout in
317 Removed: Lwt.return
318 Removed: (match
319 Removed: feedback_of_strings ~sleep ~appetite ~illness_stress ~pain
320 Removed: ~pain_detail ~motivation ~recovery ~difficulty ~note
321 Removed: with
322 Removed: | None ->
323 Removed: Pages.problem ~title:"Invalid feedback"
324 Removed: ~detail:"Choose one of the offered answers."
325 Removed: | Some feedback -> (
326 Removed: match Service.set_feedback service id feedback with
327 Removed: | Ok _ -> Pages.history ~records:(Service.history service)
328 Removed: | Error _ ->
329 Removed: Pages.problem ~title:"Could not save feedback"
330 Removed: ~detail:"The finished workout was not found.")))
239 Added: ~detail:"There was nothing to finish."))
test/test_evidence.ml
index 3e2ad2ed..53ae6443 100644..100644
@@ -465,41 +465,35 @@
465 465 Alcotest.(check (option string))
466 466 "note" (Some "controlled negative")
467 467 (Stimulus.note (List.hd (Workout.stimuli workout))) );
468 Removed: ( "feedback requires finish and completeness updates after later edits",
468 Added: ( "feedback rejects duplicate signal categories",
469 469 `Quick,
470 470 fun () ->
471 Added: let open Feedback in
472 Added: match
473 Added: make ~reported_at:(at 60) [ Sleep Below_usual; Sleep Above_usual ]
474 Added: with
475 Added: | Error (Duplicate_signal (Sleep Above_usual)) -> ()
476 Added: | _ -> Alcotest.fail "expected duplicate sleep rejection" );
477 Added: ( "feedback retains its report time and signals",
478 Added: `Quick,
479 Added: fun () ->
480 Added: let open Feedback in
471 481 let feedback =
472 Removed: Workout.Feedback.make ~sleep:Workout.Feedback.Sleep_good
473 Removed: ~appetite:Workout.Feedback.Appetite_ordinary
474 Removed: ~illness_stress:Workout.Feedback.No_illness_or_stress
475 Removed: ~pain_injury:false ~motivation:Workout.Feedback.Motivation_high
476 Removed: ~recovery:Workout.Feedback.Recovery_good
477 Removed: ~difficulty:Workout.Feedback.Difficulty_expected ()
482 Added: ok
483 Added: (make ~reported_at:(at 60)
484 Added: [
485 Added: Sleep Above_usual;
486 Added: Appetite Usual;
487 Added: Readiness Above_usual;
488 Added: Motivation Above_usual;
489 Added: Difficulty Usual;
490 Added: Preparation_insufficient;
491 Added: ])
478 492 in
479 Removed: Alcotest.(check bool)
480 Removed: "feedback rejected before finish" true
481 Removed: (Result.is_error (Workout.set_feedback (fresh ()) feedback));
482 Removed: let workout = Workout.finish (fresh ()) ~ended_at:(at 60) in
483 Removed: let workout = ok (Workout.set_feedback workout feedback) in
484 Removed: Alcotest.(check bool)
485 Removed: "incomplete" true
486 Removed: (match Workout.completeness workout with
487 Removed: | Workout.Incomplete -> true
488 Removed: | Complete -> false);
489 Removed: let workout =
490 Removed: List.fold_left
491 Removed: (fun w stimulus -> ok (Workout.add_stimulus w stimulus))
492 Removed: workout day_one_stimuli
493 Removed: in
494 Removed: Alcotest.(check bool)
495 Removed: "complete after edits" true
496 Removed: (match Workout.completeness workout with
497 Removed: | Workout.Complete -> true
498 Removed: | Incomplete -> false);
499 493 Alcotest.(check int)
500 Removed: "end time retained" 60
501 Removed: (Recovery.timestamp_to_unix_seconds
502 Removed: (Option.get (Workout.ended_at workout))) );
494 Added: "report time" 60
495 Added: (Recovery.timestamp_to_unix_seconds (reported_at feedback));
496 Added: Alcotest.(check int) "six signals" 6 (List.length (signals feedback)) );
503 497 ]
504 498
505 499 let suite =
test/test_service.ml
index 1de9a0dd..ef52f32e 100644..100644
@@ -194,15 +194,6 @@
194 194 let s = service () in
195 195 ignore (ok (S.begin_workout s ~routine:ideal ~now:(day 1) ()));
196 196 let record = Option.get (S.finish s ~ended_at:(at 120)) in
197 Removed: let feedback =
198 Removed: Workout.Feedback.make ~sleep:Workout.Feedback.Sleep_ordinary
199 Removed: ~appetite:Workout.Feedback.Appetite_ordinary
200 Removed: ~illness_stress:Workout.Feedback.No_illness_or_stress
201 Removed: ~pain_injury:false ~motivation:Workout.Feedback.Motivation_ordinary
202 Removed: ~recovery:Workout.Feedback.Recovery_ordinary
203 Removed: ~difficulty:Workout.Feedback.Difficulty_expected ()
204 Removed: in
205 Removed: let record = ok (S.set_feedback s record.Repository.id feedback) in
206 197 let record =
207 198 List.fold_left
208 199 (fun record stimulus ->
@@ -217,10 +208,7 @@
217 208 Alcotest.(check int)
218 209 "original end" 120
219 210 (Recovery.timestamp_to_unix_seconds
220 Removed: (Option.get (Workout.ended_at record.Repository.workout)));
221 Removed: Alcotest.(check bool)
222 Removed: "feedback retained" true
223 Removed: (Option.is_some (Workout.feedback record.Repository.workout)) );
211 Added: (Option.get (Workout.ended_at record.Repository.workout))) );
224 212 ]
225 213
226 214 let assessment_tests =