Replace the logging-only stub in logic_gameplay.py with the real
notify_tier_completion from helpers/refractor_notifs.py. Tier-up
events now send Discord embeds instead of just logging.
- Import notify_tier_completion from helpers.refractor_notifs
- Remove 16-line stub function and redundant inline logging
- Update tests: verify real embed-sending behavior, replace
bug-documenting T1-5 diagnostic with shape validation guards
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change `evolution/evaluate-game/` API call to `refractor/evaluate-game/` in
complete_game() hook (was calling the wrong endpoint path)
- Update all test assertions in test_complete_game_hook.py to match the
corrected endpoint path and update docstrings to "refractor" naming
- Remove helpers/evolution_notifs.py and tests/test_evolution_notifications.py
from this PR — they belong to PR #112 (WP-14 tier notifications). The
notify_tier_completion stub in logic_gameplay.py remains as the WP-14
integration target.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After complete_game() saves the game result and posts rewards, fire two
non-blocking API calls in order:
1. POST season-stats/update-game/{game_id}
2. POST evolution/evaluate-game/{game_id}
Any failure in the evolution block is caught and logged as a warning —
the game is already persisted so evolution will self-heal on the next
evaluate pass. A notify_tier_completion stub is added as a WP-14 target.
Closes#78 on cal/paper-dynasty-database
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes#25, Fixes#32, Fixes#37, Fixes#38
- Remove unused PLAYER_CACHE = {} from api_calls.py (issue #37)
- Remove dead select_speed_testing() and select_all_testing() functions
with their debug print() statements from gameplay_models.py (issue #32)
- Remove empty if-pass stubs after db_post calls in logic_gameplay.py (issue #38)
- Replace 10 bare except: clauses with except Exception: in gameplay_queries.py (issue #25)
- Add ruff.toml to configure pre-commit hook for existing codebase patterns
(F403/F405 from intentional star imports, F541/F401/F841/E712 cosmetic)
- Fix E713 in logic_gameplay.py (not x in [...] -> x not in [...]) required
by the pre-commit hook on the file already being touched
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Initialize db_game = None before try block and guard roll_back call
with `if db_game is not None:` to prevent NameError masking the
original exception when db_post("games") raises before assignment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a half-inning ended with a CS (or pickoff), the batter who was at
the plate was incorrectly skipped in the next inning. The side-switch
code unconditionally advanced the batting order by 1 without checking
whether the last play was a plate appearance. Now checks opponent_play.pa
before incrementing, matching the existing non-side-switch logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Early returns in log_chaos, log_sac_bunt, and log_stealing left play
locks permanently stuck because the lock was acquired but never released.
The new locked_play async context manager wraps checks_log_interaction()
and guarantees lock release on exception, early return, or normal exit.
Migrated all 18 locking commands in gameplay.py and removed redundant
double-locking in end_game_command.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents PositionNotFoundException from crashing mlb-campaign when a
player is placed at a position they cannot play (e.g. an outfielder
listed at Catcher in the Google Sheet). Adds early validation in
get_lineups_from_sheets and proper error handling at all read_lineup
call sites so the user gets a clear message and the game is cleaned up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HOTFIX: Production bot failed to start due to circular import.
Root cause: utilities/dropdown.py importing from command_logic/logic_gameplay.py
while logic_gameplay.py imports from utilities/dropdown.py.
Solution: Created play_lock.py as standalone module containing:
- release_play_lock()
- safe_play_lock()
Both modules now import from play_lock.py instead of each other.
Error message:
ImportError: cannot import name 'release_play_lock' from partially
initialized module 'command_logic.logic_gameplay' (most likely due
to a circular import)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CRITICAL BUG FIX: Play locks were never released on exceptions, causing
permanent user lockouts. Found 13 stuck plays in production.
Changes:
1. Added lock_play parameter to checks_log_interaction() (default True)
2. Removed unnecessary locks from read-only commands:
- /settings-ingame (game settings, not play state)
- /show-card defense (read-only display)
- /substitute commands (just show UI, lock in callback)
3. Added safe_play_lock() context manager for automatic lock release
4. Added play locking to substitution callbacks:
- SelectBatterSub.callback()
- SelectReliefPitcher.callback()
5. Global error handler now releases stuck locks automatically
Architecture:
- Commands that display UI or read data: No lock
- Commands that modify play state: Lock at last possible moment
- 3-layer defense: manual release, context manager, global handler
Resolves race condition from concurrent play modifications.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds idempotency guard to prevent race conditions when multiple users
submit commands for the same play simultaneously.
Changes:
- Add PlayLockedException for locked play detection
- Implement lock check in checks_log_interaction()
- Acquire lock (play.locked = True) before processing commands
- Release lock (play.locked = False) after play completion
- Add warning logs for rejected duplicate submissions
- Add /diagnostics endpoint to health server for debugging
This prevents database corruption and duplicate processing when users
spam commands like "log xcheck" while the first is still processing.
Tested successfully in Discord - duplicate commands now properly return
PlayLockedException with instructions to wait.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added missing await keyword on line 2981 in logic_gameplay.py
- SPD hit result was calling singles() without await, causing RuntimeWarning
- All groundball tests passing (24/24)
- Bump version to 1.7.7
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Bug: The is_game_over() function contained a debug print statement that was
printing "1: " to stdout on every call. This was causing massive log spam
in Docker container output (thousands of lines) and making it difficult to
diagnose actual issues.
Fix: Remove the print(f'1: ') statement from line 3251.
Bumps version to 1.7.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously, if get_card_or_none returned None (when a card ID from the
Google Sheet doesn't exist in the database), the code would create a
RosterLink with card=None, causing card_id to be null which violates
the NOT NULL constraint on the primary key.
Now we check if this_card is None before creating the RosterLink and
raise a CardNotFoundException with a helpful error message to guide
the user to fix their roster sheet.
Fixes the error: null value in column "card_id" of relation "rosterlink"
violates not-null constraint
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements defensive error handling for WPA (Win Probability Added) calculations when rare game states are missing from the static lookup table. The safe_wpa_lookup() function uses a three-tier fallback strategy: exact lookup, bases-empty fallback, and 0.0 default value.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Handle gamestates without full lineups
Added /set command for lineup and SP
Fixed uncapped hit bugs
Added league_name property to Games
Fix get_team for gauntlets
Fixed SelectSP dropdown bug