Skip to content

(Feat): Add Unit Test Suite for RecommendationService / Recommendation Use Case Interactor - #184

Merged
raymond367 merged 26 commits into
mainfrom
feat/recommendation-service-tests
Nov 26, 2025
Merged

(Feat): Add Unit Test Suite for RecommendationService / Recommendation Use Case Interactor#184
raymond367 merged 26 commits into
mainfrom
feat/recommendation-service-tests

Conversation

@raymond367

@raymond367 raymond367 commented Nov 26, 2025

Copy link
Copy Markdown
Collaborator

Summary

Added isolated unit tests for recommendation use case interactor with mock repositories and use case helpers.

Changes

  • Created test suite with 5 test cases covering input validation, error handling, and successful flows
  • Added mock repositories such as MockRosterRepository and MockPlayerRepository and use case helpers such as MockRosterHelper and MockPlayerHelper
  • Implemented helper functions for generating consistent test data using pytest fixtures
  • Minorly cleans up the recommendation use case interactor code (no logic changed)

Files Added

  • services/tests/test_recommendation_service.py
  • services/tests/mocks/mock_repositories.py
  • services/tests/mocks/mock_use_case_helpers.py

Closes #183

Summary by CodeRabbit

  • New Features

    • Recommendations now require a minimum roster of 9 players.
  • Bug Fixes

    • Clarified error message when a player lacks season data.
    • Recommendation selection now prioritizes highest-contribution candidates.
    • Reduced diagnostic/log output from the recommendation flow.
  • Tests

    • Added comprehensive unit tests with new in-memory mocks and configurable helpers.
    • Updated integration tests to use dependency overrides and 9-player input.
  • Chores

    • Test configuration adjusted to simplify test imports.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 26, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Enforces a 9-player minimum in RecommendationService, removes intermediate logging/timing, adds in-memory mock repositories and helpers for tests, introduces comprehensive unit tests for RecommendationService, updates an integration test to use FastAPI dependency_overrides, and adds pythonpath = . to server/pytest.ini.

Changes

Cohort / File(s) Summary
Test configuration
server/pytest.ini
Added pythonpath = . under addopts to allow imports without setting PYTHONPATH.
Service logic
server/services/recommendation_service.py
Enforces minimum roster size (raises InputValidationError if fewer than 9 player IDs), changes missing-season error text, flips contribution scoring (selects highest contributions first), removes many intermediate logs/timing/early-return branches; public signatures unchanged.
Mock repositories
server/services/tests/mocks/mock_repositories.py
Adds MockRosterRepository and MockPlayerRepository with in-memory stores and async accessors (get_players_seasons_data, get_league_unweighted_average, get_league_unweighted_std, get_all_players, get_player_by_id), stubs for other repo ops, and helper setters for test data.
Mock use-case helpers
server/services/tests/mocks/mock_use_case_helpers.py
Adds MockRosterHelper and MockPlayerHelper providing configurable deterministic behavior for validation, roster averages, weakness scoring, latest-stats extraction, adjustment-sum computation, primary-position resolution, and search-result building.
Unit tests
server/services/tests/test_recommendation_service.py
New unit tests for RecommendationService.recommend_players() covering roster-size validation, invocation of validate_player_ids, missing-season handling, weakest-position errors, candidate filtering (mlbam and missing-season skips), and success flows using the new mocks.
Integration test update
server/tests/integration/test_recommendations_integration.py
Switches test to FastAPI dependency_overrides[get_recommendation_service], removes patch fixture, sends 9 player IDs, adds try/finally to clear overrides after the test, and validates response list shape.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test
    participant API as FastAPI Endpoint
    participant Service as RecommendationService
    participant RHelper as RosterHelper (mock)
    participant Repo as Repositories (mock)

    Note over Test,API: Test posts 9 player_ids

    Test->>API: POST /recommendations (9 ids)
    API->>Service: recommend_players(player_ids)
    Service->>RHelper: validate_player_ids(player_ids)
    alt validation fails
        RHelper-->>Service: raises InputValidationError
        Service-->>API: 4xx error
    else
        Service->>Repo: get_players_seasons_data(player_ids)
        Repo-->>Service: seasons data
        Service->>RHelper: calculate_roster_averages(seasons)
        RHelper-->>Service: roster averages
        Service->>RHelper: compute_team_weakness_scores(...)
        RHelper-->>Service: weakness scores
        Service->>Service: filter candidates by weakest position
        loop per candidate
            Service->>RHelper: compute_adjustment_sum(candidate_stats,...)
            RHelper-->>Service: adjustment score & contributions
        end
        Service->>Service: rank candidates (highest contributions first)
        Service-->>API: List[PlayerSearchResult]
    end
    API-->>Test: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to:
    • server/services/recommendation_service.py: confirm the 9-player requirement, contribution sorting direction, and that removed logs didn't remove necessary control-flow or error handling.
    • server/services/tests/mocks/*: ensure mock method signatures and returned shapes match production expectations.
    • server/tests/integration/test_recommendations_integration.py: verify dependency override setup/cleanup and that the test exercises the intended flow.

Possibly related PRs

Suggested reviewers

  • qiuethan
  • hari-co
  • hiuyear

Poem

🐇 I hopped through mocks and test arrays,

Nine names lined up for stat‑filled days,
Helpers tidy, repos in store,
Recommendations rank — logs no more,
A rabbit cheers: tests run and soar.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Minor modifications to recommendation_service.py and integration test were made for refactoring and consistency, but some logging removal and scoring direction changes may exceed typical test-suite scope. Clarify whether logging removal and scoring adjustments in recommendation_service.py were intentional refactoring or should be reverted to maintain focus on test coverage only.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary change: adding a comprehensive unit test suite for RecommendationService and the recommendation use case interactor.
Linked Issues check ✅ Passed All acceptance criteria from issue #183 are met: isolated unit tests with mock repositories/helpers, validation/error/success coverage, pytest fixtures, and isolated passing tests.
Docstring Coverage ✅ Passed Docstring coverage is 82.35% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/recommendation-service-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
server/services/tests/test_recommendation_service.py (3)

13-18: Fix line formatting and improve readability.

Line 15 is excessively long and difficult to read. Consider breaking the dictionary initialization across multiple lines.

Apply this diff to improve readability:

 def create_season(year: int, **stats) -> Dict:
     """Create a season dict with default stats if not provided"""
-    defaults = {"strikeout_rate": 0.20,"walk_rate": 0.08,"isolated_power": 0.15,"on_base_percentage": 0.32,"base_running": 0.0,
-        "plate_appearances": 500 }
+    defaults = {
+        "strikeout_rate": 0.20,
+        "walk_rate": 0.08,
+        "isolated_power": 0.15,
+        "on_base_percentage": 0.32,
+        "base_running": 0.0,
+        "plate_appearances": 500
+    }
     defaults.update(stats)
     return defaults

105-106: Improve comment clarity.

The comment has a grammatical issue and unclear phrasing.

         assert "A valid roster must contain at least 9 players" in str(exc_info.value)
-        # cant check full string cause of the full error string is included iwth the input validation erro
+        # Note: Only checking partial string since InputValidationError wraps the full message

220-222: Improve comment clarity.

The comment has awkward phrasing that makes it harder to understand.

-        """If the weakest player which means, lowest adjustment score, has no position, input 
-        validation error is raised as the algorithm could not and would not accuartely return the recommendations"""
+        """If the weakest player (lowest adjustment score) has no position, InputValidationError 
+        is raised because the recommendation algorithm cannot accurately determine replacements"""
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5668934 and 3781554.

📒 Files selected for processing (5)
  • server/pytest.ini (1 hunks)
  • server/services/recommendation_service.py (3 hunks)
  • server/services/tests/mocks/mock_repositories.py (1 hunks)
  • server/services/tests/mocks/mock_use_case_helpers.py (1 hunks)
  • server/services/tests/test_recommendation_service.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
server/services/tests/mocks/mock_use_case_helpers.py (4)
server/dtos/roster_dtos.py (2)
  • RosterAvgResponse (58-66)
  • PlayerAvgStats (45-56)
server/useCaseHelpers/roster_helper.py (1)
  • RosterDomain (6-269)
server/useCaseHelpers/player_helper.py (1)
  • PlayerDomain (8-195)
server/useCaseHelpers/errors.py (1)
  • InputValidationError (19-21)
server/services/tests/mocks/mock_repositories.py (3)
server/repositories/player_repository.py (1)
  • PlayerRepository (4-30)
server/repositories/roster_avg_repository.py (1)
  • RosterRepository (4-28)
client/src/api/client.ts (1)
  • get (58-61)
server/services/recommendation_service.py (1)
server/useCaseHelpers/errors.py (2)
  • InputValidationError (19-21)
  • QueryError (29-31)
🪛 Ruff (0.14.5)
server/services/tests/test_recommendation_service.py

13-13: Unused function argument: year

(ARG001)


21-21: Unused function argument: player_id

(ARG001)


196-196: Unused method argument: mock_roster_helper

(ARG002)


196-196: Unused method argument: mock_player_repo

(ARG002)

server/services/tests/mocks/mock_use_case_helpers.py

27-27: Avoid specifying long messages outside the exception class

(TRY003)


46-46: Unused method argument: players_stats

(ARG002)


61-61: Unused method argument: team_avg

(ARG002)


61-61: Unused method argument: league_avg

(ARG002)


61-61: Unused method argument: league_std

(ARG002)


76-76: Unused method argument: seasons

(ARG002)


92-92: Unused method argument: player_latest_stats

(ARG002)


93-93: Unused method argument: league_avg

(ARG002)


94-94: Unused method argument: league_std

(ARG002)


95-95: Unused method argument: team_weakness

(ARG002)


114-114: Unused method argument: player_data

(ARG002)


114-114: Unused method argument: seasons

(ARG002)

server/services/recommendation_service.py

51-51: Avoid specifying long messages outside the exception class

(TRY003)


53-53: Local variable start_time is assigned to but never used

Remove assignment to unused variable start_time

(F841)


78-78: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (13)
server/pytest.ini (1)

29-30: LGTM! Standard pytest configuration.

This addition allows tests to import project modules without requiring external PYTHONPATH setup, which is a common and recommended practice for pytest projects.

server/services/tests/test_recommendation_service.py (5)

57-89: LGTM! Well-structured pytest fixtures.

The fixtures properly instantiate mock dependencies and compose them into the service under test, following pytest best practices for dependency injection.


92-158: LGTM! Comprehensive validation tests.

The test class properly validates both the error case (< 9 players) and the success case (exactly 9 players). The assertions correctly verify that:

  • InputValidationError is raised for insufficient players
  • Exactly 5 RF recommendations are returned for valid rosters
  • Only position-matched candidates are included in results

160-189: LGTM! Important safeguard test.

This test ensures validate_player_ids is always called, preventing future regressions where player IDs might be processed without validation.


191-211: LGTM! Proper error handling test.

The test correctly validates that missing season data triggers a QueryError, ensuring data integrity requirements are enforced.


213-249: LGTM! Edge case properly handled.

The test correctly validates that the service raises InputValidationError when it cannot determine a position for the weakest player, preventing invalid recommendations.

server/services/recommendation_service.py (3)

50-51: LGTM! Essential roster validation added.

The validation correctly enforces a minimum roster size of 9 players before processing, preventing invalid recommendations. This aligns with the test expectations in the test suite.


78-78: LGTM! Clearer error message.

The revised error message is more specific and user-friendly, clearly indicating that the player has no season data available for recommendations.


165-165: LGTM! Simplified return.

Direct return of results is cleaner now that intermediate logging has been removed.

server/services/tests/mocks/mock_repositories.py (2)

1-47: LGTM! Well-structured mock repository.

MockRosterRepository provides clean in-memory implementations with helper methods for test data setup. The async methods correctly match the repository interface.


49-75: LGTM! Clean mock player repository.

The implementation properly supports in-memory player storage with both list and dict-based lookups, matching the repository interface requirements.

server/services/tests/mocks/mock_use_case_helpers.py (2)

9-104: LGTM! Comprehensive roster helper mock.

The mock provides configurable behavior for all roster domain operations, enabling thorough testing of different scenarios including validation errors, default calculations, and cached results.


106-161: LGTM! Complete player helper mock.

MockPlayerHelper properly implements position retrieval and search result building with configurable behavior for testing edge cases. The years_active calculation logic is correct.

Comment thread server/services/recommendation_service.py Outdated
Comment thread server/services/tests/mocks/mock_repositories.py
Comment thread server/services/tests/mocks/mock_repositories.py
Comment thread server/services/tests/mocks/mock_repositories.py
Comment thread server/services/tests/mocks/mock_use_case_helpers.py
Comment thread server/services/tests/mocks/mock_use_case_helpers.py
Comment thread server/services/tests/test_recommendation_service.py
Comment thread server/services/tests/test_recommendation_service.py
Comment thread server/services/tests/test_recommendation_service.py
…t least 9 batters to avoid input validation error and remove @patch to fastapi dependency override to properly add the depednecy into the test and use the mock

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/tests/integration/test_recommendations_integration.py (1)

7-7: Remove unused patch import.

The patch import is no longer used after migrating to FastAPI's dependency override approach.

Apply this diff:

-from unittest.mock import patch, Mock, AsyncMock
+from unittest.mock import Mock, AsyncMock
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3781554 and 1aaae1d.

📒 Files selected for processing (1)
  • server/tests/integration/test_recommendations_integration.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/tests/integration/test_recommendations_integration.py (1)
server/dependency/dependencies.py (1)
  • get_recommendation_service (90-96)
🔇 Additional comments (1)
server/tests/integration/test_recommendations_integration.py (1)

62-62: LGTM: Clean signature after removing patch-based mocking.

Removing the mock_service_getter parameter is consistent with migrating to dependency overrides.

Comment thread server/tests/integration/test_recommendations_integration.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/services/recommendation_service.py (1)

48-49: Roster size check is good; consider centralizing the “9 players” requirement.

The explicit < 9 guard with an InputValidationError gives a clear, fast‑fail path for undersized rosters and matches the domain expectation of a 9‑player lineup. To avoid “9” becoming a magic number duplicated across services, helpers, and tests, consider lifting it into a shared constant (e.g., on RosterDomain or a settings module) and referencing that here.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aaae1d and 178206a.

📒 Files selected for processing (1)
  • server/services/recommendation_service.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/services/recommendation_service.py (1)
server/useCaseHelpers/errors.py (2)
  • InputValidationError (19-21)
  • QueryError (29-31)
🪛 Ruff (0.14.5)
server/services/recommendation_service.py

49-49: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (2)
server/services/recommendation_service.py (2)

72-75: Clearer QueryError message for missing season data.

Raising QueryError when seasons is None with a specific message about missing season data for player_id is sensible and should make downstream error handling and debugging easier. No functional issues from this change.


161-163: Explicit return results is appropriate.

Returning the accumulated results list at the end cleanly handles both the “no suitable candidates” case (empty list) and normal flows. No further changes needed here.

Comment thread server/services/recommendation_service.py Outdated
…e case interactor code for more intuitive approach and understanding

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
server/services/recommendation_service.py (2)

5-6: Remove unused imports.

Both logging and time are imported but never used after the logging and timing code was removed.

Apply this diff to remove the unused imports:

-import logging
-import time
-
 from dtos.player_dtos import PlayerSearchResult

32-46: Fix typos in docstring.

The docstring contains several typographical errors that should be corrected:

  • Line 33: "recommmended" → "recommended", "rooster" → "roster"
  • Line 37: "iwth" → "with"
  • Line 38: "replcae" → "replace", "there positions" → "their positions"
  • Line 42: "hypothetiically" → "hypothetically", "ne" → "new"
  • Line 46: "higest" → "highest"

Apply this diff:

     async def recommend_players(self, player_ids: List[int]) -> List[PlayerSearchResult]:
         """
-        Return 5 recommmended players for there rooster based off of the 
+        Return 5 recommended players for their roster based on the following:
         1. Get current team weakness vector (weakness_s) → where each stat's value > 0 means 
         team underperforms the league average. 
         
-        2. Find the player iwth the lowest adjustment score, this is the 
-        player we will replcae and there positions 
+        2. Find the player with the lowest adjustment score; this is the 
+        player we will replace and their positions 
         
         3. Fetch all players from firebase (there mlbid) whose position matches with what we need
         
-        4. for each team hypothetiically create a ne weakness vector with this player instead of the replaced player 
+        4. For each team hypothetically create a new weakness vector with this player instead of the replaced player 
         
         5. Store the player id with the difference between the sum of old weakness vector - new weakness vector 
         
-        6. Return the top 5 mlbid with the higest difference (maybe in a hashmap with mlbid, and the difference)"""
+        6. Return the top 5 mlbid with the highest difference (maybe in a hashmap with mlbid, and the difference)"""
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 178206a and 9173dba.

📒 Files selected for processing (2)
  • server/services/recommendation_service.py (4 hunks)
  • server/tests/integration/test_recommendations_integration.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/tests/integration/test_recommendations_integration.py
🧰 Additional context used
🧬 Code graph analysis (1)
server/services/recommendation_service.py (1)
server/useCaseHelpers/errors.py (2)
  • InputValidationError (19-21)
  • QueryError (29-31)
🪛 Ruff (0.14.5)
server/services/recommendation_service.py

49-49: Avoid specifying long messages outside the exception class

(TRY003)


74-74: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (3)
server/services/recommendation_service.py (3)

48-49: LGTM!

The minimum roster size validation correctly enforces the 9-player requirement as specified in the PR objectives and test suite.


74-74: LGTM!

The error message is more specific and clearly indicates the failure reason.


161-161: LGTM!

The simplified return statement is clean and appropriate after removing the timing/logging code.

Comment thread server/services/recommendation_service.py

@qiuethan qiuethan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…ks for edge cases in filtering candidates and another on skpping invalid candidates

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
server/services/tests/test_recommendation_service.py (3)

47-55: Normalize league std helper dict formatting and float literal

The helpers are fine functionally, but you can improve readability and fix the 0. literal in create_league_std:

 def create_league_avg() -> Dict[str, float]:
     """Create default league average stats"""
-    return {"strikeout_rate": 0.22,"walk_rate": 0.08,"isolated_power": 0.16,"on_base_percentage": 0.32,"base_running": 0.0}
+    return {
+        "strikeout_rate": 0.22,
+        "walk_rate": 0.08,
+        "isolated_power": 0.16,
+        "on_base_percentage": 0.32,
+        "base_running": 0.0,
+    }
@@
 def create_league_std() -> Dict[str, float]:
     """Create default league standard deviations"""
-    return {"strikeout_rate": 0.03,"walk_rate": 0.02,"isolated_power": 0.04,"on_base_percentage": 0.03,"base_running": 0.}
+    return {
+        "strikeout_rate": 0.03,
+        "walk_rate": 0.02,
+        "isolated_power": 0.04,
+        "on_base_percentage": 0.03,
+        "base_running": 0.0,
+    }

33-45: Fix typo in default-season comment

Minor spelling fix to keep comments clear (matches earlier feedback):

-        # dfault: 2023 sseason
+        # default: 2023 season

196-211: Clean up docstring/comment typos and unused fixture arguments

The behavior under test is correct, but the doc/comment have typos, and Ruff flags mock_roster_helper and mock_player_repo as unused. You can simplify the signature and fix the text:

-    async def test_missing_player_season_data_raises_query_error(self, service, mock_roster_repo, mock_roster_helper, mock_player_repo):
-        """Chceks if QueryError is raised when a player dosent have any data from any seasons"""
+    async def test_missing_player_season_data_raises_query_error(self, service, mock_roster_repo):
+        """Checks if QueryError is raised when a player doesn't have any data from any seasons"""
@@
-        # QueryError should be raised from to missing season data 
+        # QueryError should be raised due to missing season data

This keeps the fixture wiring intact (the service fixture still depends on the other mocks) while resolving ARG002 and making the test docs clearer.

🧹 Nitpick comments (4)
server/services/tests/test_recommendation_service.py (4)

13-30: Address unused parameters and avoid mutating caller-provided dicts in helpers

create_season doesn't use year, and create_player_seasons doesn't use player_id; Ruff flags both as unused, and season.pop("year", 2023) mutates the caller’s dict. You can keep behavior while quieting lint and avoiding mutation:

-def create_season(year: int, **stats) -> Dict:
-    """Create a season dict with default stats if not provided"""
-    defaults = {"strikeout_rate": 0.20,"walk_rate": 0.08,"isolated_power": 0.15,"on_base_percentage": 0.32,"base_running": 0.0,
-        "plate_appearances": 500 }
+def create_season(_year: int, **stats) -> Dict:
+    """Create a season dict with default stats if not provided"""
+    defaults = {
+        "strikeout_rate": 0.20,
+        "walk_rate": 0.08,
+        "isolated_power": 0.15,
+        "on_base_percentage": 0.32,
+        "base_running": 0.0,
+        "plate_appearances": 500,
+    }
     defaults.update(stats)
     return defaults
@@
-def create_player_seasons(player_id: int, *seasons) -> Dict:
+def create_player_seasons(_player_id: int, *seasons) -> Dict:
@@
-        elif isinstance(season, dict):
-            year = season.pop("year", 2023)
-            seasons_dict[str(year)] = create_season(year, **season)
+        elif isinstance(season, dict):
+            data = dict(season)
+            year = data.pop("year", 2023)
+            seasons_dict[str(year)] = create_season(year, **data)

110-158: Optionally assert PlayerSearchResult type in the success-path test

The flow and expectations for test_roster_with_9_players_passes_validation look good and robust. Since you already import PlayerSearchResult and comment that results should be of that type, you could assert it explicitly:

         # should return exactly 5 players
         assert isinstance(result, list)
         assert len(result) == 5, f"Expected 5 recommendations, got {len(result)}"
 
-        # all results must be PlayerSearchResult and match the RF candidate IDs
-        returned_ids = [player.id for player in result]
+        # all results must be PlayerSearchResult and match the RF candidate IDs
+        assert all(isinstance(player, PlayerSearchResult) for player in result)
+        returned_ids = [player.id for player in result]

218-247: Tighten wording/typos in weakest-position test for clarity

The test logic is sound; only the docstring and comments are a bit hard to read. Consider:

-        """If the weakest player which means, lowest adjustment score, has no position, input 
-        validation error is raised as the algorithm could not and would return the recommendations"""
+        """If the weakest player (lowest adjustment score) has no position, an
+        InputValidationError is raised because the algorithm cannot determine
+        recommendations."""
@@
-        # add season data to playes
+        # add season data to players
@@
-        # raise error when detected no position 
+        # raise error when no position can be determined

252-335: Polish candidate-filtering test docs and comments

Both candidate-filtering tests exercise the intended edge cases well. You can make them easier to read by fixing a few typos in class/test docs and comments:

-class TestCandidateFiltering:
-    """Tests for edge cases in filtering candidates and skipping invalid candidates that has the same positoin"""
+class TestCandidateFiltering:
+    """Tests edge cases in filtering candidates and skipping invalid candidates
+    with the same position."""
@@
-        # add mock mlb id with player inn position Rf
+        # add valid RF candidates
@@
-        # add candidate with invalid mlbam_id (here we do string ) which should be skipped
+        # add candidate with invalid mlbam_id (string) which should be skipped
@@
-        # mostly same setup as below with rest of the unit test
+        # mostly same setup as above as the rest of the unit test
@@
-        # player + rooster data setup
+        # player + roster data setup
@@
-        # add the RF candidates
+        # add the RF candidates
@@
-        # add candidate with valid mlbam_id bit no season data is added and thus shiud be skipped
+        # add candidate with valid mlbam_id but no season data; should be skipped
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9173dba and 6873141.

📒 Files selected for processing (1)
  • server/services/tests/test_recommendation_service.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.5)
server/services/tests/test_recommendation_service.py

13-13: Unused function argument: year

(ARG001)


21-21: Unused function argument: player_id

(ARG001)


196-196: Unused method argument: mock_roster_helper

(ARG002)


196-196: Unused method argument: mock_player_repo

(ARG002)

🔇 Additional comments (2)
server/services/tests/test_recommendation_service.py (2)

81-89: Fixtures and service wiring look solid

The service fixture correctly wires the four mock dependencies and keeps tests isolated via per-test fixture instantiation. No changes needed here.


160-190: Good pattern for asserting helper invocation

test_recommend_calls_validate_player_ids uses patch.object on the helper instance and assert_called_once_with, which is exactly what you want to guard against accidental removal of that validation. No issues here.

…ics for the recommendation use case interactor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
server/services/recommendation_service.py (4)

5-6: Remove unused imports.

The logging and time imports are no longer used after the cleanup that removed timing and logging statements. These should be removed to keep the code clean.

-import logging
-import time

31-47: Fix typos in docstring.

The docstring contains several spelling and grammar issues that should be corrected for clarity:

  • Line 33: "rooster" → "roster", "there" → "their"
  • Line 38: "iwth" → "with"
  • Line 39: "replcae" → "replace", "there" → "their"
  • Line 41: "there" → "their"
  • Line 43: "hypothetiically" → "hypothetically"
  • Line 47: "higest" → "highest"
     async def recommend_players(self, player_ids: List[int]) -> List[PlayerSearchResult]:
         """
-        Return 5 recommmended players for there rooster based off of the 
+        Return 5 recommended players for their roster based off of the 

         1. Get current team weakness vector (weakness_s) → where each stat's value > 0 means 
         team underperforms the league average. 
         
-        2. Find the player iwth the lowest adjustment score, this is the 
-        player we will replcae and there positions 
+        2. Find the player with the lowest adjustment score, this is the 
+        player we will replace and their positions 
         
-        3. Fetch all players from firebase (there mlbid) whose position matches with what we need
+        3. Fetch all players from firebase (their mlbid) whose position matches with what we need
         
-        4. for each team hypothetiically create a new weakness vector with this player instead of the replaced player 
+        4. for each team hypothetically create a new weakness vector with this player instead of the replaced player 
         
         5. Store the player id with the difference between the new weakness vector - old weakness vector
         
-        6. Return the top 5 mlbid with the higest difference"""
+        6. Return the top 5 mlbid with the highest difference"""

146-156: Algorithm change is correctly documented and implemented.

The contribution calculation and sorting now align with the updated z-score semantics where higher values indicate better performance. The inline comments explaining this change are helpful for future maintainers.

Minor typos in the comments: "ALGOIRTHM" → "ALGORITHM", "veector" → "vector".

-            # NOTE: DUE TO ALGOIRTHM CHANGE, weakness veector with higher values are good and lower is bad since we are using
+            # NOTE: DUE TO ALGORITHM CHANGE, weakness vector with higher values are good and lower is bad since we are using

111-112: Update stale comment to match new algorithm.

This comment still describes the old formula ("difference between the sum of original weakness vector and sum of this vector"), but the algorithm now calculates new - old. Update for consistency with line 146.

-        player_contributions = {}  # dictionary of key is mlbam_id and value is the difference between the sum of original weakness vector
-        # and sum of this vector
+        player_contributions = {}  # dictionary of key is mlbam_id and value is the difference between the sum of new weakness vector
+        # and sum of original vector (new - old); higher values indicate better candidates
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6873141 and ad845c5.

📒 Files selected for processing (1)
  • server/services/recommendation_service.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
server/services/recommendation_service.py (1)
server/useCaseHelpers/errors.py (2)
  • InputValidationError (19-21)
  • QueryError (29-31)
🪛 Ruff (0.14.5)
server/services/recommendation_service.py

50-50: Avoid specifying long messages outside the exception class

(TRY003)


75-75: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (1)
server/services/recommendation_service.py (1)

49-50: LGTM!

The roster size validation is correctly placed at the start of the method for fail-fast behavior, and the error message is clear.

@raymond367
raymond367 merged commit c136c0c into main Nov 26, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete Unit Test Suite for the recommendation service / recomendation use case interactor

2 participants