Skip to content
kewldanPublic

About

Слежу за конкурсными списками ИТМО: место, договоры, приток заявлений и вероятность поступления с прогнозом

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

🎓 ITMO Rating Bot

Telegram-бот, который следит за конкурсными списками abit.itmo.ru и оценивает вероятность поступления — в моменте и в прогнозе на дату зачисления.

CI Python aiogram PostgreSQL uv ruff

Мультипользовательский: каждый добавляет свои списки командой /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

🧮 Алгоритм вероятности поступления

Место в списке само по себе мало значит: большинство заявлений на контракт — «фантомы» (люди параллельно проходят на бюджет). Модель отвечает на вопрос «сколько человек выше меня по баллу реально займут места и хватит ли мест после них» — тремя слоями.

1️⃣ В моменте — Пуассон-биномиальная модель

Каждому конкуренту выше вас присваивается вероятность 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 ≥ мест).

2️⃣ Калибровка по динамике — байесовское уточнение приоров

Приоры из таблицы уточняются наблюдаемыми конверсиями в истории снапшотов этой же программы. Для каждой когорты (согласие / одобрен / без договора):

  1. Наблюдение: доля когорты, дошедшая до оплаты за время наблюдения — c_obs.
  2. Экстраполяция на весь горизонт до дедлайна в предположении постоянной интенсивности: c_proj = 1 − (1 − c_obs)^((T_набл + T_ост) / T_набл).
  3. Смешивание с приором: вес данных w = n_eff / (n_eff + 40), где эффективный размер выборки n_eff = n · T_набл / (T_набл + T_ост) — 12 часов без оплат не перечёркивают приор о поведении людей за месяц.
  4. Поправка применяется к шансам (odds), а не к вероятности напрямую, и зажимается в диапазон [0.2, 5].

Для бюджета калибровка отключена: там целевое событие — не оплата договора.

3️⃣ Приток — новые конкуренты до конца приёма

По окну истории (до 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%) и отток снявшихся тоже учитываются.

🏛 Бюджетные списки

Бюджетный список приходит категориями: БВИ, три квоты (особая, отдельная, целевая) и общий конкурс, с общей сквозной нумерацией. Бот собирает их воедино: БВИ считаются конкурентами общего конкурса (они занимают места КЦП), квоты исключаются — их места уже вычтены из числа мест общего конкурса. Если абитуриент числится и в квоте, и в общем конкурсе, берётся строка общего конкурса. Оценка честно помечается как приближённая: модель не решает задачу распределения по приоритетам между программами.

🚀 Запуск (docker compose)

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)

⚙️ Настройки (.env)

Переменная По умолчанию Смысл
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/         синтетические фикстуры и тесты (без сети и БД)

About

Слежу за конкурсными списками ИТМО: место, договоры, приток заявлений и вероятность поступления с прогнозом

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages