From 282c651821de929b6f58be5b3a554b7f1061fd6b Mon Sep 17 00:00:00 2001 From: vrubel Date: Sat, 20 Jun 2026 22:14:38 +0000 Subject: [PATCH] docs: comprehensive README with player tracking, cache, and jobs --- README.md | 289 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 93e47ac..56544b7 100644 --- a/README.md +++ b/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 ` | Удалить подписку | +| `/cancel` | Отменить текущую операцию | +| `URL турнира` | Прислать ссылку chess-results.com — получить жеребьёвку следующего тура | + +**Авто-отслеживание:** +- Каждые **5 минут** — проверка новых результатов подписанных игроков +- При завершении тура — расчёт и отправка жеребьёвки следующего тура (±5 пар вокруг игрока, `✅` зелёная галочка) +- Результат каждого тура: `♟ победа/ничья/поражение` + +### Админский бот + +- `/stat` — уникальные пользователи, всего запросов, запросов за сегодня + +--- ## Как запустить -### Telegram-боты (основной способ) - ```bash -cd chessCalc +# 1. Токены +cp .env.example .env # отредактировать токены -# 1. Создать .env с токенами (см. .env.example) -cp .env.example .env -# Поправить токены в .env - -# 2. Запустить обоих ботов +# 2. Запуск docker compose up -d ``` -**Клиентский бот** — скинуть ссылку на турнир chess-results.com, получить -пары следующего тура в таблице (MarkdownV2). - -**Админский бот** — команда `/stat`: уникальные пользователи, всего -запросов, запросов за сегодня. - -### CLI (для отладки) +### CLI (отладка) ```bash +# Пары следующего тура 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 --standings 'URL' ``` +--- + ## Архитектура ``` chessCalc/ ├── Dockerfile # python:3.11-slim + bbpPairings ├── docker-compose.yml # client-bot + admin-bot + volume bot-data -├── .env.example # шаблон токенов (коммитится) +├── .env.example # шаблон токенов ├── .env # реальные токены (gitignored) -├── bbpPairings.exe # FIDE-движок (C++, статическая сборка) +├── bbpPairings.exe # FIDE-движок Dutch System (C++, 2.4 MB) ├── requirements.txt # requests, bs4, python-telegram-bot, rich ├── swiss_calc/ # Ядро — парсинг + расчёт │ ├── __main__.py # CLI entry point @@ -62,104 +74,167 @@ chessCalc/ │ ├── swiss.py # Оркестрация + fallback Swiss-алгоритм │ └── display.py # Форматирование вывода (CLI) ├── bots/ # Telegram-боты -│ ├── client_bot.py # Клиентский бот (ссылка → пары) +│ ├── client_bot.py # Клиентский бот │ ├── admin_bot.py # Админский бот (/stat) +│ ├── tracker.py # Отслеживание игроков, кэш турниров, джобы │ └── stats.py # Статистика (SQLite) -└── test_*.py # Тестовые скрипты +└── TASKS.md # Журнал изменений ``` -### Поток данных +### Поток данных — жеребьёвка ``` -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: нумерованный список │ -└─────────────────────────────────────────────────────┘ +URL турнира + │ + ▼ +┌─────────────────────────────────────────┐ +│ parser.fetch_tournament() │ +│ 1. art=4 — положение │ +│ 2. art=5 — стартовый список │ +│ 3. Сопоставление имён → SNo │ +│ 4. art=2&rd=1..N — пары каждого тура │ +└─────────────────────────────────────────┘ + │ tournament_data + ▼ +┌─────────────────────────────────────────┐ +│ swiss.calculate_next_round() │ +│ 1. Генерация TRF-16 │ +│ 2. bbpPairings --dutch (FIDE 2025) │ +│ └→ fallback: упрощённый Swiss │ +└─────────────────────────────────────────┘ + │ пары (доски) + ▼ +┌─────────────────────────────────────────┐ +│ format_pairings() → _render_chunks() │ +│ MarkdownV2 таблица в code-блоке │ +└─────────────────────────────────────────┘ ``` -### Источники данных на chess-results.com +### Поток данных — подписка на игрока -| Параметр | Страница | Что даёт | -|----------|----------|----------| -| `art=4` | Положение | Имена, очки, тайбрейки | -| `art=5` | Стартовый список | SNo, рейтинги, федерации | -| `art=2&rd=N` | Пары тура N | Кто с кем играл, результат, цвет | +``` +FIDE ID (напр. 55867510) + │ + ▼ +┌─────────────────────────────────────────┐ +│ 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) — -C++-реализация Dutch System по правилам FIDE 2025/2026. Собран статически -(2.4 MB, без зависимостей от glibc). +Таблица SQLite с полным списком игроков каждого турнира: -**Резервный:** упрощённый швейцарский алгоритм на 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). Для каждого -тура 2..N: расчёт сравнивался с реальной жеребьёвкой с сайта. +### Прогрев кэша (`warmup_cache`) -| Турнир | Уч. | Формат | Точность | -|--------|-----|--------|----------| -| 1393121 | 39 | 12-кол. | **100%** | -| 1393124 | 93 | 12-кол. | **100%** | -| 1393133 | 96 | 12-кол. | **100%** | -| 1393137 | 14 | 12-кол. | **100%** | -| 1393131 | 112 | 12-кол. | **85%** | +- При старте: полный скан кластера 1.4M с шагом 1 (~50 минут) +- Сохраняет прогресс в `tnr_state` — при перезапуске продолжает +- После завершения (`warmup_tnr = done`) — пропускает, если кэш > 1000 записей +- Каждые 6 часов — перепроверка кластера -**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 - по решению арбитра (не по алгоритму) — жеребьёвка сдвигается. - Предсказать это невозможно. -2. **10-колоночный формат.** Старые турниры отдают таблицы без SNo — - игроки идентифицируются по имени. Точность ~80% из-за fuzzy-мэтчинга. -3. **Круговые турниры.** Не поддерживаются — только швейцарская система. -4. **Ручные корректировки судьи.** Перестановка досок, ручная цветовая - коррекция — алгоритм их не учитывает. - -## Технический долг - -- [ ] Поддержка круговых турниров -- [ ] Кэширование спарсенных данных -- [ ] Расчёт бухгольца и доп. коэффициентов из сырых данных -- [ ] Замена bbpPairings на JaVaFo при появлении совместимого TRF-формата +1. **Ручные bye/forfeit-ы** — решение арбитра, алгоритм не предсказывает +2. **10-колоночный формат** — старые турниры без SNo, точность ~80% +3. **Круговые турниры** — не поддерживаются +4. **TNR-пространство не сплошное** — кластеры с пропусками, требуется кэш +5. **Латиница/кириллица** — имена на chess-results могут отличаться от FIDE, матчинг по SNo