refactor share strict-prior baselines

Keep native and imported progress comparisons on the same prior-record rule, including corrections at an equal timestamp.

Commit
909d018fa2b72df4102ff22605bbefc124bb06a6
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/core/progress.ml
index 61c16b92..8a9a2561 100644..100644
@@ -168,17 +168,31 @@
168 168 current : Evidence.Stimulus.Effort.t;
169 169 }
170 170
171 Removed: (* The latest observation strictly before the workout started. This prevents a
172 Removed: correction of the saved workout from comparing an exercise against itself. *)
173 Removed: let previous_effort log ~started exercise =
174 Removed: Evidence.Log.observations log exercise
175 Removed: |> List.filter (fun observation ->
171 Added: (* The latest observation strictly before [at]. This prevents a correction from
172 Added: comparing an exercise against itself when the recorded observation shares its
173 Added: workout's start time. Callers scope the observation series by exercise before
174 Added: they call this helper. *)
175 Added: let latest_before ~at observations =
176 Added: observations
177 Added: |> List.filter (fun (observation : Evidence.Observation.t) ->
176 178 Recovery.duration_to_seconds
177 179 (Recovery.elapsed
178 180 ~since:(Evidence.Observation.performed_at observation)
179 Removed: ~now:started)
181 Added: ~now:at)
180 182 > 0)
183 Added: |> List.sort (fun a b ->
184 Added: Int.compare
185 Added: (Recovery.timestamp_to_unix_seconds
186 Added: (Evidence.Observation.performed_at a))
187 Added: (Recovery.timestamp_to_unix_seconds
188 Added: (Evidence.Observation.performed_at b)))
181 189 |> last
190 Added:
191 Added: (* The latest observation strictly before the workout started. This prevents a
192 Added: correction of the saved workout from comparing an exercise against itself. *)
193 Added: let previous_effort log ~started exercise =
194 Added: Evidence.Log.observations log exercise
195 Added: |> latest_before ~at:started
182 196 |> Option.map Evidence.Observation.effort
183 197
184 198 let workout_comparisons log workout =
@@ -221,16 +235,5 @@
221 235 let performed = Evidence.Observation.performed_at target in
222 236 observations
223 237 |> List.filter (fun (o : Evidence.Observation.t) ->
224 Removed: Exercise.equal (Evidence.Observation.exercise o) exercise
225 Removed: && Recovery.duration_to_seconds
226 Removed: (Recovery.elapsed
227 Removed: ~since:(Evidence.Observation.performed_at o)
228 Removed: ~now:performed)
229 Removed: > 0)
230 Removed: |> List.sort (fun a b ->
231 Removed: Int.compare
232 Removed: (Recovery.timestamp_to_unix_seconds
233 Removed: (Evidence.Observation.performed_at a))
234 Removed: (Recovery.timestamp_to_unix_seconds
235 Removed: (Evidence.Observation.performed_at b)))
236 Removed: |> last
238 Added: Exercise.equal (Evidence.Observation.exercise o) exercise)
239 Added: |> latest_before ~at:performed
test/test_progression.ml
index d465f3d8..470117fc 100644..100644
@@ -311,6 +311,41 @@
311 311 let log_of workouts =
312 312 List.fold_left Evidence.Log.add Evidence.Log.empty workouts
313 313
314 Added: let strict_prior_tests =
315 Added: [
316 Added: ( "an observation at the target timestamp is not a baseline",
317 Added: `Quick,
318 Added: fun () ->
319 Added: let target = seen ~on:3 12. 9 in
320 Added: let same_time = seen ~on:3 14. 7 in
321 Added: Alcotest.(check bool)
322 Added: "no prior observation" true
323 Added: (Option.is_none (Progress.previous_observation [ same_time ] target))
324 Added: );
325 Added: ( "the latest earlier observation is the baseline",
326 Added: `Quick,
327 Added: fun () ->
328 Added: let target = seen ~on:3 16. 6 in
329 Added: let earliest = seen ~on:1 12. 8 in
330 Added: let latest = seen ~on:2 14. 7 in
331 Added: match Progress.previous_observation [ latest; earliest ] target with
332 Added: | Some previous ->
333 Added: Alcotest.(check (float 0.001))
334 Added: "latest load" 14.
335 Added: (Stimulus.Effort.load (Evidence.Observation.effort previous))
336 Added: | None -> Alcotest.fail "expected a prior observation" );
337 Added: ( "a workout does not compare against another record at its start time",
338 Added: `Quick,
339 Added: fun () ->
340 Added: let first = solo ~on:3 ~load:12. ~r:8 in
341 Added: let second = solo ~on:3 ~load:14. ~r:7 in
342 Added: Alcotest.(check int)
343 Added: "no comparison at the same timestamp" 0
344 Added: (List.length
345 Added: (Progress.workout_comparisons (log_of [ first; second ]) second))
346 Added: );
347 Added: ]
348 Added:
314 349 let workout_progress_tests =
315 350 [
316 351 ( "a first workout has nothing to beat",
@@ -405,6 +440,7 @@
405 440 ("progression.remedy", remedy_tests);
406 441 ("progression.load", load_tests);
407 442 ("progression.diagnostics", diagnostic_tests);
443 Added: ("progression.strict_prior", strict_prior_tests);
408 444 ("progression.workout_progress", workout_progress_tests);
409 445 ("progression.workout_changes", workout_change_tests);
410 446 ]