blob: c4edabdd1ad85d016eee719debe0c3aa307dc6f8 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
(in-package #:hito)
;;; --- Recovery Gating System ---
;;;
;;; "The muscles DO NOT grow during training. They grow during REST.
;;; If you are not making progress, you are not overtraining —
;;; you are under-recovering."
;;;
;;; Recovery is not optional. It is where growth occurs. The system enforces
;;; minimum rest periods and extends them when stalls indicate incomplete recovery.
;;; --- Recovery Configuration ---
(defparameter *base-recovery-days* 3
"Minimum rest days between training sessions (Mentzer's HD I starting point).")
(defparameter *stall-extension-days* 2
"Additional rest days added when a stall is detected in recent sessions.")
(defparameter *consecutive-stall-extension-days* 3
"Additional rest days for consecutive stalls across sessions of the same day.")
(defparameter *max-recovery-days* 14
"Maximum recovery period before the system suggests program reassessment.")
;;; --- Recovery Calculation ---
(defun count-recent-stalls (workout-day &optional (lookback 2))
"Count sessions of WORKOUT-DAY in the last LOOKBACK completed sessions
that had at least one stall."
(let* ((sessions (mito:select-dao 'training-session
(sxql:where (:and (:= :workout-day workout-day)
(:= :completed-p 1)))
(sxql:order-by (:desc :session-date))
(sxql:limit lookback)))
(stall-count 0))
(dolist (session sessions)
(let ((records (mito:select-dao 'set-record
(sxql:where (:= :session-id (mito:object-id session))))))
(when (some (lambda (rec)
(let ((actual (set-record-actual-reps rec))
(low (set-record-prescribed-rep-low rec)))
(and actual (< actual low))))
records)
(incf stall-count))))
stall-count))
(defun calculate-recovery-days (&optional session)
"Calculate the required recovery days based on recent performance.
If SESSION is provided, uses its workout-day for stall analysis.
Otherwise uses the user's current workout day.
Returns (values days reason) where reason explains the calculation."
(let* ((workout-day (if session
(training-session-workout-day session)
(let ((profile (first (mito:select-dao 'user-profile))))
(if profile
(user-profile-current-workout-day profile)
1))))
(stalls (count-recent-stalls workout-day))
(base *base-recovery-days*)
(extension (cond
((>= stalls 2)
*consecutive-stall-extension-days*)
((>= stalls 1)
*stall-extension-days*)
(t 0)))
(total (min (+ base extension) *max-recovery-days*))
(reason (cond
((>= stalls 2)
(format nil "Consecutive stalls detected across recent sessions of Day ~D. ~
Extended recovery is not merely suggested — it is required. ~
Growth occurs during rest." workout-day))
((>= stalls 1)
(format nil "A stall was detected in your last Day ~D session. ~
Additional recovery time has been prescribed." workout-day))
(t
(format nil "Standard recovery period between sessions.")))))
(values total reason)))
;;; --- Recovery Status ---
(defstruct recovery-status
"Current recovery state for the trainee."
ready-p ; t if they can train
days-remaining ; days until ready (0 if ready)
next-date ; recommended next training date (string)
reason ; explanation text
last-session-date ; when they last trained
recovery-days) ; total prescribed recovery
(defun parse-date-string (date-str)
"Parse an ISO date string (YYYY-MM-DD) to a local-time timestamp."
(when (and date-str (plusp (length date-str)))
(local-time:parse-timestring date-str)))
(defun format-date (timestamp)
"Format a local-time timestamp as YYYY-MM-DD."
(local-time:format-timestring nil timestamp :format '(:year "-" (:month 2) "-" (:day 2))))
(defun days-since (date-string)
"Return the number of days elapsed since DATE-STRING (YYYY-MM-DD format)."
(let ((then (parse-date-string date-string))
(now (local-time:now)))
(when then
(let ((diff (local-time:timestamp-difference now then)))
(floor diff 86400))))) ; seconds -> days
(defun add-days-to-date (date-string days)
"Add DAYS to a date string, return new date string."
(let ((timestamp (parse-date-string date-string)))
(when timestamp
(format-date (local-time:timestamp+ timestamp days :day)))))
(defun recovery-status ()
"Compute the current recovery status for the trainee.
Returns a recovery-status struct."
(let* ((last-session (first (mito:select-dao 'training-session
(sxql:where (:= :completed-p 1))
(sxql:order-by (:desc :session-date))
(sxql:limit 1)))))
(if (null last-session)
;; No sessions yet — ready to train
(make-recovery-status
:ready-p t
:days-remaining 0
:next-date (format-date (local-time:now))
:reason "No previous sessions recorded. You are ready to begin."
:last-session-date nil
:recovery-days 0)
;; Calculate based on last session
(let* ((last-date (training-session-session-date last-session))
(elapsed (days-since last-date)))
(multiple-value-bind (required-days reason)
(calculate-recovery-days last-session)
(let* ((remaining (max 0 (- required-days elapsed)))
(ready (zerop remaining))
(next-date (if ready
(format-date (local-time:now))
(add-days-to-date last-date required-days))))
(make-recovery-status
:ready-p ready
:days-remaining remaining
:next-date next-date
:reason reason
:last-session-date last-date
:recovery-days required-days)))))))
;;; --- Override Mechanism ---
(defparameter *override-warning*
"You are overriding the prescribed recovery period. Understand: growth occurs ~
during rest, not during training. The muscles require time to compensate and ~
supercompensate. If you train before recovery is complete, you are not merely ~
wasting effort — you are actively undermining your progress. Proceed only if ~
you are certain your recovery is complete."
"Warning text displayed when user overrides recovery gate. Mentzer's voice.")
(defun override-recovery ()
"Log that the user has overridden the recovery gate.
Returns the warning text. The session created after this should be marked
with override-p = t."
(format t "~&[OVERRIDE] Recovery gate overridden at ~A~%"
(format-date (local-time:now)))
*override-warning*)
;;; --- Recovery Formatting ---
(defun format-recovery-status (status)
"Format recovery status for display."
(with-output-to-string (out)
(if (recovery-status-ready-p status)
(format out "READY. You may train today.")
(format out "RECOVERY IN PROGRESS.~%~
Days remaining: ~D~%~
Next session: ~A~%~
~A"
(recovery-status-days-remaining status)
(recovery-status-next-date status)
(recovery-status-reason status)))))
|