Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.6.0 #61

Merged
merged 14 commits into from
Nov 5, 2024
Merged

v3.6.0 #61

merged 14 commits into from
Nov 5, 2024

Conversation

Afischbacher
Copy link
Owner

No description provided.

- Added `GetBoxscoreByGameIdAsync` method in NhlApi.cs with documentation.
- Added `GetBoxscoreByGameIdAsync` method to INhlGameApi interface.
- Implemented `GetBoxscoreByGameIdAsync` in NhlGameApi.cs.
- Added comment on manual boxscore assignment due to API changes.
Introduced functionality to estimate and include the date and time of each play in the NHL game center play-by-play feed. Key changes include:

- Added necessary `using` directives for threading, tasks, and collections.
- Modified `GameCenterPlay` class to include a computed `Period` property and a new `EstimatedDateTimeOfPlay` property.
- Added a static `HashSet<string>` named `AllSeasons` to `SeasonYear` class.
- Implemented `AddDateTimeOfPlayForEachPlay` method in `NhlGameService`.
- Updated `GetGameCenterPlayByPlayByGameIdAsync` method and related interfaces to include a new `includeEventDateTime` parameter.
- Modified test methods to use `TimeOnly` instead of `string`.
Introduced a new `gameType` parameter to several methods in the `NhlApi` class and its interface `INhlStatisticsApi` to specify the type of game (e.g., Regular Season) for which statistics are requested. Updated method signatures to include `gameType` with a default value of `GameType.RegularSeason`:

- `GetPlayerStatisticsBySeasonAndFilterExpressionAsync`
- `GetGoalieStatisticsBySeasonAndFilterExpressionAsync`
- `GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync`
- `GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync`

Modified endpoint URL construction to append the `gameTypeId` query parameter. Updated XML documentation comments and test methods in `StatisticsTests.cs` to reflect these changes.
Copy link
Contributor

coderabbitai bot commented Oct 2, 2024

Warning

Rate limit exceeded

@Afischbacher has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 10 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a851c7 and 082c1c9.

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces several modifications across multiple classes in the Nhl.Api project, primarily focusing on updating timeout settings for HTTP clients, enhancing the NhlGameService functionality, and refining various API methods. Key changes include extending the timeout duration from 30 to 60 seconds for several HTTP client constructors, adding new methods for retrieving draft rankings and box scores, and improving the encapsulation of properties in the GameCenterPlay class. Additionally, the project files reflect a version increment to 3.6.0.

Changes

File Path Change Summary
Nhl.Api.Common/Http/NhlApiWebHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Http/NhlCmsHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Http/NhlEApiHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Http/NhlShiftChartHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Http/NhlStaticAssetsApiHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs Property Period changed to computed property; new nullable property EstimatedDateTimeOfPlay added.
Nhl.Api.Domain/Services/NhlGameService.cs New method AddEstimatedDateTimeOfPlayForEachPlay added; handles game data retrieval and estimated play time calculation.
Nhl.Api/Src/GameApi/NhlGameApi.cs Method GetGameCenterPlayByPlayByGameIdAsync updated to include includeEventDateTime parameter; new method GetBoxscoreByGameIdAsync added.
Nhl.Api.Common/Attributes/TeamActiveAttribute.cs New attribute class TeamActiveAttribute added to mark NHL teams as active/inactive.
Nhl.Api.Common/Extensions/EnumExtensions.cs Updated GetEnumMemberValue<T> method to return null explicitly if no attribute found.
Nhl.Api.Domain/Enumerations/Team/TeamEnum.cs TeamActive attribute added to each team enum member indicating active status.
Nhl.Api.Domain/Models/Draft/DraftYear.cs Class changed from regular to sealed; new static property AllSeasons added.
Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs New classes PlayerDraftCategory, PlayerDraftRanking, and PlayerDraftYear introduced to represent draft-related data.
Nhl.Api.Domain/Models/Draft/Ranks.cs Class Ranks removed.
Nhl.Api.Tests/GameTests.cs Test method updated to call GetGameCenterPlayByPlayByGameIdAsync with new parameter; multiple test cases added.
Nhl.Api.Tests/PlayerTests.cs Assertions updated for player birth province/state; new method GetPlayerDraftRankingByYearAsync_Returns_Correct_Draft_Ranking_Information added.
Nhl.Api/Src/Api/NhlApi.cs Multiple methods updated to include gameType parameter; new method GetPlayerDraftRankingByYearAsync added.
Nhl.Api/Src/PlayerApi/INhlPlayerApi.cs New method GetPlayerDraftRankingByYearAsync added to interface.
Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs New method GetPlayerDraftRankingByYearAsync added; updated method GetPlayerHeadshotImageAsync for clarity.
Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs Methods updated to include gameType parameter; enhances filtering of statistics.
Nhl.Api.Common/Http/NhlApiHttpClient.cs Constructor updated: timeoutInSeconds changed from 30 to 60.
Nhl.Api.Common/Nhl.Api.Common.csproj Project version updated from 3.5.0 to 3.6.0.
Nhl.Api.Domain/Models/Season/SeasonYears.cs Class changed from regular to sealed; new static property AllSeasons added.
Nhl.Api.Domain/Nhl.Api.Domain.csproj Project version updated from 3.5.0 to 3.6.0; model directory entries removed.
Nhl.Api.Domain/Services/NhlTeamService.cs Multiple methods refactored to use switch expressions for better readability.
Nhl.Api.Tests/Nhl.Api.Tests.csproj Project version updated from 3.5.0 to 3.6.0.
Nhl.Api.Tests/StatisticsTests.cs Test methods updated to include gameType parameter for player and goalie statistics retrieval.
Nhl.Api/Nhl.Api.csproj Project version updated from 3.5.0 to 3.6.0.
Nhl.Api/README.md Documentation updated to reflect new methods and parameters; enhanced clarity on API usage.

Possibly related PRs

  • v3.5.0 #60: The Nhl.Api.Common.Http/NhlApiHttpClient.cs file has a constructor change similar to the main PR, where the timeoutInSeconds parameter is updated from 30 to 60, indicating a direct relation in the modification of timeout settings across different HTTP client classes.

🐰 In the land where hockey plays,
Timeout's stretched for better days.
From thirty seconds to a minute,
Our clients now won't be in a limit!
With new methods, our stats will shine,
In the game of hockey, all will be fine! 🏒✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Outside diff range and nitpick comments (11)
Nhl.Api.Common/Http/NhlScoresHtmlReportsApiHttpClient.cs (2)

3-4: Remove unused namespace import.

The System.Threading.Tasks namespace is imported but not used in the current implementation. Consider removing it to keep the code clean and avoid potential confusion.

Apply this diff to remove the unused namespace:

using System;
using System.Net.Http;
using System.Threading;
-using System.Threading.Tasks;

Line range hint 30-45: LGTM! Consider extracting the HttpClient initialization for improved readability.

The implementation of the HttpClient property looks good. It ensures thread-safety and uses lazy initialization correctly. However, to improve readability, consider extracting the HttpClient initialization into a separate method.

Here's a suggested refactoring for improved readability:

public override HttpClient HttpClient
{
    get
    {
        lock (_lock)
        {
            _httpClient ??= CreateHttpClient();
            return _httpClient;
        }
    }
}

private HttpClient CreateHttpClient()
{
    return new HttpClient()
    {
        BaseAddress = new Uri($"{Client}{ClientVersion}"),
        Timeout = Timeout
    };
}

This refactoring separates the concerns of thread-safety and HttpClient initialization, making the code easier to read and maintain.

Nhl.Api/Src/GameApi/INhlGameApi.cs (2)

108-111: LGTM! Consider enhancing the documentation.

The addition of the includeEventDateTime parameter with a default value of false is a good improvement, allowing for more flexibility while maintaining backward compatibility.

Consider adding a brief explanation of what including the event datetime means in terms of the returned data. For example:

/// <param name="includeEventDateTime">A flag to determine whether the event timestamp for each play by play event is included in the response. If true, each event will have an additional datetime field.</param>

137-143: LGTM! Consider enhancing the documentation.

The addition of the GetBoxscoreByGameIdAsync method is a good improvement to the API, providing access to boxscore data for specific games.

Consider enhancing the documentation to provide more details about what a Boxscore contains. For example:

/// <summary>
/// Returns the NHL game boxscore for the specified game id, including detailed statistics for teams and players.
/// </summary>
/// <param name="gameId">The NHL game identifier, Example: 2023020204</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns>Returns the NHL game boxscore, containing comprehensive game statistics, player performance data, and other relevant game information.</returns>
Nhl.Api/Src/StatisticsApi/INhlStatisticsApi.cs (1)

Line range hint 125-167: Overall: Consistent and valuable API enhancement

The addition of the gameType parameter to these four methods is a well-implemented enhancement to the NHL Statistics API. It provides users with more granular control over the statistics they retrieve, while maintaining backward compatibility through the use of default values.

Consider adding a brief explanation of the GameType enum in the interface-level documentation to provide users with quick reference to available game types without needing to navigate to the enum definition.

Nhl.Api.Tests/StatisticsTests.cs (1)

3-3: Unnecessary import statement.

The import statement for System.Security.AccessControl appears to be unused in this file. Consider removing it to keep the imports clean and relevant.

-using System.Security.AccessControl;
Nhl.Api.Domain/Services/NhlGameService.cs (2)

23-23: Add XML documentation for the interface method.

The AddDateTimeOfPlayForEachPlay method in the INhlGameService interface lacks XML documentation comments. Providing XML documentation helps other developers understand the purpose and usage of the method.

Consider adding XML documentation comments to the method in the interface.


158-159: Complete the XML documentation comment for CalculateMultiplier.

At line 158, the XML summary comment is incomplete: /// A linear . This does not provide meaningful information about the method.

Update the XML comment to accurately describe the purpose and functionality of the CalculateMultiplier method. For example:

-/// A linear 
+/// <summary>
+/// Calculates a multiplier based on the duration and number of events.
+/// </summary>
Nhl.Api/Src/GameApi/NhlGameApi.cs (1)

16-17: Consider using dependency injection for better testability

Introducing static readonly fields _nhlScoresHtmlReportsApiHttpClient and _nhlGameService may lead to tight coupling and make unit testing more challenging. Using dependency injection to pass these services as parameters can enhance testability and adhere to the inversion of control principle.

Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs (1)

371-371: Add [JsonProperty("estimatedDateTimeOfPlay")] attribute for consistent serialization

The EstimatedDateTimeOfPlay property lacks the [JsonProperty] attribute, which is used throughout the class to map property names during JSON serialization and deserialization. For consistency and to ensure that this property is correctly serialized with the expected JSON property name, consider adding [JsonProperty("estimatedDateTimeOfPlay")].

Apply this diff:

+     [JsonProperty("estimatedDateTimeOfPlay")]
      public DateTimeOffset EstimatedDateTimeOfPlay { get; set; }
Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (1)

Line range hint 736-747: Correct the Logic in the 'takeaway' Case in GetPlayerStatisticTotal Method

In the case "takeaway" block of the GetPlayerStatisticTotal method, the code updates the Giveaway statistic instead of the Takeaway statistic. This appears to be a copy-paste error.

Apply this diff to fix the issue:

-case "takeaway":
    gameStatisticTotals[PlayerGameCenterStatistic.Takeaway] = play.Details.PlayerId == playerId
        ? gameStatisticTotals[PlayerGameCenterStatistic.Giveaway] += 1
        : gameStatisticTotals[PlayerGameCenterStatistic.Giveaway];
+case "takeaway":
    gameStatisticTotals[PlayerGameCenterStatistic.Takeaway] = play.Details.PlayerId == playerId
        ? gameStatisticTotals[PlayerGameCenterStatistic.Takeaway] += 1
        : gameStatisticTotals[PlayerGameCenterStatistic.Takeaway];
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d721f97 and ebf99a6.

📒 Files selected for processing (11)
  • Nhl.Api.Common/Http/NhlScoresHtmlReportsApiHttpClient.cs (1 hunks)
  • Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs (2 hunks)
  • Nhl.Api.Domain/Models/Season/SeasonYears.cs (1 hunks)
  • Nhl.Api.Domain/Services/NhlGameService.cs (1 hunks)
  • Nhl.Api.Tests/GameTests.cs (1 hunks)
  • Nhl.Api.Tests/StatisticsTests.cs (10 hunks)
  • Nhl.Api/Src/Api/NhlApi.cs (5 hunks)
  • Nhl.Api/Src/GameApi/INhlGameApi.cs (3 hunks)
  • Nhl.Api/Src/GameApi/NhlGameApi.cs (4 hunks)
  • Nhl.Api/Src/StatisticsApi/INhlStatisticsApi.cs (4 hunks)
  • Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (14 hunks)
🔇 Additional comments (46)
Nhl.Api.Common/Http/NhlScoresHtmlReportsApiHttpClient.cs (1)

Line range hint 1-45: Overall, the changes improve thread-safety and prepare for future enhancements.

The modifications to NhlScoresHtmlReportsApiHttpClient class enhance its thread-safety and set the groundwork for potential asynchronous operations. These changes align well with the v3.6.0 update and improve the robustness of the HTTP client implementation without introducing breaking changes.

Key improvements:

  1. Thread-safe lazy initialization of HttpClient.
  2. Preparation for potential asynchronous operations (though not yet implemented).

These changes contribute to a more reliable and future-proof implementation of the NHL scores HTML reports API client.

Nhl.Api.Tests/GameTests.cs (3)

213-219: Improved type safety with TimeOnly

The change from List<string> to List<TimeOnly> for period times is a good improvement. It provides better type safety and more accurately represents time data, aligning with C# best practices for handling time-related information.


235-251: Consistent use of TimeOnly in period assignments

The changes in assigning parsed times to different periods (P1, P2, P3, OT, SH) are consistent with the switch to TimeOnly. The logic for period assignment remains unchanged and appears correct.


230-231: ⚠️ Potential issue

Potential time ambiguity issue

The simplified regex pattern @"([0-9]{1,2}:[0-9]{2})" captures only hours and minutes, omitting the AM/PM designation. This change assumes all times are PM (line 231), which may not always be correct. Consider keeping the AM/PM capture in the regex to ensure accurate time representation for games that might span across AM/PM boundaries.

To verify if there are any games spanning AM/PM boundaries, we can check the game start times:

Nhl.Api/Src/GameApi/INhlGameApi.cs (1)

Line range hint 1-143: Overall, the changes look good and enhance the API.

The modifications to INhlGameApi.cs are consistent with the existing interface style and conventions. The updated GetGameCenterPlayByPlayByGameIdAsync method provides more flexibility, and the new GetBoxscoreByGameIdAsync method adds valuable functionality for retrieving detailed game statistics. These changes align well with the v3.6.0 update mentioned in the PR objectives.

Great job on these enhancements! The minor documentation improvements suggested earlier will further increase the usability of the API.

Nhl.Api.Domain/Models/Season/SeasonYears.cs (1)

11-36: LGTM: Addition of AllSeasons property is well-implemented

The new AllSeasons property is a valuable addition to the SeasonYear class. It provides a centralized, efficient way to access all NHL seasons. The use of a HashSet<string> ensures fast lookups and uniqueness of seasons. The static readonly modifier guarantees thread-safety and immutability, which is appropriate for this type of data.

Nhl.Api/Src/StatisticsApi/INhlStatisticsApi.cs (4)

125-128: LGTM: New parameter added consistently

The gameType parameter has been added to the GetPlayerStatisticsBySeasonAndFilterExpressionAsync method with a default value of GameType.RegularSeason. This change enhances the API's flexibility while maintaining backward compatibility. The method's comment has also been updated to include a description of the new parameter.


138-141: LGTM: Consistent parameter addition

The gameType parameter has been added to the GetGoalieStatisticsBySeasonAndFilterExpressionAsync method, consistent with the previous method. The default value and updated comment are also in line with the established pattern.


151-154: LGTM: Consistent parameter addition

The gameType parameter has been added to the GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync method, maintaining consistency with the previous methods. The default value and updated comment follow the same pattern.


164-167: LGTM: Consistent parameter addition

The gameType parameter has been added to the GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync method, consistent with all previous method updates. The default value and updated comment maintain the established pattern.

Nhl.Api.Tests/StatisticsTests.cs (10)

1309-1312: New parameter added to method call.

The gameType parameter has been added to the GetPlayerStatisticsBySeasonAndFilterExpressionAsync method call. This change is consistent with the method signature update mentioned in the AI-generated summary.


1330-1334: New parameter added to test method.

The gameType parameter has been correctly added to the test method for checking null season year input. This change is consistent with the method signature update.


1348-1351: New parameter added to test method.

The gameType parameter has been correctly added to the test method for checking null player filter input. This change is consistent with the method signature update.


1365-1369: New parameter added to test method.

The gameType parameter has been correctly added to the test method for checking invalid limit input. This change is consistent with the method signature update.


1383-1386: New parameter added to test method.

The gameType parameter has been correctly added to the test method for checking invalid offset start input. This change is consistent with the method signature update.


1401-1405: New parameter added to test method for goalie statistics.

The gameType parameter has been correctly added to the test method for checking null season year input in goalie statistics. This change is consistent with the method signature update.


1419-1422: New parameter added to test method for goalie statistics.

The gameType parameter has been correctly added to the test method for checking null player filter input in goalie statistics. This change is consistent with the method signature update.


1436-1439: New parameter added to test method for goalie statistics.

The gameType parameter has been correctly added to the test method for checking invalid limit input in goalie statistics. This change is consistent with the method signature update.


1453-1457: New parameter added to test method for goalie statistics.

The gameType parameter has been correctly added to the test method for checking invalid offset start input in goalie statistics. This change is consistent with the method signature update.


Line range hint 1-1457: Overall assessment of changes

The changes in this file consistently add the gameType parameter to various test methods for both player and goalie statistics. This modification aligns with the updated method signatures in the NHL API, allowing for more specific testing of different game types (e.g., regular season, playoffs).

The implementation is consistent across all affected methods, demonstrating a thorough update of the test suite. This change enhances the test coverage by allowing for game type-specific scenarios to be tested.

One minor improvement could be made by removing the unused import statement for System.Security.AccessControl at the beginning of the file.

Overall, these changes appear to be well-implemented and improve the flexibility and coverage of the test suite.

Nhl.Api.Domain/Services/NhlGameService.cs (4)

70-72: Verify the correctness of time calculations for periods greater than 3.

In the calculation at lines 70-72, when period > 1, you calculate regularPeriodTime and averageDelayInIntermission. For period > 3, regularPeriodTime is set to TimeSpan.FromMinutes(105), and averageDelayInIntermission is set to TimeSpan.FromMinutes(60).

Ensure that these hardcoded values accurately reflect the actual durations for overtime periods. If the durations vary for different periods, consider parameterizing these values or using configuration settings.


105-107: Ensure correct parsing of time strings and apply time zone offsets appropriately.

At lines 105-107, the time is parsed from a regex match and used to create a DateTime object with a specific time zone offset.

Confirm that the time strings are in the expected format and that the time zone offsets are correctly applied, especially considering daylight saving time changes. Incorrect parsing or offset application could lead to inaccurate estimated play times.


162-167: Review the calculation logic in the CalculateMultiplier method.

The formula used in the CalculateMultiplier method may not be immediately clear to other developers.

Ensure that the calculation is correct and consider adding comments to explain the rationale behind the formula. This improves code readability and maintainability.


93-100: ⚠️ Potential issue

Correct the initialization of the startTimeOfEachPeriod dictionary.

The dictionary initialization at lines 93-100 uses incorrect syntax { 1, [] }, which is not valid in C#.

Update the dictionary initialization to properly instantiate the lists:

-var startTimeOfEachPeriod = new Dictionary<int, List<DateTime>>
-{
-    { 1, [] },
-    { 2, [] },
-    { 3, [] },
-    { 4, [] },
-    { 5, [] },
-};
+var startTimeOfEachPeriod = new Dictionary<int, List<DateTime>>
+{
+    { 1, new List<DateTime>() },
+    { 2, new List<DateTime>() },
+    { 3, new List<DateTime>() },
+    { 4, new List<DateTime>() },
+    { 5, new List<DateTime>() },
+};

Likely invalid or redundant comment.

Nhl.Api/Src/GameApi/NhlGameApi.cs (1)

186-193: Addition of GetBoxscoreByGameIdAsync method looks good

The new method GetBoxscoreByGameIdAsync appropriately retrieves the direct box score for a given game ID. The implementation and documentation are consistent with the existing codebase.

Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs (1)

297-297: Potential breaking change: Period property is now read-only

By changing the Period property from a read/write property (get; set;) to a read-only computed property (=>), any code that previously assigned a value to Period will now fail to compile. This is a breaking change that may affect other parts of the codebase or consumers of this class. Ensure that all assignments to Period have been reviewed and refactored accordingly.

Run the following script to identify any assignments to Period:

✅ Verification successful

Verified: The Period property change to read-only does not introduce any breaking changes. No assignments to Period were found in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all assignments to the 'Period' property.

# Search for assignments to 'Period' in C# files. Expect: No matches if 'Period' is not being assigned elsewhere.
rg --type cs --fixed-strings 'Period = '

Length of output: 438

Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (15)

170-170: Performance Optimization: Excluding Event Date-Time Data

Setting includeEventDateTime: false in the GetGameCenterPlayByPlayByGameIdAsync call enhances performance by reducing unnecessary data retrieval. This change is appropriate.


236-236: Performance Optimization: Excluding Event Date-Time Data

Setting includeEventDateTime: false in the GetGameCenterPlayByPlayByGameIdAsync call enhances performance by reducing unnecessary data retrieval. This change is appropriate.


Line range hint 237-251: Ensure Correct Usage of Switch Expression in Lambda

The switch expression within the Count method effectively filters plays based on playerGameCenterStatistic. Ensure that all cases are correctly mapped and that playerId is accurately referenced.


317-317: Performance Optimization: Excluding Event Date-Time Data

Setting includeEventDateTime: false in the GetGameCenterPlayByPlayByGameIdAsync call enhances performance by reducing unnecessary data retrieval. This change is appropriate.


411-411: Performance Optimization: Excluding Event Date-Time Data

Setting includeEventDateTime: false in the GetGameCenterPlayByPlayByGameIdAsync call enhances performance by reducing unnecessary data retrieval. This change is appropriate.


519-519: Performance Optimization: Excluding Event Date-Time Data

Setting includeEventDateTime: false in the GetGameCenterPlayByPlayByGameIdAsync call enhances performance by reducing unnecessary data retrieval. This change is appropriate.


542-545: Enhancement: Added gameType Parameter to Increase Method Flexibility

The addition of the GameType gameType = GameType.RegularSeason parameter to GetPlayerStatisticsBySeasonAndFilterExpressionAsync enhances functionality by allowing the specification of game types. The parameter is appropriately documented and defaults to RegularSeason.


568-568: Updated Endpoint with gameTypeId Parameter

Appending &gameTypeId={(int)gameType} to the endpoint ensures that the API request includes the selected game type. This change aligns the endpoint with the new parameter and is correct.


585-588: Enhancement: Added gameType Parameter to Increase Method Flexibility

The addition of the GameType gameType = GameType.RegularSeason parameter to GetGoalieStatisticsBySeasonAndFilterExpressionAsync enhances functionality by allowing the specification of game types. The parameter is appropriately documented and defaults to RegularSeason.


611-611: Updated Endpoint with gameTypeId Parameter

Appending &gameTypeId={(int)gameType} to the endpoint ensures that the API request includes the selected game type. This change aligns the endpoint with the new parameter and is correct.


628-631: Enhancement: Added gameType Parameter to Increase Method Flexibility

The addition of the GameType gameType = GameType.RegularSeason parameter to GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync enhances functionality by allowing the specification of game types. The parameter is appropriately documented and defaults to RegularSeason.


654-654: Updated Endpoint with gameTypeId Parameter

Appending &gameTypeId={(int)gameType} to the endpoint ensures that the API request includes the selected game type. This change aligns the endpoint with the new parameter and is correct.


671-674: Enhancement: Added gameType Parameter to Increase Method Flexibility

The addition of the GameType gameType = GameType.RegularSeason parameter to GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync enhances functionality by allowing the specification of game types. The parameter is appropriately documented and defaults to RegularSeason.


697-697: Updated Endpoint with gameTypeId Parameter

Appending &gameTypeId={(int)gameType} to the endpoint ensures that the API request includes the selected game type. This change aligns the endpoint with the new parameter and is correct.


542-545: Consider Impact of Adding New Optional Parameters on Existing Callers

Adding new optional parameters (gameType) to public methods can potentially impact existing callers, especially those using named arguments. Ensure that existing consumers of these methods are not adversely affected by this change.

Would you like assistance in generating a script to verify usage across the codebase?

Also applies to: 588-588, 631-631, 674-674

Nhl.Api/Src/Api/NhlApi.cs (5)

714-720: New method GetBoxscoreByGameIdAsync looks good

The method GetBoxscoreByGameIdAsync is correctly implemented with appropriate XML documentation comments. The parameters and return type are clear.


521-522: Verify method usage after signature change

The method GetGameCenterPlayByPlayByGameIdAsync now includes a new parameter bool includeEventDateTime = false. Please ensure that all calls to this method in the codebase are reviewed to confirm that they either accept the default value or are updated accordingly to handle the new parameter.

Run the following script to locate all usages of GetGameCenterPlayByPlayByGameIdAsync:

✅ Verification successful

All usages of GetGameCenterPlayByPlayByGameIdAsync have been verified. Each call either provides the new includeEventDateTime parameter or relies on the default value. No issues found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of GetGameCenterPlayByPlayByGameIdAsync in the codebase.

# Search for method calls to GetGameCenterPlayByPlayByGameIdAsync.
rg --type csharp -A 2 'GetGameCenterPlayByPlayByGameIdAsync('

Length of output: 351


Script:

#!/bin/bash
# Description: Find all usages of GetGameCenterPlayByPlayByGameIdAsync in the codebase.

rg --type csharp -A 2 'GetGameCenterPlayByPlayByGameIdAsync\('

Length of output: 3588


670-670: Verify method usage after adding 'gameType' parameter

The method GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync now includes a new parameter GameType gameType = GameType.RegularSeason. Please ensure that all calls to this method are updated to include the new parameter or that the default value is acceptable.

Run the following script to find usages of this method:

✅ Verification successful

Method Usage Verified

All calls to GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync have been reviewed. In production code, the new gameType parameter is appropriately included. Test cases utilize the default value GameType.RegularSeason, which is acceptable.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync

rg --type csharp -A 2 'GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync('

Length of output: 351


Script:

#!/bin/bash
# Description: Find all usages of GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync

rg --type csharp -A 2 'GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync\('

Length of output: 3107


684-684: Verify method usage after adding 'gameType' parameter

The method GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync now includes a new parameter GameType gameType = GameType.RegularSeason. Please ensure that all calls to this method are updated accordingly.

Run the following script to find usages of this method:

✅ Verification successful

All usages of GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync have been updated to include the gameType parameter.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync

rg --type csharp -A 2 'GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync('

Length of output: 327


Script:

#!/bin/bash
# Description: Find all usages of GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync

rg --type csharp -A 2 'GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync('

Length of output: 260


Script:

#!/bin/bash
# Description: Find all usages of GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync without regex

rg --type csharp -F 'GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync('

Length of output: 1755


698-698: Verify method usage after adding 'gameType' parameter

The method GetPlayerStatisticsBySeasonAndFilterExpressionAsync now includes a new parameter GameType gameType = GameType.RegularSeason. Please verify that all usages of this method are compatible with the updated signature.

Run the following script to find usages of this method:

✅ Verification successful

Script Error: Regex Parse Failure

The previous script failed due to a regex parse error. Here is the corrected script to find all usages of GetPlayerStatisticsBySeasonAndFilterExpressionAsync:


All method usages are compatible with the updated signature.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of GetPlayerStatisticsBySeasonAndFilterExpressionAsync

rg --type csharp -A 2 'GetPlayerStatisticsBySeasonAndFilterExpressionAsync('

Length of output: 327


Script:

#!/bin/bash
# Description: Find all usages of GetPlayerStatisticsBySeasonAndFilterExpressionAsync with escaped parenthesis

rg --type csharp -A 2 'GetPlayerStatisticsBySeasonAndFilterExpressionAsync\('

Length of output: 5909

{
var match = regex[i].Value;
var value = Regex.Match(match, @"([0-9]{1,2}:[0-9]{2}\s[A-Z]{3})", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)).Groups[0].Value;
var value = Regex.Match(match, @"([0-9]{1,2}:[0-9]{2})", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)).Groups[0].Value;
var time = TimeOnly.Parse($"{value} PM");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve time parsing robustness

The current implementation assumes all times are PM, which may not always be correct. To improve robustness, consider the following approaches:

  1. If possible, extract the AM/PM information from the HTML report.
  2. Use game start time to determine the correct AM/PM for each period.
  3. Implement logic to handle time rollovers (e.g., from PM to AM for late-night games).

Example implementation:

var gameStartTime = ExtractGameStartTime(gameReport); // Implement this method
var time = ParseTimeWithContext(value, gameStartTime);

// ...

private TimeOnly ParseTimeWithContext(string timeString, TimeOnly gameStartTime)
{
    var parsedTime = TimeOnly.Parse(timeString);
    if (parsedTime < gameStartTime && gameStartTime.Hour >= 12)
    {
        // If parsed time is earlier than game start and game started in PM, assume next day
        parsedTime = parsedTime.AddHours(12);
    }
    return parsedTime;
}

This approach would provide more accurate time representations for games spanning different times of day.

Comment on lines 213 to 251
var dictionary = new Dictionary<string, List<TimeOnly>>
{
{ "P1", new List<string>() },
{ "P2", new List<string>() },
{ "P3", new List<string>() },
{ "OT", new List<string>() },
{ "SH", new List<string>() },
{ "P1", new List<TimeOnly>() },
{ "P2", new List<TimeOnly>() },
{ "P3", new List<TimeOnly>() },
{ "OT", new List<TimeOnly>() },
{ "SH", new List<TimeOnly>() },
};

var httpClient = new NhlScoresHtmlReportsApiHttpClient();
var gameReport = await httpClient.GetStringAsync("/20232024/PL020206.HTM");

var regex = Regex.Matches(gameReport, @"(?<=<td class="" \+ bborder"">)Period(.*?)(?=</td>)", RegexOptions.Compiled, TimeSpan.FromSeconds(30)).ToList();

for (int i = 0; i < regex.Count; i++)
for (var i = 0; i < regex.Count; i++)
{
var match = regex[i].Value;
var value = Regex.Match(match, @"([0-9]{1,2}:[0-9]{2}\s[A-Z]{3})", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)).Groups[0].Value;
var value = Regex.Match(match, @"([0-9]{1,2}:[0-9]{2})", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)).Groups[0].Value;
var time = TimeOnly.Parse($"{value} PM");

if (i <= 1)
{
dictionary["P1"].Add(value);
dictionary["P1"].Add(time);
}
else if (i >= 2 && i <= 3)
{
dictionary["P2"].Add(value);
dictionary["P2"].Add(time);
}
else if (i >= 4 && i <= 5)
{
dictionary["P3"].Add(value);
dictionary["P3"].Add(time);
}
else if (i >= 6 && i <= 7)
{
dictionary["OT"].Add(value);
dictionary["OT"].Add(time);
}
else if (i <= 9)
{
dictionary["SH"].Add(value);
dictionary["SH"].Add(time);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling and validation

While the overall structure of the method is sound and the changes improve type safety, consider adding the following enhancements:

  1. Error handling for regex matches and time parsing.
  2. Validation of the extracted times (e.g., ensure they are in a logical sequence).
  3. Logging of any parsing issues or unexpected data.

Example:

try
{
    var time = TimeOnly.Parse($"{value} PM");
    if (time < previousTime)
    {
        // Log warning about unexpected time sequence
    }
    // ... existing code ...
}
catch (FormatException ex)
{
    // Log error about invalid time format
    // Consider how to handle this case (skip, use default value, etc.)
}

These additions would make the method more robust and easier to debug in case of unexpected data or parsing issues.

Nhl.Api.Domain/Models/Season/SeasonYears.cs Outdated Show resolved Hide resolved
}

/// <summary>
/// The NHL game service, enabling data enrichment and functionality to the Nhl.Api
/// </summary>
public class NhlGameService : INhlGameService
{
private readonly NhlScoresHtmlReportsApiHttpClient _nhlScoresHtmlReportsApiHttpClient = new();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Inject the HTTP client via dependency injection.

The _nhlScoresHtmlReportsApiHttpClient is instantiated directly within the NhlGameService class. This tight coupling makes unit testing more difficult and violates the Dependency Inversion Principle.

Consider injecting the NhlScoresHtmlReportsApiHttpClient through the constructor or using an interface to allow for better testability and adherence to SOLID principles.

Nhl.Api.Domain/Services/NhlGameService.cs Outdated Show resolved Hide resolved
Comment on lines 145 to 154
public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, bool includeEventDateTime = false, CancellationToken cancellationToken = default)
{
var gameCenterPlayByPlay = await _nhlApiWebHttpClient.GetAsync<GameCenterPlayByPlay>($"/gamecenter/{gameId}/play-by-play", cancellationToken);
if (includeEventDateTime)
{
gameCenterPlayByPlay = await _nhlGameService.AddDateTimeOfPlayForEachPlay(gameCenterPlayByPlay);
}

return gameCenterPlayByPlay;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Adding a default parameter to a public method may cause breaking changes

The addition of the optional parameter includeEventDateTime to the public method GetGameCenterPlayByPlayByGameIdAsync could introduce breaking changes for consumers who use named arguments. To maintain backward compatibility, consider overloading the method instead of modifying its signature.

Apply this diff to overload the method:

+        /// <summary>
+        /// Returns the NHL game center feed for the specified game id.
+        /// </summary>
+        /// <param name="gameId">The NHL game identifier, Example: 2023020204 </param>
+        /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
+        /// <returns>Returns the NHL game center feed for the specified game id, including the game information, game status, game venue, and more</returns>
+        public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, CancellationToken cancellationToken = default)
+        {
+            return await GetGameCenterPlayByPlayByGameIdAsync(gameId, false, cancellationToken);
+        }
+
         public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, bool includeEventDateTime, CancellationToken cancellationToken = default)
         {
             var gameCenterPlayByPlay = await _nhlApiWebHttpClient.GetAsync<GameCenterPlayByPlay>($"/gamecenter/{gameId}/play-by-play", cancellationToken);
             if (includeEventDateTime)
             {
                 gameCenterPlayByPlay = await _nhlGameService.AddDateTimeOfPlayForEachPlay(gameCenterPlayByPlay);
             }
             return gameCenterPlayByPlay;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, bool includeEventDateTime = false, CancellationToken cancellationToken = default)
{
var gameCenterPlayByPlay = await _nhlApiWebHttpClient.GetAsync<GameCenterPlayByPlay>($"/gamecenter/{gameId}/play-by-play", cancellationToken);
if (includeEventDateTime)
{
gameCenterPlayByPlay = await _nhlGameService.AddDateTimeOfPlayForEachPlay(gameCenterPlayByPlay);
}
return gameCenterPlayByPlay;
}
/// <summary>
/// Returns the NHL game center feed for the specified game id.
/// </summary>
/// <param name="gameId">The NHL game identifier, Example: 2023020204 </param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns>Returns the NHL game center feed for the specified game id, including the game information, game status, game venue, and more</returns>
public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, CancellationToken cancellationToken = default)
{
return await GetGameCenterPlayByPlayByGameIdAsync(gameId, false, cancellationToken);
}
public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, bool includeEventDateTime, CancellationToken cancellationToken = default)
{
var gameCenterPlayByPlay = await _nhlApiWebHttpClient.GetAsync<GameCenterPlayByPlay>($"/gamecenter/{gameId}/play-by-play", cancellationToken);
if (includeEventDateTime)
{
gameCenterPlayByPlay = await _nhlGameService.AddDateTimeOfPlayForEachPlay(gameCenterPlayByPlay);
}
return gameCenterPlayByPlay;
}

Nhl.Api/Src/Api/NhlApi.cs Show resolved Hide resolved
/// <param name="cancellationToken">A cancellation token to cancel the asynchronous operation</param>
/// <returns> Returns all the NHL player game center statistics for a specific player for a specific season including face off percentage, points per game, overtime goals, short handed points , power play points, shooting percentage, shots, time on ice per game and more </returns>
public async Task<PlayerStatisticsFilterResult> GetPlayerStatisticsBySeasonAndFilterExpressionAsync(string seasonYear, ExpressionPlayerFilter expressionPlayerFilter, PlayerStatisticsFilter playerStatisticsFilterToSortBy = PlayerStatisticsFilter.Points, int limit = -1, int offsetStart = 0, CancellationToken cancellationToken = default) =>
await _nhlStatisticsApi.GetPlayerStatisticsBySeasonAndFilterExpressionAsync(seasonYear, expressionPlayerFilter, playerStatisticsFilterToSortBy, limit, offsetStart, cancellationToken);
public async Task<PlayerStatisticsFilterResult> GetPlayerStatisticsBySeasonAndFilterExpressionAsync(string seasonYear, ExpressionPlayerFilter expressionPlayerFilter, PlayerStatisticsFilter playerStatisticsFilterToSortBy = PlayerStatisticsFilter.Points, int limit = -1, int offsetStart = 0, GameType gameType = GameType.RegularSeason, CancellationToken cancellationToken = default) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the <returns> documentation comment

The <returns> comment incorrectly states "Returns all the NHL player game center statistics for a specific player..." However, this method retrieves statistics for players matching the provided filter expression, not just a specific player. Please update the documentation to accurately reflect the method's functionality.

Apply this diff to correct the comment:

-/// <returns> Returns all the NHL player game center statistics for a specific player for a specific season including face off percentage, points per game, overtime goals, short handed points , power play points, shooting percentage, shots, time on ice per game and more </returns>
+/// <returns> Returns all the NHL player statistics for players matching the filter expression for the specified season, including face-off percentage, points per game, overtime goals, shorthanded points, power-play points, shooting percentage, shots, time on ice per game, and more. </returns>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<PlayerStatisticsFilterResult> GetPlayerStatisticsBySeasonAndFilterExpressionAsync(string seasonYear, ExpressionPlayerFilter expressionPlayerFilter, PlayerStatisticsFilter playerStatisticsFilterToSortBy = PlayerStatisticsFilter.Points, int limit = -1, int offsetStart = 0, GameType gameType = GameType.RegularSeason, CancellationToken cancellationToken = default) =>
/// <returns> Returns all the NHL player statistics for players matching the filter expression for the specified season, including face-off percentage, points per game, overtime goals, shorthanded points, power-play points, shooting percentage, shots, time on ice per game, and more. </returns>
public async Task<PlayerStatisticsFilterResult> GetPlayerStatisticsBySeasonAndFilterExpressionAsync(string seasonYear, ExpressionPlayerFilter expressionPlayerFilter, PlayerStatisticsFilter playerStatisticsFilterToSortBy = PlayerStatisticsFilter.Points, int limit = -1, int offsetStart = 0, GameType gameType = GameType.RegularSeason, CancellationToken cancellationToken = default) =>

Nhl.Api/Src/Api/NhlApi.cs Show resolved Hide resolved
Made `EstimatedDateTimeOfPlay` nullable in `GameCenterPlay`.

Renamed methods for clarity:
- `AddDateTimeOfPlayForEachPlay` to `AddEstimatedDateTimeOfPlayForEachPlay` in `INhlGameService` and `NhlGameService`.

Updated `NhlGameApi` to use the renamed method.

Improved logic and comments in `NhlGameService` for estimating play time.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (6)
Nhl.Api.Common/Http/NhlApiWebHttpClient.cs (1)

Line range hint 30-41: Minor suggestions for improved readability and maintainability.

The current implementation is generally good, but consider the following suggestions:

  1. Add a comment explaining the thread-safety of the lazy initialization:
// Thread-safe lazy initialization of HttpClient
_httpClient ??= new HttpClient()
{
    BaseAddress = new Uri($"{Client}{ClientVersion}"),
    Timeout = Timeout
};
  1. Simplify the base address construction:
BaseAddress = new Uri(Client + ClientVersion),

These changes don't affect functionality but might improve code readability and maintainability.

Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs (1)

Line range hint 13-13: Consider making the ClientApiUrl configurable.

The ClientApiUrl is currently defined as a constant. While this works well for most cases, it might be beneficial to make it configurable, especially if there's a possibility of using different API endpoints in various environments (e.g., development, staging, production).

Consider the following changes to make the ClientApiUrl configurable:

  1. Change the constant to a static property:
private static string _clientApiUrl = "https://search.d3.nhle.com/api/";
public static string ClientApiUrl
{
    get => _clientApiUrl;
    set => _clientApiUrl = value ?? throw new ArgumentNullException(nameof(value));
}
  1. Update the constructor to use this property:
public NhlSuggestionApiHttpClient() : base(clientApiUri: ClientApiUrl, clientVersion: "v1", timeoutInSeconds: 60)
{
}

This change would allow the API URL to be configured at runtime if needed, while still providing a default value.

Nhl.Api.Common/Http/NhlEApiHttpClient.cs (1)

Line range hint 30-42: Optimize HttpClient property getter.

The current implementation of the HttpClient property recreates the entire client if it's null. This approach can be optimized to improve performance and reduce the chance of race conditions.

Consider using the double-checked locking pattern to optimize the HttpClient initialization:

 public override HttpClient HttpClient
 {
     get
     {
-        lock (_lock)
+        if (_httpClient == null)
         {
-            _httpClient ??= new HttpClient()
+            lock (_lock)
             {
-                BaseAddress = new Uri($"{Client}{ClientVersion}"),
-                Timeout = Timeout
-            };
-
-            return _httpClient;
+                if (_httpClient == null)
+                {
+                    _httpClient = new HttpClient()
+                    {
+                        BaseAddress = new Uri($"{Client}{ClientVersion}"),
+                        Timeout = Timeout
+                    };
+                }
+            }
         }
+        return _httpClient;
     }
 }

This optimization reduces the time spent in the lock, potentially improving performance in high-concurrency scenarios.

Nhl.Api.Common/Http/NhlCmsHttpClient.cs (1)

21-21: Consider documenting the reason for increasing the timeout.

The timeout for the NHL CMS HTTP client has been increased from 30 to 60 seconds. While this change can potentially improve reliability for slower connections or during high server load, it may also lead to longer wait times for users in case of actual server issues.

Consider the following suggestions:

  1. Add a comment explaining the rationale behind this change to aid future maintenance.
  2. Ensure that this increased timeout aligns with the overall application's responsiveness requirements.
  3. Consider making the timeout configurable, allowing for easier adjustments in different environments.

Would you like assistance in drafting a comment to document this change?

Nhl.Api.Domain/Services/NhlGameService.cs (2)

105-173: Improve error handling and add logging for better debugging.

The logic for calculating play times with valid API response is complex but seems reasonable. However, the error handling could be improved to provide better insights into potential issues.

Consider adding logging and more specific error handling:

using Microsoft.Extensions.Logging;

// Inject ILogger in the constructor
private readonly ILogger<NhlGameService> _logger;

public NhlGameService(ILogger<NhlGameService> logger)
{
    _logger = logger;
}

// In the method:
try
{
    // Existing logic
}
catch (Exception ex)
{
    _logger.LogError(ex, "Error calculating play times for game {GameId}", gameCenterPlayByPlay.Id);
    // Optionally, you can rethrow the exception or handle it as needed
}

This will help in identifying and debugging issues that may occur during the calculation process.

The overall logic for calculating play times with valid API response is approved, noting its complexity.


178-189: Add documentation to explain the multiplier calculation formula.

The CalculateMultiplier method implements a specific algorithm for time estimation, but the purpose and derivation of the formula are not clear from the code alone.

Consider adding more detailed documentation to explain the formula:

/// <summary>
/// Calculates a multiplier used for estimating play times within a period.
/// </summary>
/// <param name="startTime">The start time of the period.</param>
/// <param name="endTime">The end time of the period.</param>
/// <param name="events">The number of events (plays) in the period.</param>
/// <returns>A multiplier value used in play time estimation.</returns>
/// <remarks>
/// The formula used is: ((41 * duration) + (2 * events) - 381) / 3000
/// This formula is derived from [explain the reasoning or source of this formula].
/// It adjusts the time estimation based on the duration of the period and the number of events,
/// accounting for [explain what factors this accounts for, e.g., varying pace of play].
/// </remarks>
private double CalculateMultiplier(DateTime startTime, DateTime endTime, int events)
{
    // Existing implementation
}

Adding this documentation will help future maintainers understand the purpose and derivation of this formula.

The method implementation is approved, noting the need for additional documentation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between ebf99a6 and 965bfe5.

📒 Files selected for processing (9)
  • Nhl.Api.Common/Http/NhlApiWebHttpClient.cs (1 hunks)
  • Nhl.Api.Common/Http/NhlCmsHttpClient.cs (2 hunks)
  • Nhl.Api.Common/Http/NhlEApiHttpClient.cs (2 hunks)
  • Nhl.Api.Common/Http/NhlShiftChartHttpClient.cs (1 hunks)
  • Nhl.Api.Common/Http/NhlStaticAssetsApiHttpClient.cs (1 hunks)
  • Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs (1 hunks)
  • Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs (2 hunks)
  • Nhl.Api.Domain/Services/NhlGameService.cs (1 hunks)
  • Nhl.Api/Src/GameApi/NhlGameApi.cs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs
🔇 Additional comments (12)
Nhl.Api.Common/Http/NhlApiWebHttpClient.cs (1)

Line range hint 1-41: Overall, the changes and existing implementation look good.

The main change of increasing the timeout from 30 to 60 seconds has been addressed. The class structure and use of a static HttpClient are well-implemented for performance. A few suggestions have been made for improved configurability and readability.

Please consider the recommendations regarding the timeout increase and the minor code improvements. Once these are addressed or considered, this change is ready for approval.

Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs (1)

21-21: Consider the implications of increasing the timeout and provide rationale.

The timeout for the NhlSuggestionApiHttpClient has been increased from 30 to 60 seconds. While this change can potentially reduce timeout errors for users with slower connections or during periods of high API load, it's important to consider the following implications:

  1. User experience: Longer timeouts may lead to extended wait times before users receive error messages in case of actual API issues.
  2. System resources: This change might result in longer-held connections, potentially impacting system resources if many requests are made simultaneously.
  3. Consistency: We should verify if this timeout increase is consistent with other parts of the system or if it's an isolated adjustment.

To ensure consistency across the codebase, let's check for other timeout configurations:

Could you please provide more information on the rationale behind this change? Were there frequent timeout issues reported by users or observed in logs?

✅ Verification successful

Timeout Configuration Consistency Verified

The timeoutInSeconds parameter has been uniformly increased to 60 seconds across all relevant HTTP client classes:

  • NhlShiftChartHttpClient.cs
  • NhlScoresHtmlReportsApiHttpClient.cs
  • NhlEApiHttpClient.cs
  • NhlCmsHttpClient.cs
  • NhlApiWebHttpClient.cs
  • NhlSuggestionApiHttpClient.cs

This ensures consistent timeout behavior throughout the application.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other timeout configurations in the codebase
rg --type csharp 'timeoutInSeconds:' -C 3

Length of output: 4041

Nhl.Api.Common/Http/NhlEApiHttpClient.cs (1)

22-22: Timeout increase approved, but requires verification and monitoring.

The increase in timeout from 30 to 60 seconds has been noted. This change can help reduce timeout errors for slower connections or during high load periods.

However, please consider the following:

  1. Verify if this change is necessary. Are there documented issues or user reports indicating frequent timeouts with the previous 30-second limit?
  2. Monitor the impact of this change after deployment. Look for any changes in error rates, response times, and resource utilization.
  3. Consider implementing a more robust solution if timeouts are a persistent issue, such as implementing retry logic with exponential backoff.

To help verify the necessity of this change, please run the following script to check for timeout-related issues in recent logs or issue trackers:

Nhl.Api.Common/Http/NhlCmsHttpClient.cs (1)

Line range hint 1-41: Well-structured HTTP client implementation with room for minor clarification.

The NhlCmsHttpClient class demonstrates several good practices:

  1. Use of a static HttpClient for better performance through connection pooling.
  2. Thread-safe lazy initialization of the HttpClient.
  3. Constant definition of the API URL for easy maintenance.

To further improve the code:

  1. Verify that the Client and ClientVersion properties used in the HttpClient.BaseAddress are correctly defined in the base class.
  2. Consider adding XML documentation to the HttpClient property to explain its thread-safe nature and usage.

To verify the base class properties, run the following script:

Nhl.Api.Common/Http/NhlShiftChartHttpClient.cs (1)

21-21: Verify the necessity of increasing the timeout duration.

The timeout for the NhlShiftChartHttpClient has been increased from 30 to 60 seconds. While this change can help reduce timeout errors if the NHL API occasionally takes longer to respond, it may also lead to longer wait times for users when the API is genuinely unreachable.

Please confirm if this change is based on observed timeout issues with the NHL shift charts API. Consider running the following script to check for recent timeout-related issues in the project's issue tracker or pull requests:

If there's evidence supporting this change, consider adding a code comment explaining the rationale behind the increased timeout.

Nhl.Api.Domain/Services/NhlGameService.cs (3)

11-31: LGTM: Interface and class declarations are correctly updated.

The INhlGameService interface and NhlGameService class have been properly updated with the new AddEstimatedDateTimeOfPlayForEachPlay method.


38-43: LGTM: Method signature and initial logic are correct.

The method signature matches the interface declaration, and the initial logic for making the API call and extracting period information using regex is appropriate.


1-189: Overall assessment: Significant functionality improvement with some areas for enhancement.

The AddEstimatedDateTimeOfPlayForEachPlay method introduces a complex but valuable functionality for estimating play times in NHL games. The implementation handles different scenarios and uses both predefined estimates and API data when available.

Key points:

  1. The overall logic is sound and addresses the task effectively.
  2. Error handling and logging could be improved in some areas.
  3. Some parts of the code, particularly the CalculateMultiplier method, would benefit from additional documentation.
  4. Consider making the initial game delay calculation more flexible.

These enhancements will improve the maintainability and robustness of the code. Great work on implementing this complex feature!

Nhl.Api/Src/GameApi/NhlGameApi.cs (4)

16-17: LGTM: New dependencies added

The addition of _nhlScoresHtmlReportsApiHttpClient and _nhlGameService as static readonly fields is consistent with the existing pattern in the class. These new dependencies likely support the enhanced functionality in the modified and new methods.


186-192: LGTM: New method GetBoxscoreByGameIdAsync added

The new GetBoxscoreByGameIdAsync method is a good addition to the API. It follows the existing patterns in the class, is well-documented, and provides a straightforward way to retrieve the box score for a specific game. The implementation is concise and uses the existing _nhlApiWebHttpClient consistently with other methods.


179-182: LGTM: Improved GetGameCenterBoxScoreByGameIdAsync implementation

The manual assignment of the boxscore and the added comment provide a good explanation for why this step is necessary due to changes in the NHL API structure. This improves the maintainability of the code and helps future developers understand the reason for this implementation detail.


142-154: ⚠️ Potential issue

Consider overloading the method instead of modifying its signature

The addition of the includeEventDateTime parameter to GetGameCenterPlayByPlayByGameIdAsync could introduce breaking changes for clients using named arguments. To maintain backward compatibility, consider overloading the method as suggested in a previous review:

+    public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, CancellationToken cancellationToken = default)
+    {
+        return await GetGameCenterPlayByPlayByGameIdAsync(gameId, false, cancellationToken);
+    }

     public async Task<GameCenterPlayByPlay> GetGameCenterPlayByPlayByGameIdAsync(int gameId, bool includeEventDateTime = false, CancellationToken cancellationToken = default)
     {
         // ... (existing implementation)
     }

This approach preserves the existing method signature while allowing the new functionality to be used when needed.

Nhl.Api.Common/Http/NhlApiWebHttpClient.cs Show resolved Hide resolved
Nhl.Api.Common/Http/NhlEApiHttpClient.cs Show resolved Hide resolved
Nhl.Api.Domain/Services/NhlGameService.cs Show resolved Hide resolved
Afischbacher and others added 5 commits November 4, 2024 21:25
Enhanced the codebase with several key updates:
- Added new namespaces across multiple files for better organization.
- Introduced `TeamActive` attribute in `TeamEnum` to indicate team status.
- Removed the `Ranks` class and its properties.
- Updated `GameCenterPlayByPlay` property documentation and default value.
- Refactored test methods in `GameTests.cs` and `PlayerTests.cs` for improved validation.
- Added `GetPlayerDraftRankingByYearAsync` method in `NhlApi.cs`, `INhlPlayerApi.cs`, and `NhlPlayerApi.cs` to fetch draft rankings.
- Excluded `ArizonaCoyotes` from NHL teams for seasons 2024-2025 or later in `NhlStatisticsApi.cs`.
- Introduced new classes to represent NHL draft player rankings and categories.
Refactor and enhance HTTP clients and season generation

- Change default timeout in NhlApiHttpClient to 60 seconds.
- Add System.Net.Http directive to NhlSuggestionApiHttpClient.cs.
- Include System.Linq in SeasonYears.cs for LINQ operations.
- Refactor AllSeasons in SeasonYear to use GenerateAllSeasons.
- Update NhlGameService to use INhlApiHttpClient for DI.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (17)
Nhl.Api.Common/Attributes/TeamActiveAttribute.cs (2)

5-11: Improve XML documentation quality and correctness.

The XML documentation has several issues that should be addressed:

  • Fix the typo "leauge" to "league" in the parameter description
  • Remove or enhance the <remarks> section as it currently adds no value
  • Consider expanding the parameter description to be more informative
 /// <summary>
 /// An enumeration to specifically mark NHL teams active or inactive
 /// </summary>
-/// <remarks>
-/// The constructor
-/// </remarks>
-/// <param name="isActive">Whether an NHL team is active in the leauge</param>
+/// <param name="isActive">Indicates whether the NHL team is currently active in the league (true) or inactive/retired (false)</param>

15-19: Consider adding runtime validation for team status changes.

While the current implementation is correct, consider adding runtime validation to prevent accidental status changes, especially for historically significant teams.

 public class TeamActiveAttribute(bool isActive) : Attribute
 {
+    private static readonly HashSet<string> HistoricalTeams = new()
+    {
+        // Add historically significant teams that should never be marked as active
+    };
+
     /// <summary>
     /// Determines if the NHL team is currently active
     /// </summary>
     public bool IsActive { get; } = isActive;
+
+    /// <summary>
+    /// Validates the active status against historical team records
+    /// </summary>
+    public bool ValidateStatus(string teamName)
+    {
+        if (HistoricalTeams.Contains(teamName) && IsActive)
+        {
+            throw new InvalidOperationException($"Historical team {teamName} cannot be marked as active");
+        }
+        return true;
+    }
 }
Nhl.Api.Common/Extensions/EnumExtensions.cs (1)

Line range hint 1-24: Remove redundant null-coalescing operator.

The ?? null operator at the end of the expression is redundant because:

  1. The method returns string which is already nullable
  2. When no attribute is found or the attribute has no value, the expression would return null anyway
  3. Using null-coalescing operator with null literal doesn't change the behavior
    public static string GetEnumMemberValue<T>(this T value) where T : Enum => typeof(T)
            .GetTypeInfo()
            .DeclaredMembers
            .SingleOrDefault(x => x.Name == value.ToString())
            ?.GetCustomAttribute<EnumMemberAttribute>(false)
-           ?.Value ?? null;
+           ?.Value;
Nhl.Api.Domain/Enumerations/Team/TeamEnum.cs (1)

Line range hint 6-8: Update class documentation to reflect inactive teams

The current documentation states "all active NHL teams" but the enum now includes inactive teams (ArizonaCoyotes). Consider updating to "An enumeration for all NHL teams" or "An enumeration for all active and inactive NHL teams".

-/// An enumeration for all active NHL teams
+/// An enumeration for all NHL teams, including both active and inactive teams
Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs (1)

9-28: Consider adding nullable annotations for string properties.

Since this is a data model that will be populated from JSON, the string properties should be marked as nullable to handle potential null values during deserialization.

-    public string Name { get; set; }
+    public string? Name { get; set; }

-    public string ConsumerKey { get; set; }
+    public string? ConsumerKey { get; set; }
Nhl.Api.Tests/GameTests.cs (1)

211-234: Consider organizing test data for better maintainability.

While the test data provides comprehensive coverage across different seasons (2000-2023), consider organizing it more systematically:

  1. Group test cases by meaningful categories (e.g., pre-lockout, post-lockout, modern era).
  2. Add comments explaining the significance of specific game IDs.
  3. Consider using a custom test data source instead of multiple DataRow attributes.

Example organization:

private static IEnumerable<object[]> GetTestGameIds()
{
    // Pre-lockout era (2000-2004)
    yield return new object[] { 2000020004 };
    yield return new object[] { 2001020004 };
    
    // Post-lockout era (2005-2012)
    yield return new object[] { 2005020037 };
    yield return new object[] { 2006020046 };
    
    // Modern era (2013-present)
    yield return new object[] { 2013020090 };
    yield return new object[] { 2023020090 };
}

[DynamicData(nameof(GetTestGameIds))]
public async Task GetGameCenterPlayByPlayByGameIdAsync_Returns_Valid_Estimated_DateTime_Of_Play_Information(int gameId)
Nhl.Api.Domain/Models/Draft/DraftYear.cs (3)

Line range hint 1-350: Consider refactoring to use a more type-safe and maintainable approach.

The current implementation using string constants has several drawbacks:

  1. Lack of type safety - years are stored as strings
  2. No validation or helper methods
  3. Repetitive code and documentation
  4. Hard-coded future years that will need maintenance

Consider these alternatives:

Option 1: Use an enum with integer values (Recommended)

public enum DraftYear
{
    Year1963 = 1963,
    // ... other years
    Year2030 = 2030
}

Option 2: Use a readonly collection with validation

public static class DraftYear
{
    private static readonly HashSet<int> ValidYears = new(Enumerable.Range(1963, 68)); // 68 years from 1963 to 2030

    public static bool IsValidYear(int year) => ValidYears.Contains(year);
    
    public static IReadOnlyCollection<int> GetValidYears() => ValidYears;
    
    // Helper method to convert to string format if needed
    public static string ToStringFormat(int year) => 
        IsValidYear(year) ? year.ToString() : throw new ArgumentException($"Invalid draft year: {year}");
}

Option 3: Use a value object with validation

public readonly struct DraftYear : IEquatable<DraftYear>
{
    private const int MinYear = 1963;
    private const int MaxYear = 2030;
    
    public int Value { get; }
    
    public DraftYear(int year)
    {
        if (year < MinYear || year > MaxYear)
            throw new ArgumentException($"Draft year must be between {MinYear} and {MaxYear}");
        
        Value = year;
    }
    
    public override string ToString() => Value.ToString();
    
    // Implement equality members...
}

Benefits of these alternatives:

  • Type safety through compile-time checking
  • Runtime validation of year values
  • Reduced code duplication
  • Easier maintenance
  • Better encapsulation of draft year logic
  • More flexible for future changes

Line range hint 1-350: Enhance documentation with validation rules and usage examples.

The current XML comments are repetitive and don't provide enough context about:

  • Valid year ranges
  • Usage examples
  • Validation rules
  • Purpose and intended use cases

Add a comprehensive class-level documentation:

/// <summary>
/// Provides valid NHL draft years from 1963 to 2030.
/// </summary>
/// <remarks>
/// <para>
/// Valid years range from 1963 (first NHL Amateur Draft) to 2030 (future projections).
/// All years are represented in "YYYY" format.
/// </para>
/// <para>
/// Usage example:
/// <code>
/// string draftYear = DraftYear.draftYear2023; // Returns "2023"
/// </code>
/// </para>
/// </remarks>

Consider removing unused DraftYear constants class

Based on the analysis:

  1. No direct usage of any DraftYear constants was found in the codebase
  2. Year values are being used directly as string literals in tests and other files
  3. The DraftYear class appears to be unused despite being imported in some files

The DraftYear class with its string constants appears to be dead code that can be safely removed unless there are specific future plans for its usage.

🔗 Analysis chain

Line range hint 1-350: Verify the usage of string constants in the codebase.

Let's analyze how these constants are being used to ensure our refactoring suggestions are appropriate.


Let me run one more search to check if there are any direct usages of these constants in the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of DraftYear constants to understand the impact of refactoring

# Test 1: Find direct references to DraftYear constants
echo "Finding direct references to DraftYear constants..."
rg "DraftYear\.(draftYear\d{4})" -t cs

# Test 2: Find string literal year usage that might need to use these constants
echo "Finding string literal years that might need to use constants..."
rg '"(19|20)\d{2}"' -t cs

# Test 3: Find potential validation of year strings
echo "Finding year validation patterns..."
rg -A 5 "(TryParse|Parse|Convert).*(19|20)\d{2}" -t cs

Length of output: 8501


Script:

#!/bin/bash
# Search for any imports or usages of the DraftYear class
rg -g "*.cs" "using.*Nhl\.Api\.Models\.Draft" -B 2 -A 10

# Search for any references to the DraftYear class
rg -g "*.cs" "DraftYear\." -B 2 -A 2

Length of output: 2371

Nhl.Api/Src/PlayerApi/INhlPlayerApi.cs (1)

133-140: Consider strengthening the seasonYear parameter type.

While the method signature and documentation are well-structured, consider these improvements:

  1. The seasonYear parameter could benefit from being a strongly-typed value (enum or int) instead of a string to prevent invalid inputs.
  2. Add validation constraints to the documentation (e.g., valid year ranges, minimum startingPosition).

Example documentation addition:

    /// <param name="seasonYear"> The NHL draft year </param>
+   /// <remarks>
+   /// Valid season years range from 1963 to present.
+   /// Format: "YYYY" (e.g., "2023")
+   /// </remarks>
Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs (2)

100-100: LGTM! Note on style.

The addition of the this keyword is functionally equivalent but can improve readability by making it explicit that we're calling an instance method.


286-307: Consider additional input validation and caching.

The implementation looks good overall, but consider these improvements:

  1. Add format validation for seasonYear to ensure it's a valid draft year (YYYY format)
  2. Consider adding an upper bound validation for startingPosition
  3. Consider implementing caching for performance optimization, similar to other methods in the class

Example validation improvement:

 if (string.IsNullOrWhiteSpace(seasonYear))
 {
     throw new ArgumentException($"The {nameof(seasonYear)} parameter is required", nameof(seasonYear));
 }
+if (!int.TryParse(seasonYear, out int year) || year < 1917 || year > DateTime.Now.Year + 5)
+{
+    throw new ArgumentException($"The {nameof(seasonYear)} parameter must be a valid draft year in YYYY format", nameof(seasonYear));
+}

 if (startingPosition < 1)
 {
     throw new ArgumentException($"The {nameof(startingPosition)} parameter must be greater than 0", nameof(startingPosition));
 }
+if (startingPosition > 1000) // Adjust max value based on business requirements
+{
+    throw new ArgumentException($"The {nameof(startingPosition)} parameter exceeds maximum allowed value", nameof(startingPosition));
+}
Nhl.Api.Tests/PlayerTests.cs (1)

885-925: Consider enhancing draft rankings test coverage.

While the test covers basic player information, consider the following improvements:

  1. Testing the future draft year 2024 might be unreliable as the data may not be available yet.
  2. Add assertions for draft-specific fields (rank, position, etc.).
  3. Add negative test cases for invalid years.
  4. Consider reducing the number of test years or grouping them into separate test methods to improve test execution time.

Example improvements:

 [TestMethodWithRetry(RetryCount = 5)]
-[DataRow("2024")]  // Remove future year
 public async Task GetPlayerDraftRankingByYearAsync_Returns_Correct_Draft_Ranking_Information(string seasonYear)
 {
     // ... existing code ...
     foreach (var playerDraftRanking in draft.Rankings)
     {
         Assert.IsNotNull(playerDraftRanking);
+        // Add draft-specific assertions
+        Assert.IsNotNull(playerDraftRanking.Rank, "Rank should not be null");
+        Assert.IsNotNull(playerDraftRanking.Position, "Position should not be null");
+        Assert.IsNotNull(playerDraftRanking.DraftStatus, "Draft status should not be null");
         // ... existing assertions ...
     }
 }

+[TestMethod]
+public async Task GetPlayerDraftRankingByYearAsync_Should_Handle_Invalid_Year()
+{
+    // Arrange
+    await using var nhlApi = new NhlApi();
+
+    // Act & Assert
+    await Assert.ThrowsExceptionAsync<ArgumentException>(
+        () => nhlApi.GetPlayerDraftRankingByYearAsync("invalid"));
+}
Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (2)

171-171: Consider setting a default value for includeEventDateTime.

The includeEventDateTime parameter is consistently set to false across all usages. Consider setting this as the default value in the GetGameCenterPlayByPlayByGameIdAsync method signature to reduce code duplication and improve maintainability.

Also applies to: 237-237, 318-318, 412-412, 523-523


Line range hint 896-897: Fix incorrect statistic tracking for takeaways.

There's a bug in the takeaway statistic tracking. The code is incorrectly incrementing the Giveaway statistic instead of the Takeaway statistic.

Apply this fix:

-    ? gameStatisticTotals[PlayerGameCenterStatistic.Giveaway] += 1
-    : gameStatisticTotals[PlayerGameCenterStatistic.Giveaway];
+    ? gameStatisticTotals[PlayerGameCenterStatistic.Takeaway] += 1
+    : gameStatisticTotals[PlayerGameCenterStatistic.Takeaway];
Nhl.Api/Src/Api/NhlApi.cs (2)

521-521: Typographical correction in documentation comment

Consider hyphenating "play by play" to "play-by-play" for consistency and clarity.

Apply this diff to correct the documentation:

-/// <param name="includeEventDateTime"> A flag to include the event date and time for the NHL game center play by play feed</param>
+/// <param name="includeEventDateTime"> A flag to include the event date and time for the NHL game center play-by-play feed</param>

717-717: Typographical correction in documentation comment

Consider adding a space in "linescores" to read "line scores" for clarity.

Apply this diff to correct the documentation:

-/// Returns the NHL game direct box score including information such as summaries, linescores, shots by period and more
+/// Returns the NHL game direct box score including information such as summaries, line scores, shots by period, and more
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 965bfe5 and 5e8c748.

📒 Files selected for processing (13)
  • Nhl.Api.Common/Attributes/TeamActiveAttribute.cs (1 hunks)
  • Nhl.Api.Common/Extensions/EnumExtensions.cs (1 hunks)
  • Nhl.Api.Domain/Enumerations/Team/TeamEnum.cs (2 hunks)
  • Nhl.Api.Domain/Models/Draft/DraftYear.cs (1 hunks)
  • Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs (1 hunks)
  • Nhl.Api.Domain/Models/Draft/Ranks.cs (0 hunks)
  • Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs (2 hunks)
  • Nhl.Api.Tests/GameTests.cs (1 hunks)
  • Nhl.Api.Tests/PlayerTests.cs (4 hunks)
  • Nhl.Api/Src/Api/NhlApi.cs (6 hunks)
  • Nhl.Api/Src/PlayerApi/INhlPlayerApi.cs (2 hunks)
  • Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs (3 hunks)
  • Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (16 hunks)
💤 Files with no reviewable changes (1)
  • Nhl.Api.Domain/Models/Draft/Ranks.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • Nhl.Api.Domain/Models/Game/GameCenterPlayByPlay.cs
🔇 Additional comments (21)
Nhl.Api.Common/Attributes/TeamActiveAttribute.cs (2)

12-14: LGTM! Well-structured attribute definition.

The attribute is properly configured with appropriate usage restrictions and follows C# naming conventions.


1-19: Verify consistent attribute usage across team enumerations.

Let's ensure this attribute is consistently applied across all team-related enumerations in the codebase.

✅ Verification successful

Let me check the actual enum definitions to ensure we have a complete picture.


Let me check the TeamLogoType enum to ensure we have a complete picture of all team-related enums.


TeamActiveAttribute is correctly implemented and used

The attribute is consistently applied across all team entries in the TeamEnum, with appropriate active/inactive status. The TeamLogoType enum doesn't require this attribute as it's not related to team status but rather to logo display options.

Notable observations:

  • All NHL teams are properly decorated with [TeamActive]
  • Most teams are marked as active with [TeamActive(true)]
  • Arizona Coyotes correctly marked as inactive with [TeamActive(false)]
  • Utah Hockey Club is included as an active team, reflecting recent NHL expansion
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of TeamActiveAttribute across team enums

# Find all enum definitions related to teams
ast-grep --pattern 'enum $ENUM_NAME {
  $$$
}'

# Search for any team-related enums that might be missing the attribute
rg -l "enum.*Team" | xargs rg -L "\[TeamActive"

Length of output: 2343


Script:

#!/bin/bash
# Get the full content of TeamEnum.cs to verify the attribute usage
cat Nhl.Api.Domain/Enumerations/Team/TeamEnum.cs

# Also check if there are any other team-related enums in the codebase
fd -e cs | xargs rg "enum.*Team"

Length of output: 5166


Script:

#!/bin/bash
# Get the content of TeamLogoType enum
cat Nhl.Api.Domain/Models/Team/TeamLogo.cs

Length of output: 1129

Nhl.Api.Domain/Enumerations/Team/TeamEnum.cs (4)

1-2: LGTM: Import statement correctly added

The new import for TeamActive attribute is properly placed.


171-174: Verify the Utah Hockey Club implementation details

A few points need verification regarding the new UtahHockeyClub entry:

  1. Confirm if the team ID 59 follows NHL's official team ID assignment
  2. Verify if the team should be marked as active, considering it's a new franchise
  3. Ensure all necessary team-related data (logos, colors, etc.) is available in related configuration files
#!/bin/bash
# Search for other references to Utah Hockey Club to ensure consistency
rg "Utah.*Hockey" --type cs -C 3

# Look for team ID patterns to verify consistency
rg "= \d{2}," --type cs

Line range hint 1-174: Verify team configuration consistency across the solution

The enum changes look systematic and well-structured. However, please ensure that:

  1. All team-related configuration files (appsettings, constants, etc.) are updated to reflect these changes
  2. Any caching mechanisms for team data are updated to consider the new TeamActive attribute
#!/bin/bash
# Search for team-related configuration files
fd -e json -e config -e xml | xargs rg "teams?" -l

# Look for potential caching implementations
ast-grep --pattern 'cache.$_($$$)'

158-160: Verify handling of inactive teams across the codebase

The ArizonaCoyotes team is now marked as inactive. This status change might affect existing code that doesn't account for inactive teams.

✅ Verification successful

Let me analyze the usage of ArizonaCoyotes and team activity checks to verify if the inactive status is properly handled.


Let me check how the team activity status is being handled in the codebase.


Let me check how teams are handled differently based on seasons and if there's proper handling for the ArizonaCoyotes inactive status.


Proper handling of ArizonaCoyotes inactive status confirmed

The codebase correctly handles the ArizonaCoyotes' inactive status:

  • The team is marked as inactive using [TeamActive(false)] in TeamEnum
  • NhlStatisticsApi explicitly removes ArizonaCoyotes from team lists for seasons 2024-2025 and beyond
  • Test cases exist for ArizonaCoyotes only for the 2023-2024 season, showing awareness of the timeline
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct usage of ArizonaCoyotes to ensure proper handling
rg "ArizonaCoyotes" --type cs -C 3

# Search for patterns that might need to check team activity status
ast-grep --pattern 'TeamEnum.$_'

Length of output: 13217


Script:

#!/bin/bash
# Search for direct usage of ArizonaCoyotes and team activity checks
rg "ArizonaCoyotes" --type cs -C 3

# Search for TeamActive attribute usage
rg "TeamActive" --type cs -C 3

# Search for patterns checking team activity status
ast-grep --pattern 'GetTeamActive($$$)'

Length of output: 21257


Script:

#!/bin/bash
# Search for team activity checks in the codebase
rg "TeamActive.*isActive" --type cs -C 3

# Search for any methods that might handle team activity status
rg "GetTeam.*Active" --type cs -C 3

# Look for places where ArizonaCoyotes is handled specially for 2024-2025 season
rg "2024.*2025.*Arizona|Arizona.*2024" --type cs -C 3

Length of output: 2007


Script:

#!/bin/bash
# Search for season-specific team handling in the codebase
rg "season2024.*2025" --type cs -C 5

# Look for any methods that filter or handle teams based on season
rg "GetTeams.*Season|Teams.*Season" --type cs -C 3

# Check for any special handling in statistics API
rg "allNhlTeams.*Remove" --type cs -C 3

Length of output: 3251

Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs (2)

123-160: LGTM! Well-structured class with proper nullability.

The class demonstrates good practices:

  • Proper nullable annotations
  • Modern C# collection expressions
  • Initialized collections preventing null reference exceptions
  • Comprehensive documentation

1-5: Verify consistency with existing draft-related models.

Let's ensure there are no duplicate or conflicting draft-related models in the codebase.

✅ Verification successful

Let me check the content of these files to ensure there's no duplication or inconsistency.


Draft-related models are well-organized and non-redundant

The draft-related models are properly structured with clear separation of concerns:

  • DraftYear.cs contains constants for valid NHL draft years
  • PlayerDraftRanking.cs contains three complementary classes:
    • PlayerDraftCategory for draft categories
    • PlayerDraftRanking for individual player rankings
    • PlayerDraftYear for organizing rankings by year

No duplicate or conflicting models found. The namespace and documentation are consistent across files.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other draft-related model files
echo "Searching for other draft-related models..."
fd -e cs -E "**/bin/**" -E "**/obj/**" | rg -i "draft.*\.cs$"

# Search for similar property patterns to detect potential duplicate models
echo "Searching for similar draft ranking properties..."
rg -l "draftYear|categoryId|finalRank" --type cs

Length of output: 502


Script:

#!/bin/bash
# Check content of both draft-related files
echo "Content of PlayerDraftRanking.cs:"
cat "Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs"

echo -e "\nContent of DraftYear.cs:"
cat "Nhl.Api.Domain/Models/Draft/DraftYear.cs"

Length of output: 12132

Nhl.Api/Src/PlayerApi/INhlPlayerApi.cs (1)

1-1: LGTM!

The using directive is appropriately scoped for the new draft-related functionality.

Nhl.Api.Tests/PlayerTests.cs (2)

68-70: LGTM! Good practice adding default case.

Adding a default case to switch statements is a good practice, even if empty, as it makes the code more maintainable and prevents potential issues with unhandled cases.


34-34: LGTM! Verify API changes for province/state codes.

The standardization to two-letter province codes (e.g., "ON" instead of "Ontario") is consistent. This change aligns with common geographic code standards.

Also applies to: 60-60, 127-127

✅ Verification successful

Let me gather more information about the province/state codes usage in the actual implementation.


Let me check the actual model class to understand how BirthProvinceState is defined.


Province/state code changes are correct and aligned with the API model

Looking at the PlayerSearchResult model, the BirthProvinceState property's documentation explicitly shows "ON" as the example format, confirming that two-letter codes are the expected format. The test changes correctly align with this model's specification.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of province/state codes across the codebase
# Test: Search for other occurrences of full province names that might need updating
rg -i "ontario|quebec|alberta|manitoba|saskatchewan|british columbia" --type cs

Length of output: 885


Script:

#!/bin/bash
# Check for province/state code usage in the implementation
ast-grep --pattern 'BirthProvinceState = "$_"'

# Also check for any province/state mapping or conversion logic
rg -l "ProvinceState" --type cs

Length of output: 160


Script:

#!/bin/bash
# Check the PlayerSearchResult model definition
cat Nhl.Api.Domain/Models/Player/PlayerSearchResult.cs

Length of output: 4552

Nhl.Api/Src/StatisticsApi/NhlStatisticsApi.cs (3)

465-471: LGTM! Well-handled team removal logic.

The code properly handles the removal of Arizona Coyotes from the NHL teams list for the 2024-2025 season onwards, with appropriate culture-specific string parsing and clear comments.


546-549: LGTM! Consistent parameter addition across methods.

The addition of the gameType parameter with a default value of GameType.RegularSeason is well-implemented and properly documented across all relevant methods.

Also applies to: 589-592, 632-635, 675-678


572-572: LGTM! Consistent endpoint construction.

The endpoint construction is well-implemented using StringBuilder and properly includes the new gameTypeId parameter.

Also applies to: 615-615, 658-658, 701-701

Nhl.Api/Src/Api/NhlApi.cs (7)

1-1: Addition of required namespace import

The addition of using Nhl.Api.Models.Draft; is appropriate for accessing draft-related models.


521-524: Addition of optional parameter includeEventDateTime

The inclusion of the optional includeEventDateTime parameter enhances functionality by allowing users to include event date and time in the game center feed. The default value maintains backward compatibility.


668-668: Addition of gameType parameter to method

Adding the gameType parameter to GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync provides more precise control over the type of games included in statistics retrieval. The default value of GameType.RegularSeason ensures backward compatibility.

Also applies to: 671-672


682-682: Addition of gameType parameter to method

Including the gameType parameter in GetTimeOnIcePlayerStatisticsBySeasonAndFilterExpressionAsync allows specification of the game type for time-on-ice statistics. The default value maintains compatibility with existing code.

Also applies to: 685-686


696-696: Addition of gameType parameter to method

The addition of the gameType parameter to GetPlayerStatisticsBySeasonAndFilterExpressionAsync enables retrieval of player statistics for specific game types. The default value of GameType.RegularSeason maintains backward compatibility.

Also applies to: 699-700


710-710: Addition of gameType parameter to method

By adding the gameType parameter to GetGoalieStatisticsBySeasonAndFilterExpressionAsync, the method now supports specifying game types for goalie statistics retrieval. The default value ensures backward compatibility.

Also applies to: 713-714


716-722: Addition of GetBoxscoreByGameIdAsync method

The new method GetBoxscoreByGameIdAsync provides functionality to retrieve game box score information, enhancing the API's capabilities. Documentation appears accurate and complete.

Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs Outdated Show resolved Hide resolved
Nhl.Api.Domain/Models/Draft/DraftYear.cs Show resolved Hide resolved
Updated the version number for the projects `Nhl.Api.Common.csproj`,
`Nhl.Api.Domain.csproj`, `Nhl.Api.Tests.csproj`, and `Nhl.Api.csproj`
from `3.5.0` to `3.6.0`. Additionally, updated the target framework
for these projects to `net8.0`.
The DraftYear and SeasonYear classes have been changed to sealed
classes, preventing inheritance. This is indicated by the addition
of the sealed keyword in DraftYear.cs and SeasonYears.cs.

In PlayerDraftRanking.cs, several properties in the PlayerDraftRanking
class have been updated to allow nullable strings. This is indicated
by the addition of the ? after the string type for properties such as
LastName, FirstName, PositionCode, ShootsCatches, LastAmateurClub,
LastAmateurLeague, BirthDate, BirthCity, BirthStateProvince, and
BirthCountry.
The method `AddEstimatedDateTimeOfPlayForEachPlay` in the `NhlGameService` class now includes error handling for the HTTP request to fetch the score report. A nullable string variable `scoreReport` is declared at the beginning of the method and initialized within a `try` block. A `catch` block has been added to handle exceptions, returning the `gameCenterPlayByPlay` object without further processing if an error occurs. This change improves the robustness of the method by preventing potential runtime errors due to failed HTTP requests.
Removed unused `using` directives from multiple files to reduce clutter.
Enhanced `GetPlayerStatisticsBySeasonAndFilterExpressionAsync` and
`GetRealtimePlayerStatisticsBySeasonAndFilterExpressionAsync` method
calls in `StatisticsTests.cs` by adding parameters for better filtering
and sorting: `playerStatisticsFilterToSortBy`, `limit`, `offsetStart`,
and `gameType`.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (7)
Nhl.Api.Common/Http/NhlApiHttpClient.cs (2)

62-62: LGTM! Consider making the timeout configurable via configuration.

The increase in default timeout from 30 to 60 seconds is reasonable for handling potentially slow NHL API responses. However, since this is a base class used by multiple HTTP clients, consider making the timeout configurable through dependency injection or configuration settings rather than hardcoding it.

Consider:

  1. Moving the timeout to configuration (e.g., appsettings.json)
  2. Allowing different timeouts for different NHL API endpoints
  3. Adding logging when requests approach the timeout threshold

Example configuration structure:

{
  "NhlApi": {
    "Timeouts": {
      "Default": 60,
      "GameStats": 90,
      "LiveFeed": 30
    }
  }
}

Line range hint 62-71: Consider adding resilience patterns.

The constructor sets up basic HTTP client settings. Given this is an API client that other services depend on, consider enhancing it with resilience patterns.

Recommendations:

  1. Add retry policies for transient failures
  2. Add circuit breaker pattern for API outages
  3. Consider implementing request caching where appropriate

Example using Polly:

public NhlApiHttpClient(string clientApiUri, string clientVersion, int timeoutInSeconds = 60)
{
    ServicePointManager.ReusePort = true;
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
    Client = clientApiUri;
    ClientVersion = clientVersion;
    Timeout = TimeSpan.FromSeconds(timeoutInSeconds);
    
    // Add resilience policies
    var retryPolicy = Policy<HttpResponseMessage>
        .Handle<HttpRequestException>()
        .WaitAndRetryAsync(3, retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
            
    var circuitBreaker = Policy<HttpResponseMessage>
        .Handle<HttpRequestException>()
        .CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: 2,
            durationOfBreak: TimeSpan.FromSeconds(30));
}
Nhl.Api.Tests/GameTests.cs (1)

209-232: Document test data selection criteria.

While the test data provides good coverage across different seasons (2000-2023), consider adding comments to explain:

  • The significance of chosen game IDs
  • Why these specific seasons were selected
  • Any special characteristics of these games (regular season, playoffs, etc.)

This documentation would help maintainers understand the test coverage strategy.

Example:

// Test data includes:
// - Regular season games from each season (2000-2023)
// - Games from different months within seasons
// - Games with known play-by-play data availability
[DataRow(2000020004)] // Early season game from 2000-01
[DataRow(2001020004)] // Early season game from 2001-02
// ... etc
Nhl.Api.Domain/Services/NhlGameService.cs (1)

68-71: Extract magic numbers into named constants.

The time-related values should be defined as private constants at the class level for better maintainability and clarity.

+private const double AVERAGE_PERIOD_DURATION_MINUTES = 42.5;
+private const double INTERMISSION_DURATION_MINUTES = 20;
+private const double OVERTIME_INTERMISSION_DURATION_MINUTES = 2.5;
+private const int MAX_REGULAR_PERIODS = 3;

-var averagePeriodDuration = TimeSpan.FromMinutes(42.5);
-var intermissionDuration = TimeSpan.FromMinutes(20);
-var overtimeIntermissionDuration = TimeSpan.FromMinutes(2.5);
-var maxRegularPeriods = 3;
+var averagePeriodDuration = TimeSpan.FromMinutes(AVERAGE_PERIOD_DURATION_MINUTES);
+var intermissionDuration = TimeSpan.FromMinutes(INTERMISSION_DURATION_MINUTES);
+var overtimeIntermissionDuration = TimeSpan.FromMinutes(OVERTIME_INTERMISSION_DURATION_MINUTES);
+var maxRegularPeriods = MAX_REGULAR_PERIODS;
Nhl.Api.Domain/Models/Season/SeasonYears.cs (1)

12-15: Enhance documentation for AllSeasons property.

While having both individual constants and the AllSeasons collection serves different purposes (constants for compile-time safety, collection for runtime operations), the relationship and intended usage of each should be clearly documented.

Consider updating the XML documentation to:

     /// <summary>
-    /// A collection of all the NHL seasons
+    /// A collection of all valid NHL seasons for runtime validation and iteration.
+    /// For compile-time safety, prefer using the individual season constants when the season is known at compile time.
     /// </summary>
Nhl.Api/Src/GameApi/NhlGameApi.cs (1)

182-188: Consider reusing boxscore retrieval logic.

While the implementation is correct, consider extracting the boxscore retrieval logic into a private method to avoid duplication with GetGameCenterBoxScoreByGameIdAsync.

+    private async Task<Boxscore> GetBoxscoreInternalAsync(int gameId, CancellationToken cancellationToken = default) => 
+        await _nhlApiWebHttpClient.GetAsync<Boxscore>($"/gamecenter/{gameId}/right-rail", cancellationToken);
+
     public async Task<Boxscore> GetBoxscoreByGameIdAsync(int gameId, CancellationToken cancellationToken = default) => 
-        await _nhlApiWebHttpClient.GetAsync<Boxscore>($"/gamecenter/{gameId}/right-rail", cancellationToken);
+        await GetBoxscoreInternalAsync(gameId, cancellationToken);

Then update GetGameCenterBoxScoreByGameIdAsync to use this method:

     public async Task<GameCenterBoxScore> GetGameCenterBoxScoreByGameIdAsync(int gameId, CancellationToken cancellationToken = default)
     {
-        var boxScoreTask = _nhlApiWebHttpClient.GetAsync<Boxscore>($"/gamecenter/{gameId}/right-rail", cancellationToken);
+        var boxScoreTask = GetBoxscoreInternalAsync(gameId, cancellationToken);
         var gameCenterBoxScoreTask = _nhlApiWebHttpClient.GetAsync<GameCenterBoxScore>($"/gamecenter/{gameId}/boxscore", cancellationToken);
Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs (1)

286-307: Consider enhancing input validation and adding caching.

The implementation looks good with proper error handling and documentation. Consider these improvements:

  1. Add validation for seasonYear format and range
  2. Implement caching similar to other methods in the class
  3. Validate that seasonYear represents a valid NHL draft year

Example implementation with additional validation:

 public async Task<PlayerDraftYear> GetPlayerDraftRankingByYearAsync(string seasonYear, int startingPosition = 1, CancellationToken cancellationToken = default)
 {
     if (string.IsNullOrWhiteSpace(seasonYear))
     {
         throw new ArgumentException($"The {nameof(seasonYear)} parameter is required", nameof(seasonYear));
     }
+    
+    // Validate year format and range
+    if (!int.TryParse(seasonYear, out int year) || year < 1917 || year > DateTime.Now.Year + 1)
+    {
+        throw new ArgumentException($"The {nameof(seasonYear)} parameter must be a valid NHL draft year between 1917 and {DateTime.Now.Year + 1}", nameof(seasonYear));
+    }

     if (startingPosition < 1)
     {
         throw new ArgumentException($"The {nameof(startingPosition)} parameter must be greater than 0", nameof(startingPosition));
     }

+    // Check cache first
+    var cacheKey = $"draft_rankings_{seasonYear}_{startingPosition}";
+    if (_cachingService.TryGetValue(cacheKey, out PlayerDraftYear cachedResult))
+    {
+        return cachedResult;
+    }

-    return await _nhlApiWebHttpClient.GetAsync<PlayerDraftYear>($"/draft/rankings/{seasonYear}/{startingPosition}", cancellationToken);
+    var result = await _nhlApiWebHttpClient.GetAsync<PlayerDraftYear>($"/draft/rankings/{seasonYear}/{startingPosition}", cancellationToken);
+    
+    // Cache the result
+    _cachingService.Set(cacheKey, result);
+    
+    return result;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8c748 and 0a851c7.

📒 Files selected for processing (17)
  • Nhl.Api.Common/Http/NhlApiHttpClient.cs (1 hunks)
  • Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs (2 hunks)
  • Nhl.Api.Common/Nhl.Api.Common.csproj (1 hunks)
  • Nhl.Api.Domain/Models/Draft/DraftYear.cs (1 hunks)
  • Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs (1 hunks)
  • Nhl.Api.Domain/Models/Season/SeasonYears.cs (1 hunks)
  • Nhl.Api.Domain/Nhl.Api.Domain.csproj (1 hunks)
  • Nhl.Api.Domain/Services/NhlGameService.cs (1 hunks)
  • Nhl.Api.Domain/Services/NhlTeamService.cs (0 hunks)
  • Nhl.Api.Tests/GameTests.cs (1 hunks)
  • Nhl.Api.Tests/Nhl.Api.Tests.csproj (1 hunks)
  • Nhl.Api.Tests/StatisticsTests.cs (21 hunks)
  • Nhl.Api/Nhl.Api.csproj (1 hunks)
  • Nhl.Api/README.md (28 hunks)
  • Nhl.Api/Src/Api/NhlApi.cs (6 hunks)
  • Nhl.Api/Src/GameApi/NhlGameApi.cs (3 hunks)
  • Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs (2 hunks)
💤 Files with no reviewable changes (1)
  • Nhl.Api.Domain/Services/NhlTeamService.cs
✅ Files skipped from review due to trivial changes (4)
  • Nhl.Api.Common/Nhl.Api.Common.csproj
  • Nhl.Api.Domain/Nhl.Api.Domain.csproj
  • Nhl.Api.Tests/Nhl.Api.Tests.csproj
  • Nhl.Api/Nhl.Api.csproj
🚧 Files skipped from review as they are similar to previous changes (3)
  • Nhl.Api.Common/Http/NhlSuggestionApiHttpClient.cs
  • Nhl.Api.Domain/Models/Draft/DraftYear.cs
  • Nhl.Api.Domain/Models/Draft/PlayerDraftRanking.cs
🧰 Additional context used
🪛 Markdownlint
Nhl.Api/README.md

456-456: Expected: h4; Actual: h5
Heading levels should only increment by one level at a time

(MD001, heading-increment)


711-711: null
Link fragments should be valid

(MD051, link-fragments)


883-883: Expected: h4; Actual: h5
Heading levels should only increment by one level at a time

(MD001, heading-increment)


1050-1050: null
Link fragments should be valid

(MD051, link-fragments)


1073-1073: null
Link fragments should be valid

(MD051, link-fragments)


1512-1512: null
Link fragments should be valid

(MD051, link-fragments)


1704-1704: Expected: h4; Actual: h5
Heading levels should only increment by one level at a time

(MD001, heading-increment)


2617-2617: Expected: h4; Actual: h5
Heading levels should only increment by one level at a time

(MD001, heading-increment)


2903-2903: null
Link fragments should be valid

(MD051, link-fragments)


2947-2947: null
Link fragments should be valid

(MD051, link-fragments)


2970-2970: null
Link fragments should be valid

(MD051, link-fragments)


3090-3090: null
Link fragments should be valid

(MD051, link-fragments)

🔇 Additional comments (23)
Nhl.Api.Common/Http/NhlApiHttpClient.cs (1)

62-62: Verify consistent timeout changes across the codebase.

Let's ensure the timeout changes are consistent across all NHL API clients and tests.

✅ Verification successful

Timeout configuration is consistent across all NHL API clients

All derived HTTP clients consistently use the same default timeout value of 60 seconds, inheriting from the base NhlApiHttpClient class. The verification shows:

  • All client implementations (NhlScoresHtmlReportsApiHttpClient, NhlCmsHttpClient, NhlSuggestionApiHttpClient, NhlEApiHttpClient, NhlApiWebHttpClient, NhlStaticAssetsApiHttpClient, NhlShiftChartHttpClient) use the same timeout value of 60 seconds
  • The timeout is properly propagated through the base class constructor
  • No conflicting timeout configurations were found in the codebase
  • No test-specific timeout overrides were detected

The timeout parameter change in NhlApiHttpClient is safe and maintains consistency across the entire codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for timeout values across the codebase

# Check for timeout values in HTTP client constructors
echo "Checking HTTP client timeouts..."
ast-grep --pattern 'public $_(string $_Uri, string $_Version, int timeoutInSeconds = $_) {'

# Check for timeout values in test files
echo -e "\nChecking test timeouts..."
rg -g "*.cs" "timeoutInSeconds.*=.*\d+" --type cs

# Check for timeout configuration in project files
echo -e "\nChecking configuration files..."
rg -g "*.json" '"timeout":\s*\d+' --type json

Length of output: 701


Script:

#!/bin/bash
# Let's perform a more comprehensive check for timeout-related code

# Check for any timeout-related code in .cs files
echo "Checking timeout usage in C# files..."
rg -i "timeout" --type cs -A 2 -B 2

# Check for HttpClient configurations
echo -e "\nChecking HttpClient configurations..."
ast-grep --pattern 'HttpClient.$_ = $_'

# Check for any test files that might be using the NhlApiHttpClient
echo -e "\nChecking test usage of NhlApiHttpClient..."
rg "NhlApiHttpClient" --type cs -A 2 -B 2

Length of output: 12407

Nhl.Api.Domain/Services/NhlGameService.cs (4)

30-30: Consider using dependency injection for HTTP client.

The HTTP client is still directly instantiated within the service. This makes the code harder to test and violates dependency injection principles.


17-22: LGTM - Interface documentation is clear and descriptive.

The new interface method is well-documented with clear parameter and return value descriptions.


110-113: Avoid swallowing exceptions silently.

Both catch blocks silently swallow exceptions, making it difficult to diagnose issues in production.

Also applies to: 179-181


77-77: Verify time parsing operations across the codebase.

Multiple time parsing operations could fail with invalid input. Let's verify the format of time strings across the codebase.

Also applies to: 131-132, 174-174

✅ Verification successful

Based on the results, I can now generate the final response:

Time parsing operations are properly handled with consistent formats and culture-aware parsing

All time parsing operations in the codebase follow consistent patterns:

  • TimeSpan parsing uses CultureInfo.InvariantCulture with explicit formats (e.g., mm\:ss)
  • DateTime/TimeOnly parsing includes proper timezone offsets and uses CultureInfo.InvariantCulture
  • Time formats are consistently used across the codebase with proper error handling

The specific time parsing in NhlGameService.cs at line 77 and related lines (131-132, 174-174) follows the established pattern and is safe.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for time string formats in the codebase to ensure consistency

# Search for time parsing patterns
echo "Checking time parsing patterns..."
rg -n "TimeOnly\.Parse|TimeSpan\.Parse|DateTime\.Parse" --type csharp

# Search for time format strings
echo "Checking time format strings..."
rg -n "mm:ss|HH:mm|PM|AM" --type csharp

# Search for potential time-related variables
echo "Checking time-related variable names..."
rg -n "timeInPeriod|startTime|endTime" --type csharp

Length of output: 7526

Nhl.Api.Domain/Models/Season/SeasonYears.cs (1)

10-10: LGTM! Good practice sealing the class.

Marking the class as sealed is appropriate since this is a utility class containing constants and static members, preventing unintended inheritance.

Nhl.Api/Src/GameApi/NhlGameApi.cs (3)

12-13: LGTM: New service fields follow established patterns.

The new static readonly fields are properly initialized and follow the existing naming conventions.


138-150: Method needs overloading to maintain backward compatibility.

The existing review comment about breaking changes is still valid. Adding an optional parameter can break clients using named arguments.

Additionally, consider renaming the parameter to includeEstimatedEventDateTime to better reflect that these are estimated times.


175-177: LGTM: Good handling of API endpoint change.

The parallel execution of tasks and manual assignment with clear documentation effectively handles the NHL API endpoint change.

Nhl.Api/Src/PlayerApi/NhlPlayerApi.cs (1)

2-2: LGTM!

The import is correctly added to support the new draft-related functionality.

Nhl.Api.Tests/StatisticsTests.cs (6)

453-454: LGTM: Proper integration of gameType parameter

The addition of the gameType parameter is correctly implemented, using GameType.RegularSeason for filtering player statistics.


486-487: LGTM: Consistent implementation for historical season data

The gameType parameter is properly integrated for historical season statistics retrieval.


511-512: LGTM: Proper handling of empty filter expression with gameType

The gameType parameter is correctly integrated with the empty filter expression test case.


549-550: LGTM: Proper integration with complex filter queries

The gameType parameter is correctly implemented in the complex query test scenario.


1317-1320: LGTM: Proper error handling test implementation

The gameType parameter is correctly integrated into the validation test case.


1338-1342: LGTM: Comprehensive error handling test coverage

The gameType parameter is consistently integrated across all error handling test cases, maintaining proper validation for both player and goalie statistics.

Also applies to: 1356-1359, 1373-1377, 1391-1394, 1409-1413, 1427-1430, 1444-1447, 1461-1465

Nhl.Api/Src/Api/NhlApi.cs (3)

521-524: LGTM! Well-documented parameter addition

The new includeEventDateTime parameter is properly documented and implemented with a sensible default value that maintains backward compatibility.


668-672: LGTM! Consistent parameter additions across statistics methods

The addition of the gameType parameter with default value GameType.RegularSeason is consistently implemented across all statistics methods. The changes maintain backward compatibility and are well-documented.

Also applies to: 682-686, 696-700, 710-715


717-723: LGTM! Well-documented new boxscore method

The new GetBoxscoreByGameIdAsync method is properly documented and follows the established pattern of delegating to the underlying API.

Nhl.Api/README.md (4)

131-131: LGTM! New box score method is well documented.

The new GetBoxscoreByGameIdAsync method is properly documented with clear parameter descriptions and return values.

Also applies to: 453-470


154-154: LGTM! New draft ranking method is well documented.

The new GetPlayerDraftRankingByYearAsync method is properly documented with clear parameter descriptions and return values.

Also applies to: 880-898


136-136: LGTM! Method signature updates are consistently documented.

The addition of the gameType parameter and includeEventDateTime parameter is well documented across all affected methods with clear descriptions.

Also applies to: 543-560, 691-711, 927-947, 1030-1050, 1053-1073, 1492-1512, 2883-2903, 2927-2947, 2950-2970, 3070-3090


456-456: Documentation structure follows standard API reference format.

The heading structure and link references follow the established pattern used throughout the documentation.

Also applies to: 883-883, 1704-1704, 2617-2617

🧰 Tools
🪛 Markdownlint

456-456: Expected: h4; Actual: h5
Heading levels should only increment by one level at a time

(MD001, heading-increment)

Comment on lines +233 to +248
public async Task GetGameCenterPlayByPlayByGameIdAsync_Returns_Valid_Estimated_DateTime_Of_Play_Information(int gameId)
{
var dictionary = new Dictionary<string, List<string>>
{
{ "P1", new List<string>() },
{ "P2", new List<string>() },
{ "P3", new List<string>() },
{ "OT", new List<string>() },
{ "SH", new List<string>() },
};

var httpClient = new NhlScoresHtmlReportsApiHttpClient();
var gameReport = await httpClient.GetStringAsync("/20232024/PL020206.HTM");

var regex = Regex.Matches(gameReport, @"(?<=<td class="" \+ bborder"">)Period(.*?)(?=</td>)", RegexOptions.Compiled, TimeSpan.FromSeconds(30)).ToList();

for (int i = 0; i < regex.Count; i++)
{
var match = regex[i].Value;
var value = Regex.Match(match, @"([0-9]{1,2}:[0-9]{2}\s[A-Z]{3})", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(30)).Groups[0].Value;
if (i <= 1)
{
dictionary["P1"].Add(value);
}
else if (i >= 2 && i <= 3)
{
dictionary["P2"].Add(value);
}
else if (i >= 4 && i <= 5)
{
dictionary["P3"].Add(value);
}
else if (i >= 6 && i <= 7)
{
dictionary["OT"].Add(value);
}
else if (i <= 9)
{
dictionary["SH"].Add(value);
}
}
// Arrange
await using var nhlApi = new NhlApi();

// Act
var results = await nhlApi.GetGameCenterPlayByPlayByGameIdAsync(gameId, includeEventDateTime: true);

// Assert
Assert.IsNotNull(results);
Assert.IsNotNull(results.GameDate);
Assert.IsNotNull(results.GameType);
Assert.IsNotNull(results.Id);
Assert.IsNotNull(results.Clock);
Assert.IsNotNull(results.Plays);
Assert.IsTrue(results.Plays.All(p => p.EstimatedDateTimeOfPlay.HasValue));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage with additional test cases and assertions.

While the test verifies basic functionality, consider adding:

  1. Additional test methods for negative scenarios:

    • includeEventDateTime: false
    • Invalid game IDs
    • Games with missing play data
  2. Stronger assertions in the current test:

    • Verify chronological order of plays
    • Validate time values against game start time
    • Check for reasonable time gaps between plays

Example implementation:

[TestMethod]
public async Task GetGameCenterPlayByPlayByGameIdAsync_Without_DateTime_Returns_Valid_Information()
{
    await using var nhlApi = new NhlApi();
    var results = await nhlApi.GetGameCenterPlayByPlayByGameIdAsync(2023020090, includeEventDateTime: false);
    
    Assert.IsNotNull(results.Plays);
    Assert.IsTrue(results.Plays.All(p => !p.EstimatedDateTimeOfPlay.HasValue));
}

// Enhance current test with additional assertions
public async Task GetGameCenterPlayByPlayByGameIdAsync_Returns_Valid_Estimated_DateTime_Of_Play_Information(int gameId)
{
    // ... existing code ...

    // Verify chronological order
    var orderedPlays = results.Plays.OrderBy(p => p.EstimatedDateTimeOfPlay).ToList();
    CollectionAssert.AreEqual(orderedPlays, results.Plays.ToList(), 
        "Plays should be in chronological order");

    // Verify times are within game bounds
    Assert.IsTrue(results.Plays.All(p => 
        p.EstimatedDateTimeOfPlay >= results.GameDate), 
        "All plays should occur after game start");

    // Verify reasonable time gaps
    for (int i = 1; i < results.Plays.Count; i++)
    {
        var timeDiff = results.Plays[i].EstimatedDateTimeOfPlay - 
                      results.Plays[i-1].EstimatedDateTimeOfPlay;
        Assert.IsTrue(timeDiff?.TotalMinutes <= 60, 
            "Suspicious time gap between plays");
    }
}

Nhl.Api.Domain/Services/NhlGameService.cs Show resolved Hide resolved
Comment on lines 187 to 198
/// <summary>
/// A linear
/// </summary>
private double CalculateMultiplier(DateTime startTime, DateTime endTime, int events)
{
// Calculate duration in minutes
var duration = (endTime - startTime).TotalMinutes;

var multiplier = ((41 * duration) + (2 * events) - 381) / 3000;

return multiplier;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve documentation and extract magic numbers in CalculateMultiplier.

The method's documentation is incomplete, and the formula uses magic numbers without explanation.

 /// <summary>
-/// A linear 
+/// Calculates a multiplier for time estimation based on period duration and number of events.
+/// The multiplier is used to adjust the distribution of plays within a period.
 /// </summary>
+/// <param name="startTime">The start time of the period</param>
+/// <param name="endTime">The end time of the period</param>
+/// <param name="events">The number of events in the period</param>
+/// <returns>A multiplier value for time distribution calculations</returns>
 private double CalculateMultiplier(DateTime startTime, DateTime endTime, int events)
 {
+    // Constants for the multiplier formula
+    private const double DURATION_COEFFICIENT = 41.0;
+    private const double EVENTS_COEFFICIENT = 2.0;
+    private const double CONSTANT_TERM = 381.0;
+    private const double NORMALIZER = 3000.0;
+
     // Calculate duration in minutes
     var duration = (endTime - startTime).TotalMinutes;
 
-    var multiplier = ((41 * duration) + (2 * events) - 381) / 3000;
+    var multiplier = ((DURATION_COEFFICIENT * duration) + 
+                     (EVENTS_COEFFICIENT * events) - 
+                     CONSTANT_TERM) / NORMALIZER;
 
     return multiplier;
 }

Committable suggestion skipped: line range outside the PR's diff.

Nhl.Api.Domain/Models/Season/SeasonYears.cs Show resolved Hide resolved
Nhl.Api.Domain/Models/Season/SeasonYears.cs Show resolved Hide resolved
Comment on lines +726 to +734
/// <summary>
/// Returns the NHL draft ranking by the specified year and starting position for the draft year
/// </summary>
/// <param name="seasonYear"> The NHL draft year </param>
/// <param name="startingPosition"> The starting position of the NHL draft by the year </param>
/// <param name="cancellationToken"> A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns> Returns the NHL draft ranking by the specified year and starting position for the draft year </returns>
public async Task<PlayerDraftYear> GetPlayerDraftRankingByYearAsync(string seasonYear, int startingPosition = 1, CancellationToken cancellationToken = default) =>
await _nhlPlayerApi.GetPlayerDraftRankingByYearAsync(seasonYear, startingPosition, cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding input validation for draft ranking parameters

While the method is well-documented, consider adding input validation to ensure data integrity:

  • Validate seasonYear format and range
  • Ensure startingPosition is a positive number

Here's a suggested implementation with validation:

 public async Task<PlayerDraftYear> GetPlayerDraftRankingByYearAsync(string seasonYear, int startingPosition = 1, CancellationToken cancellationToken = default)
 {
+    if (string.IsNullOrEmpty(seasonYear))
+    {
+        throw new ArgumentNullException(nameof(seasonYear));
+    }
+    
+    if (!int.TryParse(seasonYear, out int year) || year < 1917 || year > DateTime.Now.Year)
+    {
+        throw new ArgumentException("Invalid season year format or value", nameof(seasonYear));
+    }
+
+    if (startingPosition < 1)
+    {
+        throw new ArgumentException("Starting position must be a positive number", nameof(startingPosition));
+    }
+
     return await _nhlPlayerApi.GetPlayerDraftRankingByYearAsync(seasonYear, startingPosition, cancellationToken);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// <summary>
/// Returns the NHL draft ranking by the specified year and starting position for the draft year
/// </summary>
/// <param name="seasonYear"> The NHL draft year </param>
/// <param name="startingPosition"> The starting position of the NHL draft by the year </param>
/// <param name="cancellationToken"> A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns> Returns the NHL draft ranking by the specified year and starting position for the draft year </returns>
public async Task<PlayerDraftYear> GetPlayerDraftRankingByYearAsync(string seasonYear, int startingPosition = 1, CancellationToken cancellationToken = default) =>
await _nhlPlayerApi.GetPlayerDraftRankingByYearAsync(seasonYear, startingPosition, cancellationToken);
/// <summary>
/// Returns the NHL draft ranking by the specified year and starting position for the draft year
/// </summary>
/// <param name="seasonYear"> The NHL draft year </param>
/// <param name="startingPosition"> The starting position of the NHL draft by the year </param>
/// <param name="cancellationToken"> A cancellation token that can be used by other objects or threads to receive notice of cancellation</param>
/// <returns> Returns the NHL draft ranking by the specified year and starting position for the draft year </returns>
public async Task<PlayerDraftYear> GetPlayerDraftRankingByYearAsync(string seasonYear, int startingPosition = 1, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(seasonYear))
{
throw new ArgumentNullException(nameof(seasonYear));
}
if (!int.TryParse(seasonYear, out int year) || year < 1917 || year > DateTime.Now.Year)
{
throw new ArgumentException("Invalid season year format or value", nameof(seasonYear));
}
if (startingPosition < 1)
{
throw new ArgumentException("Starting position must be a positive number", nameof(startingPosition));
}
return await _nhlPlayerApi.GetPlayerDraftRankingByYearAsync(seasonYear, startingPosition, cancellationToken);
}

Added a private method `CalculateMultiplier` to the `NhlGameService` class for time estimation based on period duration and event count. Expanded method documentation for clarity. Introduced constants to replace hardcoded values in the multiplier formula, enhancing readability and maintainability.
@Afischbacher Afischbacher merged commit 4ad27d0 into master Nov 5, 2024
4 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.

1 participant