simplify per-game table: tracked player only, no rating diff column
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 31s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 31s
Reworked the per-game row layout to be relative to the tracked player instead of white/black: the tracked player's name+accuracy+rating are always shown first, the opponent's rating+accuracy (no name) always second, regardless of which color each side played in a given game - this keeps columns aligned across rows even as the tracked player switches sides. Dropped the rating-change-per-game column added earlier; it pushed the line past mobile width limits.
This commit is contained in:
parent
d479e14bf9
commit
26cb066515
3 changed files with 29 additions and 39 deletions
|
|
@ -47,22 +47,14 @@ class StatsFormatter:
|
||||||
tracked_won = (result == "1-0" and tracked_is_white) or (result == "0-1" and not tracked_is_white)
|
tracked_won = (result == "1-0" and tracked_is_white) or (result == "0-1" and not tracked_is_white)
|
||||||
return "🟢" if tracked_won else "🔴"
|
return "🟢" if tracked_won else "🔴"
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _format_rating_diff_paren(diff: Optional[int]) -> str:
|
|
||||||
"""Format the tracked user's rating change for this game, e.g. '(+5)' / '(-7)' / '(0)'"""
|
|
||||||
diff = diff or 0
|
|
||||||
if diff > 0:
|
|
||||||
return f"(+{diff})"
|
|
||||||
elif diff < 0:
|
|
||||||
return f"({diff})"
|
|
||||||
else:
|
|
||||||
return "(0)"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_game_rows_block(rows: list) -> str:
|
def _format_game_rows_block(rows: list) -> str:
|
||||||
"""
|
"""
|
||||||
Format a list of individual game rows as a column-aligned, monospace table:
|
Format a list of individual game rows as a column-aligned, monospace table:
|
||||||
outcome | rating_diff | accuracy1 | White | rating1 | result | Black | rating2 | accuracy2
|
outcome | accuracy(tracked) | name(tracked) | rating(tracked) | result | rating(opponent) | accuracy(opponent)
|
||||||
|
|
||||||
|
Only the tracked player is named — the opponent is shown by rating/accuracy
|
||||||
|
only, keeping the line short enough for mobile screens.
|
||||||
|
|
||||||
Column widths are computed from the actual data so every column lines up
|
Column widths are computed from the actual data so every column lines up
|
||||||
character-for-character. Caller is expected to wrap the result in a
|
character-for-character. Caller is expected to wrap the result in a
|
||||||
|
|
@ -76,29 +68,36 @@ class StatsFormatter:
|
||||||
prepared = []
|
prepared = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
circle = StatsFormatter._format_row_outcome_circle(row)
|
circle = StatsFormatter._format_row_outcome_circle(row)
|
||||||
diff_str = StatsFormatter._format_rating_diff_paren(row.get('tracked_rating_diff'))
|
if row.get('tracked_is_white') is False:
|
||||||
white = (row.get('white_name') or '?')[:NAME_WIDTH]
|
tracked_name = row.get('black_name') or '?'
|
||||||
black = (row.get('black_name') or '?')[:NAME_WIDTH]
|
tracked_rating = row.get('black_rating')
|
||||||
wr = row.get('white_rating')
|
tracked_accuracy = row.get('black_accuracy')
|
||||||
br = row.get('black_rating')
|
opp_rating = row.get('white_rating')
|
||||||
wr_str = str(wr) if wr is not None else "-"
|
opp_accuracy = row.get('white_accuracy')
|
||||||
br_str = str(br) if br is not None else "-"
|
else:
|
||||||
wa = StatsFormatter._format_accuracy(row.get('white_accuracy'))
|
tracked_name = row.get('white_name') or '?'
|
||||||
ba = StatsFormatter._format_accuracy(row.get('black_accuracy'))
|
tracked_rating = row.get('white_rating')
|
||||||
|
tracked_accuracy = row.get('white_accuracy')
|
||||||
|
opp_rating = row.get('black_rating')
|
||||||
|
opp_accuracy = row.get('black_accuracy')
|
||||||
|
tracked_name = tracked_name[:NAME_WIDTH]
|
||||||
|
tr_str = str(tracked_rating) if tracked_rating is not None else "-"
|
||||||
|
or_str = str(opp_rating) if opp_rating is not None else "-"
|
||||||
|
ta = StatsFormatter._format_accuracy(tracked_accuracy)
|
||||||
|
oa = StatsFormatter._format_accuracy(opp_accuracy)
|
||||||
result = StatsFormatter._format_result(row.get('result', ''))
|
result = StatsFormatter._format_result(row.get('result', ''))
|
||||||
prepared.append((circle, diff_str, wa, white, wr_str, result, black, br_str, ba))
|
prepared.append((circle, ta, tracked_name, tr_str, result, or_str, oa))
|
||||||
|
|
||||||
diff_width = max(len(p[1]) for p in prepared)
|
acc_width = max(max(len(p[1]), len(p[6])) for p in prepared)
|
||||||
acc_width = max(max(len(p[2]), len(p[8])) for p in prepared)
|
|
||||||
name_width = NAME_WIDTH
|
name_width = NAME_WIDTH
|
||||||
rating_width = max(max(len(p[4]), len(p[7])) for p in prepared)
|
rating_width = max(max(len(p[3]), len(p[5])) for p in prepared)
|
||||||
result_width = max(len(p[5]) for p in prepared)
|
result_width = max(len(p[4]) for p in prepared)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for circle, diff_str, wa, white, wr_str, result, black, br_str, ba in prepared:
|
for circle, ta, tracked_name, tr_str, result, or_str, oa in prepared:
|
||||||
lines.append(
|
lines.append(
|
||||||
f"{circle} {diff_str:>{diff_width}} {wa:>{acc_width}} {white:<{name_width}} {wr_str:>{rating_width}} "
|
f"{circle} {ta:>{acc_width}} {tracked_name:<{name_width}} {tr_str:>{rating_width}} "
|
||||||
f"{result:^{result_width}} {black:<{name_width}} {br_str:>{rating_width}} {ba:>{acc_width}}"
|
f"{result:^{result_width}} {or_str:>{rating_width}} {oa:>{acc_width}}"
|
||||||
)
|
)
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,6 @@ class GameRow(BaseModel):
|
||||||
result: str = Field(..., description="Результат партии: '1-0', '0-1' или '1/2-1/2'")
|
result: str = Field(..., description="Результат партии: '1-0', '0-1' или '1/2-1/2'")
|
||||||
created_at: int = Field(..., description="Время создания партии (Unix timestamp в миллисекундах)")
|
created_at: int = Field(..., description="Время создания партии (Unix timestamp в миллисекундах)")
|
||||||
tracked_is_white: Optional[bool] = Field(None, description="True, если отслеживаемый пользователь играл белыми; False — чёрными; None — не удалось определить сторону")
|
tracked_is_white: Optional[bool] = Field(None, description="True, если отслеживаемый пользователь играл белыми; False — чёрными; None — не удалось определить сторону")
|
||||||
tracked_rating_diff: Optional[int] = Field(None, description="Изменение рейтинга отслеживаемого пользователя по итогам этой партии")
|
|
||||||
|
|
||||||
class GameStats(BaseModel):
|
class GameStats(BaseModel):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -583,13 +583,6 @@ class StatsService:
|
||||||
else:
|
else:
|
||||||
tracked_is_white = None
|
tracked_is_white = None
|
||||||
|
|
||||||
if tracked_is_white is True:
|
|
||||||
tracked_rating_diff = white.get('ratingDiff')
|
|
||||||
elif tracked_is_white is False:
|
|
||||||
tracked_rating_diff = black.get('ratingDiff')
|
|
||||||
else:
|
|
||||||
tracked_rating_diff = None
|
|
||||||
|
|
||||||
return GameRow(
|
return GameRow(
|
||||||
white_name=white_name,
|
white_name=white_name,
|
||||||
black_name=black_name,
|
black_name=black_name,
|
||||||
|
|
@ -599,8 +592,7 @@ class StatsService:
|
||||||
black_accuracy=(black.get('analysis') or {}).get('accuracy'),
|
black_accuracy=(black.get('analysis') or {}).get('accuracy'),
|
||||||
result=result,
|
result=result,
|
||||||
created_at=game.get('createdAt', 0),
|
created_at=game.get('createdAt', 0),
|
||||||
tracked_is_white=tracked_is_white,
|
tracked_is_white=tracked_is_white
|
||||||
tracked_rating_diff=tracked_rating_diff
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _process_games_of_period(self, games: List[Dict[str, Any]], username: str, include_games: bool = False) -> GamesOfPeriodStats:
|
def _process_games_of_period(self, games: List[Dict[str, Any]], username: str, include_games: bool = False) -> GamesOfPeriodStats:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue