- Extract canonical FIDE ID from profile page (profile-info-id) - Add tnr_cache table for local tournament index with players - scan_for_player uses cache-only search (instant SQL, 0 HTTP) - warmup_cache builds full index at step 1 (1445000→1434000) - Save/resume warmup progress via tnr_state - Auto-migrate missing columns (start_date/end_date) - Add hourly rescan_new_tournaments job - Fix discover_max_tnr (larger probe steps, lower default) - Use asyncio.to_thread() for warmup (no crutches) |
||
|---|---|---|
| .forgejo/workflows | ||
| bots | ||
| swiss_calc | ||
| .env.example | ||
| .gitignore | ||
| bbpPairings.exe | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| requirements.txt | ||
| test_all_rounds.py | ||
| test_round_by_round.py | ||
♟️ chessCalc — калькулятор швейцарской жеребьёвки
Расчёт пар следующего тура шахматного турнира до официальной публикации на chess-results.com. Два Telegram-бота: клиентский принимает ссылку и выдаёт пары в таблице, админский показывает статистику использования.
Зачем
На турнирах пары следующего тура публикуются с задержкой (судьи проверяют результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный расчёт — тренер или родитель видит пары сразу после окончания предыдущего тура, не дожидаясь официальной публикации (30–60 минут).
Как запустить
Telegram-боты (основной способ)
cd chessCalc
# 1. Создать .env с токенами (см. .env.example)
cp .env.example .env
# Поправить токены в .env
# 2. Запустить обоих ботов
docker compose up -d
Клиентский бот — скинуть ссылку на турнир chess-results.com, получить пары следующего тура в таблице (MarkdownV2).
Админский бот — команда /stat: уникальные пользователи, всего
запросов, запросов за сегодня.
CLI (для отладки)
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 — 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% совпадение.
Ограничения
- Ручные bye и forfeit-ы. Если игрок снимается или получает bye по решению арбитра (не по алгоритму) — жеребьёвка сдвигается. Предсказать это невозможно.
- 10-колоночный формат. Старые турниры отдают таблицы без SNo — игроки идентифицируются по имени. Точность ~80% из-за fuzzy-мэтчинга.
- Круговые турниры. Не поддерживаются — только швейцарская система.
- Ручные корректировки судьи. Перестановка досок, ручная цветовая коррекция — алгоритм их не учитывает.
Технический долг
- Поддержка круговых турниров
- Кэширование спарсенных данных
- Расчёт бухгольца и доп. коэффициентов из сырых данных
- Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата