Skip to content

feat: add season-level player catalog - #50

Merged
Mattsface merged 3 commits into
mainfrom
milestone-6-player-catalog
Sep 11, 2026
Merged

Mattsface merged 3 commits into
mainfrom
milestone-6-player-catalog

Conversation

@Mattsface

@Mattsface Mattsface commented Sep 10, 2026

Copy link
Copy Markdown
Member

Summary

Adds the season-level Player catalog that closes out the Player discovery foundation for M6.

The application can now discover MLB players for a season with one bulk Mlb.get_people(sport_id=1, season=...) request, persist stable player identity separately from season membership, and support future DB-only Player search/selection without requiring users to know MLB player IDs.

No Player UI, charts, game logs, pitching ingestion, team-stint model, or mass per-player stats ingestion is added here.

Why

PR #49 established the first Player data slice, but explicit player-season ingestion still required a known MLB player ID.

Before Player analytics can have a usable selector, the local database needs to answer a simpler question:

Which MLB players belong to season X?

A live audit confirmed that get_people(sport_id=1, season=YEAR) behaves as a season-scoped Player directory and returns one bulk population rather than requiring one request per player. The audit also showed that historical responses expose current biographical identity fields, so season membership must remain separate from global Player identity.

What

  • add player_seasons persistence keyed by (player_id, season)
  • keep players as the single source of current normalized Player identity
  • keep player_season_hitting independent from catalog membership
  • add an Alembic migration and backfill catalog membership from existing Player-season hitting rows
  • add PlayerSeasonCatalogEntry and typed catalog-ingestion results
  • add season-level Player discovery using one Mlb.get_people(...) request
  • validate empty responses, non-player records, missing required identity fields, and duplicate MLB IDs before persistence
  • add atomic/idempotent season catalog ingestion
  • reuse one service-owned Mlb() client when the caller does not supply one
  • keep all MLB network work outside the database transaction
  • add scripts/import_player_catalog.py --season <year> as a thin CLI adapter
  • make the existing one-player season importer also ensure the corresponding catalog membership
  • add a DB read path for listing the locally stored Player catalog by season
  • document non-destructive reconciliation: reruns upsert discovered memberships but do not delete rows absent from a later response without an explicit completeness contract
  • document the live discovery audit and the decision not to add an async catalog path because discovery is already one bulk request

Tests

  • GitHub Actions is green on the current PR head
  • Ruff lint passes
  • Ruff formatting check passes
  • full pytest suite passes
  • offline coverage includes:
    • season-scoped discovery request behavior
    • missing/invalid Player identity handling
    • duplicate-ID rejection
    • client ownership and cleanup
    • Player identity + season membership persistence
    • idempotent reruns
    • identity updates without duplicate membership
    • season-scoped catalog listing
    • transaction/network ordering
    • migration table/index/FK/constraint behavior
    • migration backfill and downgrade preservation
    • CLI argument/output/error handling
    • existing one-player hitting imports recording catalog membership

Live discovery audit documented in docs/player-season-catalog.md covered multiple seasons and confirmed unique Player IDs with no missing required names or primary positions in the audited populations.

Risk and impact

Normal

This adds a new persistence concept and a bulk ingestion path, but keeps the existing architecture deliberately conservative:

  • browser requests remain database-only
  • Player catalog ingestion is synchronous because discovery is one bulk request
  • no concurrent database writes are introduced
  • catalog membership does not imply hitting or pitching data exists
  • historical identity is not fabricated; names/positions remain current global identity attributes
  • absent players are not destructively removed on rerun without an explicit completeness model
  • the migration preserves existing Player identity and hitting data and backfills only relationships already evidenced by stored hitting rows

Future Player UI can query this catalog locally and join to available stat tables without calling MLB from a browser request.

Mattsface and others added 3 commits September 10, 2026 09:56
Discovery and the persisted catalog read returned two different orders for
the same players, and neither was name order. Discovery sorted on casefold,
which does not fold accents; the repository sorted in SQL, where SQLite's
default collation compares raw bytes and puts every lowercase name after
every uppercase one. A stored directory read back as Bo Bichette, Zack
Wheeler, aaron Judge, Ángel Martínez.

Move the rule onto PlayerIdentity.name_sort_key, which folds case and
strips combining marks, and apply it in both the discovery service and
list_player_catalog. Accented names now sort under their base letter, and a
discovered directory compares equal to a catalog read of the same players.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Mattsface
Mattsface merged commit cab3397 into main Sep 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant