JOIN the Player table in the evolutions query so p_name can be
included in each serialized item without N+1 queries.
Closes#115
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two endpoints for reading EvolutionCardState:
GET /api/v2/teams/{team_id}/evolutions
- Optional filters: card_type, tier
- Pagination: page / per_page (default 10, max 100)
- Joins EvolutionTrack so card_type filter is a single query
- Returns {count, items} with full card state + threshold context
GET /api/v2/evolution/cards/{card_id}
- Resolves card_id -> (player_id, team_id) via Card table
- Duplicate cards for same player+team share one state row
- Returns 404 when card missing or has no evolution state
Both endpoints:
- Require bearer token auth (valid_token dependency)
- Embed the EvolutionTrack in each item (not just the FK id)
- Compute next_threshold: threshold for tier above current (null at T4)
- Share _build_card_state_response() helper in evolution.py
Also cleans up 30 pre-existing ruff violations in teams.py that were
blocking the pre-commit hook: F541 bare f-strings, E712 boolean
comparisons (now noqa where Peewee ORM requires == False/True),
and F841 unused variable assignments.
Tests: tests/test_evolution_state_api.py — 10 integration tests that
skip automatically without POSTGRES_HOST, following the same pattern as
test_evolution_track_api.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Starters face both LHH and RHH, so the OPS aggregation formula should
penalise the weaker platoon split (higher OPS allowed) rather than
reward the stronger one. Changed min(ops_vl, ops_vr) → max(ops_vl, ops_vr)
in both get_total_ops (line 621) and sort_starters (line 703) and
replaced the TODO comment with an explanatory note.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 71 broad `except Exception` blocks in 19 router files with the
specific `peewee.DoesNotExist` exception. GET endpoints that call
`Model.get_by_id()` now only catch the expected DoesNotExist error,
allowing real DB failures (connection errors, etc.) to propagate as
500s rather than being masked as 404s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace per-player get_or_none() calls in get_bratings() with two bulk
SELECT queries before the position loop, keyed by player_id and card+hand.
This reduces DB round trips from O(3N) to O(2) for all lineup difficulties.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all logging.warning(f'Bad Token: {token}') calls with
logging.warning('Bad Token: [REDACTED]') across 30 router files.
Full bearer tokens were being written to log files on auth failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add None checks for vlval/vrval in get_total_ops inside sort_pitchers()
and sort_starters(). Returns float("inf") when ratings are missing so
pitchers without ratings sort to the end rather than raising AttributeError.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moved logging.basicConfig() to app/main.py as the single source of truth.
Removed duplicate (no-op) calls from app/db_engine.py, app/dependencies.py,
and all 30 router files in app/routers_v2/. Removed the now-unused LOG_DATA
dict and date/log_level locals from dependencies.py and db_engine.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace two get_or_none calls per row in sort_pitchers and sort_starters
with a single batched SELECT for all card IDs, reducing N*2 queries to 1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed 55 unused imports across 26 router files. Most were `db` imports
left over after the db.close() removal in the previous commit, plus
additional stale imports (scipy.stats, chunked, copy, base64, Html2Image,
pandas.DataFrame, pydantic.validator, etc.) that were already unused.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add db_session_middleware to main.py that opens the connection at the
start of each request and closes it in a try/finally block, ensuring
connections are always returned even on uncaught exceptions.
Remove all individual db.close() calls from 30 router files in
app/routers_v2/ — the middleware now handles all code paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`all_teams.where(Team.is_ai)` always filtered for AI teams regardless
of the caller's intent. Match the existing has_guide pattern and use
explicit boolean comparison so False is handled correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add explicit ORDER BY id to all queries for consistent results across SQLite and PostgreSQL
- PostgreSQL does not guarantee row order without ORDER BY, unlike SQLite
- Skip table creation when DATABASE_TYPE=postgresql (production tables already exist)
- Fix datetime handling in notifications (PostgreSQL native datetime vs SQLite timestamp)
- Fix grouped query count() calls that don't work in PostgreSQL
- Update .gitignore to include storage/templates/ directory
This completes the PostgreSQL migration compatibility layer while maintaining
backwards compatibility with SQLite for local development.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add SQL migration script to update all franchise values
- Change AI roster queries from Team.lname to Team.sname
- Add FRANCHISE_NORMALIZE helper for bulk imports
- Update St Louis Cardinals hardcoded fix
Enables cross-era player matching for AI rosters (fixes Oakland Athletics issue)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>