From 87aee894814529f856030be4768316bea75a32e8 Mon Sep 17 00:00:00 2001 From: QueryPlanner Date: Sun, 7 Jun 2026 20:18:18 +0530 Subject: [PATCH] refactor: remove redundant workout tracking tools - Remove get_exercise_progress and list_recent_workouts tools - Remove unused Pydantic models ExerciseHistoryEntry and WorkoutSummary - Remove storage methods get_exercise_history and get_recent_sessions - Update tool registry and tests to reflect removed features --- src/blacki/registry.py | 4 -- src/blacki/workouts/__init__.py | 4 -- src/blacki/workouts/storage.py | 110 -------------------------------- src/blacki/workouts/tools.py | 41 ------------ tests/test_registry.py | 2 +- tests/workouts/test_storage.py | 108 +------------------------------ tests/workouts/test_tools.py | 37 +---------- 7 files changed, 4 insertions(+), 302 deletions(-) diff --git a/src/blacki/registry.py b/src/blacki/registry.py index 8c116e8..c2efa51 100644 --- a/src/blacki/registry.py +++ b/src/blacki/registry.py @@ -117,13 +117,11 @@ def _build_workout_tools() -> list[Any]: from blacki.workouts import ( advance_training_cycle, delete_workout, - get_exercise_progress, get_last_workout, get_todays_training, get_todays_workout, get_training_history, get_training_metrics, - list_recent_workouts, log_training, log_workout, set_training_program, @@ -141,8 +139,6 @@ def _build_workout_tools() -> list[Any]: update_training_metrics, log_workout, get_last_workout, - get_exercise_progress, - list_recent_workouts, delete_workout, set_workout_split, get_todays_workout, diff --git a/src/blacki/workouts/__init__.py b/src/blacki/workouts/__init__.py index 724a292..dc9a3ac 100644 --- a/src/blacki/workouts/__init__.py +++ b/src/blacki/workouts/__init__.py @@ -1,13 +1,11 @@ from .tools import ( advance_training_cycle, delete_workout, - get_exercise_progress, get_last_workout, get_todays_training, get_todays_workout, get_training_history, get_training_metrics, - list_recent_workouts, log_training, log_workout, set_training_program, @@ -18,13 +16,11 @@ __all__ = [ "advance_training_cycle", "delete_workout", - "get_exercise_progress", "get_last_workout", "get_todays_training", "get_todays_workout", "get_training_history", "get_training_metrics", - "list_recent_workouts", "log_workout", "log_training", "set_training_program", diff --git a/src/blacki/workouts/storage.py b/src/blacki/workouts/storage.py index 5ecc6ac..d295895 100644 --- a/src/blacki/workouts/storage.py +++ b/src/blacki/workouts/storage.py @@ -71,29 +71,6 @@ class WorkoutSession(BaseModel): metrics: dict[str, Any] = Field(default_factory=dict) -class WorkoutSessionSummary(BaseModel): - """Lightweight view for listing — no exercise data.""" - - id: int - workout_date: str - split_name: str - exercise_count: int - cycle_day: int | None = None - session_type: str | None = None - completion_status: str = "completed" - - -class ExerciseHistoryEntry(BaseModel): - """One instance of an exercise across time for progressive overload tracking.""" - - workout_date: str - split_name: str - sets: list[SetDetail] - best_set_weight_kg: float - best_set_reps: int - total_volume_kg: float - - class TrainingProgramDay(BaseModel): """One scheduled day in a rotating training program.""" @@ -470,43 +447,6 @@ async def get_latest_split_session( return await self.get_session(row["id"], user_id) - async def get_recent_sessions( - self, user_id: str, limit: int = 10 - ) -> list[WorkoutSessionSummary]: - """Returns lightweight view of recent sessions.""" - limit = min(limit, 20) - rows = await self._fetch_all( - """ - SELECT - s.id, - s.workout_date, - s.split_name, - s.cycle_day, - s.session_type, - s.completion_status, - COUNT(e.id) as exercise_count - FROM workout_sessions s - LEFT JOIN workout_exercises e ON s.id = e.session_id - WHERE s.user_id = ? - GROUP BY s.id - ORDER BY s.workout_date DESC, s.created_at DESC - LIMIT ? - """, - (user_id, limit), - ) - return [ - WorkoutSessionSummary( - id=r["id"], - workout_date=r["workout_date"], - split_name=r["split_name"], - exercise_count=r["exercise_count"], - cycle_day=r["cycle_day"], - session_type=r["session_type"], - completion_status=r["completion_status"], - ) - for r in rows - ] - async def create_training_program( self, program: TrainingProgram, @@ -811,56 +751,6 @@ async def get_latest_training_metrics( ) return [self._row_to_training_metric(row) for row in rows] - async def get_exercise_history( - self, user_id: str, exercise_name: str, limit: int = 8 - ) -> list[ExerciseHistoryEntry]: - """Returns the last N instances of a specific exercise.""" - limit = min(limit, 8) - rows = await self._fetch_all( - """ - SELECT s.workout_date, s.split_name, e.sets - FROM workout_exercises e - JOIN workout_sessions s ON e.session_id = s.id - WHERE s.user_id = ? AND e.exercise_name = ? - ORDER BY s.workout_date DESC, s.created_at DESC - LIMIT ? - """, - (user_id, exercise_name.lower(), limit), - ) - - history = [] - for r in rows: - sets_data = ( - json.loads(r["sets"]) if isinstance(r["sets"], str) else r["sets"] - ) - sets = [SetDetail(**s) for s in sets_data] - - best_weight = 0.0 - best_reps = 0 - volume = 0.0 - - for s in sets: - if not s.is_warmup: - volume += s.weight_kg * s.reps - if s.weight_kg > best_weight or ( - s.weight_kg == best_weight and s.reps > best_reps - ): - best_weight = s.weight_kg - best_reps = s.reps - - history.append( - ExerciseHistoryEntry( - workout_date=r["workout_date"], - split_name=r["split_name"], - sets=sets, - best_set_weight_kg=best_weight, - best_set_reps=best_reps, - total_volume_kg=volume, - ) - ) - - return history - async def delete_session(self, session_id: int, user_id: str) -> bool: """Cascades to exercises.""" async with self._lock: diff --git a/src/blacki/workouts/tools.py b/src/blacki/workouts/tools.py index 1bfd54c..5de9a13 100644 --- a/src/blacki/workouts/tools.py +++ b/src/blacki/workouts/tools.py @@ -626,47 +626,6 @@ async def get_last_workout( } -async def get_exercise_progress( - tool_context: ToolContext, - exercise_name: str, - weeks: int = 4, -) -> dict[str, Any]: - """Progressive overload view for a single exercise over N weeks.""" - user_id = tool_context.user_id - if not user_id: - return {"status": "error", "message": "Missing user_id in tool_context"} - - storage = get_storage() - # cap at 8 entries as per instructions - limit = min(weeks * 2, 8) - - history = await storage.get_exercise_history(user_id, exercise_name, limit=limit) - - return { - "status": "success", - "exercise_name": exercise_name.lower(), - "history": [h.model_dump() for h in history], - } - - -async def list_recent_workouts( - tool_context: ToolContext, - limit: int = 10, -) -> dict[str, Any]: - """Overview of recent sessions (lightweight).""" - user_id = tool_context.user_id - if not user_id: - return {"status": "error", "message": "Missing user_id in tool_context"} - - storage = get_storage() - sessions = await storage.get_recent_sessions(user_id, limit=limit) - - return { - "status": "success", - "sessions": [s.model_dump() for s in sessions], - } - - async def delete_workout( tool_context: ToolContext, session_id: int, diff --git a/tests/test_registry.py b/tests/test_registry.py index ef604f9..1b6d5a5 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -227,7 +227,7 @@ def test_returns_tools_when_available(self) -> None: tools = _build_workout_tools() - assert len(tools) == 14 + assert len(tools) == 12 class TestBuildSandboxTools: diff --git a/tests/workouts/test_storage.py b/tests/workouts/test_storage.py index 58a97df..3e3f6c9 100644 --- a/tests/workouts/test_storage.py +++ b/tests/workouts/test_storage.py @@ -282,110 +282,6 @@ async def test_get_session_wrong_user(self, storage) -> None: assert result is None - @pytest.mark.asyncio - async def test_get_recent_sessions(self, storage) -> None: - """Should get recent sessions with exercise counts.""" - for i in range(3): - session = WorkoutSession( - user_id="user1", - workout_date=f"2026-04-{26 - i:02d}", - split_name="push" if i % 2 == 0 else "pull", - created_at=f"2026-04-{26 - i:02d}T10:00:00", - exercises=[ - WorkoutExercise( - exercise_name=f"exercise {j}", - sets=[SetDetail(set_num=1, weight_kg=100, reps=10)], - ) - for j in range(i + 1) - ], - ) - await storage.create_session(session) - - sessions = await storage.get_recent_sessions("user1") - - assert len(sessions) == 3 - assert sessions[0].workout_date == "2026-04-26" - assert sessions[0].exercise_count == 1 - assert sessions[1].exercise_count == 2 - assert sessions[2].exercise_count == 3 - - @pytest.mark.asyncio - async def test_get_recent_sessions_respects_limit(self, storage) -> None: - """Should limit the number of sessions returned.""" - for i in range(15): - session = WorkoutSession( - user_id="user1", - workout_date=f"2026-04-{26 - i:02d}", - split_name="push", - created_at=f"2026-04-{26 - i:02d}T10:00:00", - exercises=[], - ) - await storage.create_session(session) - - sessions = await storage.get_recent_sessions("user1", limit=5) - - assert len(sessions) == 5 - - @pytest.mark.asyncio - async def test_get_exercise_history(self, storage) -> None: - """Should get exercise history with best sets.""" - for i in range(3): - session = WorkoutSession( - user_id="user1", - workout_date=f"2026-04-{26 - i:02d}", - split_name="push", - created_at=f"2026-04-{26 - i:02d}T10:00:00", - exercises=[ - WorkoutExercise( - exercise_name="bench press", - sets=[ - SetDetail( - set_num=1, - weight_kg=100.0 + (2 - i) * 5, - reps=10 - (2 - i), - ), - SetDetail( - set_num=2, - weight_kg=95.0 + (2 - i) * 5, - reps=12 - (2 - i), - ), - ], - ) - ], - ) - await storage.create_session(session) - - history = await storage.get_exercise_history("user1", "bench press") - - assert len(history) == 3 - assert history[0].best_set_weight_kg == 110.0 - assert history[0].best_set_reps == 8 - - @pytest.mark.asyncio - async def test_get_exercise_history_excludes_warmup(self, storage) -> None: - """Should exclude warmup sets from best set calculation.""" - session = WorkoutSession( - user_id="user1", - workout_date="2026-04-26", - split_name="push", - created_at="2026-04-26T10:00:00", - exercises=[ - WorkoutExercise( - exercise_name="bench press", - sets=[ - SetDetail(set_num=1, weight_kg=60.0, reps=15, is_warmup=True), - SetDetail(set_num=2, weight_kg=100.0, reps=10, is_warmup=False), - ], - ) - ], - ) - await storage.create_session(session) - - history = await storage.get_exercise_history("user1", "bench press") - - assert history[0].best_set_weight_kg == 100.0 - assert history[0].best_set_reps == 10 - @pytest.mark.asyncio async def test_delete_session(self, storage) -> None: """Should delete a session and cascade to exercises.""" @@ -502,8 +398,8 @@ async def test_multiple_users_isolated(self, storage) -> None: assert s1.split_name == "push" assert s2.split_name == "pull" - sessions1 = await storage.get_recent_sessions("user1") - sessions2 = await storage.get_recent_sessions("user2") + sessions1 = await storage.get_training_history("user1") + sessions2 = await storage.get_training_history("user2") assert len(sessions1) == 1 assert len(sessions2) == 1 diff --git a/tests/workouts/test_tools.py b/tests/workouts/test_tools.py index 12ec3db..ecc1a7d 100644 --- a/tests/workouts/test_tools.py +++ b/tests/workouts/test_tools.py @@ -4,13 +4,11 @@ import pytest from google.adk.tools import ToolContext -from blacki.workouts.storage import WorkoutSession, WorkoutSessionSummary +from blacki.workouts.storage import WorkoutSession from blacki.workouts.tools import ( delete_workout, - get_exercise_progress, get_last_workout, get_todays_workout, - list_recent_workouts, log_workout, set_workout_split, ) @@ -79,37 +77,6 @@ async def test_get_last_workout(mock_get_storage, mock_tool_context) -> None: assert result["session"]["id"] == 1 -@pytest.mark.asyncio -@patch("blacki.workouts.tools.get_storage") -async def test_get_exercise_progress(mock_get_storage, mock_tool_context) -> None: - mock_storage = AsyncMock() - mock_get_storage.return_value = mock_storage - mock_storage.get_exercise_history.return_value = [] - - result = await get_exercise_progress(mock_tool_context, "bench press") - - assert result["status"] == "success" - assert result["exercise_name"] == "bench press" - mock_storage.get_exercise_history.assert_called_once() - - -@pytest.mark.asyncio -@patch("blacki.workouts.tools.get_storage") -async def test_list_recent_workouts(mock_get_storage, mock_tool_context) -> None: - mock_storage = AsyncMock() - mock_get_storage.return_value = mock_storage - mock_storage.get_recent_sessions.return_value = [ - WorkoutSessionSummary( - id=1, workout_date="2026-04-26", split_name="push", exercise_count=5 - ) - ] - - result = await list_recent_workouts(mock_tool_context) - - assert result["status"] == "success" - assert len(result["sessions"]) == 1 - - @pytest.mark.asyncio @patch("blacki.workouts.tools.get_storage") async def test_delete_workout(mock_get_storage, mock_tool_context) -> None: @@ -262,8 +229,6 @@ async def test_missing_user_id() -> None: assert (await log_workout(mock_context, "push", []))["status"] == "error" assert (await get_last_workout(mock_context, "push"))["status"] == "error" - assert (await get_exercise_progress(mock_context, "bench"))["status"] == "error" - assert (await list_recent_workouts(mock_context))["status"] == "error" assert (await delete_workout(mock_context, 1))["status"] == "error" assert (await set_workout_split(mock_context, {}))["status"] == "error" assert (await get_todays_workout(mock_context))["status"] == "error"