Основные изменения: - swiss.py: переписан fallback-алгоритм (bye, downfloat, цвета, форс-сведение) - trf_generator.py: генератор TRF-16 для bbpPairings - bbp_wrapper.py: обёртка subprocess для bbpPairings.exe - parser.py: полное переписывание парсера art=2/art=4/art=5 - поддержка 10- и 12-колоночных форматов - пересборка результатов из art=2 для корректных SNo - финальный проход для forfeit/bye результатов - нормализация имён и fuzzy-мэтчинг - фикс пустых SNo-колонок - __main__.py: починен JSON-вывод, поддержка bye - display.py: отображение bye и источника расчёта Ключевой фикс точности: убран XXC rank из TRF — bbpPairings теперь использует порядок из турнирной таблицы вместо поля Rank. Проверено на 5 турнирах (4 из 5 — 100%, 1 — 85% из-за ручных bye).
90 lines
2.8 KiB
Python
90 lines
2.8 KiB
Python
"""
|
||
Output formatting for Telegram Markdown.
|
||
"""
|
||
|
||
from .swiss import Player
|
||
|
||
|
||
def format_pairings_table(pairings_data: dict, tournament_name: str) -> str:
|
||
"""Format pairings as a Telegram-friendly markdown table.
|
||
|
||
Telegram auto-converts pipe tables to bullet lists, so we produce
|
||
clean formatted output that works well.
|
||
"""
|
||
pairings = pairings_data['pairings']
|
||
rnd = pairings_data['round']
|
||
|
||
lines = []
|
||
lines.append(f"**{tournament_name}**")
|
||
lines.append(f"📋 **Тур {rnd}** — пары")
|
||
lines.append("")
|
||
|
||
board = 1
|
||
for pairing in pairings:
|
||
if len(pairing) == 3:
|
||
p1, p2, color = pairing
|
||
if color == 'bye':
|
||
lines.append(f" {board}. {p1.name} — BYE (свободен)")
|
||
board += 1
|
||
continue
|
||
if color == 'w':
|
||
white, black = p1, p2
|
||
else:
|
||
white, black = p2, p1
|
||
elif len(pairing) == 5:
|
||
# raw tuple: (sno1, name1, color, sno2, name2)
|
||
sno1, name1, color, sno2, name2 = pairing
|
||
if color == 'w':
|
||
lines.append(f" {board}. {name1} 🏳️ — {name2}")
|
||
else:
|
||
lines.append(f" {board}. {name2} 🏳️ — {name1}")
|
||
board += 1
|
||
continue
|
||
else:
|
||
continue
|
||
|
||
w_name = white.name
|
||
b_name = black.name
|
||
w_pts = white.points
|
||
b_pts = black.points
|
||
w_rating = white.rating
|
||
b_rating = black.rating
|
||
|
||
pts_str = f" ({w_pts}/{b_pts})" if w_pts or b_pts else ""
|
||
rating_str = f" ⚡{w_rating}/{b_rating}" if w_rating or b_rating else ""
|
||
|
||
lines.append(f" {board}. {w_name} 🏳️ — {b_name}{pts_str}{rating_str}")
|
||
|
||
board += 1
|
||
|
||
lines.append("")
|
||
lines.append(f"Всего пар: {len(pairings)}")
|
||
if pairings_data.get('source') == 'bbp_pairings_fide_2025':
|
||
lines.append("_рассчитано по FIDE 2025 (bbpPairings)_")
|
||
elif pairings_data.get('source') == 'chess_results_precalculated':
|
||
lines.append("_пары с сайта chess-results.com_")
|
||
|
||
return '\n'.join(lines)
|
||
|
||
|
||
def format_player_standings(tournament_data: dict, top_n: int = 10) -> str:
|
||
"""Format top N players with their results."""
|
||
standings = tournament_data['standings'][:top_n]
|
||
if not standings:
|
||
return "Нет данных"
|
||
|
||
lines = []
|
||
lines.append(f"**{tournament_data['name']}**")
|
||
lines.append(f"📊 Положение после тура {tournament_data['current_round']}")
|
||
lines.append("")
|
||
|
||
for s in standings:
|
||
rank = s['rank']
|
||
name = s['name']
|
||
pts = s['points']
|
||
tb = s.get('tb', [])
|
||
|
||
tb_str = f" (Б: {tb[0]})" if tb else ""
|
||
lines.append(f" {rank}. {name} — **{pts}** очк{tb_str}")
|
||
|
||
return '\n'.join(lines)
|