**Problem:**
The _is_spreadsheet_error() check was logging a warning and silently skipping
rows with formula errors (#REF!, #N/A, etc.). This could lead to incomplete
game data being submitted without the user knowing.
**Solution:**
Raise SheetsException immediately when spreadsheet errors are detected,
providing:
- Exact row number and field name
- Actual error value found in the cell
- Common error type explanations
- Clear action required to fix
**Impact:**
- Users get immediate feedback about spreadsheet errors
- No partial/incomplete data submitted to API
- Clear instructions on what needs to be fixed
- Better data integrity
**Example Error Message:**
```
❌ Spreadsheet Error Detected
**Location:** Row 7, Column 'pitcher_id'
**Value Found:** `#REF!`
This cell contains a formula error that must be fixed before submission.
**Action Required:** Fix cell pitcher_id in row 7 and resubmit.
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added robust validation to handle spreadsheet errors and invalid data
when reading pitching decisions from scorecards.
Problem:
- POST /api/v3/decisions was failing with 422 errors
- Google Sheets cells containing "#N/A" were passed directly to API
- API correctly rejected invalid team_id values like "#N/A" string
- No validation of integer fields or required fields
Root Cause:
- sheets_service.py:read_pitching_decisions() read values without
validation or type checking
- Spreadsheet formula errors (#N/A, #REF!, etc.) passed through
- Invalid data types not caught until API validation failed
Solution:
1. Added _is_spreadsheet_error() to detect formula errors
2. Added _sanitize_int_field() to validate and convert integers
3. Enhanced read_pitching_decisions() to:
- Detect and skip rows with spreadsheet errors
- Validate integer fields (pitcher_id, team_id, etc.)
- Ensure required fields (pitcher_id, team_id) are present
- Log warnings for invalid data with row numbers
- Only return valid, sanitized decision data
Impact:
- Prevents 422 errors from bad spreadsheet data
- Provides clear warnings in logs when data is invalid
- Gracefully skips invalid rows instead of crashing
- Helps identify scorecard data entry errors
Testing:
- Handles #N/A, #REF!, #VALUE!, #DIV/0! and other errors
- Converts "123.0" strings to integers correctly
- Validates required fields before sending to API
- Logs row numbers for debugging bad data
Production logs showed:
"Input should be a valid integer, unable to parse string as
an integer", input: "#N/A" for team_id field
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The all_seasons=True parameter causes the API search query to take 15+ seconds,
exceeding Discord's 3-second autocomplete timeout. Changed to all_seasons=False
to search only the current season, which is fast enough.
Users can still access historical players by manually typing the name and using
the season parameter in the /player command.
Fixes: discord.errors.NotFound 404 (error code: 10062): Unknown interaction
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix /injury roll and /injury clear commands crashing when player.team is None
- Add team fetching logic after search_players() for all injury commands
- The search_players() API endpoint returns team as ID only (not nested object)
- Added null checks and explicit team fetching using team_service.get_team()
- Add Gitea Actions workflow for automated Docker builds
- Implements semantic version validation on PRs
- Builds and pushes to Docker Hub on main branch merges
- Sends Discord notifications on success/failure
- Generates 3 image tags: latest, v{VERSION}, v{VERSION}-{COMMIT}
Fixes AttributeError: 'NoneType' object has no attribute 'roster_type'
Fixes AttributeError: 'NoneType' object has no attribute 'thumbnail'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The search_players() API endpoint returns team as an ID only (not nested object),
causing player.team to be None. Added null check before accessing team.thumbnail.
Fixes AttributeError: 'NoneType' object has no attribute 'thumbnail'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The API expects 'demotion_week' as the query parameter name, not 'dem_week'.
Updated service to send correct parameter name and tests to verify.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevents double emoji display by clearing the "✅ Confirmed!" content
when showing the delete result embed.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changed from command.creator_id (database ID) to command.creator.discord_id
to properly compare against the deleter's Discord user ID.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The delete confirmation was showing success but never calling the delete
service. Added the actual delete_command() call when user confirms.
Co-Authored-By: Claude <noreply@anthropic.com>
- Use channel.send() instead of followup.send() for custom command output
(webhook-based followup messages don't trigger mention notifications)
- Add ephemeral "Sending..." confirmation to satisfy interaction response
- Add utils/mentions.py for converting text @mentions to Discord format
- Add tests for mention conversion utility
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive guide covering:
- Build/lint/test commands including single test execution
- Code style: imports, formatting, types, naming, error handling
- Discord patterns: @logged_command, autocomplete, embed emojis
- Service layer abstraction rules
- Model patterns (from_api_data, required IDs)
- Testing with aioresponses and complete model data
- Critical rules for git, services, and commits
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- PlayerService.search_players() now supports all_seasons=True to search across all 13 seasons
- Autocomplete shows unique player names (most recent season's team) instead of duplicates
- Command defaults to most recent season when no season parameter specified
- Users can specify season parameter for historical data
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Production server: ssh akamai
- Container path: /root/container-data/major-domo
- Service name: discord-app
- Full release workflow documented
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bug Fix:
- Fixed /dropadd transactions being marked frozen=True during thaw period
- Now uses current_state.freeze to set frozen flag correctly
- Transactions entered Sat-Sun are now unfrozen and execute Monday
New Feature - Transaction Thaw Report:
- Added data structures for thaw reporting (ThawReport, ThawedMove,
CancelledMove, ConflictResolution, ConflictContender)
- Modified resolve_contested_transactions() to return conflict details
- Added _post_thaw_report() to post formatted report to admin channel
- Report shows thawed moves, cancelled moves, and conflict resolution
- Handles Discord's 2000 char limit with _send_long_report()
Tests:
- Updated test_views_transaction_embed.py for frozen flag behavior
- Added test for thaw period (freeze=False) scenario
- Updated test_tasks_transaction_freeze.py for new return values
- All tests passing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The setup_profile_commands() function was returning None instead of the
expected (successful, failed, failed_modules) tuple, causing bot startup
to log "Failed to load profile package: cannot unpack non-iterable NoneType".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Root cause: league_service.update_current_state() was calling self.patch()
without use_query_params=True. The API expected query params but received
JSON body, so database updates for week/freeze silently failed.
Changes:
- Add use_query_params=True to league_service.py:99
- Fix service layer violation in transaction_freeze.py - now uses
player_service.update_player_team() instead of direct API client
- Bump version to 2.25.8
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- New !loaded <d6> <2d6> <d20> [user_id] command for predetermined dice
- Loaded values consumed on next /ab roll (one-shot)
- Supports targeting other users by ID for testing
- Admin-restricted prefix commands (!loaded, !unload, !checkload)
- Self-contained in commands/dev/ package
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add sWAR cap validation to TransactionBuilder.validate_transaction()
- Use team-specific salary_cap from Team.salary_cap field
- Fall back to config.swar_cap_limit (32.0) if team has no custom cap
- Add major_league_swar_cap field to RosterValidationResult
- Update major_league_swar_status to show ✅/❌ with cap limit
- Add 4 new tests for sWAR cap validation
This fixes a bug where IL moves could put a team over their sWAR cap
because the validation only checked roster counts, not sWAR limits.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed /injury roll, /injury set-new, and /injury clear to use
search_players() instead of get_players_by_name(). The /players?name=
API endpoint fails to find some players (e.g., Gunnar Henderson) while
the /players/search?q= endpoint works correctly.
This fixes the "Player Not Found" error when users try to clear
injuries for players that exist in the database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three bugs identified and fixed:
1. Deduplication logic tracked wrong week (transaction_freeze.py:216-219)
- Saved freeze_from_week BEFORE _begin_freeze() modifies current.week
- Prevents re-execution when API returns stale data
2. _run_transactions() bypassed service layer (transaction_freeze.py:350-394)
- Added get_regular_transactions_by_week() to transaction_service.py
- Now properly filters frozen=false and cancelled=false
- Uses Transaction model objects instead of raw dict access
3. CRITICAL: Hardcoded current_id=1 (league_service.py:88-106)
- Current table has one row PER SEASON, not a single row
- Was patching Season 3 (id=1) instead of Season 13 (id=11)
- Now fetches actual current state ID before patching
Root cause: The hardcoded ID caused every PATCH to update the wrong
season's record, so freeze was never actually set to True on the
current season. This caused the dedup check to pass 60 times (once
per minute during hour 0).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The API was returning keepers transactions (week=0) when querying for
week 2 transactions. Changed from 'week' to 'week_start' parameter to
properly filter out earlier weeks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Release includes:
- Add pending transaction validation for /dropadd command
- Players already in a pending transaction cannot be added to another
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Prevents players who are already claimed in another team's pending
transaction (frozen=false, cancelled=false) from being added to a new
transaction for the same week.
Changes:
- Add is_player_in_pending_transaction() to TransactionService
- Make TransactionBuilder.add_move() async with validation
- Add check_pending_transactions flag (default True for /dropadd)
- Skip validation for /ilmove and trades (check_pending_transactions=False)
- Add tests/conftest.py for proper test isolation
- Add 4 new tests for pending transaction validation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Security fix: Remove user_id from ConfirmationView so only the player's
team GM(s) can click "Log Injury" button. Anyone can still run /injury roll
to see the result, but only authorized GMs can record it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Features:
- Post injury announcements to #sba-network-news when injuries are logged
- Update #injury-log channel with two embeds:
- All injuries grouped by Major League team with return dates
- All injuries grouped by return week, sorted ascending
- Auto-purge old messages before posting updated injury log
Bug Fixes:
- Fix BaseView interaction_check logic that incorrectly rejected command users
- Old: Rejected if (not user_id match) OR (not in responders)
- New: Allow if (user_id match) OR (in responders)
- Filter None values from responders list (handles missing gmid2)
Changes:
- services/injury_service.py: Add get_all_active_injuries_raw() method
- utils/injury_log.py: New utility for injury channel posting
- views/modals.py: Call injury posting after successful injury logging
- views/base.py: Fix interaction authorization logic
- config.py: Update to Season 13 Players role
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
IL moves (/ilmove) are intra-team transactions that should always be
visible immediately. Only scheduled transactions (/dropadd) need to
remain hidden during the freeze period.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>