# ♟️ chessCalc — калькулятор швейцарской жеребьёвки Расчёт пар следующего тура шахматного турнира **до официальной публикации** на chess-results.com. Два Telegram-бота: клиентский принимает ссылку и выдаёт пары в таблице, админский показывает статистику использования. ## Зачем На турнирах пары следующего тура публикуются с задержкой (судьи проверяют результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный расчёт — тренер или родитель видит пары сразу после окончания предыдущего тура, не дожидаясь официальной публикации (30–60 минут). ## Как запустить ### Telegram-боты (основной способ) ```bash cd chessCalc # 1. Создать .env с токенами (см. .env.example) cp .env.example .env # Поправить токены в .env # 2. Запустить обоих ботов docker compose up -d ``` **Клиентский бот** — скинуть ссылку на турнир 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/ ├── Dockerfile # python:3.11-slim + bbpPairings ├── docker-compose.yml # client-bot + admin-bot + volume bot-data ├── .env.example # шаблон токенов (коммитится) ├── .env # реальные токены (gitignored) ├── bbpPairings.exe # FIDE-движок (C++, статическая сборка) ├── requirements.txt # requests, bs4, python-telegram-bot, rich ├── swiss_calc/ # Ядро — парсинг + расчёт │ ├── __main__.py # CLI entry point │ ├── parser.py # Парсинг chess-results.com (art=2/4/5) │ ├── trf_generator.py # Генерация TRF-16 для bbpPairings │ ├── bbp_wrapper.py # Вызов bbpPairings.exe через subprocess │ ├── swiss.py # Оркестрация + fallback Swiss-алгоритм │ └── display.py # Форматирование вывода (CLI) ├── bots/ # Telegram-боты │ ├── client_bot.py # Клиентский бот (ссылка → пары) │ ├── admin_bot.py # Админский бот (/stat) │ └── stats.py # Статистика (SQLite) └── test_*.py # Тестовые скрипты ``` ### Поток данных ``` Telegram-бот CLI (отладка) │ │ │ URL турнира │ URL турнира ▼ ▼ ┌─────────────────────────────────────────────────────┐ │ parser.fetch_tournament() │ │ │ │ 1. art=4 — положение (имена, очки, тайбрейки) │ │ 2. art=5 — стартовый список (SNo, рейтинги, FED) │ │ 3. Сопоставление имён → SNo (fuzzy-мэтчинг) │ │ 4. art=2&rd=1..N — пары/результаты каждого тура │ │ 5. Сборка player.results[] из art=2 + forfeit/bye │ └─────────────────────────────────────────────────────┘ │ tournament_data ▼ ┌─────────────────────────────────────────────────────┐ │ swiss.calculate_next_round() │ │ │ │ 1. Генерация TRF-16 (trf_generator) │ │ 2. bbpPairings --dutch (FIDE 2025 Dutch System) │ │ → список пар (w_sno, b_sno) │ │ │ │ fallback: упрощённый Swiss (swiss.py) │ │ — если bbpPairings недоступен │ └─────────────────────────────────────────────────────┘ │ пары ▼ ┌─────────────────────────────────────────────────────┐ │ Форматирование │ │ │ │ Telegram: MarkdownV2 + таблица в code-блоке │ │ CLI: нумерованный список │ └─────────────────────────────────────────────────────┘ ``` ### Источники данных на 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) — bye-трекинг, абсолютные цвета, downfloat, форс-сведение. ## Точность Проверено пошагово на 5 турнирах Первенства России (2026). Для каждого тура 2..N: расчёт сравнивался с реальной жеребьёвкой с сайта. | Турнир | Уч. | Формат | Точность | |--------|-----|--------|----------| | 1393121 | 39 | 12-кол. | **100%** | | 1393124 | 93 | 12-кол. | **100%** | | 1393133 | 96 | 12-кол. | **100%** | | 1393137 | 14 | 12-кол. | **100%** | | 1393131 | 112 | 12-кол. | **85%** | **1393131 (85%)** — расхождения только в нижних досках. Причина: 4 ручных bye/forfeit-а (игрок #8 снялся после 1-го тура, ещё трое получили bye). Алгоритм не может предсказать снятие игрока — это решение арбитра. На идущих турнирах без снятий — **100% совпадение**. ## Ограничения 1. **Ручные bye и forfeit-ы.** Если игрок снимается или получает bye по решению арбитра (не по алгоритму) — жеребьёвка сдвигается. Предсказать это невозможно. 2. **10-колоночный формат.** Старые турниры отдают таблицы без SNo — игроки идентифицируются по имени. Точность ~80% из-за fuzzy-мэтчинга. 3. **Круговые турниры.** Не поддерживаются — только швейцарская система. 4. **Ручные корректировки судьи.** Перестановка досок, ручная цветовая коррекция — алгоритм их не учитывает. ## Технический долг - [ ] Поддержка круговых турниров - [ ] Кэширование спарсенных данных - [ ] Расчёт бухгольца и доп. коэффициентов из сырых данных - [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата