Changelog
Every release, in order.
Rendered from the repo’s own CHANGELOG.md, so this page and the package can’t disagree.
0.7.1
latestAdded
- A worked example in
docs/examples/diaz-blown-saves.md, withexamples/diaz_blown_saves.pyas its runnable companion: fact-checking a closer’s postgame explanation (“I was throwing my fastball right in the middle”) against his pitch locations. Covers the path from a name to an answer — finding a pitcher’s recent games and their IDs, exporting every pitch, breaking down mix and arsenal by game, defining “the middle” against each batter’s own zone, and pulling the video for the pitches that got hit.
Fixed
- Games in progress are no longer cached. Caching keys on
game_pkalone, on the premise that a finished game’s data never changes — but a game cached while it was still being played kept whatever partial feed existed at that moment, permanently, and a later query would silently come up short (a reliever who pitched the ninth simply missing from a game he appeared in).fetch_game_feed()now writes only feeds whose Savant game status reads final, and ignores a cached feed that doesn’t, so entries already poisoned by an earlier version repair themselves on the next run once the game is over. A game still in progress re-fetches every time.
Changed
plot_zone(kind="heatmap")no longer draws a “Fewer”/“More” colorbar, matchingkind="kde", which dropped its own in 0.4.0. Darker already reads as more pitches, and the vertical bar shrank the plot area enough to push the strike zone and home plate off-center relative to every other plot kind — a visible misalignment when heatmap and scatter panels sit side by side, and doubled in a--split-bypair.
0.7.0
Added
- Batter filtering, for matchup views from the pitcher’s side:
PitchCollection.filter(batter=...)andPitcher.pitches(batter=...), plus a--batterflag onmound pitches,mix,results,arsenal,zoneandvideo. Takes a name or an MLB player ID, or a list mixing the two; names match any part of the name Savant reports, ignoring case and accents, so--batter perdomois enough.filter(pitcher=...)is the same thing for the other side, useful once a collection spans more than one arm. Batter, the mirror image ofPitcher: the pitches a hitter faced, discovered from his own game log and pulled from every pitcher who faced him in those games.Batter("Geraldo Perdomo").pitches(last=5, pitcher="Roki Sasaki")andPitcher("Roki Sasaki").pitches(last=5, batter="Geraldo Perdomo")return the same matchup from either side; the pitcher’s side fetches far fewer games, since a starter appears in a fraction of the games a hitter plays.PitchCollection.chase_rate()(with the sameby_pitch_typeoption asswing_rate()/whiff_rate()): swings divided by pitches outside the zone, so a chase pitch’s real job shows up as its own number. Location comes fromin_zonegeometry rather than theis_strikeruling, and pitches with no plate coordinates drop out of the denominator instead of counting as strikes.mound arsenalgains achase_ratecolumn alongsidewhiff_rate.plot_zone()labels matchups: a plot narrowed to one hitter notes “vs. ” in its dek, and a batter-side collection headlines as “Pitch locations to ” rather than crediting the hitter with throwing them.
Changed
mound.statsapi.pitching_game_log()/pitching_game_log_seasons()are nowgame_log()/game_log_seasons()with agroupargument ("pitching"or"hitting"), since the same Stats API endpoint serves both sides of the ball. Internal client functions, not part of the documentedPitcher/PitchCollectionAPI.
0.6.1
Fixed
mound zone --outnow creates missing parent directories before saving, matching how--exportandvideo --out-diralready behave, instead of raising a rawFileNotFoundError.- CLI commands no longer dump a full traceback (including every local variable, e.g. an entire pitch DataFrame) for an unwritable
--out/--out-dir/--exportpath;zone,pitches --export,videoandvideo-idnow fail with one clear line instead.
0.6.0
Added
mound --versionprints the installed CLI’s version and exits, so you can check what you have installed without dropping into Python.
0.5.1
Changed
- Consolidated the CLI’s repeated
--last/--since/--until/--game/--pitch/--stand/--at-bat/--pitch-number/--cache/--cache-diroption declarations, previously duplicated acrosspitches,mix,results,arsenal,zoneandvideo, into sharedAnnotatedtype aliases inmound/cli.py— each flag’s name and help text now live in one place instead of five. _get_pitches()’s internal helper is now keyword-only, closing off the possibility of a silent argument-order mistake at a call site.video --limitnow goes through a newPitchCollection.limit()method instead of constructing aPitchCollectiondirectly from another collection’s internals._fail()is now typed-> NoReturn, making explicit (for both readers and type checkers) that it always exits rather than returning.
0.5.0
Added
Pitchgainsspin_rate,release_extension,release_pos_x,release_pos_z,horizontal_breakandinduced_vertical_break, parsed from fields Savant’s/gffeed already returns but Mound wasn’t yet surfacing. All default toNonerather than raising when a pitch predates or otherwise lacks tracking coverage for them. Flow through automatically toto_frame()/CSV/JSON/Parquet export; no new fetch required.Pitchgainsis_swingandis_whiff, derived frompitch_callthe same wayis_strikealready is.PitchCollection.swing_rate()and.whiff_rate()(each with aby_pitch_typeoption, matchingstrike_rate()) calculate the resulting percentages — whiff rate is misses divided by swings, matching Baseball Savant’s own convention, not misses divided by every pitch thrown.PitchCollection.pitch_metrics()averages velocity, spin rate and movement by pitch type, using the fields above. Combined withwhiff_rate()in the newmound arsenalCLI command, for questions like how nasty a pitch looked in one start (--game) versus across a season (--last/--since).
Changed
- Adding
is_swing/is_whiffshiftsat_bat_resultanddescriptionone position later inPitch’s field order; only matters for code constructing aPitchpositionally rather than by keyword.
0.4.0
Added
mound video --limit Ncaps how many clips a run downloads (e.g.--limit 1for a single clip), rather than always fetching every pitch matching the other filters.PitchCollection.filter()/Pitcher.pitches()acceptat_bat_numberandpitch_number, exposed on the CLI as--at-bat/--pitch-number, to narrow down to one specific at-bat or one exact pitch (pair withgame/--game, since an at-bat number is only unique within a single game). Useful on its own, and withmound videoto download the clip for one particular pitch.mound.video.download_video_by_id()and themound video-id <pitch_id>CLI command download a broadcast clip directly from a knownpitch_id(e.g. one saved from an earlier export), with no pitcher/game lookup needed first.
Changed
- Refined
kind="kde"/kind="heatmap"styling inplot_zone(): both now share a single on-brand warm gradient instead of matplotlib’s genericYlOrRd. KDE surfaces default to a fixed bandwidth (rather than scipy’s sample-size-dependent Scott’s rule, which oversmoothed small pitch samples into one shapeless blob) and a gamma-corrected color scale that keeps the true “hot zone” distinct from its faint tail. The KDE colorbar is removed — its density values are an arbitrary scale, not a pitch count, so a “Fewer”/“More” legend was either meaningless or redundant with what the color already shows. plot_zone()’s y-axis now marks its topmost tick with a foot mark (e.g.4′), so the plate-height scale reads in feet without needing a full axis label.
0.3.0
Added
- Optional local file cache for Baseball Savant game-feed responses, keyed by
game_pk. Enable withPitcher.pitches(cache=True)/--cache(or a custom directory viacache="/some/dir"/--cache-dir, defaulting to~/.cache/mound). Because a finished game’s data never changes, repeat queries automatically fetch only games not already cached, with no separate “update” step needed. plot_zone(kind="kde")renders a kernel density estimate instead of the plain 2D-histogram heatmap, for a smoother density surface on larger pitch samples. Requires the new optionalscipydependency (pip install "mound[viz]"); bandwidth is configurable viabw_method, exposed on the CLI asmound zone --kind kde --bw-method.Pitch.download_video()/PitchCollection.download_videos()and themound videoCLI command download a pitch’s Baseball Savant broadcast clip, resolved from itspitch_id. Captures the clip page’s default embedded angle only (in practice, the home broadcast feed).
0.2.0
Added
batter_standfield onPitch, populated from Statcast’sstand. Filter with.filter(stand="L")/Pitcher.pitches(stand=...)(accepts"L"/"left"/"LHB","R"/"right"/"RHB", case-insensitive) or the CLI’s--standoption.plot_zone(split_by="stand")facets a zone chart into side-by-side vs-LHB/vs-RHB panels (each with its own strike zone and pitch count), exposed via the CLI’s--split-byoption onmound zone.
Changed
- README documents PyPI installation, since
moundis now published there.
0.1.0
Added
- Initial prototype: resolve a pitcher by name or MLB ID, retrieve Statcast pitch-level data (filterable by game, date range, last-N-starts or pitch type), calculate pitch mix and strike rate, plot pitch locations against the strike zone, and export to CSV/JSON/Parquet.
Pitcher/PitchCollectionPython API and amoundCLI (search,pitches,mix,results,zone) sharing the same underlying implementation.- Data sourced directly from the MLB Stats API and Baseball Savant’s
/gfendpoint, nopybaseballdependency. - Pytest suite covering player resolution, game-log/pitch parsing, filtering, analysis and export, run against mocked HTTP fixtures.
- README, ROADMAP and an end-to-end example using Roki Sasaki’s splitter.