Commit Graph

162 Commits

Author SHA1 Message Date
cal
32ca21558e Merge pull request 'feat: Track Catalog API endpoints (WP-06) (#71)' (#86) from ai/paper-dynasty-database#71 into next-release
Some checks failed
Build Docker Image / build (push) Failing after 4m43s
Reviewed-on: #86
2026-03-16 16:14:58 +00:00
cal
01c8aa140c Merge pull request 'feat: PlayerSeasonStats Peewee model (#67)' (#82) from ai/paper-dynasty-database#67 into next-release
Some checks are pending
Build Docker Image / build (push) Waiting to run
Reviewed-on: #82
2026-03-16 16:13:06 +00:00
cal
223743d89f Merge pull request 'feat: evolution track seed data and tests (WP-03) (#68)' (#83) from ai/paper-dynasty-database#68 into next-release
Some checks are pending
Build Docker Image / build (push) Waiting to run
Reviewed-on: #83
2026-03-16 16:12:18 +00:00
cal
44763a07ec Merge pull request 'feat: formula engine for evolution value computation (WP-09) (#74)' (#85) from ai/paper-dynasty-database#74 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #85
2026-03-16 16:10:42 +00:00
cal
8d0111df32 Merge pull request 'fix: batch Paperdex lookups to avoid N+1 queries (#17)' (#53) from ai/paper-dynasty-database#17 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #53
Reviewed-by: Claude <cal.corum+openclaw@gmail.com>
2026-03-16 16:09:54 +00:00
cal
8a437c7ef3 Merge pull request 'fix: remove stub live_update_pitching endpoint (#11)' (#57) from ai/paper-dynasty-database#11 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #57
Reviewed-by: Claude <cal.corum+openclaw@gmail.com>
2026-03-16 16:09:13 +00:00
Cal Corum
ddf6ff5961 feat: Track Catalog API endpoints (WP-06) (#71)
Closes #71

Adds GET /api/v2/evolution/tracks and GET /api/v2/evolution/tracks/{track_id}
endpoints for browsing evolution tracks and their thresholds.  Both endpoints
require Bearer token auth and return a track dict with formula and t1-t4
threshold fields.  The card_type query param filters the list endpoint.

EvolutionTrack is lazy-imported inside each handler so the app can start
before WP-01 (EvolutionTrack model) is merged into next-release.

Also suppresses pre-existing E402/F541 ruff warnings in app/main.py via
pyproject.toml per-file-ignores so the pre-commit hook does not block
unrelated future commits to that file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:40:38 -05:00
Cal Corum
40e988ac9d feat: formula engine for evolution value computation (WP-09)
Closes #74

Adds app/services/formula_engine.py with three pure formula functions
(compute_batter_value, compute_sp_value, compute_rp_value), a dispatch
helper (compute_value_for_track), and a tier classifier (tier_from_value).
Tier boundaries and thresholds match the locked seed data from WP-03.

Note: pitcher formulas use stats.k (not stats.so) to match the
PlayerSeasonStats model field name introduced in WP-02.

19 unit tests in tests/test_formula_engine.py — all pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 19:34:40 -05:00
Cal Corum
25f04892c2 feat: evolution track seed data and tests (WP-03) (#68)
Closes #68

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:35:12 -05:00
Cal Corum
8dfc5ef371 fix: remove evolution models from WP-02 PR (#82)
Evolution models (EvolutionTrack, EvolutionCardState, EvolutionTierBoost,
EvolutionCosmetic), their re-export module, and tests were included in
this PR without disclosure. Removed to keep this PR scoped to
PlayerSeasonStats (WP-02) only per review feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 17:02:00 -05:00
Cal Corum
4bfd878486 feat: add PlayerSeasonStats Peewee model (#67)
Closes #67

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 16:35:02 -05:00
Cal Corum
4445acb7d0 fix: materialize final_players queryset before double-iteration in get_random_player
When no position filters are applied, `final_players` is a lazy Peewee queryset
with `ORDER BY RANDOM() LIMIT n`. Iterating it twice (once to build player_ids,
once for the response loop) executes two separate DB queries with different random
seeds, causing dex_by_player to be built for a different player set than returned,
silently producing empty paperdex for all players.

Add `final_players = list(final_players)` before building player_ids to ensure
both iterations operate on the same materialized result. Also fix pre-existing
syntax error in import statement and minor ruff lint issues in the same file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 14:03:26 -05:00
cal
a66ef9bd7c Merge pull request 'fix: use max() for pitcher OPS split weighting (#6)' (#60) from ai/paper-dynasty-database#6 into next-release
Some checks failed
Build Docker Image / build (push) Failing after 5m11s
Reviewed-on: #60
2026-03-10 14:42:59 +00:00
Cal Corum
7f139770d1 fix: remove stray syntax error in players.py db_engine import
Some checks failed
Build Docker Image / build (push) Failing after 6m28s
Build Docker Image / build (pull_request) Successful in 3m19s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:43:03 -05:00
Cal Corum
3b7bb2b6b5 fix: remove stray syntax error in teams.py db_engine import
All checks were successful
Build Docker Image / build (push) Successful in 49s
Build Docker Image / build (pull_request) Successful in 46s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 14:36:41 +00:00
Cal Corum
4f2513ae8b fix: use max() for pitcher OPS split weighting (#6)
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>
2026-03-07 16:32:56 -06:00
Cal Corum
c3732ef33e fix: remove stub live_update_pitching endpoint (#11)
The /live-update/pitching POST endpoint was a placeholder that only
validated auth and returned the input unchanged. No pitching processing
logic existed anywhere in the codebase. Removed the dead endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 01:37:52 -06:00
Cal Corum
2c4ff01ff8 fix: batch Paperdex lookups to avoid N+1 queries (#17)
Replace per-player/card Paperdex.select().where() calls with a single
batched query grouped by player_id. Eliminates N+1 queries in:
- players list endpoint (get_players, with inc_dex flag)
- players by team endpoint
- cards list endpoint (also materializes query to avoid double count())

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 01:37:03 -06:00
cal
7295e77c96 Merge pull request 'fix: refactor Roster from 26 FK columns to RosterSlot junction table (#29)' (#58) from ai/paper-dynasty-database#29 into next-release
Some checks failed
Build Docker Image / build (push) Successful in 3m28s
Build Docker Image / build (pull_request) Has been cancelled
Reviewed-on: #58
2026-03-07 03:23:41 +00:00
cal
be02ba1e3f Merge pull request 'fix: remove broken live_update_batting stub endpoint (#10)' (#54) from ai/paper-dynasty-database#10 into next-release
Some checks are pending
Build Docker Image / build (push) Waiting to run
Reviewed-on: #54
2026-03-07 03:22:08 +00:00
cal
3ddb7028f3 Merge pull request 'fix: replace broad except Exception blocks with DoesNotExist (#15)' (#48) from ai/paper-dynasty-database#15 into next-release
Some checks are pending
Build Docker Image / build (push) Waiting to run
Reviewed-on: #48
2026-03-07 03:18:56 +00:00
Cal Corum
44b6222ad5 fix: refactor Roster from 26 FK columns to RosterSlot junction table (#29)
- 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>
2026-03-05 15:34:39 -06:00
Cal Corum
7b494faa99 fix: remove broken live_update_batting stub endpoint (#10)
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>
2026-03-05 15:34:06 -06:00
Cal Corum
0c042165b7 fix: replace broad except Exception blocks with DoesNotExist (#15)
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>
2026-03-05 15:32:53 -06:00
Cal Corum
62b205bde2 fix: batch BattingCard/BattingCardRatings lookups in lineup builder (#18)
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>
2026-03-05 15:31:13 -06:00
Cal Corum
5e182bedac feat: add scout_opportunities and scout_claims tables and API endpoints (#44)
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>
2026-03-05 03:45:38 +00:00
Cal Corum
19ac5ffd0a fix: use constant-time comparison for bearer token validation (#8)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 03:43:59 +00:00
Cal Corum
35389cac24 fix: remove plaintext bearer token from warning logs (#7)
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>
2026-03-05 03:43:27 +00:00
Cal Corum
f1d289a0e9 fix: consolidate redundant double-query in get_one_play (#14)
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>
2026-03-05 03:35:59 +00:00
Cal Corum
0166c7dda4 fix: compute CSV after appending data row in get_one_player (#12)
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>
2026-03-05 03:32:09 +00:00
Cal Corum
0948db0b49 fix: guard against None rating objects in pitcher sorting functions (#13)
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>
2026-03-05 03:25:28 +00:00
Cal Corum
870c753bf7 fix: document SQLite synchronous=0 pragma in db_engine.py (#20)
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>
2026-03-05 03:23:30 +00:00
Cal Corum
053fcbab05 fix: centralize logging config in main.py — remove basicConfig from 32 files (#26)
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>
2026-03-05 03:22:02 +00:00
Cal Corum
ae8c20ea1c fix: batch-fetch PitchingCardRatings instead of per-row queries (#19)
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>
2026-03-05 03:19:43 +00:00
Cal Corum
5f86c8cb20 fix: add type annotations to untyped path parameters (#27)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 03:18:38 +00:00
Cal Corum
86b4338b66 fix: remove duplicate ranking_max filter in get_teams (#21)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:01:52 -06:00
Cal Corum
bcdbf2add1 fix: remove unused imports in PR #33 files
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>
2026-03-03 15:52:56 -06:00
Cal Corum
8d86b3fec6 fix: replace 467 manual db.close() calls with middleware (#30)
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>
2026-03-03 15:52:56 -06:00
Cal Corum
6130eb993f fix: use Field(default_factory) for offense_col random default (#24)
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>
2026-03-03 21:46:03 +00:00
Cal Corum
6a2e8a2d2a fix: remove dead roster fields from CSV in v1_cards_get_one (#25)
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>
2026-03-03 21:44:59 +00:00
Cal Corum
9fc7a9449e fix: correct inverted TESTING env check and leading space in .env (#23)
- 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>
2026-03-03 21:43:54 +00:00
Cal Corum
3e15acbb9d fix: respect is_ai=False in get_teams filter (#22)
`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>
2026-03-03 21:38:50 +00:00
Cal Corum
65ad72c299 fix: remove debug print(req.scope) from get_docs route (#31)
All checks were successful
Build Docker Image / build (pull_request) Successful in 9m50s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:36:15 -06:00
Cal Corum
b8a6c6bd2c Fix KeyError: 'human' in gauntlet-9 CARDSETS
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>
2026-02-04 08:47:21 -06:00
Cal Corum
40c512c665 Add PostgreSQL compatibility fixes for query ordering
- 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>
2026-02-03 10:39:14 -06:00
Cal Corum
985a6ed2b0 Add default ORDER BY id to BaseModel.select() for PostgreSQL compatibility
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.
2026-01-31 16:06:44 -06:00
Cal Corum
8c039dedf8 Fix DateTimeField defaults for PostgreSQL compatibility
Paperdex and GauntletRun models used int timestamps as defaults which
worked in SQLite but fail in PostgreSQL. Changed to datetime.now.
2026-01-31 15:56:33 -06:00
Cal Corum
1f78bd188b Fix missed timestamp issues in stats POST handlers
- 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>
2026-01-30 22:45:15 -06:00
Cal Corum
f4aafa35e7 Fix PostgreSQL timestamp conversion for stats GET filters
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>
2026-01-30 22:41:00 -06:00
Cal Corum
f3b0b90860 Fix PostgreSQL timestamp handling in gauntletruns.py
- 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>
2026-01-30 22:39:45 -06:00