fix: clean up refractor status display — suffix tags, compact layout, dead code removal
All checks were successful
Ruff Lint / lint (pull_request) Successful in 20s

- Tier labels as suffix tags: **Name** — Base Chrome [T1] (T0 gets no suffix)
- Compact progress line: bar value/threshold (pct) — removed formula and tier arrow
- Fully evolved shows `MAX` instead of FULLY EVOLVED
- Deleted unused FORMULA_LABELS dict
- Added _FULL_BAR constant, moved T0-branch lookups into else
- Fixed mock API shape in test (cards → items)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-26 00:22:35 -05:00
parent 2d7c19814e
commit bbad1daba2
2 changed files with 63 additions and 137 deletions

View File

@ -34,21 +34,17 @@ TIER_NAMES = {
4: "Superfractor", 4: "Superfractor",
} }
FORMULA_LABELS = { # Tier-specific labels for the status display.
"batter": "PA+TB×2",
"sp": "IP+K",
"rp": "IP+K",
}
# Tier-specific labels for the status display. T0 is blank (base cards need no prefix).
TIER_SYMBOLS = { TIER_SYMBOLS = {
0: "", # Base Card — no prefix 0: "Base", # Base Card — used in summary only, not in per-card display
1: "T1", # Base Chrome 1: "T1", # Base Chrome
2: "T2", # Refractor 2: "T2", # Refractor
3: "T3", # Gold Refractor 3: "T3", # Gold Refractor
4: "T4★", # Superfractor — star earned 4: "T4★", # Superfractor
} }
_FULL_BAR = "" * 12
# Embed accent colors per tier (used for single-tier filtered views). # Embed accent colors per tier (used for single-tier filtered views).
TIER_COLORS = { TIER_COLORS = {
0: 0x95A5A6, # slate grey 0: 0x95A5A6, # slate grey
@ -86,40 +82,38 @@ def _pct_label(current: int, threshold: int) -> str:
def format_refractor_entry(card_state: dict) -> str: def format_refractor_entry(card_state: dict) -> str:
""" """
Format a single card state dict as a rich display string. Format a single card state dict as a compact two-line display string.
Output example (in-progress): Output example (base card no suffix):
**Mike Trout** Base Chrome **Mike Trout**
`120/149` 80% · PA+TB×2 · T1 T2 120/149 (80%)
Output example (evolved suffix tag):
**Mike Trout** Base Chrome [T1]
120/149 (80%)
Output example (fully evolved): Output example (fully evolved):
**Barry Bonds** Superfractor **Barry Bonds** Superfractor [T4]
FULLY EVOLVED `MAX`
""" """
player_name = card_state.get("player_name", "Unknown") player_name = card_state.get("player_name", "Unknown")
track = card_state.get("track", {})
card_type = track.get("card_type", "batter")
current_tier = card_state.get("current_tier", 0) current_tier = card_state.get("current_tier", 0)
formula_value = int(card_state.get("current_value", 0)) formula_value = int(card_state.get("current_value", 0))
next_threshold = int(card_state.get("next_threshold") or 0) or None next_threshold = int(card_state.get("next_threshold") or 0) or None
tier_label = TIER_NAMES.get(current_tier, f"T{current_tier}") if current_tier == 0:
formula_label = FORMULA_LABELS.get(card_type, card_type) first_line = f"**{player_name}**"
symbol = TIER_SYMBOLS.get(current_tier, "") else:
prefix = f"{symbol} " if symbol else "" tier_label = TIER_NAMES.get(current_tier, f"T{current_tier}")
symbol = TIER_SYMBOLS.get(current_tier, "")
first_line = f"{prefix}**{player_name}** — {tier_label}" first_line = f"**{player_name}** — {tier_label} [{symbol}]"
if current_tier >= 4 or next_threshold is None: if current_tier >= 4 or next_threshold is None:
bar = render_progress_bar(1, 1) second_line = f"{_FULL_BAR} `MAX`"
second_line = f"{bar} ✧ FULLY EVOLVED ✧"
else: else:
bar = render_progress_bar(formula_value, next_threshold) bar = render_progress_bar(formula_value, next_threshold)
pct = _pct_label(formula_value, next_threshold) pct = _pct_label(formula_value, next_threshold)
second_line = ( second_line = f"{bar} {formula_value}/{next_threshold} ({pct})"
f"{bar} `{formula_value}/{next_threshold}` {pct}"
f" · {formula_label} · T{current_tier} → T{current_tier + 1}"
)
return f"{first_line}\n{second_line}" return f"{first_line}\n{second_line}"
@ -139,8 +133,7 @@ def build_tier_summary(items: list, total_count: int) -> str:
parts = [] parts = []
for t in range(5): for t in range(5):
if counts[t] > 0: if counts[t] > 0:
label = TIER_SYMBOLS[t] or "Base" parts.append(f"{TIER_SYMBOLS[t]}: {counts[t]}")
parts.append(f"{label}: {counts[t]}")
summary = " ".join(parts) if parts else "No cards" summary = " ".join(parts) if parts else "No cards"
return f"{summary}{total_count} total" return f"{summary}{total_count} total"
@ -165,17 +158,11 @@ def build_status_embed(
if tier_filter is not None and tier_filter in TIER_COLORS: if tier_filter is not None and tier_filter in TIER_COLORS:
embed.color = TIER_COLORS[tier_filter] embed.color = TIER_COLORS[tier_filter]
# Header: tier distribution summary
header = build_tier_summary(items, total_count) header = build_tier_summary(items, total_count)
# Card entries
lines = [format_refractor_entry(state) for state in items] lines = [format_refractor_entry(state) for state in items]
body = "\n\n".join(lines) if lines else "*No cards found.*" body = "\n\n".join(lines) if lines else "*No cards found.*"
# Separator between header and cards
embed.description = f"```{header}```\n{body}" embed.description = f"```{header}```\n{body}"
# Page indicator in footer (append to existing footer text)
existing_footer = embed.footer.text or "" existing_footer = embed.footer.text or ""
page_text = f"Page {page}/{total_pages}" page_text = f"Page {page}/{total_pages}"
embed.set_footer( embed.set_footer(

View File

@ -153,37 +153,22 @@ class TestFormatRefractorEntry:
result = format_refractor_entry(batter_state) result = format_refractor_entry(batter_state)
assert "120/149" in result assert "120/149" in result
def test_formula_label_batter(self, batter_state): def test_percentage_in_output(self, batter_state):
"""Batter formula label PA+TB×2 appears in output.""" """Percentage appears in parentheses in output."""
result = format_refractor_entry(batter_state) result = format_refractor_entry(batter_state)
assert "PA+TB×2" in result assert "(80%)" in result or "(81%)" in result
def test_tier_progression_arrow(self, batter_state):
"""T1 → T2 arrow progression appears for non-evolved cards."""
result = format_refractor_entry(batter_state)
assert "T1 → T2" in result
def test_sp_formula_label(self, sp_state):
"""SP formula label IP+K appears for starting pitchers."""
result = format_refractor_entry(sp_state)
assert "IP+K" in result
def test_fully_evolved_no_threshold(self, evolved_state): def test_fully_evolved_no_threshold(self, evolved_state):
"""T4 card with next_threshold=None shows FULLY EVOLVED.""" """T4 card with next_threshold=None shows MAX."""
result = format_refractor_entry(evolved_state) result = format_refractor_entry(evolved_state)
assert "FULLY EVOLVED" in result assert "`MAX`" in result
def test_fully_evolved_by_tier(self, batter_state): def test_fully_evolved_by_tier(self, batter_state):
"""current_tier=4 triggers fully evolved display even with a threshold.""" """current_tier=4 triggers fully evolved display even with a threshold."""
batter_state["current_tier"] = 4 batter_state["current_tier"] = 4
batter_state["next_threshold"] = 200 batter_state["next_threshold"] = 200
result = format_refractor_entry(batter_state) result = format_refractor_entry(batter_state)
assert "FULLY EVOLVED" in result assert "`MAX`" in result
def test_fully_evolved_no_arrow(self, evolved_state):
"""Fully evolved cards don't show a tier arrow."""
result = format_refractor_entry(evolved_state)
assert "" not in result
def test_two_line_output(self, batter_state): def test_two_line_output(self, batter_state):
"""Output always has exactly two lines (name line + bar line).""" """Output always has exactly two lines (name line + bar line)."""
@ -205,7 +190,7 @@ class TestTierSymbols:
def test_t0_symbol(self): def test_t0_symbol(self):
"""T0 label is empty (base cards get no prefix).""" """T0 label is empty (base cards get no prefix)."""
assert TIER_SYMBOLS[0] == "" assert TIER_SYMBOLS[0] == "Base"
def test_t1_symbol(self): def test_t1_symbol(self):
"""T1 label is 'T1'.""" """T1 label is 'T1'."""
@ -223,41 +208,40 @@ class TestTierSymbols:
"""T4 label is 'T4★'.""" """T4 label is 'T4★'."""
assert TIER_SYMBOLS[4] == "T4★" assert TIER_SYMBOLS[4] == "T4★"
def test_format_entry_t1_label_present(self, batter_state): def test_format_entry_t1_suffix_tag(self, batter_state):
"""format_refractor_entry prepends T1 label for T1 cards.""" """T1 cards show [T1] suffix tag after the tier name."""
result = format_refractor_entry(batter_state) result = format_refractor_entry(batter_state)
assert "T1 " in result assert "[T1]" in result
def test_format_entry_t2_label_present(self, sp_state): def test_format_entry_t2_suffix_tag(self, sp_state):
"""format_refractor_entry prepends T2 label for T2 cards.""" """T2 cards show [T2] suffix tag."""
result = format_refractor_entry(sp_state) result = format_refractor_entry(sp_state)
assert "T2 " in result assert "[T2]" in result
def test_format_entry_t4_label_present(self, evolved_state): def test_format_entry_t4_suffix_tag(self, evolved_state):
"""format_refractor_entry prepends T4★ label for T4 cards.""" """T4 cards show [T4★] suffix tag."""
result = format_refractor_entry(evolved_state) result = format_refractor_entry(evolved_state)
assert "T4★" in result assert "[T4★]" in result
def test_format_entry_t0_no_prefix(self): def test_format_entry_t0_name_only(self):
"""T0 cards have no tier prefix — name starts the line.""" """T0 cards show just the bold name, no tier suffix."""
state = { state = {
"player_name": "Rookie Player", "player_name": "Rookie Player",
"track": {"card_type": "batter"},
"current_tier": 0, "current_tier": 0,
"current_value": 10, "current_value": 10,
"next_threshold": 50, "next_threshold": 50,
} }
result = format_refractor_entry(state) result = format_refractor_entry(state)
first_line = result.split("\n")[0] first_line = result.split("\n")[0]
assert first_line.startswith("**Rookie Player**") assert first_line == "**Rookie Player**"
def test_format_entry_label_before_name(self, batter_state): def test_format_entry_tag_after_name(self, batter_state):
"""Label appears before the player name in the first line.""" """Tag appears after the player name in the first line."""
result = format_refractor_entry(batter_state) result = format_refractor_entry(batter_state)
first_line = result.split("\n")[0] first_line = result.split("\n")[0]
label_pos = first_line.find("T1")
name_pos = first_line.find("Mike Trout") name_pos = first_line.find("Mike Trout")
assert label_pos < name_pos tag_pos = first_line.find("[T1]")
assert name_pos < tag_pos
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -589,11 +573,10 @@ class TestFormatRefractorEntryMalformedInput:
lines = result.split("\n") lines = result.split("\n")
assert len(lines) == 2 assert len(lines) == 2
def test_missing_card_type_uses_raw_fallback(self): def test_missing_card_type_does_not_crash(self):
""" """
When card_type is absent from the track, the code defaults to 'batter' When card_type is absent from the track, the code should still
internally (via .get("card_type", "batter")), so "PA+TB×2" should produce a valid two-line output without crashing.
appear as the formula label.
""" """
state = { state = {
"player_name": "Test Player", "player_name": "Test Player",
@ -602,7 +585,7 @@ class TestFormatRefractorEntryMalformedInput:
"next_threshold": 100, "next_threshold": 100,
} }
result = format_refractor_entry(state) result = format_refractor_entry(state)
assert "PA+TB×2" in result assert "50/100" in result
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -670,22 +653,14 @@ class TestRenderProgressBarBoundaryPrecision:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
class TestRPFormulaLabel: class TestCardTypeVariants:
""" """
T3-4: Verify that relief pitchers (card_type="rp") show the "IP+K" formula T3-4/T3-5: Verify that format_refractor_entry produces valid output for
label in format_refractor_entry output. all card types including unknown ones, without crashing.
Why: FORMULA_LABELS maps both "sp" and "rp" to "IP+K". The existing test
suite only verifies "sp" (via the sp_state fixture). Adding "rp" explicitly
prevents a future refactor from accidentally giving RPs a different label
or falling through to the raw card_type fallback.
""" """
def test_rp_formula_label_is_ip_plus_k(self): def test_rp_card_produces_valid_output(self):
""" """Relief pitcher card produces a valid two-line string."""
A card with card_type="rp" must show "IP+K" as the formula label
in its progress line.
"""
rp_state = { rp_state = {
"player_name": "Edwin Diaz", "player_name": "Edwin Diaz",
"track": {"card_type": "rp"}, "track": {"card_type": "rp"},
@ -694,50 +669,11 @@ class TestRPFormulaLabel:
"next_threshold": 60, "next_threshold": 60,
} }
result = format_refractor_entry(rp_state) result = format_refractor_entry(rp_state)
assert "IP+K" in result, ( assert "Edwin Diaz" in result
f"Relief pitcher card should show 'IP+K' formula label, got: {result!r}" assert "45/60" in result
)
# ---------------------------------------------------------------------------
# T3-5: Unknown card_type fallback
# ---------------------------------------------------------------------------
class TestUnknownCardTypeFallback:
"""
T3-5: format_refractor_entry should use the raw card_type string as the
formula label when the type is not in FORMULA_LABELS, rather than crashing.
Why: FORMULA_LABELS only covers "batter", "sp", and "rp". If the API
introduces a new card type (e.g. "util" for utility players) before the
bot is updated, FORMULA_LABELS.get(card_type, card_type) will fall back to
the raw string. This test ensures that fallback path produces readable
output rather than an error, and explicitly documents what to expect.
"""
def test_unknown_card_type_uses_raw_string_as_label(self):
"""
card_type="util" is not in FORMULA_LABELS. The output should include
"util" as the formula label (the raw fallback) and must not raise.
"""
util_state = {
"player_name": "Ben Zobrist",
"track": {"card_type": "util"},
"current_tier": 2,
"current_value": 80,
"next_threshold": 120,
}
result = format_refractor_entry(util_state)
assert "util" in result, (
f"Unknown card_type should appear verbatim as the formula label, got: {result!r}"
)
def test_unknown_card_type_does_not_crash(self): def test_unknown_card_type_does_not_crash(self):
""" """Unknown card_type produces a valid two-line string."""
Any unknown card_type must produce a valid two-line string without
raising an exception.
"""
state = { state = {
"player_name": "Test Player", "player_name": "Test Player",
"track": {"card_type": "dh"}, "track": {"card_type": "dh"},
@ -793,7 +729,10 @@ async def test_refractor_status_empty_roster(mock_bot, mock_interaction):
team = {"id": 1, "sname": "Test"} team = {"id": 1, "sname": "Test"}
with patch("cogs.refractor.get_team_by_owner", new=AsyncMock(return_value=team)): with patch("cogs.refractor.get_team_by_owner", new=AsyncMock(return_value=team)):
with patch("cogs.refractor.db_get", new=AsyncMock(return_value={"cards": []})): with patch(
"cogs.refractor.db_get",
new=AsyncMock(return_value={"items": [], "count": 0}),
):
await cog.refractor_status.callback(cog, mock_interaction) await cog.refractor_status.callback(cog, mock_interaction)
call_kwargs = mock_interaction.edit_original_response.call_args call_kwargs = mock_interaction.edit_original_response.call_args