refactor return a result from Progress.assess

A record too young to judge is an ordinary state, not a violated precondition, yet assess raised Invalid Insufficient_data and the service caught the exception only to turn it back into Error. The core now returns the result directly and the exception is gone, so the service passes the judgment through unchanged. Recoverable outcomes travel as values; exceptions stay for broken authored plans.

Commit
736e4ef341f073ac29fd2651a8d9c33f973a0426
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 ecfa260f..4411782f 100644..100644
@@ -349,9 +349,7 @@
349 349 let progress t trainee exercise =
350 350 R.log t.repo trainee >>= fun log ->
351 351 R.imported_records t.repo trainee >|= fun records ->
352 Removed: match Progress.assess (merged_observations ~log ~records exercise) with
353 Removed: | assessment -> Ok assessment
354 Removed: | exception Progress.Invalid error -> Error error
352 Added: Progress.assess (merged_observations ~log ~records exercise)
355 353
356 354 (* The merged native and imported observation series for one exercise,
357 355 oldest first. The same merge the progress verdict is built on, exposed
lib/core/progress.ml
index 95ba15bd..61c16b92 100644..100644
@@ -15,8 +15,6 @@
15 15 type assessment = Progressing | Stalled | Decompensating
16 16 type error = Insufficient_data
17 17
18 Removed: exception Invalid of error
19 Removed:
20 18 let pp_assessment ppf = function
21 19 | Progressing -> Format.pp_print_string ppf "progressing"
22 20 | Stalled -> Format.pp_print_string ppf "stalled"
@@ -54,7 +52,7 @@
54 52 let assess observations =
55 53 let window = Recovery.duration_to_seconds stall_window in
56 54 match (observations, last observations) with
57 Removed: | ([] | [ _ ]), _ | _, None -> raise (Invalid Insufficient_data)
55 Added: | ([] | [ _ ]), _ | _, None -> Error Insufficient_data
58 56 | first :: _, Some latest -> (
59 57 (* After the window passes without progress, separate a halt from a loss.
60 58 Strength below the reference is decompensation. Strength that holds at
@@ -69,14 +67,14 @@
69 67 match last_advance observations with
70 68 | Some advance ->
71 69 if elapsed_between advance latest >= window then
72 Removed: verdict ~reference:advance
73 Removed: else Progressing
70 Added: Ok (verdict ~reference:advance)
71 Added: else Ok Progressing
74 72 | None ->
75 73 (* Never advanced. Only a stall once the record is long enough to say
76 74 so. Otherwise two sessions a day apart would condemn the routine. *)
77 75 if elapsed_between first latest >= window then
78 Removed: verdict ~reference:first
79 Removed: else raise (Invalid Insufficient_data))
76 Added: Ok (verdict ~reference:first)
77 Added: else Error Insufficient_data)
80 78
81 79 type remedy =
82 80 | Lay_off_then_reduce of {
lib/core/progress.mli
index f866abf9..c1c5a18e 100644..100644
@@ -12,17 +12,16 @@
12 12
13 13 type error = Insufficient_data
14 14
15 Removed: exception Invalid of error
16 Removed:
17 15 val pp_assessment : Format.formatter -> assessment -> unit
18 16 val pp_error : Format.formatter -> error -> unit
19 17
20 18 val stall_window : Recovery.duration
21 19 (** 14 days. *)
22 20
23 Removed: val assess : Evidence.Observation.t list -> assessment
24 Removed: (** Raises [Invalid Insufficient_data] when the record cannot support a
25 Removed: judgment. *)
21 Added: val assess : Evidence.Observation.t list -> (assessment, error) result
22 Added: (** [Error Insufficient_data] when the record cannot yet support a judgment: too
23 Added: few observations, or a record that never advanced but is younger than the
24 Added: stall window. This is an ordinary state of a young record, not a fault. *)
26 25
27 26 type remedy =
28 27 | Lay_off_then_reduce of {
test/test_progression.ml
index e78d6447..d465f3d8 100644..100644
@@ -29,11 +29,9 @@
29 29 Evidence.Observation.make ~exercise:(get "laterals") ~effort:(move load r)
30 30 ~performed_at:(day on)
31 31
32 Removed: let raises_insufficient_data f =
33 Removed: try
34 Removed: let _ = f () in
35 Removed: false
36 Removed: with Progress.Invalid Progress.Insufficient_data -> true
32 Added: (* A young record the judge cannot yet read answers [Error], an ordinary state,
33 Added: not a raised fault. *)
34 Added: let insufficient_data f = f () = Error Progress.Insufficient_data
37 35
38 36 let assess_tests =
39 37 [
@@ -46,14 +44,13 @@
46 44 fun () ->
47 45 Alcotest.(check bool)
48 46 "nothing" true
49 Removed: (raises_insufficient_data (fun () -> Progress.assess []));
47 Added: (insufficient_data (fun () -> Progress.assess []));
50 48 Alcotest.(check bool)
51 49 "a single session" true
52 Removed: (raises_insufficient_data (fun () ->
53 Removed: Progress.assess [ seen ~on:1 12. 8 ]));
50 Added: (insufficient_data (fun () -> Progress.assess [ seen ~on:1 12. 8 ]));
54 51 Alcotest.(check bool)
55 52 "flat but recent" true
56 Removed: (raises_insufficient_data (fun () ->
53 Added: (insufficient_data (fun () ->
57 54 Progress.assess [ seen ~on:1 12. 8; seen ~on:3 12. 8 ])) );
58 55 ( "a recent advance is progress",
59 56 `Quick,
@@ -62,7 +59,7 @@
62 59 "progressing" true
63 60 (Progress.assess
64 61 [ seen ~on:1 12. 8; seen ~on:3 12. 9; seen ~on:5 12. 9 ]
65 Removed: = Progress.Progressing) );
62 Added: = Ok Progress.Progressing) );
66 63 ( "no advance for two weeks is a stall",
67 64 `Quick,
68 65 fun () ->
@@ -70,7 +67,7 @@
70 67 "stalled" true
71 68 (Progress.assess
72 69 [ seen ~on:1 12. 8; seen ~on:8 12. 8; seen ~on:16 12. 8 ]
73 Removed: = Progress.Stalled) );
70 Added: = Ok Progress.Stalled) );
74 71 ( "an advance more than two weeks ago is also a stall",
75 72 `Quick,
76 73 fun () ->
@@ -78,7 +75,7 @@
78 75 "stalled" true
79 76 (Progress.assess
80 77 [ seen ~on:1 12. 8; seen ~on:3 12. 9; seen ~on:20 12. 9 ]
81 Removed: = Progress.Stalled) );
78 Added: = Ok Progress.Stalled) );
82 79 ( "a two-week decline is decompensation",
83 80 `Quick,
84 81 fun () ->
@@ -86,7 +83,7 @@
86 83 "decompensating" true
87 84 (Progress.assess
88 85 [ seen ~on:1 12. 8; seen ~on:8 12. 8; seen ~on:16 10. 7 ]
89 Removed: = Progress.Decompensating) );
86 Added: = Ok Progress.Decompensating) );
90 87 ]
91 88
92 89 let remedy_tests =
@@ -299,7 +296,7 @@
299 296 "and the routine is stalled" true
300 297 (Progress.assess
301 298 [ seen ~on:1 12. 8; seen ~on:8 12. 8; seen ~on:16 12. 8 ]
302 Removed: = Progress.Stalled) );
299 Added: = Ok Progress.Stalled) );
303 300 ]
304 301
305 302 (* --- per-workout progress count --- *)