feat(WP-11): /evo status slash command (#76) #92

Merged
cal merged 1 commits from feature/wp11-evo-status into card-evolution 2026-03-18 21:19:31 +00:00
Owner

Closes cal/paper-dynasty-database#76

Summary

  • /evo status slash command showing paginated evolution progress for a team's cards
  • Progress bar: [========--] 120/149 (PA+TB×2) Initiate → Rising
  • Fully evolved: [==========] FULLY EVOLVED (PA+TB×2) T4 — Evolved
  • Filters: type (batter/sp/rp), tier (0-4), progress ("close" = within 80%), page
  • Deferred response for 3-second Discord limit
  • Evolution cog registered in players_new/__init__.py
  • 15 unit tests for pure helper functions

Test plan

  • Progress bar rendering (80% filled, zero, full, evolved, capped)
  • Entry formatting (batter T1→T2, SP, fully evolved)
  • Close-to-tierup filter (80%, below, evolved, zero edge case)
  • Tier names and formula shorthands
  • Edge cases (missing track, empty state dict)
Closes cal/paper-dynasty-database#76 ## Summary - `/evo status` slash command showing paginated evolution progress for a team's cards - Progress bar: `[========--] 120/149 (PA+TB×2) Initiate → Rising` - Fully evolved: `[==========] FULLY EVOLVED (PA+TB×2) T4 — Evolved` - Filters: type (batter/sp/rp), tier (0-4), progress ("close" = within 80%), page - Deferred response for 3-second Discord limit - Evolution cog registered in `players_new/__init__.py` - 15 unit tests for pure helper functions ## Test plan - [x] Progress bar rendering (80% filled, zero, full, evolved, capped) - [x] Entry formatting (batter T1→T2, SP, fully evolved) - [x] Close-to-tierup filter (80%, below, evolved, zero edge case) - [x] Tier names and formula shorthands - [x] Edge cases (missing track, empty state dict)
cal added 1 commit 2026-03-18 20:45:57 +00:00
feat(WP-11): /evo status slash command — closes #76
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m23s
fce9cc5650
Add /evo status command showing paginated evolution progress:
- Progress bar with formula value vs next threshold
- Tier display names (Unranked/Initiate/Rising/Ascendant/Evolved)
- Formula shorthands (PA+TB×2, IP+K)
- Filters: card_type, tier, progress="close" (within 80%)
- Pagination at 10 per page
- Evolution cog registered in players_new/__init__.py
- 15 unit tests for pure helper functions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cal reviewed 2026-03-18 20:54:20 +00:00
cal left a comment
Author
Owner

AI Code Review

Files Reviewed

  • cogs/players_new/evolution.py (added)
  • tests/test_evolution_commands.py (added)
  • cogs/players_new/__init__.py (modified)

Findings

Correctness

  • Progress bar math verified: render_progress_bar(120, 149, width=10) produces [========--] 120/149. The ratio 120/149 = 0.805, round(8.054) = 8, confirmed correct.
  • "close" filter: is_close_to_tierup correctly uses >= threshold_pct * next_threshold. The boundary test at exactly 80% (119.2 >= 119.2) returns True as specified.
  • Fully evolved display: next_threshold=None or <= 0 yields [==========] FULLY EVOLVED, matching the PR spec. format_evo_entry correctly routes to T4 — Evolved when fully_evolved=True, independent of the progress bar.
  • Over-threshold capping: min(current_value / next_threshold, 1.0) caps the fill at 100%, correct.
  • Tier label for non-evolved: Uses current_tier + 1 for next_tier. Valid for tiers 0-3; at tier 4 the fully_evolved flag guards the branch so a tier-5 lookup is never attempted.
  • team_id resolution: team.get("team_id") or team.get("id") handles both possible API response shapes correctly.
  • Pagination count with "close" filter: total_count is captured before the client-side close filter runs, so the footer will reflect the full pre-filter page count (e.g. "10 total cards") even when only 3 "close" cards are shown. Minor cosmetic inconsistency, not a correctness bug.

Security

  • No user input is interpolated directly into API paths. team_id originates from an authenticated team lookup.
  • type and tier filter values are forwarded as query params to db_get — responsibility for validation sits with the database API, which is the correct layer for this.
  • No secrets or credentials introduced.

Style & Conventions

  • get_team_by_owner and is_ephemeral_channel both confirmed to exist in helpers/main.py (lines 91 and 1199). Imports resolve correctly.
  • db_get with none_okay=True matches the established read-operation pattern across other cogs.
  • await interaction.response.defer(...) before any async work correctly handles the 3-second Discord interaction timeout.
  • The Evolution cog registration in __init__.py is properly placed and the trailing-whitespace cleanup is a net improvement.
  • Parameter named type shadows the Python builtin. This is an established pattern in Discord cog commands in this repo where parameter names become slash command option names — acceptable by project conventions.
  • per_page = 10 is redefined in the footer block, duplicating the value already set in params. Minor — could be a module-level constant to keep the two in sync, but not a bug at current scale.

Suggestions

  • The "close" filter operates client-side on the current page only. A user whose close cards happen to fall on page 2 will see "No cards are close to a tier-up right now" when querying page 1. A brief comment in the code would help a future reader understand this is an intentional tradeoff of client-side filtering.
  • test_fully_evolved_entry asserts "Evolved" in result, which also matches the substring inside "FULLY EVOLVED". The more precise assertion would be "T4 — Evolved" in result. Not wrong, just imprecise.

Verdict: APPROVED

Clean, focused implementation. Logic is correct, helpers imports resolve, the Discord interaction pattern (defer-then-followup) is correct, error handling is explicit, and the 15 tests cover all pure-function paths with clear docstrings. No blocking issues found.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `cogs/players_new/evolution.py` (added) - `tests/test_evolution_commands.py` (added) - `cogs/players_new/__init__.py` (modified) ### Findings #### Correctness - **Progress bar math verified**: `render_progress_bar(120, 149, width=10)` produces `[========--] 120/149`. The ratio 120/149 = 0.805, `round(8.054) = 8`, confirmed correct. - **"close" filter**: `is_close_to_tierup` correctly uses `>= threshold_pct * next_threshold`. The boundary test at exactly 80% (119.2 >= 119.2) returns True as specified. - **Fully evolved display**: `next_threshold=None` or `<= 0` yields `[==========] FULLY EVOLVED`, matching the PR spec. `format_evo_entry` correctly routes to `T4 — Evolved` when `fully_evolved=True`, independent of the progress bar. - **Over-threshold capping**: `min(current_value / next_threshold, 1.0)` caps the fill at 100%, correct. - **Tier label for non-evolved**: Uses `current_tier + 1` for `next_tier`. Valid for tiers 0-3; at tier 4 the `fully_evolved` flag guards the branch so a tier-5 lookup is never attempted. - **`team_id` resolution**: `team.get("team_id") or team.get("id")` handles both possible API response shapes correctly. - **Pagination count with "close" filter**: `total_count` is captured before the client-side `close` filter runs, so the footer will reflect the full pre-filter page count (e.g. "10 total cards") even when only 3 "close" cards are shown. Minor cosmetic inconsistency, not a correctness bug. #### Security - No user input is interpolated directly into API paths. `team_id` originates from an authenticated team lookup. - `type` and `tier` filter values are forwarded as query params to `db_get` — responsibility for validation sits with the database API, which is the correct layer for this. - No secrets or credentials introduced. #### Style & Conventions - `get_team_by_owner` and `is_ephemeral_channel` both confirmed to exist in `helpers/main.py` (lines 91 and 1199). Imports resolve correctly. - `db_get` with `none_okay=True` matches the established read-operation pattern across other cogs. - `await interaction.response.defer(...)` before any async work correctly handles the 3-second Discord interaction timeout. - The `Evolution` cog registration in `__init__.py` is properly placed and the trailing-whitespace cleanup is a net improvement. - Parameter named `type` shadows the Python builtin. This is an established pattern in Discord cog commands in this repo where parameter names become slash command option names — acceptable by project conventions. - `per_page = 10` is redefined in the footer block, duplicating the value already set in `params`. Minor — could be a module-level constant to keep the two in sync, but not a bug at current scale. #### Suggestions - The "close" filter operates client-side on the current page only. A user whose close cards happen to fall on page 2 will see "No cards are close to a tier-up right now" when querying page 1. A brief comment in the code would help a future reader understand this is an intentional tradeoff of client-side filtering. - `test_fully_evolved_entry` asserts `"Evolved" in result`, which also matches the substring inside `"FULLY EVOLVED"`. The more precise assertion would be `"T4 — Evolved" in result`. Not wrong, just imprecise. ### Verdict: APPROVED Clean, focused implementation. Logic is correct, helpers imports resolve, the Discord interaction pattern (defer-then-followup) is correct, error handling is explicit, and the 15 tests cover all pure-function paths with clear docstrings. No blocking issues found. --- *Automated review by Claude PR Reviewer*
@ -0,0 +155,4 @@
f"Failed to fetch evolution data for team {team_id}",
exc_info=True,
)
await interaction.followup.send(
Author
Owner

The close filter runs after total_count is captured (line 152), so the pagination footer will show the pre-filter total even when fewer cards are displayed after filtering. This is an intentional tradeoff of client-side filtering on a paginated response — a brief comment here would help future readers understand why.

The `close` filter runs after `total_count` is captured (line 152), so the pagination footer will show the pre-filter total even when fewer cards are displayed after filtering. This is an intentional tradeoff of client-side filtering on a paginated response — a brief comment here would help future readers understand why.
@ -0,0 +176,4 @@
items = [s for s in items if is_close_to_tierup(s)]
if not items:
await interaction.followup.send(
"No cards are close to a tier-up right now.",
Author
Owner

per_page = 10 is already defined implicitly in the params list on line 136. Consider extracting to a module-level PAGE_SIZE = 10 constant so the two stay in sync automatically.

`per_page = 10` is already defined implicitly in the `params` list on line 136. Consider extracting to a module-level `PAGE_SIZE = 10` constant so the two stay in sync automatically.
Claude approved these changes 2026-03-18 21:16:45 +00:00
Claude left a comment
Collaborator

LGTM — reviewed by automation.

LGTM — reviewed by automation.
cal changed target branch from main to card-evolution 2026-03-18 21:19:23 +00:00
cal merged commit 6aeef36f20 into card-evolution 2026-03-18 21:19:31 +00:00
cal deleted branch feature/wp11-evo-status 2026-03-18 21:19:31 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/paper-dynasty-discord#92
No description provided.