Проект для индивидуального тура олимпиады по промышленной разработке PROD 2024/25
Результат: Призёр I степени (победитель)
- Запуск
- Документация API
- Тестирование
- Визуализация статистики
- Бот Telegram
- Технические решения
- Логика работы основных компонентов
- Схема БД
-
backend
- Главный сервис проекта, отвечает за работу бэкенда и API
- Использует образ python3.12:alpine, alpine версия выбрана для оптимизации веса конечного образа
- Оснащён механизмом healthcheck для бесперебойной работы сервиса
- Зависит от сервисов redis (необходим для хранения информации о дне), minio и db
- Заданы все необходимые для работы переменные среды
- Во внешнюю сеть открыт порт 8080
-
bot
- Сервис, отвечающий за функционирование телеграм бота и используется исключительно как обёртка над API
- Использует образ python3.12:alpine, alpine версия выбрана для оптимизации веса конечного образа
- Заданы все необходимые для работы переменные среды
- Зависит от сервисов redis (для хранения состояния пользователей), backend
-
db
- Сервис, отвечающий за работу базы данных
- Использует образ postgres:alpine
- Для предотвращения переполнения максимального количества сессий передан параметр
max-connections=1000
- Заданы переменные среды, задающие параметры для авторизации в БД
- Оснащён механизмом healthcheck для бесперебойной работы сервиса
- Внутрь сети контейнеров выделен порт 5432
- Выделено хранилище pgdata для сохранения данных из БД после перезапуска
-
redis
- Сервис, отвечающий за работу redis
- Использует образ redis:alpine
- Внутрь сети контейнеров выделен порт 6379
- Выделено хранилище redis_data для сохранения всех состояний после перезапуска
-
minio
- Сервис, отвечающий за работу S3 объектного хранилища
- Использует образ minio/minio
- Заданы переменные среды, задающие параметры для авторизации в сервисе
- Оснащён механизмом healthcheck для бесперебойной работы сервиса
- Внутрь сети контейнеров выделен порт 9000
- Выделено хранилище minio_data для сохранения данных из БД после перезапуска
-
prometheus
- Сервис, отвечающий за сбор данных о нагрузке с сервиса backend
- Использует образ prom/prometheus:latest
- Внутрь сети контейнеров выделен порт 9090
- Зависит от сервиса backend
- Параметры конфигурации сервиса передаются из
prometheus/prometheus.yml
-
grafana
- Сервис, отвечающий за визуализацию всех статистики
- Использует образ grafana/grafana
- Во внешнюю сеть открыт порт 3000
- Зависит от сервисов prometheus и db (часть статистики идёт из БД)
- Параметры конфигурации сервиса передаются из
grafana/dashboard.yml
,grafana/datasource.yml
иgrafana/dashboards/*
- Выделено хранилище grafana_data для сохранения всех настроек после перезапуска
- Склонируйте данный репозиторий
- Убедитесь, что на вашем устройстве установлен
docker
иdocker compose
- Откройте терминал и выполните команду
docker compose up -d --build
и дождитесь завершения процесса запуска - Приложение успешно запущено!
Для получения информации о нашем API перейдите по пути http://127.0.0.1:8080/docs.
На данной странице предоставлена подробная Swagger документация.
-
Перейдите в директорию
backend
. -
Убедитесь, что на вашем устройстве установлены Python (версии 3.12 или выше), PIP и Docker.
-
Откройте терминал и последовательно выполните команды:
pip install poetry poetry install poetry shell
-
Не выходя из окна терминала, выполните команду
pytest
.
Среднее время, необходимое на прогон тестов — 40 секунд.
Мы предоставляем вам возможность посмотреть на красивую статистику с подробными графиками с помощью сервиса Grafana, для этого:
- Перейдите по пути http://127.0.0.1:3000.
- Войдите под именем пользователя
prod
и паролемprod
. - Перейдите во вкладку Dashboards.
- Для общей статистики по «ZoomAD Platform», выберите Statistic Dashboard; для статистики по конкретному рекламодателю — Advertiser Dashboard; для информации по загруженности платформы — Backend Dasboard.
Примечание: для Advertiser Dashboard сверху можно будет выбрать значения Advertiser (рекламодатель)
и Campaign (рекламная кампания)
, по заданным параметрам и будет составляться статистика.
Пример того, как выглядит статистика, можно найти в этом видео. Примечание: предоставлен не последний вариант, в последней версии появились дополнительные визуализации.
Функционал рекламодателя доступен в нашем Telegram-боте! Просто переходите по ссылке и следуйте инструкциям.
Возможности: создание и изменение рекламодателей и их рекламных кампаний; просмотр статистики.
Дополнительная функция — удобный предпросмотр рекламы, который позволяет оценить то, как будущие пользователи будут видеть рекламу.
После старта бота нас приветствует начальное меню, с помощью которого мы можем найти или создавать рекламодателя, зарегистрировать клиента, посмотреть рекламу или сменить день:
Давайте попробуем создать нового рекламодателя:
Нас встречает меню рекламодателя, давайте создадим новую рекламную кампанию для конкурса грантов (тут же можно увидеть пример генерации рекламного текста при помощи ИИ):
Теперь мы можем редактировать рекламную кампанию:
Так выглядит меню редактирования после запуска кампании (после её окончания возможности вызвать это меню нет):
Если включён режим модерации (см. документацию API /security/moderation
), то как при создании, так
и при изменении у вас не получится написать неэтичный текст рекламной кампании:
Теперь давайте вернёмся назад и попробуем перейти к уже существующей рекламной кампании:
Сейчас тут только 1 кампания, что и не удивительно, однако при большем их количестве будет возможность перемещаться по страницам при помощи кнопок (на данный момент они недоступны). Давайте создадим ещё пару кампаний.
Управление статистикой эквивалентно как для рекламодателя, так и для рекламной кампании, поэтому посмотрим на пример статистики рекламодателя (предварительно я посмотрел несколько реклам).
Предлагаю снова вернуться назад и вместо создания рекламодателя, попробуем выбрать уже существующего, в целом этот процесс схож с выбором рекламной кампании:
Из самого интересного остался лишь просмотр рекламы, позволяющий продемонстрировать наш алгоритм её подбора, в процессе просмотра вы будете смотреть от имени случайного существующего клиента.
А всё остальное вы можете попробовать уже сами в нашем боте по ссылке https://t.me/zoomprod_bot!
PostgreSQL была выбрана в качестве нашей базы данных так как на данный момент за счёт скорости своей работы и функционала она является одной из самых лучших СУБД на рынке. PostgreSQL применяется в таких кампаниях как Apple, Microsoft, Google и другие.
MinIO используется в качестве объектного хранилища по стандартам AWS S3, она легко поднимается локально и позволяет быстро и без лишних проблем хранить изображения рекламных кампаний. MinIO применяет компания, создавшая популярный linux дистрибутив Ubuntu - Canonical Group.
Redis позволяет быстро работать с состояниями пользователей в телеграм-боте за счёт их хранения прямо в оперативной памяти, но при этом возможностью сохранять эти данные при перезагрузке. Redis активно используется такими гигантами как Twitter, Amazon, OpenAI и другие.
Grafana — один из лучших вариантов визуализации любой статистики, при этом обеспечивая быструю работу, приятное оформление и гибкость, а также простоту настройки. В том числе Grafana поддерживает широкий спектр источников данных, таких как например PostgreSQL, используемый в проекте. Grafana используется Sony, eBay, Salesforce и другими компаниями.
Prometheus используется для сбора технической статистики с backend части нашей платформы, обеспечивая постоянное получение актуальной информации о загруженности в реальном времени. Prometheus является одним из наиболее используемых TSDB (Time Series Data Base), его применяет например PayPal, DigitalOcean и Docker.
OpenAI является лидером на рынке услуг Искусственного Интеллекта, при этом предоставляя достаточно дешёвые услуги. Мы используем их модели для генерации и модерации текстов рекламных кампаний. Их продукцию используется повсеместно многими компаниями, на основе их моделей работает поисковик Bing, Github Copilot, Duolingo и другие продукты.
/clients/bulk
, /advertisers/bulk
Для реализации данной функции используется конструкция ON CONFLICT, используемая в нашей СУБД PostgreSQL А сам запрос проходит односторонний путь от вызова API до PostgreSQL, поскольку все выходные данные уже предоставлены пользователем и информация из БД не нужна. Путь запроса можно увидеть на этой блок-схеме:
/advertisers/:advertiser_id/campaigns
Процесс создания рекламной кампании уже более сложен и состоит из нескольких сервисов, поскольку необходимо получение текущего дня и состояния активации модерации из Redis, модерация с помощью GPT, информации о существующих файлах из S3, ну и конечно информация из PostgreSQL, всё можно увидеть на блок-схеме:
На схеме можно заметить первоначальный запрос к AdvertiserService с обратной красной стрелкой, это необходимо для проверки того, что рекламодатель, от имени которого мы хотим создать кампанию вообще существует.
Для остальных процессов блок-схема в общем виде остаётся почти такой же, поэтому не вижу смысла её дублировать, разве что в случае что с получением и удалением кампаний она сильно упрощается:
/stats/
Отдельно стоит описать статистику, поскольку она взаимодействует со многими сущностями базы данных. Мы достаём все данные из БД, агрегируя по дням если это необходимо. Пример запроса по получению статистики можно увидеть на блок-схеме:
Рекламный алгоритм подбираем рекламу, сортируя её по убыванию показателя ожидаемой прибыльности рекламы (Expected profit), вычисляемого по формуле ниже:
где
Данная формула меняется в случае, если объявление уже было показано клиенту таким образом, что оно уже не сможет выпасть, пока существуют другие непросмотренные объявления.
Также при показе объявления учитывается, что оно подпадает под указанные настройки таргетирования, и что по данному объявлению ещё не был совершён переход, ведь в таком случае мы уже не сможем с него заработать.
Остальную информацию об API эндпоинтах вы можете найти на странице документации http://127.0.0.1:8080/docs.
Поле | Тип | Описание |
---|---|---|
advertiser_id | uuid, primary | ID рекламодателя |
name | varchar | Имя рекламодателя |
created_at | timestamp, with timezone | Дата создания (для сортировки, пагинации) |
Поле | Тип | Описание |
---|---|---|
client_id | uuid, primary | ID клиента |
login | varchar | Логин клиента |
age | integer | Возраст клиента |
location | varchar | Локация клиента |
gender | varchar | Пол клиента (MALE/FEMALE) |
Поле | Тип | Описание |
---|---|---|
campaign_id | uuid, primary | ID кампании |
advertiser_id | uuid, fk advertisers | ID рекламодателя |
impressions_limit | integer | Лимит показов |
clicks_limit | integer | Лимит переходов |
cost_per_impression | numeric | Стоимость за показ |
cost_per_click | numeric | Стоимость за переход |
ad_title | varchar | Заголовок рекламы |
ad_text | varchar | Текст рекламы |
ad_image_id | varchar? | ID изображения рекламы |
start_date | integer | Дата начала кампании |
end_date | integer | Дата окончания кампании |
target_gender | varchar? | Целевой пол |
target_age_from | integer? | Минимальный возраст аудитории |
target_age_to | integer? | Максимальный возраст аудитории |
target_location | varchar? | Целевая локация |
created_at | timestamp, with timezone | Дата создания (для сортировки, пагинации) |
Поле | Тип | Описание |
---|---|---|
client_id | uuid, primary, cascade delete, fk clients | ID клиента |
advertiser_id | uuid, primary, cascade delete, fk advertisers | ID рекламодателя |
score | integer | Оценка релевантности |
Поле | Тип | Описание |
---|---|---|
client_id | uuid, primary, cascade delete, fk clients | ID клиента |
campaign_id | uuid, primary, cascade delete, fk campaigns | ID кампании |
date | integer | Дата показа |
cost | numeric | Стоимость показа |
Поле | Тип | Описание |
---|---|---|
client_id | uuid, primary, cascade delete, fk clients | ID клиента |
campaign_id | uuid, primary, cascade delete, fk campaigns | ID кампании |
date | integer | Дата перехода |
cost | numeric | Стоимость перехода |