Commit Graph

356 Commits

Author SHA1 Message Date
Cal Corum
6016afb999 feat: enforce FA lock deadline — block signing FA players after week 14
The fa_lock_week config existed but was never enforced. Now /dropadd blocks
adding players FROM Free Agency when current_week >= fa_lock_week (14).
Dropping players TO FA remains allowed after the deadline.

Also consolidates two league_service.get_current_state() calls into one
shared fetch at the top of add_move() to eliminate a redundant API round-trip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:07:28 -05:00
cal
f95c857363 Merge pull request 'perf: eliminate redundant API calls in trade views (#94)' (#116) from ai/major-domo-v2#94 into main
Reviewed-on: #116
2026-03-31 19:54:18 +00:00
Cal Corum
174ce4474d fix: use per-user dict for _checked_teams to prevent race condition (#116)
Replace single `_checked_team: Optional[Team]` with `_checked_teams: dict[int, Team]`
keyed by `interaction.user.id`. The previous single field was shared across all
concurrent interaction tasks, allowing User B's interaction_check to overwrite
`_checked_team` before User A's button handler read it — crediting the wrong team.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:46:42 +00:00
Cal Corum
2091302b8a perf: eliminate redundant API calls in trade views (#94)
Closes #94

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:46:42 +00:00
cal
27a272b813 Merge pull request 'perf: eliminate redundant GET after create/update and parallelize stats (#95)' (#112) from ai/major-domo-v2#95 into main
Reviewed-on: #112
2026-03-31 19:46:19 +00:00
Cal Corum
95010bfd5d perf: eliminate redundant GET after create/update and parallelize stats (#95)
- custom_commands_service: return POST response directly from create_command()
  instead of a follow-up GET by_name
- custom_commands_service: return PUT response directly from update_command()
  instead of a follow-up GET by_name
- custom_commands_service: avoid GET after PUT in get_or_create_creator() by
  constructing updated creator from model_copy()
- custom_commands_service: return POST response directly from get_or_create_creator()
  creator creation instead of a follow-up GET
- custom_commands_service: parallelize all 9 sequential API calls in
  get_statistics() with asyncio.gather()
- help_commands_service: return POST response directly from create_help()
  instead of a follow-up GET by_name
- help_commands_service: return PUT response directly from update_help()
  instead of a follow-up GET by_name
- tests: update test_update_help_success to mock PUT returning dict data

Closes #95

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:45:44 +00:00
cal
deb40476a4 Merge pull request 'perf: parallelize N+1 player/creator lookups with asyncio.gather (#89)' (#118) from ai/major-domo-v2-89 into main
Reviewed-on: #118
2026-03-31 19:45:01 +00:00
Cal Corum
65d3099a7c perf: parallelize N+1 player/creator lookups with asyncio.gather (#89)
Closes #89

Replace sequential per-item await loops with asyncio.gather() to fetch
all results in parallel:

- decision_service.find_winning_losing_pitchers: gather wp, lp, sv,
  hold_ids, and bsv_ids (5-10 calls) in a single parallel batch
- custom_commands_service: parallelize get_creator_by_id() in
  get_popular_commands, get_commands_needing_warning, and
  get_commands_eligible_for_deletion using return_exceptions=True to
  preserve the existing BotException-skip / re-raise-other behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 19:42:53 +00:00
cal
8e02889fd4 Merge pull request 'feat: enforce trade deadline in /trade commands' (#121) from feature/trade-deadline-enforcement into main
All checks were successful
Build Docker Image / build (push) Successful in 1m30s
2026-03-30 21:46:18 +00:00
Cal Corum
b872a05397 feat: enforce trade deadline in /trade commands
Add is_past_trade_deadline property to Current model and guard /trade initiate,
submit, and finalize flows. All checks fail-closed (block if API unreachable).
981 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 16:39:04 -05:00
cal
6889499fff Merge pull request 'fix: update chart_service path from data/ to storage/' (#119) from fix/chart-service-storage-path into main
All checks were successful
Build Docker Image / build (push) Successful in 1m46s
Reviewed-on: #119
2026-03-21 02:01:55 +00:00
Cal Corum
3c453c89ce fix: update chart_service path from data/ to storage/
PR #86 moved state files to storage/ but missed chart_service.py,
which still pointed to data/charts.json. The file exists at
/app/storage/charts.json in the container but the code looked
in /app/data/charts.json, causing empty autocomplete results.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:30:41 -05:00
cal
be4213aab6 Merge pull request 'hotfix: make ScorecardTracker methods async to match await callers' (#117) from hotfix/scorecard-tracker-async into main
All checks were successful
Build Docker Image / build (push) Successful in 1m21s
Reviewed-on: #117
2026-03-20 18:41:44 +00:00
Cal Corum
4e75656225 hotfix: make ScorecardTracker methods async to match await callers
PR #106 added await to scorecard_tracker calls but the tracker
methods were still sync, causing TypeError in production:
- /scorebug: "object NoneType can't be used in 'await' expression"
- live_scorebug_tracker: "object list can't be used in 'await' expression"

Also fixes 5 missing awaits in cleanup_service.py and updates tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 13:37:47 -05:00
cal
c30e0ad321 Merge pull request 'ci: add release script for tag-triggered deployments' (#115) from ci/add-release-script into main
All checks were successful
Build Docker Image / build (push) Successful in 1m20s
Reviewed-on: #115
2026-03-20 18:27:05 +00:00
Cal Corum
b57f91833b ci: add release script for tag-triggered deployments
Auto-generates next CalVer tag (YYYY.M.BUILD) or accepts explicit
version. Shows commits since last tag, confirms, then pushes tag
to trigger CI build.

Usage:
  .scripts/release.sh           # auto-generate next version
  .scripts/release.sh 2026.3.11 # explicit version
  .scripts/release.sh -y        # skip confirmation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 13:26:25 -05:00
cal
04efc46382 Merge pull request 'fix: update deploy script for tag-triggered releases' (#114) from fix/deploy-script-update into main
Reviewed-on: #114
2026-03-20 18:25:00 +00:00
Cal Corum
7e7aa46a73 fix: update deploy script for tag-triggered releases
- Use SSH alias (ssh akamai) instead of manual ssh -i command
- Change image tag from :latest to :production
- Fix rollback command to use SSH alias

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 13:24:29 -05:00
cal
91b367af93 Merge pull request 'ci: switch to tag-triggered releases with production Docker tag' (#113) from ci/tag-triggered-releases into main
All checks were successful
Build Docker Image / build (push) Successful in 1m21s
Reviewed-on: #113
2026-03-20 18:20:38 +00:00
Cal Corum
3c24e03a0c ci: switch to tag-triggered releases with production Docker tag
Replace branch-push trigger with tag-push trigger (20* pattern).
Version is extracted from the git tag itself instead of auto-generated.
Docker images are tagged with the CalVer version + floating "production" tag.

To release: git tag YYYY.M.BUILD && git push --tags

Also updates CLAUDE.md to document the new workflow and removes all
next-release branch references (branch retired).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 13:18:49 -05:00
cal
fd24a41422 Merge pull request 'Merge next-release into main' (#111) from next-release into main
All checks were successful
Build Docker Image / build (push) Successful in 1m2s
Reviewed-on: #111
2026-03-20 17:54:28 +00:00
cal
daa3366b60 Merge pull request 'cleanup: remove dead maintenance mode artifacts in bot.py (#104)' (#105) from ai/major-domo-v2-104 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m13s
Reviewed-on: #105
2026-03-20 17:49:40 +00:00
cal
ee2387a385 Merge pull request 'perf: parallelize independent API calls (#90)' (#106) from ai/major-domo-v2-90 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #106
2026-03-20 17:48:27 +00:00
Cal Corum
6f3339a42e perf: parallelize independent API calls (#90)
Closes #90

Replace sequential awaits with asyncio.gather() in all locations identified
in the issue:

- commands/gameplay/scorebug.py: parallel team lookups in publish_scorecard
  and scorebug commands; also fix missing await on async scorecard_tracker calls
- commands/league/submit_scorecard.py: parallel away/home team lookups
- tasks/live_scorebug_tracker.py: parallel team lookups inside per-scorecard
  loop (compounds across multiple active games); fix missing await on
  get_all_scorecards
- commands/injuries/management.py: parallel get_current_state() +
  search_players() in injury_roll, injury_set_new, and injury_clear
- services/trade_builder.py: parallel per-participant roster validation in
  validate_trade()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:48:10 +00:00
cal
498fcdfe51 Merge pull request 'perf: cache inspect.signature() at decoration time (#97)' (#107) from ai/major-domo-v2-97 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m23s
Reviewed-on: #107
2026-03-20 17:45:51 +00:00
Cal Corum
a3e63f730f perf: cache inspect.signature() at decoration time (#97)
Move inspect.signature(func) calls from inside wrapper functions to the
outer decorator function so the introspection cost is paid once at
decoration time instead of on every invocation.

- logged_command: sig, param_names, and exclude_set computed at decoration time;
  wrapper.__signature__ reuses the pre-computed sig
- cached_api_call: sig moved to decorator scope; bound_args still computed
  per-call (requires runtime args)
- cached_single_item: same as cached_api_call

Closes #97

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 17:45:06 +00:00
cal
f0934937cb Merge pull request 'perf: use channel.purge() instead of per-message delete loops (#93)' (#108) from ai/major-domo-v2#93 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m21s
Reviewed-on: #108
2026-03-20 17:44:29 +00:00
cal
4775c175c5 Merge pull request 'ci: only build Docker images on merge to main/next-release' (#110) from ci/remove-pr-trigger into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #110
2026-03-20 17:44:11 +00:00
Cal Corum
ce2c47ca0c ci: remove pull_request trigger from Docker build workflow
The PR trigger caused Docker builds on every push to PR branches,
wasting CI resources. Only build on merge to main/next-release.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:43:38 -05:00
cal
0c041bce99 Merge pull request 'perf: replace json.dumps serialization test with isinstance fast path (#96)' (#109) from ai/major-domo-v2-96 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m13s
Reviewed-on: #109
2026-03-20 17:38:57 +00:00
Cal Corum
70c4555a74 perf: replace json.dumps serialization test with isinstance fast path (#96)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m22s
Closes #96

Replaces the per-field `json.dumps(value)` probe — which fully serialized
and discarded the result just to check serializability — with a type-check
fast path using `isinstance()`. The `_SERIALIZABLE_TYPES` tuple is defined
at module level so it's not recreated on every log call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:32:34 -05:00
Cal Corum
8878ce85f7 perf: use channel.purge() instead of per-message delete loops (#93)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m16s
Closes #93

Replace sequential message.delete() loops with channel.purge() bulk
delete in three locations:
- commands/admin/management.py: admin_clear_scorecards (up to 100 msgs)
- tasks/live_scorebug_tracker.py: _post_scorebugs_to_channel (25 msgs)
- tasks/live_scorebug_tracker.py: _clear_live_scores_channel (25 msgs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 12:02:26 -05:00
Cal Corum
008d6be86c cleanup: remove dead maintenance mode artifacts in bot.py (#104)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m6s
Closes #104

- Remove duplicate `self.maintenance_mode: bool = False` assignment (merge
  artifact from PR #83)
- Delete dead `@self.tree.interaction_check` block in `setup_hook` that
  generated a RuntimeWarning at startup; `MaintenanceAwareTree.interaction_check()`
  already handles this correctly via the `tree_cls=MaintenanceAwareTree` kwarg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:32:52 -05:00
cal
18ab1393c0 Merge pull request 'fix: split read-only data volume to allow state file writes (#85)' (#86) from ai/major-domo-v2-85 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m9s
Reviewed-on: #86
2026-03-20 15:28:13 +00:00
cal
8862850c59 Merge pull request 'perf: cache user team lookup in player_autocomplete, reduce limit to 25' (#100) from ai/major-domo-v2#99 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #100
2026-03-20 15:27:32 +00:00
cal
8d97e1dd17 Merge pull request 'perf: replace Redis KEYS with SCAN for cache invalidation (#98)' (#101) from ai/major-domo-v2-98 into next-release
Some checks failed
Build Docker Image / build (push) Has been cancelled
Reviewed-on: #101
2026-03-20 15:26:42 +00:00
cal
52fa56cb69 Merge pull request 'perf: parallelize schedule_service API calls with asyncio.gather' (#103) from ai/major-domo-v2-88 into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m16s
Reviewed-on: #103
2026-03-20 15:16:40 +00:00
Cal Corum
d4e7246166 cleanup: remove unused weeks_ahead parameter from get_upcoming_games
The parameter was already ignored (body hardcodes range(1, 19)).
Remove from signature and the one caller that passed it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:11:18 -05:00
Cal Corum
0992acf718 refactor: use GameFactory/TeamFactory in schedule service tests
All checks were successful
Build Docker Image / build (pull_request) Successful in 58s
Replace custom _make_game/_make_team helpers with existing test
factories for consistency with the rest of the test suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:05:28 -05:00
Cal Corum
b480120731 perf: parallelize schedule_service week fetches with asyncio.gather (#88)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m20s
Closes #88

Replaced sequential for-loops in get_team_schedule(), get_recent_games(),
and get_upcoming_games() with asyncio.gather() to fire all per-week HTTP
requests concurrently. Also adds import asyncio which was missing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:02:11 -05:00
cal
6d3c7305ce Merge pull request 'perf: replace sequential awaits with asyncio.gather()' (#102) from fix/sequential-awaits into next-release
All checks were successful
Build Docker Image / build (push) Successful in 1m25s
Reviewed-on: #102
2026-03-20 14:22:48 +00:00
Cal Corum
9df8d77fa0 perf: replace sequential awaits with asyncio.gather() for true parallelism
Fixes #87

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:14:14 -05:00
Cal Corum
df9e9bedbe perf: replace Redis KEYS with SCAN in clear_prefix (#98)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m22s
Closes #98

Replace blocking `client.keys(pattern)` with non-blocking
`client.scan_iter(match=pattern)` to avoid full-keyspace scans
that block the Redis server during cache invalidation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:02:22 -05:00
Cal Corum
c8ed4dee38 perf: cache user team lookup in player_autocomplete, reduce limit to 25
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m16s
Closes #99

- Add module-level `_user_team_cache` dict with 60-second TTL so
  `get_user_major_league_team` is called at most once per minute per
  user instead of on every keystroke.
- Reduce `search_players(limit=50)` to `limit=25` to match Discord's
  25-choice display cap and avoid fetching unused results.
- Add `TestGetCachedUserTeam` class covering cache hit, TTL expiry, and
  None caching; add `clear_user_team_cache` autouse fixture to prevent
  test interference via module-level state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 08:34:32 -05:00
Cal Corum
03dd449551 fix: split read-only data volume to allow state file writes (#85)
All checks were successful
Build Docker Image / build (pull_request) Successful in 57s
The data/ volume was mounted :ro to protect Google Sheets credentials,
but this also prevented all state trackers from persisting JSON files
(scorecards, voice channels, trade channels, soak data), causing silent
save failures and stale data accumulating across restarts.

- Mount only the credentials file as :ro (file-level mount)
- Add a separate :rw storage/ volume for runtime state files
- Move all tracker default paths from data/ to storage/
- Add STATE_HOST_PATH env var (defaults to ./storage)
- Update SHEETS_CREDENTIALS_HOST_PATH semantics: now a file path
  (e.g. ./data/major-domo-service-creds.json) instead of a directory
- Add storage/ to .gitignore

Closes #85

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 13:34:43 -05:00
cal
6c49233392 Merge pull request 'fix: replace broken interaction_check decorator with MaintenanceAwareTree subclass' (#83) from fix/maintenance-mode-interaction-check into main
All checks were successful
Build Docker Image / build (push) Successful in 49s
Reviewed-on: #83
2026-03-17 17:34:15 +00:00
Cal Corum
9a4ecda564 Merge origin/main into fix branch to resolve conflict
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m3s
Keep both the type: ignore annotation and the logger.info call
in admin_maintenance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:32:38 -05:00
Cal Corum
d295f27afe fix: replace broken @self.tree.interaction_check with MaintenanceAwareTree subclass
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m11s
The previous code attempted to register a maintenance mode gate via
@self.tree.interaction_check inside setup_hook.  That pattern is invalid
in discord.py — interaction_check is an overridable method on CommandTree,
not a decorator.  The assignment was silently dropped, making maintenance
mode a no-op and producing a RuntimeWarning about an unawaited coroutine.

Changes:
- Add MaintenanceAwareTree(discord.app_commands.CommandTree) that overrides
  interaction_check: blocks non-admins when bot.maintenance_mode is True,
  always passes admins through, no-op when maintenance mode is off
- Pass tree_cls=MaintenanceAwareTree to super().__init__() in SBABot.__init__
- Add self.maintenance_mode: bool = False to SBABot.__init__
- Update /admin-maintenance command to actually toggle bot.maintenance_mode
- Add tests/test_bot_maintenance_tree.py with 8 unit tests covering all
  maintenance mode states, admin pass-through, DM context, and missing attr

Closes #82

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 12:25:01 -05:00
cal
0a2df0f36e Merge pull request 'Merge next-release into main' (#81) from next-release into main
All checks were successful
Build Docker Image / build (push) Successful in 53s
Reviewed-on: #81
2026-03-17 16:44:42 +00:00
Cal Corum
910a27e356 Merge main into next-release
All checks were successful
Build Docker Image / build (push) Successful in 3m26s
Build Docker Image / build (pull_request) Successful in 58s
Resolve conflict in views/trade_embed.py: keep main's hotfix
(emoji-stripped UI, inline validation errors) and apply next-release's
import refactor (lazy imports hoisted to top-level).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 11:28:05 -05:00