diff options
| author | Marius Peter <dev@marius-peter.com> | 2026-07-18 23:23:35 +0200 |
|---|---|---|
| committer | Marius Peter <dev@marius-peter.com> | 2026-07-18 23:23:35 +0200 |
| commit | 753e25e99ddf57be78ff27ad89cb7e1bbc99d041 (patch) | |
| tree | 43db69ec341520f2923f2f13d0cd8846a9acac35 /src/models.lisp | |
Initial commit: HITO MVP
Heavy Duty I prescriptive training system encoding Mentzer's methodology.
- 4-day split program (Chest & Back, Legs, Shoulders, Arms)
- Prescription engine with pre-exhaust superset structure
- Strict progression logic (+5kg large / +2.5kg small muscles)
- Recovery gating with stall-based extension and override
- Onboarding via experience level (Novice/Intermediate/Advanced)
- Session logging with full progression analysis
- Exercise substitution (33 alternatives for 17 canonical movements)
- Objectivist aesthetic: black/gold, serif, rectilinear
Stack: Hunchentoot, Mito/SQLite, Djula, SBCL
Diffstat (limited to 'src/models.lisp')
| -rw-r--r-- | src/models.lisp | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/models.lisp b/src/models.lisp new file mode 100644 index 0000000..72a128c --- /dev/null +++ b/src/models.lisp @@ -0,0 +1,186 @@ +(in-package #:hito) + +;;; --- Database Configuration --- + +(defvar *db-path* + (asdf:system-relative-pathname "hito" "hito.db") + "Path to the SQLite database file.") + +;;; --- Table Definitions --- + +(mito:deftable exercise () + ((name :col-type (:varchar 128) + :documentation "Exercise name, e.g. 'Pec Deck'") + (muscle-group :col-type (:varchar 64) + :documentation "Primary muscle group: chest, back, legs, shoulders, arms") + (is-default-p :col-type :boolean + :initform t + :documentation "Whether this is a Mentzer canonical exercise") + (substitution-for :col-type (or :bigint :null) + :initform nil + :documentation "FK to exercise this substitutes for (nil if canonical)")) + (:documentation "An exercise in the HITO system. Mentzer's canonical movements are seeded by default.")) + +(mito:deftable user-profile () + ((name :col-type (:varchar 128) + :initform "Trainee" + :documentation "User's name") + (current-workout-day :col-type :integer + :initform 1 + :documentation "Next workout day in the rotation (1-4)") + (last-session-date :col-type (or :varchar :null) + :initform nil + :documentation "ISO date string of last completed session") + (training-start-date :col-type (or :varchar :null) + :initform nil + :documentation "ISO date string of when training began")) + (:documentation "Single user profile. Architected for multi-user extension.")) + +(mito:deftable training-session () + ((session-date :col-type (:varchar 32) + :documentation "ISO date string of this session") + (workout-day :col-type :integer + :documentation "Which day in the rotation (1-4)") + (completed-p :col-type :boolean + :initform nil + :documentation "Whether the session was completed") + (notes :col-type (or :text :null) + :initform nil + :documentation "Optional session notes") + (override-p :col-type :boolean + :initform nil + :documentation "Whether recovery gate was overridden for this session")) + (:documentation "A single training session, one workout day.")) + +(mito:deftable set-record () + ((session-id :col-type :bigint + :documentation "FK to training-session") + (exercise-id :col-type :bigint + :documentation "FK to exercise") + (set-number :col-type :integer + :documentation "Order within the exercise (1, 2, ...)") + (prescribed-weight :col-type :real + :documentation "Target weight in kg") + (prescribed-rep-low :col-type :integer + :initform 6 + :documentation "Lower bound of rep range") + (prescribed-rep-high :col-type :integer + :initform 10 + :documentation "Upper bound of rep range") + (actual-weight :col-type (or :real :null) + :initform nil + :documentation "Weight actually used (filled after session)") + (actual-reps :col-type (or :integer :null) + :initform nil + :documentation "Reps actually achieved (filled after session)") + (to-failure-p :col-type (or :boolean :null) + :initform nil + :documentation "Whether the set was taken to muscular failure")) + (:documentation "A single set within a session — both prescription and result.")) + +;;; --- Database Initialization --- + +(defun connect-db () + "Connect to the SQLite database." + (mito:connect-toplevel :sqlite3 + :database-name *db-path*)) + +(defun ensure-tables () + "Create or migrate all tables." + (mito:ensure-table-exists 'exercise) + (mito:ensure-table-exists 'user-profile) + (mito:ensure-table-exists 'training-session) + (mito:ensure-table-exists 'set-record)) + +;;; --- Seed Data: Mentzer's HD I Canonical Exercises --- + +(defparameter *hd1-exercises* + '(;; Day 1: Chest & Back + ("Pec Deck" "chest") + ("Incline Barbell Press" "chest") + ("Straight-Arm Pulldowns" "back") + ("Close-Grip Palms-Up Pulldowns" "back") + ("Bent-Over Barbell Rows" "back") + ;; Day 2: Legs + ("Leg Extensions" "legs") + ("Leg Press" "legs") + ("Squats" "legs") + ("Leg Curls" "legs") + ("Standing Calf Raises" "legs") + ("Toe Presses" "legs") + ;; Day 3: Shoulders + ("Lateral Raises" "shoulders") + ("Overhead Press" "shoulders") + ("Bent-Over Lateral Raises" "shoulders") + ;; Day 4: Arms + ("Barbell Curls" "arms") + ("Triceps Pushdowns" "arms") + ("Dips" "arms")) + "Mentzer's canonical Heavy Duty I exercises.") + +(defparameter *substitution-options* + '(;; Each entry: (canonical-exercise-name . list-of-substitute-names) + ("Pec Deck" "Cable Crossovers" "Dumbbell Flyes") + ("Incline Barbell Press" "Incline Dumbbell Press" "Flat Barbell Press") + ("Straight-Arm Pulldowns" "Dumbbell Pullovers" "Straight-Arm Cable Pushdowns") + ("Close-Grip Palms-Up Pulldowns" "Chin-Ups" "Close-Grip Cable Rows") + ("Bent-Over Barbell Rows" "T-Bar Rows" "Seated Cable Rows") + ("Leg Extensions" "Sissy Squats") + ("Leg Press" "Hack Squats" "Front Squats") + ("Squats" "Belt Squats" "Bulgarian Split Squats") + ("Leg Curls" "Romanian Deadlifts" "Glute-Ham Raises") + ("Standing Calf Raises" "Seated Calf Raises" "Donkey Calf Raises") + ("Toe Presses" "Calf Press on Leg Press") + ("Lateral Raises" "Cable Lateral Raises" "Machine Lateral Raises") + ("Overhead Press" "Dumbbell Shoulder Press" "Machine Shoulder Press") + ("Bent-Over Lateral Raises" "Reverse Pec Deck" "Face Pulls") + ("Barbell Curls" "Dumbbell Curls" "EZ-Bar Curls") + ("Triceps Pushdowns" "Overhead Triceps Extension" "Close-Grip Bench Press") + ("Dips" "Weighted Dips" "Decline Close-Grip Press")) + "Available substitutions for each canonical exercise. +The movement pattern is what matters, not brand loyalty to specific equipment.") + +(defun seed-exercises () + "Seed the database with Mentzer's canonical exercises if not already present." + (when (zerop (mito:count-dao 'exercise)) + (dolist (entry *hd1-exercises*) + (mito:create-dao 'exercise + :name (first entry) + :muscle-group (second entry) + :is-default-p t + :substitution-for nil)) + (format t "~&Seeded ~D canonical exercises.~%" (length *hd1-exercises*)))) + +(defun find-exercise-by-name (name) + "Look up an exercise DAO by name. Returns nil if not found." + (first (mito:select-dao 'exercise + (sxql:where (:= :name name))))) + +(defun seed-substitution-options () + "Seed available substitution exercises (not active, just available for selection). +These are stored with is-default-p = nil and substitution-for = nil until activated." + (dolist (entry *substitution-options*) + (let* ((canonical-name (first entry)) + (canonical (find-exercise-by-name canonical-name)) + (subs (rest entry))) + (when canonical + (dolist (sub-name subs) + ;; Only create if not already in DB + (unless (find-exercise-by-name sub-name) + (let ((muscle-group (exercise-muscle-group canonical))) + (mito:create-dao 'exercise + :name sub-name + :muscle-group muscle-group + :is-default-p nil + :substitution-for nil))))))) + (format t "~&Seeded substitution options.~%")) + +;;; --- Top-Level Initialization --- + +(defun init-db () + "Initialize the database: connect, create tables, seed data." + (connect-db) + (ensure-tables) + (seed-exercises) + (seed-substitution-options) + (format t "~&Database initialized at ~A~%" *db-path*)) |