aboutsummaryrefslogtreecommitdiffstats
path: root/egometrics.py
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-11 21:16:57 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-11 21:47:56 +0100
commit6e7e00846e658cb79d0c23e18939c59fedba06dd (patch)
tree72b9565842186c66a2837ff3561b1010020daebc /egometrics.py
downloadEgoMetrics-6e7e00846e658cb79d0c23e18939c59fedba06dd.tar.gz
EgoMetrics-6e7e00846e658cb79d0c23e18939c59fedba06dd.zip
Add workout logging CLI with SQLite storage
Exercises CRUD, session logging with sets/reps/RPE/rest/LSRPE, session viewing and deletion. Interactive terminal menu.
Diffstat (limited to 'egometrics.py')
-rw-r--r--egometrics.py278
1 files changed, 278 insertions, 0 deletions
diff --git a/egometrics.py b/egometrics.py
new file mode 100644
index 0000000..eda2faa
--- /dev/null
+++ b/egometrics.py
@@ -0,0 +1,278 @@
+import sqlite3
+
+from db import get_connection, init_db
+import models
+import ui
+
+
+def main_menu(conn: sqlite3.Connection) -> None:
+ while True:
+ print("\n=== EgoMetrics ===\n")
+ print("1. Log Workout")
+ print("2. View Sessions")
+ print("3. Manage Exercises")
+ print("4. Quit")
+ choice = input("\n> ").strip()
+ if choice == "1":
+ log_workout(conn)
+ elif choice == "2":
+ view_sessions(conn)
+ elif choice == "3":
+ manage_exercises(conn)
+ elif choice == "4":
+ break
+
+
+def manage_exercises(conn: sqlite3.Connection) -> None:
+ while True:
+ ui.print_header("Manage Exercises")
+ print("1. List Exercises")
+ print("2. Add Exercise")
+ print("3. Edit Exercise")
+ print("4. Delete Exercise")
+ print("5. Back")
+ choice = input("\n> ").strip()
+ if choice == "1":
+ list_exercises(conn)
+ elif choice == "2":
+ add_exercise(conn)
+ elif choice == "3":
+ edit_exercise(conn)
+ elif choice == "4":
+ delete_exercise(conn)
+ elif choice == "5":
+ break
+
+
+def list_exercises(conn: sqlite3.Connection) -> None:
+ exercises = models.list_exercises(conn)
+ ui.print_header("All Exercises")
+ ui.print_table(
+ ["ID", "Name", "Note"],
+ [[str(e["id"]), e["name"], e["note"] or ""] for e in exercises],
+ )
+
+
+def add_exercise(conn: sqlite3.Connection) -> None:
+ name = ui.prompt_str("Name: ")
+ assert name is not None
+ note = ui.prompt_str("Note (optional): ", required=False)
+ try:
+ models.add_exercise(conn, name, note)
+ print(f'Exercise "{name}" added.')
+ except sqlite3.IntegrityError:
+ print(f'Exercise "{name}" already exists.')
+
+
+def edit_exercise(conn: sqlite3.Connection) -> None:
+ list_exercises(conn)
+ eid = ui.prompt_int("\nExercise ID to edit: ")
+ assert eid is not None
+ ex = models.get_exercise(conn, eid)
+ if not ex:
+ print("Exercise not found.")
+ return
+ print(f'Editing "{ex["name"]}" (leave blank to keep current value)')
+ name = ui.prompt_str(f"Name [{ex['name']}]: ", required=False)
+ note = ui.prompt_str(f"Note [{ex['note'] or ''}]: ", required=False)
+ try:
+ models.update_exercise(conn, eid, name, note)
+ print("Exercise updated.")
+ except sqlite3.IntegrityError:
+ print(f'An exercise named "{name}" already exists.')
+
+
+def delete_exercise(conn: sqlite3.Connection) -> None:
+ list_exercises(conn)
+ eid = ui.prompt_int("\nExercise ID to delete: ")
+ assert eid is not None
+ ex = models.get_exercise(conn, eid)
+ if not ex:
+ print("Exercise not found.")
+ return
+ if not ui.confirm(f'Delete "{ex["name"]}"?'):
+ return
+ try:
+ models.delete_exercise(conn, eid)
+ print("Exercise deleted.")
+ except sqlite3.IntegrityError:
+ print(f'Cannot delete "{ex["name"]}" — it is used in existing sessions.')
+
+
+def log_workout(conn: sqlite3.Connection) -> None:
+ exercises = models.list_exercises(conn)
+ if not exercises:
+ print("No exercises defined. Add some first via Manage Exercises.")
+ return
+
+ # Session metadata
+ ui.print_header("Log Workout")
+ date_time = ui.prompt_datetime("Date/Time")
+ session_note = ui.prompt_str("Session note (optional): ", required=False)
+
+ # Build exercise list — collect everything in memory before saving
+ entries = []
+ while True:
+ print("\nAvailable exercises:")
+ for e in exercises:
+ print(f" {e['id']}. {e['name']}")
+ print()
+ choice = input("Select exercise ID ('n' = new exercise, 'd' = done): ").strip()
+
+ if choice.lower() == "d":
+ if not entries:
+ print("No exercises added. Aborting.")
+ return
+ break
+
+ # Inline exercise creation
+ elif choice.lower() == "n":
+ name = ui.prompt_str("Exercise name: ")
+ assert name is not None
+ note = ui.prompt_str("Note (optional): ", required=False)
+ try:
+ models.add_exercise(conn, name, note)
+ exercises = models.list_exercises(conn)
+ print(f'Exercise "{name}" added.')
+ except sqlite3.IntegrityError:
+ print(f'Exercise "{name}" already exists.')
+ continue
+
+ # Select existing exercise and prompt for set details
+ try:
+ eid = int(choice)
+ except ValueError:
+ print("Invalid input.")
+ continue
+ ex = models.get_exercise(conn, eid)
+ if not ex:
+ print("Exercise not found.")
+ continue
+
+ print(f"\n -- {ex['name']} --")
+ sets = ui.prompt_int(" Sets: ", min_val=1)
+ reps = ui.prompt_int(" Reps: ", min_val=1)
+ rpe = ui.prompt_int(" RPE (optional): ", required=False, min_val=1, max_val=10)
+ rest_time = ui.prompt_int(
+ " Rest time in seconds (optional): ", required=False, min_val=0
+ )
+ lsrpe = ui.prompt_int(
+ " Last Set RPE (optional): ", required=False, min_val=1, max_val=10
+ )
+ note = ui.prompt_str(" Note (optional): ", required=False)
+
+ entries.append(
+ {
+ "exercise_id": eid,
+ "exercise_name": ex["name"],
+ "sets": sets,
+ "reps": reps,
+ "rpe": rpe,
+ "rest_time": rest_time,
+ "lsrpe": lsrpe,
+ "note": note,
+ }
+ )
+ print(f' "{ex["name"]}" added to session.')
+
+ # Show summary and confirm before writing to DB
+ ui.print_header("Session Summary")
+ print(f"Date: {date_time}")
+ if session_note:
+ print(f"Note: {session_note}")
+ ui.print_table(
+ ["#", "Exercise", "Sets", "Reps", "RPE", "Rest", "LSRPE", "Note"],
+ [
+ [
+ str(i),
+ e["exercise_name"],
+ str(e["sets"]),
+ str(e["reps"]),
+ str(e["rpe"] or ""),
+ f"{e['rest_time']}s" if e["rest_time"] else "",
+ str(e["lsrpe"] or ""),
+ e["note"] or "",
+ ]
+ for i, e in enumerate(entries, 1)
+ ],
+ )
+
+ if ui.confirm("\nSave this session?"):
+ models.save_session(conn, date_time, session_note, entries)
+ print("Session saved!")
+ else:
+ print("Session discarded.")
+
+
+def view_sessions(conn: sqlite3.Connection) -> None:
+ while True:
+ # List all sessions with exercise name preview
+ sessions = models.list_sessions(conn)
+ if not sessions:
+ print("\nNo sessions recorded yet.")
+ return
+ ui.print_header("Past Sessions")
+ ui.print_table(
+ ["#", "Date", "Exercises", "Note"],
+ [
+ [str(i), s["date_time"], s["exercises"] or "", s["note"] or ""]
+ for i, s in enumerate(sessions, 1)
+ ],
+ )
+
+ # Select a session to view details
+ choice = input("\nSelect # for details ('b' = back): ").strip()
+ if choice.lower() == "b":
+ break
+ try:
+ idx = int(choice) - 1
+ if idx < 0 or idx >= len(sessions):
+ print("Invalid selection.")
+ continue
+ except ValueError:
+ print("Invalid input.")
+ continue
+
+ # Show full session detail with all exercise entries
+ session_id = sessions[idx]["id"]
+ session, entries = models.get_session_detail(conn, session_id)
+ assert session is not None
+ ui.print_header(f"Session: {session['date_time']}")
+ if session["note"]:
+ print(f"Note: {session['note']}\n")
+ ui.print_table(
+ ["#", "Exercise", "Sets", "Reps", "RPE", "Rest", "LSRPE", "Note"],
+ [
+ [
+ str(e["position"]),
+ e["exercise_name"],
+ str(e["sets"]),
+ str(e["reps"]),
+ str(e["rpe"] or ""),
+ f"{e['rest_time']}s" if e["rest_time"] else "",
+ str(e["lsrpe"] or ""),
+ e["note"] or "",
+ ]
+ for e in entries
+ ],
+ )
+ action = input("\nActions: (b)ack, (d)elete session\n> ").strip().lower()
+ if action == "d":
+ if ui.confirm("Delete this session?"):
+ models.delete_session(conn, session_id)
+ print("Session deleted.")
+
+
+def main() -> None:
+ init_db()
+ conn = get_connection()
+ try:
+ main_menu(conn)
+ except KeyboardInterrupt:
+ print("\nGoodbye!")
+ finally:
+ conn.close()
+
+
+if __name__ == "__main__":
+ main()