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

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:
vrubelroman 2026-07-03 10:08:35 +00:00
parent d479e14bf9
commit 26cb066515
3 changed files with 29 additions and 39 deletions

View file

@ -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)

View file

@ -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):
""" """

View file

@ -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: