|
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 7s
- fetch_fide_player: parse rapid and blitz ratings from FIDE profile - subscriptions: add rapid/blitz columns (auto-migration) - /myplayers card: show rapid (📍) and blitz (⚡) ratings - /addplayer confirmation: show rapid/blitz alongside standard rating - find_player_in_cache: extract instant SQL-only search from scan_for_player - /addplayer: use find_player_in_cache instead of full HTTP scan — instant response - tournament names in /myplayers and /addplayer are now hyperlinks - add_player_watch: ON CONFLICT DO UPDATE to reactivate deleted players - my_players: try/except per card with plain-text fallback, error logged to stderr Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .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. Плюс подписка на игрока по FIDE ID: бот сам находит турниры, отслеживает результаты и присылает жеребьёвку с подсветкой игрока.
Два Telegram-бота: клиентский и админский.
Возможности
Клиентский бот
| Команда | Описание |
|---|---|
/addplayer |
Подписаться на игрока по FIDE ID — бот сам найдёт турнир на chess-results |
/myplayers |
Список активных подписок |
/removeplayer <N> |
Удалить подписку |
/cancel |
Отменить текущую операцию |
URL турнира |
Прислать ссылку chess-results.com — получить жеребьёвку следующего тура |
Авто-отслеживание:
- Каждые 5 минут — проверка новых результатов подписанных игроков
- При завершении тура — расчёт и отправка жеребьёвки следующего тура (±5 пар вокруг игрока,
✅зелёная галочка) - Результат каждого тура:
♟ победа/ничья/поражение
Админский бот
/stat— уникальные пользователи, всего запросов, запросов за сегодня
Как запустить
# 1. Токены
cp .env.example .env # отредактировать токены
# 2. Запуск
docker compose up -d
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-движок Dutch System (C++, 2.4 MB)
├── 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)
│ ├── tracker.py # Отслеживание игроков, кэш турниров, джобы
│ └── stats.py # Статистика (SQLite)
└── TASKS.md # Журнал изменений
Поток данных — жеребьёвка
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-блоке │
└─────────────────────────────────────────┘
Поток данных — подписка на игрока
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 │
│ → сообщение пользователю │
└─────────────────────────────────────────┘
Локальный кэш турниров (tnr_cache)
Таблица SQLite с полным списком игроков каждого турнира:
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
Прогрев кэша (warmup_cache)
- При старте: полный скан кластера 1.4M с шагом 1 (~50 минут)
- Сохраняет прогресс в
tnr_state— при перезапуске продолжает - После завершения (
warmup_tnr = done) — пропускает, если кэш > 1000 записей - Каждые 6 часов — перепроверка кластера
Фоновые задачи
| Задача | Интервал | Что делает |
|---|---|---|
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 — 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-ов.
Ограничения
- Ручные bye/forfeit-ы — решение арбитра, алгоритм не предсказывает
- 10-колоночный формат — старые турниры без SNo, точность ~80%
- Круговые турниры — не поддерживаются
- TNR-пространство не сплошное — кластеры с пропусками, требуется кэш
- Латиница/кириллица — имена на chess-results могут отличаться от FIDE, матчинг по SNo