log_workout
Record workout details including exercises, sets, and metrics to track fitness progress in a personal database.
Instructions
Log a complete workout with exercises and sets.
Returns the fully logged workout with all exercises and sets for confirmation.
Args: date_time: ISO datetime string (e.g., "2026-01-06T18:30:00") workout_type: Optional type/category for the workout tags: Optional list of tags (e.g., ["legs", "sprint"]) notes: Optional notes for the workout exercises: List of exercises with sets. Each exercise should have: - name: str (required) - category: Optional[str] - notes: Optional[str] - sets: List of sets with fields like reps, weight_kg, weight_lbs, distance_yards, side, etc.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| date_time | Yes | ||
| workout_type | No | ||
| tags | No | ||
| notes | No | ||
| exercises | No |
Implementation Reference
- src/main.py:76-162 (handler)The 'log_workout' tool implementation, which processes the input arguments, handles potential JSON strings for tags/exercises, inserts the workout and exercise/set data into the SQLite database, and returns the fully hydrated workout details.
@app.tool() def log_workout( date_time: str, workout_type: Optional[str] = None, tags: Optional[Any] = None, notes: Optional[str] = None, exercises: Optional[Any] = None, ) -> dict[str, Any]: """Log a complete workout with exercises and sets. Returns the fully logged workout with all exercises and sets for confirmation. Args: date_time: ISO datetime string (e.g., "2026-01-06T18:30:00") workout_type: Optional type/category for the workout tags: Optional list of tags (e.g., ["legs", "sprint"]) notes: Optional notes for the workout exercises: List of exercises with sets. Each exercise should have: - name: str (required) - category: Optional[str] - notes: Optional[str] - sets: List of sets with fields like reps, weight_kg, weight_lbs, distance_yards, side, etc. """ # Handle JSON string inputs (for clients that serialize arrays as strings) tags = _parse_json_array(tags) exercises = _parse_json_array(exercises) # Validate types after parsing if tags is not None and not isinstance(tags, list): raise TypeError(f"tags must be a list, got {type(tags).__name__}") if exercises is not None and not isinstance(exercises, list): raise TypeError(f"exercises must be a list, got {type(exercises).__name__}") date_time = _ensure_iso_date(date_time) tags_json = serialize_tags(tags) conn = get_connection() cursor = conn.cursor() cursor.execute( "INSERT INTO workouts (date_time, workout_type, tags, notes) VALUES (?, ?, ?, ?)", (date_time, workout_type, tags_json, notes), ) workout_id = cursor.lastrowid if exercises: for order_index, exercise in enumerate(exercises, start=1): exercise_name = exercise.get("name") if not exercise_name: raise ValueError(f"Exercise at index {order_index} is missing required 'name' field") cursor.execute( "INSERT INTO exercises (workout_id, order_index, name, category, notes) VALUES (?, ?, ?, ?, ?)", (workout_id, order_index, exercise_name, exercise.get("category"), exercise.get("notes")), ) exercise_id = cursor.lastrowid for set_index, set_payload in enumerate(exercise.get("sets", []), start=1): cursor.execute( """INSERT INTO sets ( exercise_id, set_index, reps, weight_kg, weight_lbs, distance_m, distance_yards, duration_s, side, rpe, rir, is_warmup ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( exercise_id, set_payload.get("set_index") or set_index, set_payload.get("reps"), set_payload.get("weight_kg"), set_payload.get("weight_lbs"), set_payload.get("distance_m"), set_payload.get("distance_yards"), set_payload.get("duration_s"), set_payload.get("side"), set_payload.get("rpe"), set_payload.get("rir"), 1 if set_payload.get("is_warmup") else 0, ), ) conn.commit() # Return the fully hydrated workout for confirmation workout = cursor.execute("SELECT * FROM workouts WHERE id = ?", (workout_id,)).fetchone() result = _hydrate_workout(conn, _row_to_dict(workout)) conn.close() return result