Replace per-game update_standings() calls with pre-fetched dicts and
in-memory accumulation, then a single bulk_update at the end.
Reduces ~1,100+ queries for a full season to ~5 queries.
Closes#75
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes DATABASE_TYPE conditional entirely. PostgreSQL is now the only
supported backend. Moves PooledPostgresqlDatabase import to top-level
and raises RuntimeError at startup if POSTGRES_PASSWORD is unset,
preventing silent misconnection with misleading errors.
Closes#70
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set timeout=5 so pool exhaustion surfaces as an error instead of hanging forever
- Set autoconnect=False to require explicit connection acquisition
- Add HTTP middleware in main.py to open/close connections per request
Closes#80
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#78
Change CharField(max_length=20) to BigIntegerField to match the BIGINT
column created by the migration. Remove the str() workaround in
get_creator_by_discord_id() that was compensating for the type mismatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After removing db.close() calls, 22 finally: blocks were left empty
(12 in custom_commands.py, 10 in help_commands.py), causing
IndentationError at import time. Removed the finally: clause entirely
since connection lifecycle is now handled by the middleware.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sp.on_first/on_second/on_third don't exist — the actual columns are
on_first_id/on_second_id/on_third_id. This caused failures when
updating season pitching stats after games.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The decorator was catching all exceptions including intentional
HTTPException (401, 404, etc.) and re-wrapping them as 500 "Database
error". This masked auth failures and other deliberate HTTP errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MAX_LIMIT/DEFAULT_LIMIT caps added in 16f3f8d are too restrictive
for the /players endpoint — bot and website consumers need full player
lists without pagination. Reverts limit param to Optional[int] with no
ceiling while keeping caps on all other endpoints.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Capture total_count before .limit() so the response count reflects
all matching rows, not just the capped page size. Resolves#100.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add MAX_LIMIT=500 cap across all list endpoints, empty string
stripping middleware, and limit/offset to /transactions. Resolves#98.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add DISCORD_WEBHOOK_URL to docker-compose.yml api service environment block
- Add empty placeholder entry in .env for discoverability
- Move DISCORD_WEBHOOK_URL constant to the env-var constants section at top of dependencies.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace inline webhook URL+token with DISCORD_WEBHOOK_URL env var.
Logs a warning and returns False gracefully if the var is unset.
The exposed webhook token should be rotated in Discord.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Raise RuntimeError on startup if POSTGRES_PASSWORD env var is not set,
instead of silently falling back to a known password in source code.
Closes #C2 from postgres migration review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes#65
`PitchingStat.combined_season()` was referenced in the `get_pitstats`
handler but never defined, causing a 500 on `s_type=combined/total/all`.
Added `combined_season` as a `@staticmethod` matching the pattern of
`BattingStat.combined_season` — returns all rows for the given season
with no week filter (both regular and postseason).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guard bulk ID queries against empty lists to prevent PostgreSQL
syntax error (WHERE id IN ()) when batch POST endpoints receive
empty request bodies.
Affected endpoints:
- POST /api/v3/transactions
- POST /api/v3/results
- POST /api/v3/schedules
- POST /api/v3/battingstats
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace per-row Team/Player lookups with bulk IN-list queries before
the validation loop in post_transactions, post_results, post_schedules,
and post_batstats. A 50-move batch now uses 2 queries instead of 150.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add finally blocks to update_player, patch_player, create_players, and
delete_player in PlayerService to call invalidate_related_cache() using
the existing cache_patterns. Matches the pattern already used in
TeamService.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apply .offset() and .limit() on the Peewee query before materializing
results, instead of fetching all rows into memory and slicing in Python.
Total count is obtained via query.count() before pagination is applied.
In-memory (mock) queries continue to use Python-level slicing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both fields were hardcoded to 0.0 in the INSERT. Added SQL expressions
to the pitching_stats CTE to calculate them from stratplay data, using
the same logic as the batting stats endpoint.
- lob_2outs: count of runners stranded when pitcher recorded the 3rd out
- rbipercent: RBI allowed (excluding HR) per runner opportunity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
aiohttp follows 307 redirects but converts POST to GET, silently
dropping the request body. Standardize all @router.post('') to
@router.post('/') so the canonical URL always has a trailing slash,
preventing 307 redirects when clients POST with trailing slashes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The team_abbrev filter uppercased input but compared against mixed-case
DB values (e.g. "MKEMiL"), causing affiliate roster transactions to be
silently dropped from results. Use fn.UPPER() on the DB column to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Peewee's order_by() returns a new queryset and does not sort in place.
Both branches were discarding the result, so the sort parameter had no
effect. Assign back to all_games so the query is actually ordered.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enables career-total aggregation by real-world player identity (SbaPlayer)
across all seasons. JOINs StratPlay → Player to access Player.sbaplayer FK,
groups by that FK, and excludes players with null sbaplayer. Also refactors
stratplay router from single file into package and adds integration tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables cross-season stat queries by MLB player identity (SbaPlayer) without
requiring callers to look up every season-specific Player ID first. Filters
via Player subquery since StratPlay has no direct FK to SbaPlayer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Provide targeted context for each app subdirectory so Claude Code
understands local patterns without relying on the root CLAUDE.md.
Also simplifies root CLAUDE.md dev/prod environment sections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The teams PATCH endpoint included the `data` variable itself when
building the update dict via locals(), causing Peewee to fail with
"type object 'Team' has no attribute 'data'". The players endpoint
had the same pattern with a workaround that was order-dependent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>