backcaster 0.4.2__tar.gz → 0.4.3__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.
- {backcaster-0.4.2/src/backcaster.egg-info → backcaster-0.4.3}/PKG-INFO +129 -34
- {backcaster-0.4.2 → backcaster-0.4.3}/README.md +128 -33
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/__init__.py +1 -1
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/batters.py +2 -1
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/cli.py +49 -13
- backcaster-0.4.3/src/backcaster/eligibility.py +47 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/html_report.py +172 -41
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/lahman.py +156 -35
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/marcel.py +13 -14
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/outputs.py +55 -4
- {backcaster-0.4.2 → backcaster-0.4.3/src/backcaster.egg-info}/PKG-INFO +129 -34
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster.egg-info/SOURCES.txt +2 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_batters.py +25 -1
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_cli.py +64 -6
- backcaster-0.4.3/tests/test_eligibility.py +65 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_html_report.py +66 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_lahman.py +457 -31
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_marcel_math.py +81 -7
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_outputs.py +92 -1
- {backcaster-0.4.2 → backcaster-0.4.3}/LICENSE +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/THIRD_PARTY_NOTICES.md +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/pyproject.toml +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/setup.cfg +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/data.py +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/normalize.py +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/schemas.py +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster/stat_columns.py +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster.egg-info/dependency_links.txt +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster.egg-info/entry_points.txt +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster.egg-info/requires.txt +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/src/backcaster.egg-info/top_level.txt +0 -0
- {backcaster-0.4.2 → backcaster-0.4.3}/tests/test_normalize.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backcaster
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Marcel-style MLB projection CLI using local Lahman CSV data
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/jeffreywevans/backcaster
|
|
@@ -41,6 +41,7 @@ It supports:
|
|
|
41
41
|
- **Single team projections** (`team`)
|
|
42
42
|
- **League-wide projections** (`hitters`, `pitchers`, or `batch`)
|
|
43
43
|
- Output as terminal table, CSV, or HTML
|
|
44
|
+
- Historical comparisons against actual results in HTML reports (`--compare-actuals`)
|
|
44
45
|
|
|
45
46
|
---
|
|
46
47
|
|
|
@@ -59,10 +60,10 @@ Upgrade to the latest release available on PyPI:
|
|
|
59
60
|
python -m pip install --upgrade backcaster
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
To install a specific release, such as 0.4.
|
|
63
|
+
To install a specific release, such as 0.4.3:
|
|
63
64
|
|
|
64
65
|
```powershell
|
|
65
|
-
python -m pip install --upgrade backcaster==0.4.
|
|
66
|
+
python -m pip install --upgrade backcaster==0.4.3
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
Run the command using the same Python environment where you run Backcaster.
|
|
@@ -110,8 +111,8 @@ Subcommand-specific required options:
|
|
|
110
111
|
- `batch`, `hitters`, and `pitchers` require no player or team option
|
|
111
112
|
|
|
112
113
|
```bash
|
|
113
|
-
backcaster <player|team|batch> [subcommand-options] --year <YEAR> --kind <batting|pitching> [--format cli|csv|html] [--out PATH]
|
|
114
|
-
backcaster <hitters|pitchers> --year <YEAR> [--data-dir PATH] [--format cli|csv|html] [--out PATH]
|
|
114
|
+
backcaster <player|team|batch> [subcommand-options] --year <YEAR> --kind <batting|pitching> [--format cli|csv|html] [--compare-actuals] [--out PATH]
|
|
115
|
+
backcaster <hitters|pitchers> --year <YEAR> [--data-dir PATH] [--format cli|csv|html] [--compare-actuals] [--out PATH]
|
|
115
116
|
```
|
|
116
117
|
|
|
117
118
|
### Pre-offseason league projections
|
|
@@ -131,7 +132,8 @@ backcaster hitters --year 2026 --format csv --out hitters_2026.csv
|
|
|
131
132
|
backcaster pitchers --year 2026 --format csv --out pitchers_2026.csv
|
|
132
133
|
```
|
|
133
134
|
|
|
134
|
-
`--year 2026` projects 2026 from 2023–2025
|
|
135
|
+
`--year 2026` projects 2026 from 2023–2025. It reads 2026 statistics only when
|
|
136
|
+
`--compare-actuals` is selected for a historical HTML report.
|
|
135
137
|
Both commands support `--data-dir`, `--format cli|csv|html`, and `--out`.
|
|
136
138
|
They are shortcuts for `batch --kind batting` and `batch --kind pitching`.
|
|
137
139
|
For a different data directory, add `--data-dir "C:\Baseball\lahman"`.
|
|
@@ -140,13 +142,21 @@ File formats require `--out`; the CLI format prints to the terminal and rejects
|
|
|
140
142
|
`--out`. Invalid output options fail before data is loaded. Unwritable output
|
|
141
143
|
paths report an error on stderr and exit with status 2.
|
|
142
144
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
Exports must stay within the current working directory or its subdirectories.
|
|
146
|
+
Relative paths and absolute paths within that directory are accepted; paths that
|
|
147
|
+
escape it through `..` or symbolic links are rejected before data is loaded.
|
|
148
|
+
To export elsewhere, run Backcaster from the intended output directory.
|
|
149
|
+
|
|
150
|
+
Hitters receive a projection with any positive PA in the preceding three seasons,
|
|
151
|
+
subject to usable ages and the existing non-pitcher classification. There is no
|
|
152
|
+
minimum-PA or reliability gate. `ConfidenceTier` labels reliability at least 0.50
|
|
153
|
+
as `core` and lower values as `low-sample`; both tiers appear in every output.
|
|
154
|
+
Pitchers receive a projection with any recorded outs or batters faced in the
|
|
155
|
+
lookback window, including relievers and appearances without an out. They use
|
|
156
|
+
the same confidence labels, with no innings or reliability gate. All selected
|
|
157
|
+
players use their available three-year history.
|
|
158
|
+
Without a dated eligibility snapshot, this is a historical candidate population,
|
|
159
|
+
including potentially retired or otherwise unavailable players.
|
|
150
160
|
|
|
151
161
|
### 1) Player projection
|
|
152
162
|
|
|
@@ -186,18 +196,24 @@ FanGraphs abbreviation `LAD`). A projection year needs the preceding three seaso
|
|
|
186
196
|
is unavailable. If the directory or required files are missing, the command
|
|
187
197
|
reports the missing file and directory; it never attempts an online fetch.
|
|
188
198
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
nearest integer (502 PA for 162 games, 186 PA for 60 games). This is an analytical filter, not a
|
|
194
|
-
reconstruction of historical award eligibility or batting-title exceptions.
|
|
195
|
-
Relievers generally do not qualify under this pitching rule.
|
|
199
|
+
Batting inclusion requires positive lookback PA; pitching requires recorded outs
|
|
200
|
+
or batters faced. Neither has a minimum seasonal workload or reliability cutoff.
|
|
201
|
+
`season(..., qualified=True)` still exposes the historical PA/IP analytical filters
|
|
202
|
+
for Python callers; projection commands do not apply them.
|
|
196
203
|
|
|
197
|
-
Player stints are summed before
|
|
204
|
+
Player stints are summed before projection. Plate appearances are calculated
|
|
198
205
|
as `AB + BB + HBP + SF + SH`; missing/blank HBP, SF, and SH values count as zero.
|
|
199
206
|
Blank numeric counting-stat cells are also treated as zero; use a dataset with
|
|
200
207
|
complete records for the seasons you intend to project.
|
|
208
|
+
|
|
209
|
+
Historical batting and pitching records with missing or invalid birth years are excluded with
|
|
210
|
+
a stderr warning identifying the count and sample player IDs. Remaining players
|
|
211
|
+
are still projected; no default age is substituted. An entirely unusable
|
|
212
|
+
population produces a clear error. See [historical data caveats](docs/LAHMAN.md).
|
|
213
|
+
Pitcher playing time is `0.5 × last-year IP + 0.1 × two-years-ago IP + 60`,
|
|
214
|
+
with missing calendar years contributing zero. The third prior season still
|
|
215
|
+
contributes to regression and reliability. See [the pitcher model](docs/PITCHER_MODEL.md).
|
|
216
|
+
|
|
201
217
|
Pitching `IPouts` is divided by three for decimal innings, and `BFP` supplies
|
|
202
218
|
batters faced. Lahman player IDs keep different players with the same name separate;
|
|
203
219
|
ambiguous name lookups report the matching IDs. Select one with `--player-id`
|
|
@@ -207,18 +223,56 @@ instead of `--name`, for example:
|
|
|
207
223
|
backcaster player --player-id bettsmo01 --year 2026 --kind batting
|
|
208
224
|
```
|
|
209
225
|
|
|
210
|
-
IDs match Lahman `People.csv.playerID` exactly and retain the same
|
|
226
|
+
IDs match Lahman `People.csv.playerID` exactly and retain the same inclusion
|
|
211
227
|
rules as name lookups.
|
|
212
228
|
|
|
213
|
-
Batter league regression and rebaselining use all non-pitcher
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
For every selected player, projections use all available seasons in
|
|
220
|
-
including
|
|
221
|
-
|
|
229
|
+
Batter league regression and rebaselining use all positive-PA non-pitcher
|
|
230
|
+
player-seasons. Output selection, including status snapshots, does not change
|
|
231
|
+
that calculation. Pitcher regression likewise uses the full historical sample
|
|
232
|
+
population, independently of roster, player, or team selection. See [the batter model](docs/BATTER_MODEL.md) and
|
|
233
|
+
[the pitcher model](docs/PITCHER_MODEL.md).
|
|
234
|
+
|
|
235
|
+
For every selected player, projections use all available seasons in the window,
|
|
236
|
+
including seasons spent with other teams. Without a status snapshot, team
|
|
237
|
+
selection uses historical last-stint membership for each season.
|
|
238
|
+
|
|
239
|
+
### Dated player eligibility
|
|
240
|
+
|
|
241
|
+
For a roster/status comparison, supply an explicit eligibility snapshot and date:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
backcaster hitters --year 2025 --eligibility-file eligibility_2025.csv --as-of 2025-01-14 --format csv --out mlb_hitting_2025.csv
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The CSV uses Lahman IDs and explicit inclusion decisions:
|
|
248
|
+
|
|
249
|
+
```csv
|
|
250
|
+
PlayerID,Year,AsOf,Eligible,Team
|
|
251
|
+
troutmi01,2025,2025-01-14,true,ANA
|
|
252
|
+
bettsmo01,2025,2025-01-14,true,LAN
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Create these decisions from a roster/organization/free-agent/retirement source
|
|
256
|
+
as of the stated date. `Eligible` must be true or false. Unlisted players and
|
|
257
|
+
false rows are excluded; listed players still need a lookback sample (PA for
|
|
258
|
+
hitters, outs or batters faced for pitchers) and a usable birth year. Eligibility
|
|
259
|
+
never depends on reliability. The file can contain
|
|
260
|
+
multiple years and dates; only the exact `Year` and `AsOf` match is used, with
|
|
261
|
+
unique PlayerID values within that snapshot.
|
|
262
|
+
|
|
263
|
+
Both flags are required together and apply to batting and pitching. For pitchers,
|
|
264
|
+
use `backcaster pitchers` (or `--kind pitching`) with the same flags. Use separate
|
|
265
|
+
snapshot files if inclusion decisions differ between hitting and pitching roles.
|
|
266
|
+
`Team` is optional for league/player output and required for team output; it represents
|
|
267
|
+
membership at the snapshot date, with blanks allowed for unassigned players.
|
|
268
|
+
With a snapshot, team selection uses this membership rather than historical teams.
|
|
269
|
+
Exports include `EligibilityAsOf`. The snapshot filters output only; league
|
|
270
|
+
regression and rebaselining continue to use the full historical population.
|
|
271
|
+
|
|
272
|
+
For the January 14, 2025 Baseball Reference comparison, supply a snapshot actually
|
|
273
|
+
frozen to that date. Lahman history cannot reconstruct roster status on that date,
|
|
274
|
+
and later performance or final career dates are not used to guess it. Matching
|
|
275
|
+
504 benchmark rows requires the right status population as well as model inputs.
|
|
222
276
|
|
|
223
277
|
### 2) Team projection
|
|
224
278
|
|
|
@@ -285,7 +339,7 @@ true innings rather than the displayed notation.
|
|
|
285
339
|
### Tracked statistics and order
|
|
286
340
|
|
|
287
341
|
All outputs put available Name, Year, Player ID, and Age metadata first, followed
|
|
288
|
-
by these statistics in order and then Reliability:
|
|
342
|
+
by these statistics in order and then Reliability and ConfidenceTier; snapshot-filtered output appends EligibilityAsOf:
|
|
289
343
|
|
|
290
344
|
```text
|
|
291
345
|
Batters: PA,AB,R,H,2B,3B,HR,RBI,SB,CS,BB,SO,BA,OBP,SLG,OPS,TB,GDP,HBP,SH,SF,IBB
|
|
@@ -314,6 +368,7 @@ or share as one file. No server, CDN, fonts, or internet connection are required
|
|
|
314
368
|
```powershell
|
|
315
369
|
backcaster hitters --year 2025 --format html --out hitters_2025.html
|
|
316
370
|
backcaster pitchers --year 2025 --format html --out pitchers_2025.html
|
|
371
|
+
backcaster hitters --year 2025 --format html --compare-actuals --out hitters_accuracy_2025.html
|
|
317
372
|
```
|
|
318
373
|
|
|
319
374
|
The first four columns are **Name, Year, Player ID, Age**, followed by counting
|
|
@@ -322,6 +377,7 @@ statistics and batting/pitching rates in a consistent order. The report includes
|
|
|
322
377
|
- A player-count, projection-year, and projection-type summary.
|
|
323
378
|
- Search across player names, IDs, and statistics.
|
|
324
379
|
- Sortable column headings, with numeric sorting for statistics.
|
|
380
|
+
- Optional historical accuracy rows with a per-player expand/collapse control.
|
|
325
381
|
- A sticky name column/header and horizontal scrolling on narrow screens.
|
|
326
382
|
- A reset control and print layout for browser printing or saving as PDF.
|
|
327
383
|
|
|
@@ -331,6 +387,43 @@ Search filters the visible rows; printing respects that current view. Without
|
|
|
331
387
|
JavaScript, the complete table is still readable. See
|
|
332
388
|
[HTML report customization](docs/HTML_REPORTS.md) for theme and column constants.
|
|
333
389
|
|
|
390
|
+
### Compare projections with actual results (new in 0.4.3)
|
|
391
|
+
|
|
392
|
+
Add `--compare-actuals` to a historical HTML report for either batters or pitchers:
|
|
393
|
+
|
|
394
|
+
```powershell
|
|
395
|
+
backcaster hitters --year 2025 --format html --compare-actuals --out hitters_accuracy_2025.html
|
|
396
|
+
backcaster pitchers --year 2025 --format html --compare-actuals --out pitchers_accuracy_2025.html
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
For these examples, the local Lahman files must cover **2022–2024** for the
|
|
400
|
+
projection and **2025** for the actual results, including target-year game counts
|
|
401
|
+
in `Teams.csv`. Use `--data-dir` to select the dataset. The 1871–2025 edition can
|
|
402
|
+
support a 2025 comparison; it cannot provide actual results for 2026.
|
|
403
|
+
The option also works with `player`, `team`, and `batch`; it requires
|
|
404
|
+
`--format html` and `--out` and is rejected for terminal or CSV output.
|
|
405
|
+
|
|
406
|
+
Projection calculations use only the three preceding seasons. Target-season
|
|
407
|
+
results are loaded afterward and matched by Lahman PlayerID, combining trade
|
|
408
|
+
stints without requiring the player to meet a seasonal qualification threshold.
|
|
409
|
+
Each projected player has two additional rows:
|
|
410
|
+
|
|
411
|
+
- **Actual** shows the player's recorded target-season results.
|
|
412
|
+
- **Variance** shows `actual - projected`, calculated before display rounding.
|
|
413
|
+
For example, 25 actual HR against 20 projected HR displays `+5`.
|
|
414
|
+
|
|
415
|
+
Positive differences are green and negative differences are red. These colors
|
|
416
|
+
indicate direction: a positive ERA variance means more earned runs per nine
|
|
417
|
+
innings than projected. IP differences use signed baseball innings notation;
|
|
418
|
+
`+1.2` means one inning and two outs.
|
|
419
|
+
|
|
420
|
+
Rows start expanded. Use the control beside each player name to collapse or
|
|
421
|
+
expand them; the rows stay with their player when sorting or filtering, and
|
|
422
|
+
printing preserves the current view. A projected player without a target-season
|
|
423
|
+
record is labeled `Actual (no recorded stats)` and has blank variance cells.
|
|
424
|
+
Players absent from the projection population are not added from the actuals.
|
|
425
|
+
ConfidenceTier and Reliability describe the projection, not the actual results.
|
|
426
|
+
|
|
334
427
|
---
|
|
335
428
|
|
|
336
429
|
## Local data updates
|
|
@@ -361,7 +454,9 @@ Development dependencies (`--extra dev`) include:
|
|
|
361
454
|
|
|
362
455
|
### Lockfile policy (`uv.lock`)
|
|
363
456
|
|
|
364
|
-
`uv.lock` is committed and pins runtime, development, and build dependencies. CI
|
|
457
|
+
`uv.lock` is committed and pins runtime, development, and build dependencies. CI
|
|
458
|
+
installs uv 0.12.23 with a SHA-pinned `astral-sh/setup-uv` action, whose embedded
|
|
459
|
+
checksums verify the downloaded uv archive. CI uses
|
|
365
460
|
`uv sync --locked --extra dev` and fails if the lockfile is out of date. CI first
|
|
366
461
|
installs the locked tools with `--no-install-project`, then installs Backcaster
|
|
367
462
|
with `--no-build-isolation` to keep build dependencies pinned. CI builds
|
|
@@ -408,7 +503,7 @@ pytest
|
|
|
408
503
|
### Coverage + JUnit reports
|
|
409
504
|
|
|
410
505
|
```bash
|
|
411
|
-
pytest --junitxml=pytest.xml --cov=src --cov-report=xml:coverage.xml
|
|
506
|
+
pytest --junitxml=pytest.xml --cov=src --cov-branch --cov-report=xml:coverage.xml --cov-fail-under=100
|
|
412
507
|
```
|
|
413
508
|
|
|
414
509
|
### Lint and types
|
|
@@ -7,6 +7,7 @@ It supports:
|
|
|
7
7
|
- **Single team projections** (`team`)
|
|
8
8
|
- **League-wide projections** (`hitters`, `pitchers`, or `batch`)
|
|
9
9
|
- Output as terminal table, CSV, or HTML
|
|
10
|
+
- Historical comparisons against actual results in HTML reports (`--compare-actuals`)
|
|
10
11
|
|
|
11
12
|
---
|
|
12
13
|
|
|
@@ -25,10 +26,10 @@ Upgrade to the latest release available on PyPI:
|
|
|
25
26
|
python -m pip install --upgrade backcaster
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
To install a specific release, such as 0.4.
|
|
29
|
+
To install a specific release, such as 0.4.3:
|
|
29
30
|
|
|
30
31
|
```powershell
|
|
31
|
-
python -m pip install --upgrade backcaster==0.4.
|
|
32
|
+
python -m pip install --upgrade backcaster==0.4.3
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
Run the command using the same Python environment where you run Backcaster.
|
|
@@ -76,8 +77,8 @@ Subcommand-specific required options:
|
|
|
76
77
|
- `batch`, `hitters`, and `pitchers` require no player or team option
|
|
77
78
|
|
|
78
79
|
```bash
|
|
79
|
-
backcaster <player|team|batch> [subcommand-options] --year <YEAR> --kind <batting|pitching> [--format cli|csv|html] [--out PATH]
|
|
80
|
-
backcaster <hitters|pitchers> --year <YEAR> [--data-dir PATH] [--format cli|csv|html] [--out PATH]
|
|
80
|
+
backcaster <player|team|batch> [subcommand-options] --year <YEAR> --kind <batting|pitching> [--format cli|csv|html] [--compare-actuals] [--out PATH]
|
|
81
|
+
backcaster <hitters|pitchers> --year <YEAR> [--data-dir PATH] [--format cli|csv|html] [--compare-actuals] [--out PATH]
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
### Pre-offseason league projections
|
|
@@ -97,7 +98,8 @@ backcaster hitters --year 2026 --format csv --out hitters_2026.csv
|
|
|
97
98
|
backcaster pitchers --year 2026 --format csv --out pitchers_2026.csv
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
`--year 2026` projects 2026 from 2023–2025
|
|
101
|
+
`--year 2026` projects 2026 from 2023–2025. It reads 2026 statistics only when
|
|
102
|
+
`--compare-actuals` is selected for a historical HTML report.
|
|
101
103
|
Both commands support `--data-dir`, `--format cli|csv|html`, and `--out`.
|
|
102
104
|
They are shortcuts for `batch --kind batting` and `batch --kind pitching`.
|
|
103
105
|
For a different data directory, add `--data-dir "C:\Baseball\lahman"`.
|
|
@@ -106,13 +108,21 @@ File formats require `--out`; the CLI format prints to the terminal and rejects
|
|
|
106
108
|
`--out`. Invalid output options fail before data is loaded. Unwritable output
|
|
107
109
|
paths report an error on stderr and exit with status 2.
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
Exports must stay within the current working directory or its subdirectories.
|
|
112
|
+
Relative paths and absolute paths within that directory are accepted; paths that
|
|
113
|
+
escape it through `..` or symbolic links are rejected before data is loaded.
|
|
114
|
+
To export elsewhere, run Backcaster from the intended output directory.
|
|
115
|
+
|
|
116
|
+
Hitters receive a projection with any positive PA in the preceding three seasons,
|
|
117
|
+
subject to usable ages and the existing non-pitcher classification. There is no
|
|
118
|
+
minimum-PA or reliability gate. `ConfidenceTier` labels reliability at least 0.50
|
|
119
|
+
as `core` and lower values as `low-sample`; both tiers appear in every output.
|
|
120
|
+
Pitchers receive a projection with any recorded outs or batters faced in the
|
|
121
|
+
lookback window, including relievers and appearances without an out. They use
|
|
122
|
+
the same confidence labels, with no innings or reliability gate. All selected
|
|
123
|
+
players use their available three-year history.
|
|
124
|
+
Without a dated eligibility snapshot, this is a historical candidate population,
|
|
125
|
+
including potentially retired or otherwise unavailable players.
|
|
116
126
|
|
|
117
127
|
### 1) Player projection
|
|
118
128
|
|
|
@@ -152,18 +162,24 @@ FanGraphs abbreviation `LAD`). A projection year needs the preceding three seaso
|
|
|
152
162
|
is unavailable. If the directory or required files are missing, the command
|
|
153
163
|
reports the missing file and directory; it never attempts an online fetch.
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
nearest integer (502 PA for 162 games, 186 PA for 60 games). This is an analytical filter, not a
|
|
160
|
-
reconstruction of historical award eligibility or batting-title exceptions.
|
|
161
|
-
Relievers generally do not qualify under this pitching rule.
|
|
165
|
+
Batting inclusion requires positive lookback PA; pitching requires recorded outs
|
|
166
|
+
or batters faced. Neither has a minimum seasonal workload or reliability cutoff.
|
|
167
|
+
`season(..., qualified=True)` still exposes the historical PA/IP analytical filters
|
|
168
|
+
for Python callers; projection commands do not apply them.
|
|
162
169
|
|
|
163
|
-
Player stints are summed before
|
|
170
|
+
Player stints are summed before projection. Plate appearances are calculated
|
|
164
171
|
as `AB + BB + HBP + SF + SH`; missing/blank HBP, SF, and SH values count as zero.
|
|
165
172
|
Blank numeric counting-stat cells are also treated as zero; use a dataset with
|
|
166
173
|
complete records for the seasons you intend to project.
|
|
174
|
+
|
|
175
|
+
Historical batting and pitching records with missing or invalid birth years are excluded with
|
|
176
|
+
a stderr warning identifying the count and sample player IDs. Remaining players
|
|
177
|
+
are still projected; no default age is substituted. An entirely unusable
|
|
178
|
+
population produces a clear error. See [historical data caveats](docs/LAHMAN.md).
|
|
179
|
+
Pitcher playing time is `0.5 × last-year IP + 0.1 × two-years-ago IP + 60`,
|
|
180
|
+
with missing calendar years contributing zero. The third prior season still
|
|
181
|
+
contributes to regression and reliability. See [the pitcher model](docs/PITCHER_MODEL.md).
|
|
182
|
+
|
|
167
183
|
Pitching `IPouts` is divided by three for decimal innings, and `BFP` supplies
|
|
168
184
|
batters faced. Lahman player IDs keep different players with the same name separate;
|
|
169
185
|
ambiguous name lookups report the matching IDs. Select one with `--player-id`
|
|
@@ -173,18 +189,56 @@ instead of `--name`, for example:
|
|
|
173
189
|
backcaster player --player-id bettsmo01 --year 2026 --kind batting
|
|
174
190
|
```
|
|
175
191
|
|
|
176
|
-
IDs match Lahman `People.csv.playerID` exactly and retain the same
|
|
192
|
+
IDs match Lahman `People.csv.playerID` exactly and retain the same inclusion
|
|
177
193
|
rules as name lookups.
|
|
178
194
|
|
|
179
|
-
Batter league regression and rebaselining use all non-pitcher
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
For every selected player, projections use all available seasons in
|
|
186
|
-
including
|
|
187
|
-
|
|
195
|
+
Batter league regression and rebaselining use all positive-PA non-pitcher
|
|
196
|
+
player-seasons. Output selection, including status snapshots, does not change
|
|
197
|
+
that calculation. Pitcher regression likewise uses the full historical sample
|
|
198
|
+
population, independently of roster, player, or team selection. See [the batter model](docs/BATTER_MODEL.md) and
|
|
199
|
+
[the pitcher model](docs/PITCHER_MODEL.md).
|
|
200
|
+
|
|
201
|
+
For every selected player, projections use all available seasons in the window,
|
|
202
|
+
including seasons spent with other teams. Without a status snapshot, team
|
|
203
|
+
selection uses historical last-stint membership for each season.
|
|
204
|
+
|
|
205
|
+
### Dated player eligibility
|
|
206
|
+
|
|
207
|
+
For a roster/status comparison, supply an explicit eligibility snapshot and date:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
backcaster hitters --year 2025 --eligibility-file eligibility_2025.csv --as-of 2025-01-14 --format csv --out mlb_hitting_2025.csv
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The CSV uses Lahman IDs and explicit inclusion decisions:
|
|
214
|
+
|
|
215
|
+
```csv
|
|
216
|
+
PlayerID,Year,AsOf,Eligible,Team
|
|
217
|
+
troutmi01,2025,2025-01-14,true,ANA
|
|
218
|
+
bettsmo01,2025,2025-01-14,true,LAN
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Create these decisions from a roster/organization/free-agent/retirement source
|
|
222
|
+
as of the stated date. `Eligible` must be true or false. Unlisted players and
|
|
223
|
+
false rows are excluded; listed players still need a lookback sample (PA for
|
|
224
|
+
hitters, outs or batters faced for pitchers) and a usable birth year. Eligibility
|
|
225
|
+
never depends on reliability. The file can contain
|
|
226
|
+
multiple years and dates; only the exact `Year` and `AsOf` match is used, with
|
|
227
|
+
unique PlayerID values within that snapshot.
|
|
228
|
+
|
|
229
|
+
Both flags are required together and apply to batting and pitching. For pitchers,
|
|
230
|
+
use `backcaster pitchers` (or `--kind pitching`) with the same flags. Use separate
|
|
231
|
+
snapshot files if inclusion decisions differ between hitting and pitching roles.
|
|
232
|
+
`Team` is optional for league/player output and required for team output; it represents
|
|
233
|
+
membership at the snapshot date, with blanks allowed for unassigned players.
|
|
234
|
+
With a snapshot, team selection uses this membership rather than historical teams.
|
|
235
|
+
Exports include `EligibilityAsOf`. The snapshot filters output only; league
|
|
236
|
+
regression and rebaselining continue to use the full historical population.
|
|
237
|
+
|
|
238
|
+
For the January 14, 2025 Baseball Reference comparison, supply a snapshot actually
|
|
239
|
+
frozen to that date. Lahman history cannot reconstruct roster status on that date,
|
|
240
|
+
and later performance or final career dates are not used to guess it. Matching
|
|
241
|
+
504 benchmark rows requires the right status population as well as model inputs.
|
|
188
242
|
|
|
189
243
|
### 2) Team projection
|
|
190
244
|
|
|
@@ -251,7 +305,7 @@ true innings rather than the displayed notation.
|
|
|
251
305
|
### Tracked statistics and order
|
|
252
306
|
|
|
253
307
|
All outputs put available Name, Year, Player ID, and Age metadata first, followed
|
|
254
|
-
by these statistics in order and then Reliability:
|
|
308
|
+
by these statistics in order and then Reliability and ConfidenceTier; snapshot-filtered output appends EligibilityAsOf:
|
|
255
309
|
|
|
256
310
|
```text
|
|
257
311
|
Batters: PA,AB,R,H,2B,3B,HR,RBI,SB,CS,BB,SO,BA,OBP,SLG,OPS,TB,GDP,HBP,SH,SF,IBB
|
|
@@ -280,6 +334,7 @@ or share as one file. No server, CDN, fonts, or internet connection are required
|
|
|
280
334
|
```powershell
|
|
281
335
|
backcaster hitters --year 2025 --format html --out hitters_2025.html
|
|
282
336
|
backcaster pitchers --year 2025 --format html --out pitchers_2025.html
|
|
337
|
+
backcaster hitters --year 2025 --format html --compare-actuals --out hitters_accuracy_2025.html
|
|
283
338
|
```
|
|
284
339
|
|
|
285
340
|
The first four columns are **Name, Year, Player ID, Age**, followed by counting
|
|
@@ -288,6 +343,7 @@ statistics and batting/pitching rates in a consistent order. The report includes
|
|
|
288
343
|
- A player-count, projection-year, and projection-type summary.
|
|
289
344
|
- Search across player names, IDs, and statistics.
|
|
290
345
|
- Sortable column headings, with numeric sorting for statistics.
|
|
346
|
+
- Optional historical accuracy rows with a per-player expand/collapse control.
|
|
291
347
|
- A sticky name column/header and horizontal scrolling on narrow screens.
|
|
292
348
|
- A reset control and print layout for browser printing or saving as PDF.
|
|
293
349
|
|
|
@@ -297,6 +353,43 @@ Search filters the visible rows; printing respects that current view. Without
|
|
|
297
353
|
JavaScript, the complete table is still readable. See
|
|
298
354
|
[HTML report customization](docs/HTML_REPORTS.md) for theme and column constants.
|
|
299
355
|
|
|
356
|
+
### Compare projections with actual results (new in 0.4.3)
|
|
357
|
+
|
|
358
|
+
Add `--compare-actuals` to a historical HTML report for either batters or pitchers:
|
|
359
|
+
|
|
360
|
+
```powershell
|
|
361
|
+
backcaster hitters --year 2025 --format html --compare-actuals --out hitters_accuracy_2025.html
|
|
362
|
+
backcaster pitchers --year 2025 --format html --compare-actuals --out pitchers_accuracy_2025.html
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
For these examples, the local Lahman files must cover **2022–2024** for the
|
|
366
|
+
projection and **2025** for the actual results, including target-year game counts
|
|
367
|
+
in `Teams.csv`. Use `--data-dir` to select the dataset. The 1871–2025 edition can
|
|
368
|
+
support a 2025 comparison; it cannot provide actual results for 2026.
|
|
369
|
+
The option also works with `player`, `team`, and `batch`; it requires
|
|
370
|
+
`--format html` and `--out` and is rejected for terminal or CSV output.
|
|
371
|
+
|
|
372
|
+
Projection calculations use only the three preceding seasons. Target-season
|
|
373
|
+
results are loaded afterward and matched by Lahman PlayerID, combining trade
|
|
374
|
+
stints without requiring the player to meet a seasonal qualification threshold.
|
|
375
|
+
Each projected player has two additional rows:
|
|
376
|
+
|
|
377
|
+
- **Actual** shows the player's recorded target-season results.
|
|
378
|
+
- **Variance** shows `actual - projected`, calculated before display rounding.
|
|
379
|
+
For example, 25 actual HR against 20 projected HR displays `+5`.
|
|
380
|
+
|
|
381
|
+
Positive differences are green and negative differences are red. These colors
|
|
382
|
+
indicate direction: a positive ERA variance means more earned runs per nine
|
|
383
|
+
innings than projected. IP differences use signed baseball innings notation;
|
|
384
|
+
`+1.2` means one inning and two outs.
|
|
385
|
+
|
|
386
|
+
Rows start expanded. Use the control beside each player name to collapse or
|
|
387
|
+
expand them; the rows stay with their player when sorting or filtering, and
|
|
388
|
+
printing preserves the current view. A projected player without a target-season
|
|
389
|
+
record is labeled `Actual (no recorded stats)` and has blank variance cells.
|
|
390
|
+
Players absent from the projection population are not added from the actuals.
|
|
391
|
+
ConfidenceTier and Reliability describe the projection, not the actual results.
|
|
392
|
+
|
|
300
393
|
---
|
|
301
394
|
|
|
302
395
|
## Local data updates
|
|
@@ -327,7 +420,9 @@ Development dependencies (`--extra dev`) include:
|
|
|
327
420
|
|
|
328
421
|
### Lockfile policy (`uv.lock`)
|
|
329
422
|
|
|
330
|
-
`uv.lock` is committed and pins runtime, development, and build dependencies. CI
|
|
423
|
+
`uv.lock` is committed and pins runtime, development, and build dependencies. CI
|
|
424
|
+
installs uv 0.12.23 with a SHA-pinned `astral-sh/setup-uv` action, whose embedded
|
|
425
|
+
checksums verify the downloaded uv archive. CI uses
|
|
331
426
|
`uv sync --locked --extra dev` and fails if the lockfile is out of date. CI first
|
|
332
427
|
installs the locked tools with `--no-install-project`, then installs Backcaster
|
|
333
428
|
with `--no-build-isolation` to keep build dependencies pinned. CI builds
|
|
@@ -374,7 +469,7 @@ pytest
|
|
|
374
469
|
### Coverage + JUnit reports
|
|
375
470
|
|
|
376
471
|
```bash
|
|
377
|
-
pytest --junitxml=pytest.xml --cov=src --cov-report=xml:coverage.xml
|
|
472
|
+
pytest --junitxml=pytest.xml --cov=src --cov-branch --cov-report=xml:coverage.xml --cov-fail-under=100
|
|
378
473
|
```
|
|
379
474
|
|
|
380
475
|
### Lint and types
|
|
@@ -170,6 +170,7 @@ def project_batters(seasons: pd.DataFrame, year: int) -> pd.DataFrame:
|
|
|
170
170
|
context = _project_context(rates, pa, target, projected)
|
|
171
171
|
out = _summarize(projected.join(context), pa)
|
|
172
172
|
out["Reliability"] = evidence_pa / (evidence_pa + 1200)
|
|
173
|
+
out["ConfidenceTier"] = np.where(out["Reliability"] >= 0.5, "core", "low-sample")
|
|
173
174
|
out["Age"], out["Name"], out["Year"] = age, people["Name"], year
|
|
174
175
|
out = out.reset_index()
|
|
175
|
-
return out[[*IDENTITY_COLUMNS, *BATTING_STAT_COLUMNS, "Reliability"]]
|
|
176
|
+
return out[[*IDENTITY_COLUMNS, *BATTING_STAT_COLUMNS, "Reliability", "ConfidenceTier"]]
|
|
@@ -10,7 +10,7 @@ import pandas as pd
|
|
|
10
10
|
from backcaster.data import DataLoadError, PlayerLookupError
|
|
11
11
|
from backcaster.lahman import LahmanSource
|
|
12
12
|
from backcaster.marcel import ProjectionError
|
|
13
|
-
from backcaster.outputs import to_cli_table, to_csv, to_html
|
|
13
|
+
from backcaster.outputs import to_cli_table, to_csv, to_html, validate_output_path
|
|
14
14
|
|
|
15
15
|
_FILE_RENDERERS = {"csv": to_csv, "html": to_html}
|
|
16
16
|
_OUTPUT_FORMAT_CHOICES = ("cli", *_FILE_RENDERERS.keys())
|
|
@@ -25,20 +25,34 @@ def _default_data_dir() -> str:
|
|
|
25
25
|
return os.environ.get("LAHMAN_DATA_DIR", default)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def _validate_output_options(
|
|
28
|
+
def _validate_output_options(
|
|
29
|
+
output_format: str, out: str | None, compare_actuals: bool = False
|
|
30
|
+
) -> None:
|
|
29
31
|
if output_format == "cli" and out is not None:
|
|
30
32
|
raise ValueError("--out cannot be used with --format cli; choose --format csv or html")
|
|
31
33
|
if output_format in _FILE_RENDERERS and not out:
|
|
32
34
|
raise ValueError(f"--out is required for {output_format} output")
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
if compare_actuals and output_format != "html":
|
|
36
|
+
raise ValueError("--compare-actuals requires --format html")
|
|
37
|
+
if output_format in _FILE_RENDERERS and out:
|
|
38
|
+
validate_output_path(out)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _render(
|
|
42
|
+
df: pd.DataFrame,
|
|
43
|
+
output_format: str,
|
|
44
|
+
out: str | None,
|
|
45
|
+
actuals: pd.DataFrame | None = None,
|
|
46
|
+
) -> None:
|
|
37
47
|
if output_format == "cli":
|
|
38
48
|
print(to_cli_table(df))
|
|
39
49
|
elif output_format in _FILE_RENDERERS:
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
if not out:
|
|
51
|
+
raise ValueError(f"--out is required for {output_format} output")
|
|
52
|
+
if output_format == "html" and actuals is not None:
|
|
53
|
+
to_html(df, out, actuals)
|
|
54
|
+
else:
|
|
55
|
+
_FILE_RENDERERS[output_format](df, out)
|
|
42
56
|
else:
|
|
43
57
|
raise ValueError(f"Unsupported format: {output_format}")
|
|
44
58
|
|
|
@@ -54,7 +68,21 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
54
68
|
else:
|
|
55
69
|
sp.set_defaults(kind=kind)
|
|
56
70
|
sp.add_argument("--format", choices=_OUTPUT_FORMAT_CHOICES, default="cli")
|
|
57
|
-
sp.add_argument(
|
|
71
|
+
sp.add_argument(
|
|
72
|
+
"--eligibility-file", help="Target-season eligibility CSV keyed by Lahman PlayerID"
|
|
73
|
+
)
|
|
74
|
+
sp.add_argument(
|
|
75
|
+
"--as-of", help="Eligibility snapshot date (YYYY-MM-DD; requires --eligibility-file)"
|
|
76
|
+
)
|
|
77
|
+
sp.add_argument(
|
|
78
|
+
"--out",
|
|
79
|
+
help="Output path within the current working directory (required for csv/html; invalid with cli)",
|
|
80
|
+
)
|
|
81
|
+
sp.add_argument(
|
|
82
|
+
"--compare-actuals",
|
|
83
|
+
action="store_true",
|
|
84
|
+
help="Add expandable actual and variance rows to a historical HTML report",
|
|
85
|
+
)
|
|
58
86
|
sp.add_argument(
|
|
59
87
|
"--data-dir",
|
|
60
88
|
default=_default_data_dir(),
|
|
@@ -64,7 +92,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
64
92
|
pp = sub.add_parser("player")
|
|
65
93
|
add_common(pp)
|
|
66
94
|
player_selector = pp.add_mutually_exclusive_group(required=True)
|
|
67
|
-
player_selector.add_argument("--name", help="Player name (must identify one
|
|
95
|
+
player_selector.add_argument("--name", help="Player name (must identify one eligible player)")
|
|
68
96
|
player_selector.add_argument(
|
|
69
97
|
"--player-id", help="Exact Lahman player ID (for example, bettsmo01)"
|
|
70
98
|
)
|
|
@@ -86,16 +114,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
86
114
|
parser = build_parser()
|
|
87
115
|
args = parser.parse_args(argv)
|
|
88
116
|
try:
|
|
89
|
-
_validate_output_options(args.format, args.out)
|
|
90
|
-
|
|
117
|
+
_validate_output_options(args.format, args.out, args.compare_actuals)
|
|
118
|
+
source = LahmanSource(args.data_dir)
|
|
119
|
+
result = source.project(
|
|
91
120
|
args.command,
|
|
92
121
|
args.year,
|
|
93
122
|
args.kind,
|
|
94
123
|
name=getattr(args, "name", None),
|
|
95
124
|
team=getattr(args, "team", None),
|
|
96
125
|
player_id=getattr(args, "player_id", None),
|
|
126
|
+
eligibility_file=args.eligibility_file,
|
|
127
|
+
as_of=args.as_of,
|
|
128
|
+
)
|
|
129
|
+
actuals = (
|
|
130
|
+
source.actuals(args.year, args.kind, result["PlayerID"])
|
|
131
|
+
if args.compare_actuals
|
|
132
|
+
else None
|
|
97
133
|
)
|
|
98
|
-
_render(result, args.format, args.out)
|
|
134
|
+
_render(result, args.format, args.out, actuals)
|
|
99
135
|
return 0
|
|
100
136
|
except (DataLoadError, PlayerLookupError, ProjectionError, ValueError, OSError) as exc:
|
|
101
137
|
print(f"Error: {exc}", file=sys.stderr)
|