(** Interprets recorded training against Heavy Duty rules. *) type assessment = Progressing | Stalled type error = Insufficient_data exception Invalid of error val pp_assessment : Format.formatter -> assessment -> unit val pp_error : Format.formatter -> error -> unit val stall_window : Recovery.duration (** 14 days. *) val assess : Evidence.Log.observation list -> assessment (** Raises [Invalid Insufficient_data] when the record cannot support a judgment. *) type remedy = | Lay_off_then_reduce of { lay_off : Recovery.duration; drop_stimuli_per_workout : int; extra_rest : Recovery.duration; } val remedy : assessment -> remedy option (** [Some] only for [Stalled]; it never adds work. *) val pp_remedy : Format.formatter -> remedy -> unit val load_increase_trigger : int (** 12 reps, regardless of the prescribed range. *) val load_increase : current:float -> float * float (** 10-20% increase window. *) type load_verdict = Hold | Increase of float * float | Too_heavy val judge_load : rep_range:Prescription.Rep_range.t -> Evidence.Stimulus.Effort.t -> load_verdict (** Only the range floor matters: below it is [Too_heavy]; at twelve reps the verdict is [Increase]. *) val pp_load_verdict : Format.formatter -> load_verdict -> unit (** Counts recorded workouts that show each overtraining risk. *) type diagnostic = | Excess_volume of int | Extensions_on_every_stimulus of int | Trained_under_recovered of int val diagnose : Evidence.Log.t -> diagnostic list val pp_diagnostic : Format.formatter -> diagnostic -> unit