Переписан README под текущую реализацию

- Основной интерфейс: Telegram-боты (клиентский + админский)
- CLI оставлен для отладки
- Архитектура дополнена bots/ и docker-compose сервисами
- Точность: 4 из 5 турниров 100%, один 85% (ручные bye)
- Актуализированы ограничения и техдолг
This commit is contained in:
Roman Vrubel 2026-06-14 21:23:40 +00:00
parent 755290e43b
commit d21f4d696d

233
README.md
View file

@ -1,170 +1,165 @@
# ♟️ chessCalc — калькулятор швейцарской жеребьёвки # ♟️ chessCalc — калькулятор швейцарской жеребьёвки
Расчёт пар следующего тура шахматного турнира по швейцарской системе. Расчёт пар следующего тура шахматного турнира **до официальной публикации**
Принимает ссылку на турнир chess-results.com, парсит сыгранные партии и на chess-results.com. Два Telegram-бота: клиентский принимает ссылку и
вычисляет, кто с кем будет играть в следующем туре — без ожидания выдаёт пары в таблице, админский показывает статистику использования.
официальной жеребьёвки на сайте.
Вывод адаптирован для Telegram: нумерованный список,
🏳️ (белые) / 🏁 (чёрные).
## Зачем ## Зачем
На турнирах пары следующего тура публикуются с задержкой (судьи проверяют На турнирах пары следующего тура публикуются с задержкой (судьи проверяют
результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный
расчёт — тренер или родитель видит пары сразу после окончания предыдущего расчёт — тренер или родитель видит пары сразу после окончания предыдущего
тура, не дожидаясь официальной публикации. Особенно актуально на крупных тура, не дожидаясь официальной публикации (3060 минут).
турнирах, где задержка может быть 3060 минут.
## Как запустить ## Как запустить
```bash ### Telegram-боты (основной способ)
cd ~/projects/chessCalc
docker compose run --rm chess-calc 'https://chess-results.com/tnr1393124.aspx?lan=11'
# Показать конкретного игрока ```bash
docker compose run --rm chess-calc --player 12 'URL' cd chessCalc
# 1. Создать .env с токенами (см. .env.example)
cp .env.example .env
# Поправить токены в .env
# 2. Запустить обоих ботов
docker compose up -d
``` ```
**Требования:** Docker, сеть без блокировки Docker Hub (для первой сборки). **Клиентский бот** — скинуть ссылку на турнир chess-results.com, получить
пары следующего тура в таблице (MarkdownV2).
**Админский бот** — команда `/stat`: уникальные пользователи, всего
запросов, запросов за сегодня.
### CLI (для отладки)
```bash
docker compose run --rm client-bot python3 -m swiss_calc 'URL'
# JSON-вывод
docker compose run --rm client-bot python3 -m swiss_calc --json 'URL'
# Положение после последнего тура
docker compose run --rm client-bot python3 -m swiss_calc --standings 'URL'
```
## Архитектура ## Архитектура
``` ```
chessCalc/ chessCalc/
├── Dockerfile # python:3.11-slim + bbpPairings ├── Dockerfile # python:3.11-slim + bbpPairings
├── docker-compose.yml ├── docker-compose.yml # client-bot + admin-bot + volume bot-data
├── bbpPairings.exe # FIDE-движок (C++, статическая сборка) ├── .env.example # шаблон токенов (коммитится)
├── requirements.txt # beautifulsoup4, requests ├── .env # реальные токены (gitignored)
├── swiss_calc/ ├── bbpPairings.exe # FIDE-движок (C++, статическая сборка)
│ ├── __main__.py # CLI: приём URL, вызов парсера + движка, вывод ├── requirements.txt # requests, bs4, python-telegram-bot, rich
│ ├── parser.py # Парсинг chess-results.com ├── swiss_calc/ # Ядро — парсинг + расчёт
│ ├── trf_generator.py # Генерация TRF-файла для bbpPairings │ ├── __main__.py # CLI entry point
│ ├── bbp_wrapper.py # Вызов bbpPairings.exe через subprocess │ ├── parser.py # Парсинг chess-results.com (art=2/4/5)
│ ├── swiss.py # Оркестрация расчёта │ ├── trf_generator.py # Генерация TRF-16 для bbpPairings
│ └── display.py # Форматирование вывода для Telegram │ ├── bbp_wrapper.py # Вызов bbpPairings.exe через subprocess
└── test_*.py # Тестовые скрипты (не в образе) │ ├── swiss.py # Оркестрация + fallback Swiss-алгоритм
│ └── display.py # Форматирование вывода (CLI)
├── bots/ # Telegram-боты
│ ├── client_bot.py # Клиентский бот (ссылка → пары)
│ ├── admin_bot.py # Админский бот (/stat)
│ └── stats.py # Статистика (SQLite)
└── test_*.py # Тестовые скрипты
``` ```
### Поток данных ### Поток данных
``` ```
URL турнира Telegram-бот CLI (отладка)
│ │
│ URL турнира │ URL турнира
┌──────────────────────────────────────┐ ▼ ▼
│ parser.fetch_tournament() │ ┌─────────────────────────────────────────────────────┐
│ │ │ parser.fetch_tournament() │
│ 1. art=4 — положение (имена, очки) │ │ │
│ 2. art=5 — стартовый список (SNo) │ │ 1. art=4 — положение (имена, очки, тайбрейки) │
│ 3. Сопоставление имён → SNo │ │ 2. art=5 — стартовый список (SNo, рейтинги, FED) │
│ 4. art=2&rd=1..N — пары (результаты)│ │ 3. Сопоставление имён → SNo (fuzzy-мэтчинг) │
│ 5. Сборка player.results[] │ │ 4. art=2&rd=1..N — пары/результаты каждого тура │
└──────────────────────────────────────┘ │ 5. Сборка player.results[] из art=2 + forfeit/bye │
│ tournament_data └─────────────────────────────────────────────────────┘
│ tournament_data
┌──────────────────────────────────────┐
│ trf_generator.generate_trf() │ ┌─────────────────────────────────────────────────────┐
│ │ │ swiss.calculate_next_round() │
│ Генерация TRF-16 (FIDE C04 Annex 2)│ │ │
│ — формат, понятный bbpPairings │ │ 1. Генерация TRF-16 (trf_generator) │
└──────────────────────────────────────┘ │ 2. bbpPairings --dutch (FIDE 2025 Dutch System) │
│ TRF-строка │ → список пар (w_sno, b_sno) │
│ │
┌──────────────────────────────────────┐ │ fallback: упрощённый Swiss (swiss.py) │
│ bbp_wrapper.call_bbp() │ │ — если bbpPairings недоступен │
│ │ └─────────────────────────────────────────────────────┘
│ Запуск bbpPairings.exe │ │ пары
│ → список пар (w_sno, b_sno) │
│ │ ┌─────────────────────────────────────────────────────┐
│ fallback: упрощённый Swiss (swiss.py)│ │ Форматирование │
└──────────────────────────────────────┘ │ │
│ пары │ Telegram: MarkdownV2 + таблица в code-блоке │
│ CLI: нумерованный список │
┌──────────────────────────────────────┐ └─────────────────────────────────────────────────────┘
│ display.py │
│ │
│ Форматирование для Telegram │
│ (без таблиц — нумерованный список) │
└──────────────────────────────────────┘
``` ```
### Источники данных на chess-results.com ### Источники данных на chess-results.com
| Параметр | Страница | Что даёт | | Параметр | Страница | Что даёт |
|----------|----------|----------| |----------|----------|----------|
| `art=4` | Положение | Имена, очки, доп. коэффициенты | | `art=4` | Положение | Имена, очки, тайбрейки |
| `art=5` | Стартовый список + результаты | SNo (стартовые номера), рейтинги | | `art=5` | Стартовый список | SNo, рейтинги, федерации |
| `art=2&rd=N` | Пары тура N | Кто с кем играл, результат, цвет | | `art=2&rd=N` | Пары тура N | Кто с кем играл, результат, цвет |
Парсер поддерживает **два формата** таблиц art=2: Парсер поддерживает два формата таблиц art=2:
- **12-колоночный** (новые турниры) — SNo в отдельных ячейках - **12-колоночный** (новые турниры) — SNo в отдельных ячейках
- **10-колоночный** (старые турниры) — SNo определяется по имени через art=5 - **10-колоночный** (старые турниры) — SNo определяется по имени через art=5
### Движок жеребьёвки ### Движок жеребьёвки
**Основной:** [bbpPairings](https://github.com/BieremaBoyzProgramming/bbpPairings) — **Основной:** [bbpPairings](https://github.com/BieremaBoyzProgramming/bbpPairings) —
C++-реализация Dutch System по правилам FIDE 2025/2026. C++-реализация Dutch System по правилам FIDE 2025/2026. Собран статически
Собран статически (2.4 MB, без зависимостей от glibc). (2.4 MB, без зависимостей от glibc).
**Резервный:** упрощённый швейцарский алгоритм на Python (fold + перебор **Резервный:** упрощённый швейцарский алгоритм на Python (fold + перебор
offset) — используется, если bbpPairings недоступен. offset) — bye-трекинг, абсолютные цвета, downfloat, форс-сведение.
### Почему не JaVaFo (Swiss-Manager) ## Точность
JaVaFo — движок, используемый chess-results.com. Интеграция через TRF Проверено пошагово на 5 турнирах Первенства России (2026). Для каждого
не удалась: Swiss-Manager использует проприетарный байтовый формат TRF-16, тура 2..N: расчёт сравнивался с реальной жеребьёвкой с сайта.
несовместимый с открытой реализацией. bbpPairings выбран как эталонная
FIDE-альтернатива.
## Текущие ограничения | Турнир | Уч. | Формат | Точность |
|--------|-----|--------|----------|
| 1393121 | 39 | 12-кол. | **100%** |
| 1393124 | 93 | 12-кол. | **100%** |
| 1393133 | 96 | 12-кол. | **100%** |
| 1393137 | 14 | 12-кол. | **100%** |
| 1393131 | 112 | 12-кол. | **85%** |
### 1. Расхождение с официальной жеребьёвкой **1393131 (85%)** — расхождения только в нижних досках. Причина: 4 ручных
bye/forfeit-а (игрок #8 снялся после 1-го тура, ещё трое получили bye).
Алгоритм не может предсказать снятие игрока — это решение арбитра.
bbpPairings реализует правила **FIDE 2025/2026**, тогда как На идущих турнирах без снятий — **100% совпадение**.
chess-results.com (Swiss-Manager) использует **FIDE 2023** и ряд
проприетарных эвристик. Результат:
| Турнир | Совпадений | ## Ограничения
|--------|-----------|
| Первенство России (2026, 93 уч.) | ~24% |
| Первенство Москвы (2025, 49 уч.) | ~14% |
Обе жеребьёвки **корректны** по своим редакциям правил. Пары валидны: 1. **Ручные bye и forfeit-ы.** Если игрок снимается или получает bye
нет повторов, самоматчей, нарушений цветового баланса. Доска 1 совпадает по решению арбитра (не по алгоритму) — жеребьёвка сдвигается.
практически всегда. Предсказать это невозможно.
2. **10-колоночный формат.** Старые турниры отдают таблицы без SNo —
### 2. Старые турниры (10-колоночный формат) игроки идентифицируются по имени. Точность ~80% из-за fuzzy-мэтчинга.
3. **Круговые турниры.** Не поддерживаются — только швейцарская система.
Турниры, завершённые более 2 месяцев назад, отдают таблицы без SNo — 4. **Ручные корректировки судьи.** Перестановка досок, ручная цветовая
игроки идентифицируются по имени. Требуется дополнительный запрос к коррекция — алгоритм их не учитывает.
стартовому списку (art=5) и нормализация имён (запятые, пробелы).
### 3. Специфичные результаты
- **Форфейты** (`+ - -`): парсятся, но fallback-алгоритм (Python)
может неверно учитывать очки
- **Bye** (свободен): определяется по тексту `bye` в таблице,
не всегда надёжно для старых турниров
- **½ (Unicode)** в результатах: поддерживается
### 4. Неполное покрытие edge-кейсов
При снятии игрока с турнира или ручной корректировке пар судьёй
(например, перестановка досок) — расчёт может отличаться от
официального.
### 5. Производительность
4 последовательных HTTP-запроса к chess-results.com + запуск bbpPairings.
Полный цикл: ~57 секунд. Для турниров с >200 участниками может
потребоваться больше.
## Технический долг ## Технический долг
- [ ] Поддержка круговых турниров
- [ ] Кэширование спарсенных данных
- [ ] Расчёт бухгольца и доп. коэффициентов из сырых данных
- [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата - [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата
- [ ] Кэширование спарсенных данных (одинаковые запросы при повторных запусках)
- [ ] Поддержка круговых турниров (сейчас только швейцарка)
- [ ] Вывод в JSON для интеграции с другими сервисами
- [ ] Расчёт бухгольца и других коэффициентов из сырых данных
- [ ] Web-интерфейс или Telegram-бот вместо CLI