Add player-season hitting ingestion - #49
Conversation
Adds the first Player ingestion path (issue #46): PlayerIdentity and PlayerSeasonHitting domain schemas, MLB normalization with correct full-season aggregate split selection (never splits[0]), atomic ingest_player_season persistence, players/player_season_hitting tables with a natural-key foreign key and definitional CHECK constraints, and the scripts/import_player_season.py CLI. Spot-checked the CHECK constraints against live single-team, two-way, and traded-player seasons before encoding them in the migration, and ran a live smoke import (single-team + traded player) confirming one aggregate row per player-season with no team-stint rows persisted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FfkhPyJbQ9Lrq2FuckKaLw
|
Claude finished @Mattsface's task in 1m 50s —— View job Claude's review
The trigger comment ( This is a well-scoped, carefully implemented slice. The MLB-network-outside-transaction boundary, the atomic identity+hitting upsert, the aggregate-split selection logic (refusing to guess with One real gap I found: CLI script doesn't actually reuse one MLB client, despite that being a stated goal — Suggested fix: open one Everything else — models, repositories, schemas, the migration, and scope adherence to the exclusions list in the PR description — looks correct and consistent with the codebase's existing patterns (e.g. Note: I was not able to run the test suite or linters in this environment (Bash execution required approval that wasn't available), so this review is based on static reading of the diff rather than a green test run. |
…etches ingest_player_season previously passed client=None into get_player_identity and get_player_season_hitting independently, so a caller that supplied no client (the real scripts/import_player_season.py CLI path) caused each helper to open and close its own Mlb() context, running two separate MLB client lifecycles for one logical import. ingest_player_season now resolves the client once -- reusing a supplied client, or opening exactly one owned Mlb() for the call -- and delegates to a private _ingest_player_season that performs both MLB fetches and the persistence transaction with that single client. This mirrors the existing client-ownership pattern in ingest_league_season. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LRk3iX2wne7nk6XStpLm1x
Why
Issue #46 established the first intentionally small Player-data ingestion slice before Player analytics are added. This PR implements that slice without introducing league-wide player discovery, stint persistence, player game logs, or UI work.
What
playerspersistence for stable MLB player identityplayer_season_hittingpersistence keyed by(player_id, season)scripts/import_player_season.pyMlb.get_person(...)Mlb.get_player_stats(..., stats=["season"], groups=["hitting"])splits[0]for multi-split seasonsNoneto zeroTrade / stint semantics
This slice intentionally stores one full-season aggregate per player-season and no
team_idonplayer_season_hitting.For a single-team season, the sole split is used. For a traded player, multiple splits must contain exactly one aggregate with
team is None; that aggregate is persisted and team-specific stints are ignored. Ambiguous multi-split responses fail instead of being guessed.Validation
The branch was smoke-tested against live MLB data for a normal single-team player-season and a traded-player season, including an idempotent rerun. The implementation also includes offline coverage for normalization, aggregate selection, missing data, persistence, idempotency, updates, rollback behavior, CLI handling, and transaction/network boundaries.
Scope exclusions
Not included here:
Closes #46