Переписан README под текущую реализацию
- Основной интерфейс: Telegram-боты (клиентский + админский) - CLI оставлен для отладки - Архитектура дополнена bots/ и docker-compose сервисами - Точность: 4 из 5 турниров 100%, один 85% (ручные bye) - Актуализированы ограничения и техдолг
This commit is contained in:
parent
755290e43b
commit
d21f4d696d
1 changed files with 114 additions and 119 deletions
233
README.md
233
README.md
|
|
@ -1,170 +1,165 @@
|
||||||
# ♟️ chessCalc — калькулятор швейцарской жеребьёвки
|
# ♟️ chessCalc — калькулятор швейцарской жеребьёвки
|
||||||
|
|
||||||
Расчёт пар следующего тура шахматного турнира по швейцарской системе.
|
Расчёт пар следующего тура шахматного турнира **до официальной публикации**
|
||||||
Принимает ссылку на турнир chess-results.com, парсит сыгранные партии и
|
на chess-results.com. Два Telegram-бота: клиентский принимает ссылку и
|
||||||
вычисляет, кто с кем будет играть в следующем туре — без ожидания
|
выдаёт пары в таблице, админский показывает статистику использования.
|
||||||
официальной жеребьёвки на сайте.
|
|
||||||
|
|
||||||
Вывод адаптирован для Telegram: нумерованный список,
|
|
||||||
🏳️ (белые) / 🏁 (чёрные).
|
|
||||||
|
|
||||||
## Зачем
|
## Зачем
|
||||||
|
|
||||||
На турнирах пары следующего тура публикуются с задержкой (судьи проверяют
|
На турнирах пары следующего тура публикуются с задержкой (судьи проверяют
|
||||||
результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный
|
результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный
|
||||||
расчёт — тренер или родитель видит пары сразу после окончания предыдущего
|
расчёт — тренер или родитель видит пары сразу после окончания предыдущего
|
||||||
тура, не дожидаясь официальной публикации. Особенно актуально на крупных
|
тура, не дожидаясь официальной публикации (30–60 минут).
|
||||||
турнирах, где задержка может быть 30–60 минут.
|
|
||||||
|
|
||||||
## Как запустить
|
## Как запустить
|
||||||
|
|
||||||
```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 уч.) | ~1–4% |
|
|
||||||
|
|
||||||
Обе жеребьёвки **корректны** по своим редакциям правил. Пары валидны:
|
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.
|
|
||||||
Полный цикл: ~5–7 секунд. Для турниров с >200 участниками может
|
|
||||||
потребоваться больше.
|
|
||||||
|
|
||||||
## Технический долг
|
## Технический долг
|
||||||
|
|
||||||
|
- [ ] Поддержка круговых турниров
|
||||||
|
- [ ] Кэширование спарсенных данных
|
||||||
|
- [ ] Расчёт бухгольца и доп. коэффициентов из сырых данных
|
||||||
- [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата
|
- [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата
|
||||||
- [ ] Кэширование спарсенных данных (одинаковые запросы при повторных запусках)
|
|
||||||
- [ ] Поддержка круговых турниров (сейчас только швейцарка)
|
|
||||||
- [ ] Вывод в JSON для интеграции с другими сервисами
|
|
||||||
- [ ] Расчёт бухгольца и других коэффициентов из сырых данных
|
|
||||||
- [ ] Web-интерфейс или Telegram-бот вместо CLI
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue