Skip to content

Baseball Savant Expected Stats & Statcast Metrics #175

@grovecj

Description

@grovecj

Overview

Integrate with Baseball Savant to fetch expected statistics (xBA, xSLG, xwOBA) and Statcast metrics (exit velocity, barrel%, hard hit%, sprint speed).

Parent Issue

Part of #94 (WAR and Advanced Stats)

Data Source

Baseball Savant Expected Statistics Leaderboard:

https://baseballsavant.mlb.com/leaderboard/expected_statistics?type=batter&year={year}&min=q&csv=true

Fields to Ingest

Expected Stats (Batting):

  • xba - Expected Batting Average
  • xslg - Expected Slugging
  • xwoba - Expected wOBA
  • xwobacon - Expected wOBA on Contact

Statcast Metrics (Batting):

  • avg_exit_velocity - Average Exit Velocity
  • avg_launch_angle - Average Launch Angle
  • barrel_pct - Barrel %
  • hard_hit_pct - Hard Hit %
  • sprint_speed - Sprint Speed

Statcast Metrics (Pitching):

  • avg_exit_velocity_against - Exit Velocity Against
  • hard_hit_pct_against - Hard Hit % Against
  • whiff_pct - Whiff %
  • chase_pct - Chase %

Current Entity Fields

These columns already exist in PlayerBattingStats (from V12 migration):

  • xba, xslg, xwoba
  • avg_exit_velocity, avg_launch_angle, barrel_pct, hard_hit_pct
  • sprint_speed

And in PlayerPitchingStats:

  • avg_exit_velocity_against, hard_hit_pct_against
  • whiff_pct, chase_pct

These fields exist but are NOT currently populated!

Files to Create/Modify

Modify: ingestion/client/BaseballSavantClient.java

Add methods:

public Map<Integer, ExpectedStats> getExpectedStatsByPlayerId(Integer season);
public Map<Integer, StatcastMetrics> getStatcastMetricsByPlayerId(Integer season);

Create: ingestion/client/dto/ExpectedStatsData.java

public record ExpectedStatsData(
    Integer playerId,
    BigDecimal xba,
    BigDecimal xslg,
    BigDecimal xwoba,
    BigDecimal avgExitVelocity,
    BigDecimal avgLaunchAngle,
    BigDecimal barrelPct,
    BigDecimal hardHitPct,
    BigDecimal sprintSpeed
) {}

Create: ingestion/service/StatcastIngestionService.java

@Service
public class StatcastIngestionService {
    public int syncExpectedStats(Integer season);
    public int syncStatcastMetrics(Integer season);
}

Modify: ingestion/mapper/StatsMapper.java

Add method:

public void applyStatcastMetrics(PlayerBattingStats stats, ExpectedStatsData data);
public void applyStatcastMetrics(PlayerPitchingStats stats, PitchingStatcastData data);

Sync Strategy

  • Weekly sync (with OAA in Baseball Savant OAA Integration #172)
  • Statcast data updates after each game but aggregates change slowly
  • Expected stats are season-level, update less frequently

Test Cases

  • CSV parsing for all expected stats fields
  • Player mapping works correctly
  • Null/missing values handled gracefully
  • Updates existing stats without overwriting other fields

Acceptance Criteria

  • xBA, xSLG, xwOBA populated on batting stats
  • Exit velocity, barrel%, hard hit% populated
  • Sprint speed populated
  • Pitching Statcast metrics populated
  • Mapper tests for new apply methods

Dependencies

Notes

The ExpectedStatsResponse.java and SeasonAdvancedResponse.java DTOs already exist but aren't integrated. This issue completes that integration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions