mound 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mound-0.1.0/.github/workflows/ci.yml +29 -0
- mound-0.1.0/.gitignore +17 -0
- mound-0.1.0/CHANGELOG.md +15 -0
- mound-0.1.0/LICENSE +21 -0
- mound-0.1.0/PKG-INFO +193 -0
- mound-0.1.0/PLANNING.md +243 -0
- mound-0.1.0/README.md +158 -0
- mound-0.1.0/ROADMAP.md +31 -0
- mound-0.1.0/docs/images/roki_splitter_zone.png +0 -0
- mound-0.1.0/examples/roki_sasaki_end_to_end.py +72 -0
- mound-0.1.0/mound/__init__.py +14 -0
- mound-0.1.0/mound/analysis.py +72 -0
- mound-0.1.0/mound/cli.py +182 -0
- mound-0.1.0/mound/config.py +28 -0
- mound-0.1.0/mound/export.py +96 -0
- mound-0.1.0/mound/http.py +41 -0
- mound-0.1.0/mound/models.py +192 -0
- mound-0.1.0/mound/pitches.py +267 -0
- mound-0.1.0/mound/players.py +115 -0
- mound-0.1.0/mound/savant.py +45 -0
- mound-0.1.0/mound/statsapi.py +90 -0
- mound-0.1.0/mound/viz.py +320 -0
- mound-0.1.0/mound/zone.py +41 -0
- mound-0.1.0/pyproject.toml +55 -0
- mound-0.1.0/scripts/publish.sh +70 -0
- mound-0.1.0/tests/__init__.py +0 -0
- mound-0.1.0/tests/conftest.py +74 -0
- mound-0.1.0/tests/fixtures/game_log_2025.json +41 -0
- mound-0.1.0/tests/fixtures/game_log_empty.json +10 -0
- mound-0.1.0/tests/fixtures/gf_game_1001.json +169 -0
- mound-0.1.0/tests/fixtures/gf_game_1002.json +169 -0
- mound-0.1.0/tests/fixtures/gf_game_1003.json +201 -0
- mound-0.1.0/tests/fixtures/people_808963.json +15 -0
- mound-0.1.0/tests/fixtures/people_search_none.json +4 -0
- mound-0.1.0/tests/fixtures/people_search_sasaki.json +24 -0
- mound-0.1.0/tests/fixtures/people_search_single.json +15 -0
- mound-0.1.0/tests/fixtures/people_search_two_pitchers.json +23 -0
- mound-0.1.0/tests/test_analysis.py +108 -0
- mound-0.1.0/tests/test_export.py +101 -0
- mound-0.1.0/tests/test_pitches.py +99 -0
- mound-0.1.0/tests/test_players.py +84 -0
- mound-0.1.0/tests/test_savant.py +55 -0
- mound-0.1.0/tests/test_statsapi.py +48 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install mound
|
|
23
|
+
run: pip install -e ".[dev,parquet]"
|
|
24
|
+
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: ruff check .
|
|
27
|
+
|
|
28
|
+
- name: Test
|
|
29
|
+
run: pytest
|
mound-0.1.0/.gitignore
ADDED
mound-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
Format based on Keep a Changelog.
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-08-08
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- `Pitcher`/`PitchCollection` Python API and a `mound` CLI (`search`, `pitches`, `mix`, `results`, `zone`) sharing the same underlying implementation.
|
|
13
|
+
- Data sourced directly from the MLB Stats API and Baseball Savant's `/gf` endpoint, no `pybaseball` dependency.
|
|
14
|
+
- Pytest suite covering player resolution, game-log/pitch parsing, filtering, analysis and export, run against mocked HTTP fixtures.
|
|
15
|
+
- README, ROADMAP and an end-to-end example using Roki Sasaki's splitter.
|
mound-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Stiles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mound-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mound
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A CLI and Python toolkit for acquiring, analyzing and visualizing MLB pitch-level data.
|
|
5
|
+
Project-URL: Homepage, https://github.com/stiles/mound
|
|
6
|
+
Project-URL: Repository, https://github.com/stiles/mound
|
|
7
|
+
Project-URL: Issues, https://github.com/stiles/mound/issues
|
|
8
|
+
Author: Matt Stiles
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: baseball,cli,mlb,pitching,sports,statcast
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Games/Entertainment
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: matplotlib>=3.8
|
|
23
|
+
Requires-Dist: pandas>=2.0
|
|
24
|
+
Requires-Dist: requests>=2.31
|
|
25
|
+
Requires-Dist: typer>=0.12
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
32
|
+
Provides-Extra: parquet
|
|
33
|
+
Requires-Dist: pyarrow>=14.0; extra == 'parquet'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Mound
|
|
37
|
+
|
|
38
|
+
A CLI and Python toolkit for retrieving, analyzing and visualizing MLB pitch-level data — without needing to know MLB player IDs or the underlying API structures.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
> How many splitters did Roki Sasaki throw against the Diamondbacks last night?
|
|
42
|
+
> How often has he thrown it relative to his other pitches over his last four starts?
|
|
43
|
+
> What does its location look like over that period?
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Mound answers questions like these with a few CLI commands or a few lines of Python.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/stiles/mound.git
|
|
52
|
+
cd mound
|
|
53
|
+
pip install -e .
|
|
54
|
+
|
|
55
|
+
# Parquet export support:
|
|
56
|
+
pip install -e ".[parquet]"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires Python 3.10+.
|
|
60
|
+
|
|
61
|
+
## Quickstart
|
|
62
|
+
|
|
63
|
+
### CLI
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Find a player and their MLB ID
|
|
67
|
+
mound search "Roki Sasaki"
|
|
68
|
+
|
|
69
|
+
# Retrieve pitches from his last 4 starts
|
|
70
|
+
mound pitches "Roki Sasaki" --last 4
|
|
71
|
+
|
|
72
|
+
# Isolate one pitch type
|
|
73
|
+
mound pitches "Roki Sasaki" --last 4 --pitch splitter
|
|
74
|
+
|
|
75
|
+
# Pitch mix and results by pitch type
|
|
76
|
+
mound mix "Roki Sasaki" --last 4
|
|
77
|
+
mound results "Roki Sasaki" --last 4 --pitch splitter
|
|
78
|
+
|
|
79
|
+
# Plot pitch locations against the strike zone
|
|
80
|
+
mound zone "Roki Sasaki" --pitch splitter --last 4 --out splitter_zone.png
|
|
81
|
+
|
|
82
|
+
# Export the underlying data
|
|
83
|
+
mound pitches "Roki Sasaki" --last 4 --export roki_last4.csv
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Run `mound --help` or `mound <command> --help` for the full option list.
|
|
87
|
+
|
|
88
|
+
### Python
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from mound import Pitcher
|
|
92
|
+
|
|
93
|
+
roki = Pitcher("Roki Sasaki")
|
|
94
|
+
|
|
95
|
+
pitches = roki.pitches(last=4)
|
|
96
|
+
splitters = pitches.filter(pitch_type="splitter")
|
|
97
|
+
|
|
98
|
+
splitters.pitch_mix()
|
|
99
|
+
splitters.strike_rate()
|
|
100
|
+
splitters.plot_zone(out="splitter_zone.png")
|
|
101
|
+
|
|
102
|
+
pitches.to_csv("roki_last4.csv")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`Pitcher.pitches()` and `PitchCollection.filter()` both accept:
|
|
106
|
+
|
|
107
|
+
| Argument | Meaning |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `last` | most recent N appearances |
|
|
110
|
+
| `since` / `until` | date range (`"YYYY-MM-DD"` or `date`), inclusive |
|
|
111
|
+
| `game` | one or more MLB `game_pk` values |
|
|
112
|
+
| `pitch_type` | a pitch name, alias, or Statcast code (see below) |
|
|
113
|
+
|
|
114
|
+
Filtering a `PitchCollection` always returns another `PitchCollection`, so any combination of `.filter()`, `.pitch_mix()`, `.strike_rate()`, `.plot_zone()` and export methods composes freely.
|
|
115
|
+
|
|
116
|
+
## Plots
|
|
117
|
+
|
|
118
|
+
`plot_zone()` renders a headline, a dek (pitch count, strike rate, date range) and a source line around the strike-zone chart itself, rather than relying on axis titles or a boxed legend:
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
All three are auto-generated but overridable:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
splitters.plot_zone(
|
|
126
|
+
title="Sasaki leans on the splitter",
|
|
127
|
+
subtitle="134 pitches since the All-Star break",
|
|
128
|
+
source="Source: Baseball Savant",
|
|
129
|
+
kind="heatmap", # or "scatter" (default)
|
|
130
|
+
out="splitter_zone.png",
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Pass `subtitle=""` or `source=""` to omit either. Passing your own `ax` (e.g. for a multi-panel figure) skips the dek/source and falls back to a plain left-aligned title, so `plot_zone()` behaves as a well-mannered subplot.
|
|
135
|
+
|
|
136
|
+
## Pitch types
|
|
137
|
+
|
|
138
|
+
Statcast tags every pitch with a short code. Mound normalizes these into human-readable names and accepts common aliases when filtering, so `pitch_type="four-seam"`, `"fastball"` and `"FF"` are all equivalent.
|
|
139
|
+
|
|
140
|
+
| Code | Name | Common aliases |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `FF` | four-seam fastball | fastball, four-seam |
|
|
143
|
+
| `FT` | two-seam fastball | two-seam |
|
|
144
|
+
| `SI` | sinker | |
|
|
145
|
+
| `FC` | cutter | cut fastball |
|
|
146
|
+
| `SL` | slider | |
|
|
147
|
+
| `ST` | sweeper | sweeping slider |
|
|
148
|
+
| `SV` | slurve | |
|
|
149
|
+
| `CU` | curveball | curve |
|
|
150
|
+
| `KC` | knuckle curve | |
|
|
151
|
+
| `CH` | changeup | change-up |
|
|
152
|
+
| `FS` | splitter | split-finger |
|
|
153
|
+
| `FO` | forkball | |
|
|
154
|
+
| `SC` | screwball | |
|
|
155
|
+
| `KN` | knuckleball | knuckler |
|
|
156
|
+
| `EP` | eephus | |
|
|
157
|
+
|
|
158
|
+
**Note on Roki Sasaki's signature pitch:** Statcast classifies it inconsistently start-to-start — sometimes as a splitter (`FS`), sometimes as a forkball (`FO`), depending on its movement profile in a given game. If a `pitch_type="splitter"` query looks incomplete, check `pitch_type="forkball"` too, or filter using both.
|
|
159
|
+
|
|
160
|
+
## Data sources
|
|
161
|
+
|
|
162
|
+
Mound calls two unofficial, public MLB data services directly:
|
|
163
|
+
|
|
164
|
+
- **[MLB Stats API](https://statsapi.mlb.com)** — player search/lookup and game logs, used to resolve a pitcher's identity and discover which games to pull.
|
|
165
|
+
- **[Baseball Savant](https://baseballsavant.mlb.com)** — the `/gf` game-feed endpoint, used for pitch-by-pitch Statcast data (location, velocity, pitch type, count, outcome).
|
|
166
|
+
|
|
167
|
+
Both are unofficial and undocumented; endpoints or response shapes could change without notice. Mound sends a descriptive `User-Agent` and retries transient failures, but does not currently cache responses, so repeated queries re-fetch data from these services.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
pip install -e ".[dev]"
|
|
173
|
+
pytest
|
|
174
|
+
ruff check .
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Tests run entirely against mocked HTTP fixtures in `tests/fixtures/` (via the `responses` library) and don't require network access.
|
|
178
|
+
|
|
179
|
+
## Known limitations
|
|
180
|
+
|
|
181
|
+
- No caching yet — every call re-fetches from the MLB Stats API / Baseball Savant.
|
|
182
|
+
- Pitch classification comes from Statcast's own model and can be inconsistent for pitches with unusual movement (see the Roki Sasaki note above).
|
|
183
|
+
- Only pitchers are supported as the primary retrieval unit; there's no batter-vs-pitcher matchup view yet (see [ROADMAP.md](ROADMAP.md)).
|
|
184
|
+
- Historical data availability depends on Statcast/Savant coverage, which is generally reliable from 2015 onward.
|
|
185
|
+
- All requests are synchronous and unthrottled beyond basic retry/backoff; heavy bulk retrieval (e.g. a full season) will be slow.
|
|
186
|
+
|
|
187
|
+
## Roadmap
|
|
188
|
+
|
|
189
|
+
See [ROADMAP.md](ROADMAP.md) for planned enhancements beyond this prototype.
|
|
190
|
+
|
|
191
|
+
## Changelog
|
|
192
|
+
|
|
193
|
+
See [CHANGELOG.md](CHANGELOG.md).
|