Telegram-бот, который следит за конкурсными списками abit.itmo.ru и оценивает вероятность поступления — в моменте и в прогнозе на дату зачисления.
Мультипользовательский: каждый добавляет свои списки командой /track
(любые направления — бакалавриат/магистратура, контракт/бюджет) и свой код
ЕПГУ. Данные берутся из __NEXT_DATA__ страницы списка — API и авторизация
не нужны.
- 📡 Мониторинг — опрос списков каждые 10 минут, дедупликация по
update_timeсайта, полная история снапшотов в PostgreSQL (JSONB). - 🔔 Уведомления — при каждом обновлении списка: сколько заявлений и
оплат прибавилось, как изменилось ваше место и вероятность. Отключаются
в
/list; блокировка бота обрабатывается автоматически. - 📍 Дайджест места (
/settings) — периодическая сводка «место, эффективная позиция, вероятность» не чаще выбранного интервала (выкл/3/6/12/24 ч, по умолчанию 6 ч). Когда дайджест пора слать, он заменяет обычное уведомление — двух сообщений подряд не будет. - 📊 Детальный разбор (
/status) — место, балл и перцентиль, разбивка конкурентов выше по статусам, эффективная позиция, вероятность «сейчас» и прогноз к дедлайну со сценариями, скорость притока и оплат, дельты за сутки. - 📈 Графики (
/chart) — панель 2×2: заявления, ваше место и эффективная позиция, договоры/согласия, вероятность с коридором сценариев. PNG кэшируются по времени обновления списка. - 🔗 Кросс-анализ программ — фоновый обход всех списков степени: если конкурент выше вас проходит на другую программу с более высоким приоритетом, его «эффективный приоритет» у вас понижается — фантомы отсеиваются по фактам, а не по приорам.
⚠️ Пороговые алерты — отдельное уведомление, когда вероятность падает ниже 95/90/75/50%.- 🧾 Загрузка выгрузки ЛК — пришлите боту CSV из личного кабинета: в нём есть время подачи каждой заявки, и прогноз притока строится по реальной кривой (действует 48 часов).
- 📉 Отток — снявшиеся с конкурса считаются отдельно («снялись N, из них выше вас M»).
- 📋 /compare — сводное сравнение всех ваших программ.
- 🎉 Оплаченный договор = поступление — если ваш договор заключён и оплачен, бот прямо пишет «вы уже поступили», а шансы показывает справочно («если бы вы не заключали договор»).
- 👮 Админу (
ADMIN_TG_ID): уведомления о новых пользователях, алерты о сбоях парсинга (3 подряд) и ежедневный бэкап БД gzip-JSON документом прямо в Telegram (восстановление:python -m app.backup restore <файл>).
| Слой | Технология |
|---|---|
| Язык | Python 3.13+, asyncio |
| Telegram | aiogram 3 |
| БД | PostgreSQL 18 + raw asyncpg (без ORM), история снапшотов в JSONB |
| FSM-состояния | Valkey 8 / Redis (опционально, иначе память процесса) |
| HTTP | httpx — разбор __NEXT_DATA__ |
| Графики | matplotlib (backend Agg) |
| Конфигурация | pydantic-settings (.env) |
| Инструменты | uv · ruff (select = ["ALL"], без noqa) · ty · pytest |
Место в списке само по себе мало значит: большинство заявлений на контракт — «фантомы» (люди параллельно проходят на бюджет). Модель отвечает на вопрос «сколько человек выше меня по баллу реально займут места и хватит ли мест после них» — тремя слоями.
Каждому конкуренту выше вас присваивается вероятность p, что он реально займёт место, по его сигналам в списке:
| Сигнал | Контракт | Бюджет |
|---|---|---|
| 💳 договор оплачен | 0.97 | — |
| 📝 договор одобрен | 0.85 | — |
| ✅ согласие подано | 0.70 | 0.90 |
| приоритет 1 / 2 / 3 / 4 / 5 (ничего не подписал) | 0.30 / 0.18 / 0.12 / 0.08 / 0.06 | 0.75 / 0.45 / 0.30 / 0.20 / 0.12 |
Число занятых мест выше вас — сумма независимых бернуллиевских величин
X = Σ Bernoulli(pᵢ) (Пуассон-биномиальное распределение).
Вероятность поступления:
P(поступление) = P(X ≤ мест − 1)
Считается точным динамическим программированием за O(n·мест), без
симуляций Монте-Карло: dp[k] — вероятность ровно k занятых мест,
последняя ячейка накапливает P(X ≥ мест).
Приоры из таблицы уточняются наблюдаемыми конверсиями в истории снапшотов этой же программы. Для каждой когорты (согласие / одобрен / без договора):
- Наблюдение: доля когорты, дошедшая до оплаты за время наблюдения —
c_obs. - Экстраполяция на весь горизонт до дедлайна в предположении постоянной
интенсивности:
c_proj = 1 − (1 − c_obs)^((T_набл + T_ост) / T_набл). - Смешивание с приором: вес данных
w = n_eff / (n_eff + 40), где эффективный размер выборкиn_eff = n · T_набл / (T_набл + T_ост)— 12 часов без оплат не перечёркивают приор о поведении людей за месяц. - Поправка применяется к шансам (odds), а не к вероятности напрямую, и зажимается в диапазон [0.2, 5].
Для бюджета калибровка отключена: там целевое событие — не оплата договора.
По окну истории (до 96 ч) оцениваются:
- ⚡ скорость подачи новых заявлений
r(заявл./день); - 🎯 доля новичков с баллом выше вашего
q; - 💰 скорость оплат (для прогноза итоговых чисел).
Ожидаемое число новых реальных конкурентов выше вас к концу приёма
заявлений: μ = r · D · q · p̄, где D — дни до конца приёма, p̄ — средний
приор новичка. Итоговая вероятность — свёртка ДП из шага 1 с Пуассоном:
P = Σₖ Pois(k; μ) · P(X ≤ мест − 1 − k)
- база — как есть; пессимистично — шансы конкурентов ×2 (в odds), приток ×1.6; оптимистично — ×0.5 и ×0.5.
- Два горизонта:
APPLY_DEADLINE— конец приёма заявлений (докуда льётся приток),ENROLL_DEADLINE— конец оплат (докуда идут конверсии).
Код ЕПГУ один во всех списках. Раз в CROSS_POLL_HOURS бот обходит все
конкурсные группы степени и для каждого конкурента выше вас считает
эффективный приоритет: 1 + число более приоритетных программ, куда он
сейчас проходит (по неквотному рангу против числа мест). Эффективный
приоритет заменяет заявленный в приорах — «фантомы» отсеиваются по фактам.
Безопасная граница (место с P≥95%) и отток снявшихся тоже учитываются.
Бюджетный список приходит категориями: БВИ, три квоты (особая, отдельная, целевая) и общий конкурс, с общей сквозной нумерацией. Бот собирает их воедино: БВИ считаются конкурентами общего конкурса (они занимают места КЦП), квоты исключаются — их места уже вычтены из числа мест общего конкурса. Если абитуриент числится и в квоте, и в общем конкурсе, берётся строка общего конкурса. Оценка честно помечается как приближённая: модель не решает задачу распределения по приоритетам между программами.
cp .env.example .env # вписать BOT_TOKEN от @BotFather
docker compose up -d --buildВ составе: бот, PostgreSQL 18 (история снапшотов), Valkey 8 (FSM-состояния).
Требуются uv и Python 3.13+.
uv sync # окружение и зависимости (включая dev)
uv run ruff format app tests # форматирование
uv run ruff check app tests # линт: select = ["ALL"], без noqa
uv run ty check app tests # типы
uv run pytest -q # тесты (без сети и БД)
uv run python -m app.main # локальный запуск (нужен PostgreSQL)CI (GitHub Actions) гоняет то же самое на каждый push.
Слой БД без Docker можно проверить встроенным PostgreSQL:
uv run --no-project --python 3.12 --with pgserver,asyncpg,httpx,pydantic-settings python <тест>.
| Команда | Действие |
|---|---|
/track |
🎯 подписаться: ссылка на список → код ЕПГУ |
/status |
📊 детальный разбор и вероятность |
/chart |
📈 графики динамики |
/compare |
📋 сравнить все свои программы |
/list |
🗂 подписки: вкл/выкл уведомления, удалить |
/settings |
⚙️ периодичность дайджеста места (выкл/3/6/12/24 ч) |
/refresh |
🔄 принудительно обновить данные с сайта |
/cancel |
❌ отменить текущий диалог |
/id |
🪪 показать свой Telegram ID (для ADMIN_TG_ID) |
| Переменная | По умолчанию | Смысл |
|---|---|---|
BOT_TOKEN |
— | токен от @BotFather (обязательно) |
DATABASE_URL |
postgresql://itmo:itmo@localhost:5432/itmo |
DSN PostgreSQL (asyncpg) |
REDIS_URL |
пусто | Valkey/Redis для FSM (иначе память процесса) |
ADMIN_TG_ID |
пусто | ID админа: новые юзеры, сбои, бэкапы |
BACKUP_HOUR |
4 | час (МСК) ежедневного бэкапа в Telegram |
CROSS_ENABLED |
true | кросс-программный анализ |
CROSS_POLL_HOURS |
3 | период обхода всех списков степени |
POLL_INTERVAL_SECONDS |
600 | период опроса сайта |
ENROLL_DEADLINE |
2026-08-20 | конец приёма оплат (горизонт прогноза) |
APPLY_DEADLINE |
2026-08-10 | конец приёма заявлений (горизонт притока) |
app/
config.py настройки (.env, pydantic-settings)
fetcher.py разбор __NEXT_DATA__ (контракт/бюджет) и API направлений
metrics.py модель: приоры, калибровка, приток, сценарии, кросс-приоритеты
cross.py кросс-программный анализ: обход списков, эффективные приоритеты
db.py PostgreSQL: пул asyncpg, схема, все запросы
models.py датаклассы Program / Snapshot / Subscription / CrossList
service.py сборка анализа, контекст, ЛК-данные, точки графиков
charts.py PNG-панель 2×2 (matplotlib, Agg)
texts.py все тексты бота (HTML)
poller.py фоновый опрос, уведомления, дайджесты, пороговые алерты
backup.py ежедневный дамп БД в Telegram + CLI восстановления
admin.py служебные уведомления администратору
handlers/ aiogram-роутеры: /track (FSM), команды, загрузка CSV из ЛК
main.py точка входа: БД, диспетчер, фоновые задачи
tests/ синтетические фикстуры и тесты (без сети и БД)