docs: comprehensive README with player tracking, cache, and jobs
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 3s
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 3s
This commit is contained in:
parent
d18c5016ce
commit
282c651821
1 changed files with 182 additions and 107 deletions
289
README.md
289
README.md
|
|
@ -1,58 +1,70 @@
|
||||||
# ♟️ chessCalc — калькулятор швейцарской жеребьёвки
|
# ♟️ chessCalc — калькулятор швейцарской жеребьёвки + авто-отслеживание игроков
|
||||||
|
|
||||||
Расчёт пар следующего тура шахматного турнира **до официальной публикации**
|
Расчёт пар следующего тура шахматного турнира **до официальной публикации**
|
||||||
на chess-results.com. Два Telegram-бота: клиентский принимает ссылку и
|
на chess-results.com. Плюс подписка на игрока по FIDE ID: бот сам находит
|
||||||
выдаёт пары в таблице, админский показывает статистику использования.
|
турниры, отслеживает результаты и присылает жеребьёвку с подсветкой игрока.
|
||||||
|
|
||||||
## Зачем
|
Два Telegram-бота: клиентский и админский.
|
||||||
|
|
||||||
На турнирах пары следующего тура публикуются с задержкой (судьи проверяют
|
---
|
||||||
результаты, вручную корректируют жеребьёвку). chessCalc даёт мгновенный
|
|
||||||
расчёт — тренер или родитель видит пары сразу после окончания предыдущего
|
## Возможности
|
||||||
тура, не дожидаясь официальной публикации (30–60 минут).
|
|
||||||
|
### Клиентский бот
|
||||||
|
|
||||||
|
| Команда | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `/addplayer` | Подписаться на игрока по FIDE ID — бот сам найдёт турнир на chess-results |
|
||||||
|
| `/myplayers` | Список активных подписок |
|
||||||
|
| `/removeplayer <N>` | Удалить подписку |
|
||||||
|
| `/cancel` | Отменить текущую операцию |
|
||||||
|
| `URL турнира` | Прислать ссылку chess-results.com — получить жеребьёвку следующего тура |
|
||||||
|
|
||||||
|
**Авто-отслеживание:**
|
||||||
|
- Каждые **5 минут** — проверка новых результатов подписанных игроков
|
||||||
|
- При завершении тура — расчёт и отправка жеребьёвки следующего тура (±5 пар вокруг игрока, `✅` зелёная галочка)
|
||||||
|
- Результат каждого тура: `♟ победа/ничья/поражение`
|
||||||
|
|
||||||
|
### Админский бот
|
||||||
|
|
||||||
|
- `/stat` — уникальные пользователи, всего запросов, запросов за сегодня
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Как запустить
|
## Как запустить
|
||||||
|
|
||||||
### Telegram-боты (основной способ)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd chessCalc
|
# 1. Токены
|
||||||
|
cp .env.example .env # отредактировать токены
|
||||||
|
|
||||||
# 1. Создать .env с токенами (см. .env.example)
|
# 2. Запуск
|
||||||
cp .env.example .env
|
|
||||||
# Поправить токены в .env
|
|
||||||
|
|
||||||
# 2. Запустить обоих ботов
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
**Клиентский бот** — скинуть ссылку на турнир chess-results.com, получить
|
### CLI (отладка)
|
||||||
пары следующего тура в таблице (MarkdownV2).
|
|
||||||
|
|
||||||
**Админский бот** — команда `/stat`: уникальные пользователи, всего
|
|
||||||
запросов, запросов за сегодня.
|
|
||||||
|
|
||||||
### CLI (для отладки)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Пары следующего тура
|
||||||
docker compose run --rm client-bot python3 -m swiss_calc 'URL'
|
docker compose run --rm client-bot python3 -m swiss_calc 'URL'
|
||||||
|
|
||||||
# JSON-вывод
|
# JSON
|
||||||
docker compose run --rm client-bot python3 -m swiss_calc --json 'URL'
|
docker compose run --rm client-bot python3 -m swiss_calc --json 'URL'
|
||||||
|
|
||||||
# Положение после последнего тура
|
# Положение
|
||||||
docker compose run --rm client-bot python3 -m swiss_calc --standings '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 # client-bot + admin-bot + volume bot-data
|
├── docker-compose.yml # client-bot + admin-bot + volume bot-data
|
||||||
├── .env.example # шаблон токенов (коммитится)
|
├── .env.example # шаблон токенов
|
||||||
├── .env # реальные токены (gitignored)
|
├── .env # реальные токены (gitignored)
|
||||||
├── bbpPairings.exe # FIDE-движок (C++, статическая сборка)
|
├── bbpPairings.exe # FIDE-движок Dutch System (C++, 2.4 MB)
|
||||||
├── requirements.txt # requests, bs4, python-telegram-bot, rich
|
├── requirements.txt # requests, bs4, python-telegram-bot, rich
|
||||||
├── swiss_calc/ # Ядро — парсинг + расчёт
|
├── swiss_calc/ # Ядро — парсинг + расчёт
|
||||||
│ ├── __main__.py # CLI entry point
|
│ ├── __main__.py # CLI entry point
|
||||||
|
|
@ -62,104 +74,167 @@ chessCalc/
|
||||||
│ ├── swiss.py # Оркестрация + fallback Swiss-алгоритм
|
│ ├── swiss.py # Оркестрация + fallback Swiss-алгоритм
|
||||||
│ └── display.py # Форматирование вывода (CLI)
|
│ └── display.py # Форматирование вывода (CLI)
|
||||||
├── bots/ # Telegram-боты
|
├── bots/ # Telegram-боты
|
||||||
│ ├── client_bot.py # Клиентский бот (ссылка → пары)
|
│ ├── client_bot.py # Клиентский бот
|
||||||
│ ├── admin_bot.py # Админский бот (/stat)
|
│ ├── admin_bot.py # Админский бот (/stat)
|
||||||
|
│ ├── tracker.py # Отслеживание игроков, кэш турниров, джобы
|
||||||
│ └── stats.py # Статистика (SQLite)
|
│ └── stats.py # Статистика (SQLite)
|
||||||
└── test_*.py # Тестовые скрипты
|
└── TASKS.md # Журнал изменений
|
||||||
```
|
```
|
||||||
|
|
||||||
### Поток данных
|
### Поток данных — жеребьёвка
|
||||||
|
|
||||||
```
|
```
|
||||||
Telegram-бот CLI (отладка)
|
URL турнира
|
||||||
│ │
|
│
|
||||||
│ URL турнира │ URL турнира
|
▼
|
||||||
▼ ▼
|
┌─────────────────────────────────────────┐
|
||||||
┌─────────────────────────────────────────────────────┐
|
│ parser.fetch_tournament() │
|
||||||
│ parser.fetch_tournament() │
|
│ 1. art=4 — положение │
|
||||||
│ │
|
│ 2. art=5 — стартовый список │
|
||||||
│ 1. art=4 — положение (имена, очки, тайбрейки) │
|
│ 3. Сопоставление имён → SNo │
|
||||||
│ 2. art=5 — стартовый список (SNo, рейтинги, FED) │
|
│ 4. art=2&rd=1..N — пары каждого тура │
|
||||||
│ 3. Сопоставление имён → SNo (fuzzy-мэтчинг) │
|
└─────────────────────────────────────────┘
|
||||||
│ 4. art=2&rd=1..N — пары/результаты каждого тура │
|
│ tournament_data
|
||||||
│ 5. Сборка player.results[] из art=2 + forfeit/bye │
|
▼
|
||||||
└─────────────────────────────────────────────────────┘
|
┌─────────────────────────────────────────┐
|
||||||
│ tournament_data
|
│ swiss.calculate_next_round() │
|
||||||
▼
|
│ 1. Генерация TRF-16 │
|
||||||
┌─────────────────────────────────────────────────────┐
|
│ 2. bbpPairings --dutch (FIDE 2025) │
|
||||||
│ swiss.calculate_next_round() │
|
│ └→ fallback: упрощённый Swiss │
|
||||||
│ │
|
└─────────────────────────────────────────┘
|
||||||
│ 1. Генерация TRF-16 (trf_generator) │
|
│ пары (доски)
|
||||||
│ 2. bbpPairings --dutch (FIDE 2025 Dutch System) │
|
▼
|
||||||
│ → список пар (w_sno, b_sno) │
|
┌─────────────────────────────────────────┐
|
||||||
│ │
|
│ format_pairings() → _render_chunks() │
|
||||||
│ fallback: упрощённый Swiss (swiss.py) │
|
│ MarkdownV2 таблица в code-блоке │
|
||||||
│ — если bbpPairings недоступен │
|
└─────────────────────────────────────────┘
|
||||||
└─────────────────────────────────────────────────────┘
|
|
||||||
│ пары
|
|
||||||
▼
|
|
||||||
┌─────────────────────────────────────────────────────┐
|
|
||||||
│ Форматирование │
|
|
||||||
│ │
|
|
||||||
│ Telegram: MarkdownV2 + таблица в code-блоке │
|
|
||||||
│ CLI: нумерованный список │
|
|
||||||
└─────────────────────────────────────────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Источники данных на chess-results.com
|
### Поток данных — подписка на игрока
|
||||||
|
|
||||||
| Параметр | Страница | Что даёт |
|
```
|
||||||
|----------|----------|----------|
|
FIDE ID (напр. 55867510)
|
||||||
| `art=4` | Положение | Имена, очки, тайбрейки |
|
│
|
||||||
| `art=5` | Стартовый список | SNo, рейтинги, федерации |
|
▼
|
||||||
| `art=2&rd=N` | Пары тура N | Кто с кем играл, результат, цвет |
|
┌─────────────────────────────────────────┐
|
||||||
|
│ tracker.fetch_fide_player() │
|
||||||
|
│ Парсинг ratings.fide.com/profile/{ID} │
|
||||||
|
│ → имя, рейтинг, FED, каноничный FIDE ID│
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
│ player dict
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ tracker.scan_for_player() │
|
||||||
|
│ SQL-запрос к tnr_cache: │
|
||||||
|
│ SELECT tnr FROM tnr_cache │
|
||||||
|
│ WHERE players_json LIKE '%{fide_id}%' │
|
||||||
|
│ → список TNR с этим игроком │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
│ tournaments list
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Авто-подписка + уведомление │
|
||||||
|
│ → запись в subscriptions │
|
||||||
|
│ → сообщение пользователю │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
Парсер поддерживает два формата таблиц art=2:
|
---
|
||||||
- **12-колоночный** (новые турниры) — SNo в отдельных ячейках
|
|
||||||
- **10-колоночный** (старые турниры) — SNo определяется по имени через art=5
|
|
||||||
|
|
||||||
### Движок жеребьёвки
|
## Локальный кэш турниров (`tnr_cache`)
|
||||||
|
|
||||||
**Основной:** [bbpPairings](https://github.com/BieremaBoyzProgramming/bbpPairings) —
|
Таблица SQLite с полным списком игроков каждого турнира:
|
||||||
C++-реализация Dutch System по правилам FIDE 2025/2026. Собран статически
|
|
||||||
(2.4 MB, без зависимостей от glibc).
|
|
||||||
|
|
||||||
**Резервный:** упрощённый швейцарский алгоритм на Python (fold + перебор
|
```
|
||||||
offset) — bye-трекинг, абсолютные цвета, downfloat, форс-сведение.
|
tnr_cache
|
||||||
|
├── tnr INTEGER PRIMARY KEY — номер турнира на chess-results
|
||||||
|
├── name TEXT — название турнира
|
||||||
|
├── start_date TEXT — дата начала
|
||||||
|
├── end_date TEXT — дата окончания
|
||||||
|
└── players_json TEXT — JSON: {fide_id: {sno, name, rating, fed}}
|
||||||
|
```
|
||||||
|
|
||||||
## Точность
|
- При первом фетче art=0 страницы турнира данные сохраняются в кэш
|
||||||
|
- **1979+ турниров** в кэше (1.4M кластер, 2025-2026)
|
||||||
|
- Поиск игрока — **один SQL LIKE-запрос, ~0.015 секунды**, без HTTP
|
||||||
|
|
||||||
Проверено пошагово на 5 турнирах Первенства России (2026). Для каждого
|
### Прогрев кэша (`warmup_cache`)
|
||||||
тура 2..N: расчёт сравнивался с реальной жеребьёвкой с сайта.
|
|
||||||
|
|
||||||
| Турнир | Уч. | Формат | Точность |
|
- При старте: полный скан кластера 1.4M с шагом 1 (~50 минут)
|
||||||
|--------|-----|--------|----------|
|
- Сохраняет прогресс в `tnr_state` — при перезапуске продолжает
|
||||||
| 1393121 | 39 | 12-кол. | **100%** |
|
- После завершения (`warmup_tnr = done`) — пропускает, если кэш > 1000 записей
|
||||||
| 1393124 | 93 | 12-кол. | **100%** |
|
- Каждые 6 часов — перепроверка кластера
|
||||||
| 1393133 | 96 | 12-кол. | **100%** |
|
|
||||||
| 1393137 | 14 | 12-кол. | **100%** |
|
|
||||||
| 1393131 | 112 | 12-кол. | **85%** |
|
|
||||||
|
|
||||||
**1393131 (85%)** — расхождения только в нижних досках. Причина: 4 ручных
|
---
|
||||||
bye/forfeit-а (игрок #8 снялся после 1-го тура, ещё трое получили bye).
|
|
||||||
Алгоритм не может предсказать снятие игрока — это решение арбитра.
|
|
||||||
|
|
||||||
На идущих турнирах без снятий — **100% совпадение**.
|
## Фоновые задачи
|
||||||
|
|
||||||
|
| Задача | Интервал | Что делает |
|
||||||
|
|--------|----------|------------|
|
||||||
|
| `check_all_subscriptions` | **5 мин** | Проверяет новые результаты, считает и отправляет жеребьёвку |
|
||||||
|
| `rescan_new_tournaments` | **1 час** | Ищет новые TNR > `max_tnr_seen`, авто-подписывает |
|
||||||
|
| `rescan_existing_tournaments` | **2 часа** | Рефетчит art=0 для всех TNR (мимо кэша), находит новых игроков |
|
||||||
|
| `warmup_cache` | **6 часов** | Обновляет tnr_cache (если нужен) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## База данных (SQLite)
|
||||||
|
|
||||||
|
Таблицы в `/app/data/tournaments.db` (Docker volume `bot-data`):
|
||||||
|
|
||||||
|
| Таблица | Назначение |
|
||||||
|
|---------|------------|
|
||||||
|
| `subscriptions` | Активные подписки: user_id, fide_id, player_name, tournament_url, ... |
|
||||||
|
| `tnr_cache` | Кэш турниров: tnr, name, players_json |
|
||||||
|
| `tnr_state` | Состояние: max_tnr_seen, warmup_tnr, warmup progress |
|
||||||
|
| `users` / `requests` | Статистика использования (админский бот) |
|
||||||
|
|
||||||
|
Авто-миграция схемы: недостающие колонки добавляются при каждом `_get_conn()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Источники данных
|
||||||
|
|
||||||
|
| Источник | URL | Что даёт |
|
||||||
|
|----------|-----|----------|
|
||||||
|
| FIDE профиль | `ratings.fide.com/profile/{ID}` | Имя, рейтинг, федерация, каноничный FIDE ID |
|
||||||
|
| chess-results art=0 | `chess-results.com/tnr{N}.aspx?lan=11&art=0` | Список всех игроков турнира с FIDE ID |
|
||||||
|
| chess-results art=4 | `…&art=4` | Положение (имена, очки, тайбрейки) |
|
||||||
|
| chess-results art=5 | `…&art=5` | Стартовый список (SNo, рейтинги) |
|
||||||
|
| chess-results art=2 | `…&art=2&rd=N` | Пары тура N |
|
||||||
|
|
||||||
|
Парсер поддерживает 12-колоночный (новые турниры) и 10-колоночный (старые) форматы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Движок жеребьёвки
|
||||||
|
|
||||||
|
**Основной:** [bbpPairings](https://github.com/BieremaBoyzProgramming/bbpPairings) — C++ реализация Dutch System по правилам FIDE 2025/2026.
|
||||||
|
|
||||||
|
**Резервный:** упрощённый швейцарский алгоритм на Python (fold + перебор offset).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Точность жеребьёвки
|
||||||
|
|
||||||
|
Проверено на турнирах Первенства России (2026):
|
||||||
|
|
||||||
|
| Турнир | Уч. | Точность |
|
||||||
|
|--------|-----|----------|
|
||||||
|
| 1393121 | 39 | **100%** |
|
||||||
|
| 1393124 | 93 | **100%** |
|
||||||
|
| 1393133 | 96 | **100%** |
|
||||||
|
| 1393137 | 14 | **100%** |
|
||||||
|
| 1393131 | 112 | **85%** * |
|
||||||
|
|
||||||
|
\* Расхождения только в нижних досках из-за ручных bye/forfeit-ов.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Ограничения
|
## Ограничения
|
||||||
|
|
||||||
1. **Ручные bye и forfeit-ы.** Если игрок снимается или получает bye
|
1. **Ручные bye/forfeit-ы** — решение арбитра, алгоритм не предсказывает
|
||||||
по решению арбитра (не по алгоритму) — жеребьёвка сдвигается.
|
2. **10-колоночный формат** — старые турниры без SNo, точность ~80%
|
||||||
Предсказать это невозможно.
|
3. **Круговые турниры** — не поддерживаются
|
||||||
2. **10-колоночный формат.** Старые турниры отдают таблицы без SNo —
|
4. **TNR-пространство не сплошное** — кластеры с пропусками, требуется кэш
|
||||||
игроки идентифицируются по имени. Точность ~80% из-за fuzzy-мэтчинга.
|
5. **Латиница/кириллица** — имена на chess-results могут отличаться от FIDE, матчинг по SNo
|
||||||
3. **Круговые турниры.** Не поддерживаются — только швейцарская система.
|
|
||||||
4. **Ручные корректировки судьи.** Перестановка досок, ручная цветовая
|
|
||||||
коррекция — алгоритм их не учитывает.
|
|
||||||
|
|
||||||
## Технический долг
|
|
||||||
|
|
||||||
- [ ] Поддержка круговых турниров
|
|
||||||
- [ ] Кэширование спарсенных данных
|
|
||||||
- [ ] Расчёт бухгольца и доп. коэффициентов из сырых данных
|
|
||||||
- [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue