- Remove card_1..card_26 FK columns from Roster ORM model
- Add RosterSlot model with (roster, slot, card) and a unique index on (roster, slot)
- Activate get_cards() helper on Roster using the new junction table
- Register RosterSlot in create_tables for SQLite dev environments
- Add migrations/migrate_roster_junction_table.py to backfill existing data
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The endpoint iterated over `files.vl_basic` (a string, not parsed CSV),
causing it to loop over individual characters. The body contained only
`pass` with TODO comments and no running stats logic. Removed the
endpoint entirely along with the dead commented-out csv helper code.
The `BattingFiles` model is retained as it is still used by
`live_update_pitching`.
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>
Support the Discord bot's new scouting feature where players can scout
cards from other teams' opened packs. Stores opportunities with expiry
timestamps and tracks which teams claim which cards.
Co-Authored-By: Claude Opus 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>
Reuse the result of get_or_none instead of discarding it and calling
get_by_id again, eliminating one unnecessary round-trip per request.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
return_val was assigned from DataFrame(data_list).to_csv() before the
player data row was appended to data_list, so the CSV response contained
only the header row. Moved the to_csv() call to after the append.
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>
Add explanatory comment clarifying that synchronous=OFF is a dev-only
trade-off (production uses PostgreSQL), and describing the crash-corruption
risk and how WAL mode partially mitigates it.
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>
Pydantic evaluates bare `random.randint(1, 3)` once at class definition
time, so every PlayerModel instance shared the same value. Replaced with
`pydantic.Field(default_factory=...)` so a new random value is generated
per instance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Card model has no roster1/2/3 fields. Accessing them would raise
AttributeError at runtime. Removed the non-existent columns from
the CSV header and data row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change `== 'False'` to `== 'True'` so TESTING=True routes to dev URL
- Fix leading space on TESTING=TRUE in .env so the var is actually set
- Update .env comment to match corrected logic
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>
Added missing 'human' key to gauntlet-9 cardset configuration.
This was causing 500 errors when players tried to start gauntlet
games because the legal-check endpoint couldn't validate cards.
Error: KeyError: 'human' at app/routers_v2/cards.py:242
when checking CARDSETS[rarity_name]['human']
Co-Authored-By: Claude Sonnet 4.5 <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>
PostgreSQL does not guarantee row order without ORDER BY, unlike SQLite
which implicitly returned rows by rowid. This caused bugs where queries
returned results in unexpected order (e.g., get_team_by_owner returning
gauntlet team instead of main team).
Override select() in BaseModel to add default ordering by id. Explicit
.order_by() calls will override this default.
Also mark legacy db_engine.py as deprecated.
- Fix batstats.py and pitstats.py POST handlers to convert timestamps
- Fix Pydantic model defaults from *100000 to *1000 (wrong multiplier)
Found during second-pass audit.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert milliseconds to datetime for created filter in batstats.py
and pitstats.py GET endpoints.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Convert milliseconds to datetime for GET filters (created_after/before, ended_after/before)
- Fix is_active filter to use is_null() instead of comparing to 0
- Fix PATCH to use datetime.now() instead of int timestamps
- Fix POST to convert timestamps and use None for nullable ended field
- Update Pydantic model defaults to None instead of int
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert milliseconds timestamps to datetime for created_after filter
in notifications and created_after/before filters in paperdex.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert milliseconds timestamps from Discord bot to datetime objects
for PostgreSQL DateTimeField columns in notifications, packs, paperdex,
and rewards routers. Also fix rewards GET created_after filter.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix upsert_many() to use column_name for EXCLUDED references
(ForeignKeyField columns end in _id, e.g., batter -> batter_id)
- Add null checks in batting/pitching CSV output for player, team, game
fields to prevent 'NoneType' not subscriptable errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 2018 Promos (14) and 2022 Promos (4) to flashback mode legal cardsets
- Convert Unix timestamps to datetime in rewards POST/PATCH for PostgreSQL
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix /legal-check endpoint to handle card_ids passed as stringified list
- Add compose.production.yml for akamai deployment (pd_api container)
- Add migrate_missing_data.py script for filling gaps from initial migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PostgreSQL requires GROUP BY for all non-aggregated columns when using
aggregate functions. Added group_by(pitcher, game) to the StratPlay query
that calculates pitcher innings in the /decisions/rest endpoint.
- Fix NULL handling for FK checks in stratplays.py: use x.field_id instead
of x.field to avoid triggering FK lookups on potentially missing rows
- Cast boolean is_start to integer for SUM() - PostgreSQL cannot sum booleans
- Add missing GROUP BY clause to Decision aggregate query
- Add Case import for boolean-to-integer casting
- Update migration script with boolean/datetime column mappings
- Exclude legacy battingstat/pitchingstat tables from migration
- Add comprehensive POSTGRES_MIGRATION_GUIDE.md documentation
Tested: /plays/batting and /plays/pitching endpoints work with group_by=player
- 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>
- Refactor get_batting_totals() to conditionally build SELECT fields based on group_by parameter
- Refactor get_pitching_totals() with same pattern
- Ensures all non-aggregated SELECT fields are included in GROUP BY clause
- Based on successful Major Domo migration pattern
- Add environment-based PostgreSQL configuration to db_engine.py
- Add table_name to all 30 models (Meta class)
- Update db_migrations.py to auto-select migrator based on DB type
- Add comprehensive PostgreSQL migration plan document