The FastAPI server returns 307 redirects for URLs without trailing slashes.
aiohttp follows these redirects but converts POST to GET, silently dropping
the request body. This caused play-by-play and decision data from
/submit-scorecard to never be persisted to the database despite the API
returning success.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add centralized `is_admin(interaction)` helper that includes the
`isinstance(interaction.user, discord.Member)` guard, preventing
AttributeError in DM contexts.
Use it in `can_edit_player_image()` which previously accessed
`guild_permissions.administrator` directly without the isinstance
guard. Update the corresponding test to mock the user with
`spec=discord.Member` so the isinstance check passes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The TransactionBuilder cached pre-existing transactions on first load
and never refreshed them. This meant transactions submitted by other
sessions (or newly visible after API fixes) were invisible for the
lifetime of the builder session, causing incorrect roster projections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trade validation now automatically fetches the current week from
league_service and validates against next week's projected roster
(including pending transactions), matching /dropadd behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Roster-level errors, warnings, and suggestions now display as
"[BSG] Too many ML players" so users know which team each issue
applies to.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
validate_trade() was passing next_week=None to each team's
validate_transaction(), which skipped load_existing_transactions()
entirely. Trades were validated against the current roster only,
ignoring pending /dropadd transactions for next week.
Now auto-fetches current week from league_service and passes
next_week=current_week+1, matching /dropadd validation behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Quick Status previously only showed "X errors found" with no details.
Now lists each error and suggestion inline. Also stripped all emoji
from embed titles, field names, values, buttons, and messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the generic placeholder method from BaseService and replace the
single call site in CustomCommandsService.get_or_create_creator with a
direct client.post("custom_commands/creators", ...) call, consistent
with how _update_creator_stats and _update_creator_info already work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds next-release branch trigger and replaces separate dev/production
build steps with the shared docker-tags action for tag resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes the @pytest.mark.skip and implements the test using a
patched RosterValidation that raises on the first call, triggering
the except clause in validate_transaction. Verifies the method
returns is_legal=False with a descriptive error message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both get_disappointment_gif and get_gif previously created a new
ClientSession per call. Replace with a lazily-initialised shared
session stored on the instance, eliminating per-call TCP handshake
and DNS overhead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both _should_sync_commands and _save_command_hash contained an identical
~35-line block building the command data list and computing the SHA-256 hash.
Extracted into a new synchronous helper method _compute_command_hash() that
both callers now delegate to.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add section headers (Active Roster, Minor League, Injured List) to the
main summary embed in _create_roster_embeds so each roster section is
clearly labeled for users
- Fix incorrect docstring in team_service.get_team_roster that had the
shortil/longil mapping backwards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `maintenance_mode: bool = False` flag to `SBABot.__init__`
- Register a global `@tree.interaction_check` that blocks non-admin users
from all commands when maintenance mode is active
- Update `admin_maintenance` command to set `self.bot.maintenance_mode`
and log the state change, replacing the no-op placeholder comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Simplify review found that force_refresh=True on every validate_transaction()
call caused redundant API fetches on every embed render and button press.
Instead, invalidate the roster cache after successful submit_transaction() so
the next operation fetches fresh data. This preserves the cache for normal
interaction flows while still preventing stale data after submissions.
Also adds type annotation for roster_svc DI parameter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
TransactionBuilder cached roster data indefinitely via _roster_loaded flag,
causing validation to use stale counts when builders persisted across multiple
/ilmove invocations. Now validate_transaction() always fetches fresh roster
data. Also adds dependency injection for roster_service to improve testability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codebase audit identified ~50 lazy imports. Moved 42 unnecessary ones to
top-level imports — only keeping those justified by circular imports,
init-order dependencies, or optional dependency guards. Updated test mock
patch targets where needed. See #57 for remaining DI candidates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/trade add-player hardcoded from_roster=MAJOR_LEAGUE for all players,
causing incorrect transaction records when trading MiL/IL players. This
cascaded into false roster validation errors for other teams' /dropadd
commands because pre-existing transaction processing misidentified which
roster a player was leaving from.
Now looks up the player's actual team and calls roster_type() to determine
the correct source roster. Same fix applied to /trade supplementary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents users from managing injuries for players not on their team.
Admins bypass the check; org affiliates (MiL/IL) are recognized.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The else branch in descriptive_text() caught both "away leading" and
"tied" cases, always overwriting the tied text. Changed to if/elif/else
so tied scores display correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
load_existing_transactions only queried for the base team abbreviation
(e.g. "POR"), missing trades involving MiL/IL affiliates ("PORMIL",
"PORIL"). This caused false "too many players" errors when a pending
trade would have cleared a roster spot.
- get_team_transactions now accepts Union[str, List[str]] for team_abbrev
- load_existing_transactions queries all org affiliates [BASE, BASEMiL, BASEIL]
- Added 5 tests covering the fix and backwards compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The #weekly-info message already shows season and game times but not
which week it is, making managers check elsewhere for that context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The production container has ambiguous timezone config — /etc/localtime
points to Etc/UTC but date reports CST. The transaction freeze/thaw task
used datetime.now() (naive, relying on OS timezone), causing scheduling
to fire at unpredictable wall-clock times.
- Add utils/timezone.py with centralized Chicago timezone helpers
- Fix tasks/transaction_freeze.py to use now_chicago() for scheduling
- Fix utils/logging.py timestamp to use proper UTC-aware datetime
- Add 14 timezone utility tests
- Update freeze task tests to mock now_chicago instead of datetime
Closes#43
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the next-release staging branch pattern used for
batching changes before merging to main.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#40: ScorecardTracker cached data in memory at startup — background task never
saw newly published scorecards. Fixed by reloading from disk on every read.
#39: Win percentage defaulted to 50% when unavailable, showing a misleading
50/50 bar. Now defaults to None with "unavailable" message in embed. Parsing
handles decimal (0.75), percentage string, and empty values. Also fixed
orientation bug where win% was always shown as home team's even when the
sheet reports the away team as the leader.
Additionally: live scorebug tracker now distinguishes between "all games
confirmed final" and "sheet read failures" — transient Google Sheets errors
no longer hide the live scores channel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- #37: Fix stale comment in transaction_freeze.py referencing wrong moveid format
- #27: Change config.testing default from True to False (was masking prod behavior)
- #25: Replace deprecated asyncio.get_event_loop() with get_running_loop()
- #38: Replace naive datetime.now() with timezone-aware datetime.now(UTC) across
7 source files and 4 test files to prevent subtle timezone bugs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>