# ♟️ chessCalc — калькулятор швейцарской жеребьёвки Расчёт пар следующего тура шахматного турнира по швейцарской системе. Принимает ссылку на турнир chess-results.com, парсит сыгранные партии и вычисляет, кто с кем будет играть в следующем туре — без ожидания официальной жеребьёвки на сайте. Вывод адаптирован для Telegram: нумерованный список, 🏳️ (белые) / 🏁 (чёрные). ## Зачем На турнирах пары следующего тура публикуются с задержкой (судьи проверяют результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный расчёт — тренер или родитель видит пары сразу после окончания предыдущего тура, не дожидаясь официальной публикации. Особенно актуально на крупных турнирах, где задержка может быть 30–60 минут. ## Как запустить ```bash cd ~/projects/chessCalc docker compose run --rm chess-calc 'https://chess-results.com/tnr1393124.aspx?lan=11' # Показать конкретного игрока docker compose run --rm chess-calc --player 12 'URL' ``` **Требования:** Docker, сеть без блокировки Docker Hub (для первой сборки). ## Архитектура ``` chessCalc/ ├── Dockerfile # python:3.11-slim + bbpPairings ├── docker-compose.yml ├── bbpPairings.exe # FIDE-движок (C++, статическая сборка) ├── requirements.txt # beautifulsoup4, requests ├── swiss_calc/ │ ├── __main__.py # CLI: приём URL, вызов парсера + движка, вывод │ ├── parser.py # Парсинг chess-results.com │ ├── trf_generator.py # Генерация TRF-файла для bbpPairings │ ├── bbp_wrapper.py # Вызов bbpPairings.exe через subprocess │ ├── swiss.py # Оркестрация расчёта │ └── display.py # Форматирование вывода для Telegram └── test_*.py # Тестовые скрипты (не в образе) ``` ### Поток данных ``` URL турнира │ ▼ ┌──────────────────────────────────────┐ │ parser.fetch_tournament() │ │ │ │ 1. art=4 — положение (имена, очки) │ │ 2. art=5 — стартовый список (SNo) │ │ 3. Сопоставление имён → SNo │ │ 4. art=2&rd=1..N — пары (результаты)│ │ 5. Сборка player.results[] │ └──────────────────────────────────────┘ │ tournament_data ▼ ┌──────────────────────────────────────┐ │ trf_generator.generate_trf() │ │ │ │ Генерация TRF-16 (FIDE C04 Annex 2)│ │ — формат, понятный bbpPairings │ └──────────────────────────────────────┘ │ TRF-строка ▼ ┌──────────────────────────────────────┐ │ bbp_wrapper.call_bbp() │ │ │ │ Запуск bbpPairings.exe │ │ → список пар (w_sno, b_sno) │ │ │ │ fallback: упрощённый Swiss (swiss.py)│ └──────────────────────────────────────┘ │ пары ▼ ┌──────────────────────────────────────┐ │ display.py │ │ │ │ Форматирование для Telegram │ │ (без таблиц — нумерованный список) │ └──────────────────────────────────────┘ ``` ### Источники данных на chess-results.com | Параметр | Страница | Что даёт | |----------|----------|----------| | `art=4` | Положение | Имена, очки, доп. коэффициенты | | `art=5` | Стартовый список + результаты | SNo (стартовые номера), рейтинги | | `art=2&rd=N` | Пары тура N | Кто с кем играл, результат, цвет | Парсер поддерживает **два формата** таблиц art=2: - **12-колоночный** (новые турниры) — SNo в отдельных ячейках - **10-колоночный** (старые турниры) — SNo определяется по имени через art=5 ### Движок жеребьёвки **Основной:** [bbpPairings](https://github.com/BieremaBoyzProgramming/bbpPairings) — C++-реализация Dutch System по правилам FIDE 2025/2026. Собран статически (2.4 MB, без зависимостей от glibc). **Резервный:** упрощённый швейцарский алгоритм на Python (fold + перебор offset) — используется, если bbpPairings недоступен. ### Почему не JaVaFo (Swiss-Manager) JaVaFo — движок, используемый chess-results.com. Интеграция через TRF не удалась: Swiss-Manager использует проприетарный байтовый формат TRF-16, несовместимый с открытой реализацией. bbpPairings выбран как эталонная FIDE-альтернатива. ## Текущие ограничения ### 1. Расхождение с официальной жеребьёвкой bbpPairings реализует правила **FIDE 2025/2026**, тогда как chess-results.com (Swiss-Manager) использует **FIDE 2023** и ряд проприетарных эвристик. Результат: | Турнир | Совпадений | |--------|-----------| | Первенство России (2026, 93 уч.) | ~24% | | Первенство Москвы (2025, 49 уч.) | ~1–4% | Обе жеребьёвки **корректны** по своим редакциям правил. Пары валидны: нет повторов, самоматчей, нарушений цветового баланса. Доска 1 совпадает практически всегда. ### 2. Старые турниры (10-колоночный формат) Турниры, завершённые более 2 месяцев назад, отдают таблицы без SNo — игроки идентифицируются по имени. Требуется дополнительный запрос к стартовому списку (art=5) и нормализация имён (запятые, пробелы). ### 3. Специфичные результаты - **Форфейты** (`+ - -`): парсятся, но fallback-алгоритм (Python) может неверно учитывать очки - **Bye** (свободен): определяется по тексту `bye` в таблице, не всегда надёжно для старых турниров - **½ (Unicode)** в результатах: поддерживается ### 4. Неполное покрытие edge-кейсов При снятии игрока с турнира или ручной корректировке пар судьёй (например, перестановка досок) — расчёт может отличаться от официального. ### 5. Производительность 4 последовательных HTTP-запроса к chess-results.com + запуск bbpPairings. Полный цикл: ~5–7 секунд. Для турниров с >200 участниками может потребоваться больше. ## Технический долг - [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата - [ ] Кэширование спарсенных данных (одинаковые запросы при повторных запусках) - [ ] Поддержка круговых турниров (сейчас только швейцарка) - [ ] Вывод в JSON для интеграции с другими сервисами - [ ] Расчёт бухгольца и других коэффициентов из сырых данных - [ ] Web-интерфейс или Telegram-бот вместо CLI