Generated: 2026-03-01
Source: gym_coach.py — SCHEMA_SQL (lines 78-192) + ensure_schema() (lines 238-259)
12 tables, 3 indexes. All in single SQLite file: gym_coach.sqlite.
meta ← key-value session state store
program_days ← training program structure (days)
program_exercises ─┘ exercises per day
exercise_aliases ← NL normalization dictionary
workout_sessions ← session lifecycle (active/completed/aborted/skipped)
exercise_sets ─┘ individual logged sets per session
muscle_groups ← catalog of muscle names
exercise_muscles ← exercise-to-muscle activation mappings
user_profile ← singleton user settings row (id=1)
readiness_logs ← daily recovery/readiness entries
adaptation_decisions ← history of adaptation recommendations
session_exercise_substitutions ← exercise swap events per session
Key-value store. Stores active workout session state.
| Column | Type | Constraints | Notes |
|---|---|---|---|
| key | TEXT | PRIMARY KEY | active_session_id, last_exercise_name, active_session_paused |
| value | TEXT | NOT NULL | String value |
Known keys at runtime:
active_session_id— ID of current active workout sessionlast_exercise_name— last logged exercise (for omitting exercise in repeat sets)active_session_paused—"1"if session is paused
meta can become stale if process crashes mid-workout. No cleanup mechanism.
Defines the ordered sequence of training days in the program.
| Column | Type | Constraints |
|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| name | TEXT | NOT NULL UNIQUE |
| position | INTEGER | NOT NULL |
Exercises belonging to each program day.
| Column | Type | Constraints |
|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| day_id | INTEGER | NOT NULL, FK → program_days(id) |
| name | TEXT | NOT NULL |
| position | INTEGER | NOT NULL |
| target_sets | INTEGER | nullable |
| target_reps | TEXT | nullable, e.g. "8-10" |
Maps user input aliases to canonical exercise names.
| Column | Type | Constraints |
|---|---|---|
| alias | TEXT | PRIMARY KEY (stored lowercase) |
| canonical_name | TEXT | NOT NULL |
Example: "жим" → "Bench Press", "bench" → "Bench Press"
Core workout session record. One per training session.
| Column | Type | Constraints | Notes |
|---|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT | |
| date | TEXT | NOT NULL | YYYY-MM-DD format |
| day_name | TEXT | nullable | Program day name |
| raw_text | TEXT | nullable | Accumulated raw log text |
| created_at | TEXT | NOT NULL | ISO UTC timestamp |
| status | TEXT | NOT NULL DEFAULT 'active' |
active, completed, aborted, skipped |
| completed_at | TEXT | nullable | Set on workout done |
| aborted_at | TEXT | nullable | Set on workout abort |
| is_extra | INTEGER | DEFAULT 0 | 1 = extra/unplanned session (excluded from next) |
Status lifecycle:
start → active
done → completed
abort → aborted
skip → skipped (inserted directly with status='skipped')
cmd_log inserts without specifying status, so historical imports get status='active' — semantically incorrect. Should be 'completed'.
Indexes:
idx_sessions_dateondate
Individual sets logged within a session.
| Column | Type | Constraints | Notes |
|---|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT | |
| session_id | INTEGER | NOT NULL, FK → workout_sessions(id) | |
| exercise_name | TEXT | NOT NULL | Canonical name after alias resolution |
| set_index | INTEGER | NOT NULL | Per-exercise index within session (1-based) |
| weight | REAL | nullable | kg |
| reps | INTEGER | nullable | |
| rpe | REAL | nullable | Rate of Perceived Exertion 1-10 |
| notes | TEXT | nullable | Free-form notes |
Indexes:
idx_sets_exercise_nameonexercise_name
Catalog/registry of known muscle group names.
| Column | Type | Constraints |
|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| name | TEXT | NOT NULL UNIQUE |
Seeded by init: chest, back, lats, upper_back, rear_delts, front_delts, side_delts, biceps, triceps, quads, hamstrings, glutes, calves, abs
Activation mapping between exercises and muscle groups.
| Column | Type | Constraints | Notes |
|---|---|---|---|
| exercise_name | TEXT | NOT NULL, PK part | |
| muscle_name | TEXT | NOT NULL, PK part | |
| weight | REAL | DEFAULT 1.0 | Activation fraction (0.0–1.0) |
Primary key: (exercise_name, muscle_name)
Index: idx_exercise_muscles_muscle on muscle_name
Seeded by init from DEFAULT_EXERCISE_MUSCLES dict (30+ exercises, EN + RU aliases).
Singleton row (id=1). User coaching preferences.
| Column | Type | Notes |
|---|---|---|
| id | INTEGER | CHECK(id=1) — singleton |
| goal | TEXT | e.g. "strength+hypertrophy" |
| priority | TEXT | e.g. "aesthetics+strength" |
| periodization | TEXT | "linear", "undulating", "block" |
| rest_seconds_compound | INTEGER | DEFAULT 120 |
| rest_seconds_isolation | INTEGER | DEFAULT 90 |
| updated_at | TEXT | ISO UTC |
| days_per_week | INTEGER | Added via ALTER TABLE |
| session_minutes | INTEGER | Added via ALTER TABLE |
| injuries | TEXT | Added via ALTER TABLE |
| experience | TEXT | Added via ALTER TABLE ("beginner", "intermediate", "advanced") |
days_per_week, session_minutes, injuries, experience are added via ALTER TABLE in ensure_schema() and onboard.py independently — same migration logic duplicated in two places.
Daily recovery/readiness check-ins.
| Column | Type | Constraints | Notes |
|---|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT | |
| date | TEXT | NOT NULL | YYYY-MM-DD |
| sleep_hours | REAL | nullable | 0–24 |
| stress | INTEGER | nullable | 1–10 |
| fatigue | INTEGER | nullable | 1–10 |
| soreness | INTEGER | nullable | 1–10 |
| pain | INTEGER | nullable | 1–10 |
| mood | INTEGER | nullable | 1–10 |
| notes | TEXT | nullable | |
| created_at | TEXT | NOT NULL | ISO UTC |
Index: idx_readiness_date on date
Audit log of all adaptation recommendations generated.
| Column | Type | Notes |
|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| date | TEXT | YYYY-MM-DD |
| exercise_name | TEXT | nullable |
| decision | TEXT | "keep", "progress", "deload", "plateau" |
| load_adjust_pct | REAL | e.g. 5.0, -10.0 |
| reps_adjust | INTEGER | currently always 0 |
| reason_codes | TEXT | comma-separated, e.g. "at_top_of_rep_range,rpe_below_8" |
| inputs_json | TEXT | JSON snapshot of readiness + session count |
| created_at | TEXT | ISO UTC |
Index: idx_adapt_date on date
Records exercise swap events during a session.
| Column | Type | Notes |
|---|---|---|
| id | INTEGER | PRIMARY KEY AUTOINCREMENT |
| session_id | INTEGER | FK → workout_sessions(id) |
| original_exercise | TEXT | |
| substituted_exercise | TEXT | |
| reason | TEXT | nullable |
| created_at | TEXT | ISO UTC |
Two large dictionaries defined in gym_coach.py — data that arguably belongs in the DB:
30+ exercise→muscle activation mappings. Seeded into exercise_muscles on init.
Exists both in-memory (for fallback) and in DB. Duplication.
MEV/MAV/MRV per muscle group (Mike Israetel science-based volumes). Hardcoded only — never stored in DB, no user override possible.
program_days (1) ──< program_exercises (N)
workout_sessions (1) ──< exercise_sets (N)
workout_sessions (1) ──< session_exercise_substitutions (N)
exercise_sets.exercise_name >── exercise_muscles.exercise_name >── muscle_groups
exercise_aliases ──> exercise_sets.exercise_name (via normalization)
adaptation_decisions (standalone — exercise_name is denormalized string)
readiness_logs (standalone)
meta (standalone key-value)
user_profile (singleton)
Schema is evolved via ensure_schema() using manual PRAGMA table_info + ALTER TABLE:
- No migration versioning
- Idempotent (checks column existence before adding)
onboard.pyduplicates the same migration logic for its extra columns- Recommendation: Extract shared migration logic or use a simple version counter in
meta