О проекте
Команда: syla
Кейс HackAlem.ai: Телеком — Beeline
Стадия: Доведённый продукт
Задача кейса: Агент, который решает, каким абонентам какой тариф предложить (чистый прирост ARPU)
Суть
Агент и дашборд для маркетолога Beeline: выбирает, каким группам абонентов какой тариф и через какой канал предложить, чтобы максимизировать чистый прирост ARPU в рамках бюджета и лимита контактов. Гипотезы проверяются симулированными пилотами.
Как устроено
По истории переходов строится сглаженная априорная оценка изменения ARPU для кандидатов вида «текущий тариф × ARPU-сегмент → новый тариф». Агент отбирает до 16 групп, запускает SMS-пилоты по 150 абонентов и обновляет оценку байесовски. Кандидаты с mean + std < 0 отсеиваются, до 4 адаптивных повторных пилотов идут туда, где информация ценнее затрат. Финальный портфель (до 10 кампаний по каналам push, sms, digital_ads) выбирается по осторожной оценке «ARPU × (mean − std) − стоимость контактов». Результат выводится в HTML-дашборд и submission.csv.
Ключевые решения
- байесовское обновление
- адаптивные пилоты (explore/exploit)
- осторожная оценка mean−std
- ранний отсев гипотез
- stress-eval со сдвинутым эффектом
- learned prior (ml_prior)
- paired benchmark агентов
Особенности
Это последовательное принятие решений под неопределённостью: пилоты накапливают информацию, а не перезаписывают её, решение о повторном пилоте зависит от ценности информации против её стоимости. Устойчивость проверялась стресс-тестами со сдвинутым эффектом и парным сравнением версий агента. Черновики рекламных текстов строятся по шаблонам без LLM и не выдумывают условий тарифа.
Стек
Python 3.12 · NumPy · pandas · HTML/CSS/JS · pytest
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-96a7c982-syla.
README
README.mdBeeline Tariff Copilot
Локальное веб-приложение и агент для кейса Beeline на хакатоне HackAlem AI. Проект помогает маркетологу выбрать, какой группе абонентов предложить какой тариф и через какой канал, не выходя за бюджет и лимиты контактов.
Главная задача — не просто перевести клиента на более дорогой тариф, а увеличить суммарный ARPU после затрат на коммуникацию. Смена тарифа может снизить доход оператора, поэтому агент проверяет гипотезы пилотами и учитывает неопределённость перед запуском.
Важно: текущее приложение работает на локальной симуляции. Оно не отправляет сообщения абонентам, не подключено к CRM Beeline и не обещает реальную прибыль или балл на скрытом судействе. Все денежные значения — в условных единицах кейса.
Содержание
- Быстрый запуск
- Как пользоваться
- Какие данные нужны
- Как работает агент
- Проверка и подготовка к сдаче
- Структура проекта
- Хранение данных и ограничения
- Если не запускается
- Эксперименты и сравнения
Быстрый запуск
Что понадобится
- Python 3.12 — проект проверен на Python 3.12.14.
- Git для клонирования репозитория либо скачанный ZIP проекта.
- Современный браузер.
- Зависимости из
requirements.txt: NumPy и pandas.
Node.js, сборка фронтенда, Docker, GPU, NVIDIA-кредиты и API-ключи для запуска не нужны. Интерфейс написан на обычных HTML/CSS/JavaScript, сервер — на Python. Интернет нужен для скачивания проекта и зависимостей; затем демо работает локально.
Windows / PowerShell
Откройте PowerShell в папке, где хотите разместить проект:
git clone https://github.com/BAITC-Hacks/hack-96a7c982-syla.git
cd hack-96a7c982-syla
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe dashboard_server.py
Если проект уже скачан, пропустите клонирование и перейдите в его корневую папку — там находятся dashboard_server.py и requirements.txt. Если команда py отсутствует, но python --version показывает Python 3.12, создайте окружение командой python -m venv .venv.
Откройте http://127.0.0.1:8765. Терминал с сервером должен оставаться открытым. Для остановки нажмите Ctrl+C.
Активация окружения не требуется: команды вызывают его Python напрямую, поэтому не нужно менять политику выполнения PowerShell. Папку .venv не добавляйте в коммиты.
macOS / Linux
При установленном Python 3.12:
git clone https://github.com/BAITC-Hacks/hack-96a7c982-syla.git
cd hack-96a7c982-syla
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python dashboard_server.py
Адрес приложения тот же: http://127.0.0.1:8765.
Повторный запуск
Устанавливать зависимости заново не нужно. Из корня проекта выполните:
.\.venv\Scripts\python.exe dashboard_server.py
На macOS/Linux используйте .venv/bin/python вместо .\.venv\Scripts\python.exe.
Как пользоваться
Демо на данных кейса
- Откройте приложение и нажмите «Посмотреть на данных кейса».
- Дождитесь плана: вверху появятся число кампаний, расход бюджета, контакты и результат локальной симуляции.
- Выберите кампанию в списке. Вкладка «Почему выбрана» показывает пилоты, среднюю оценку эффекта, неопределённость, затраты и причины ограничения охвата.
- Во вкладке «Текст кампании» переключайте и копируйте черновики коммуникации.
- В разделе «Прогноз → факт» посмотрите, как оценки менялись после каждого пилота.
- Скачайте CSV плана либо агрегированный отчёт кнопками JSON и «Резюме».
Поле «Сценарий» задаёт seed случайности пилотов. При одинаковом seed, данных и версии кода результат воспроизводим. Изменение seed проверяет чувствительность к шуму, а не создаёт новую реальную аудиторию.
В демо технические коды вроде tariff_13 заменены на условные названия, например «Связь Лайт». Это не официальные названия тарифов Beeline. Исходный ID виден при наведении и сохраняется в агенте, CSV и отчётах. Для загруженной базы демонстрационные названия не применяются.
Работа со своей базой
- В разделе «Данные» выберите три CSV: базу абонентов, справочник тарифов и историю переходов.
- Нажмите «Проверить файлы». Исправьте перечисленные ошибки и ознакомьтесь с предупреждениями.
- Нажмите «Построить план».
- Проверьте результаты и скачайте
campaign_plan.csv.
Расчёт использует именно загруженную базу и историю. Однако пилоты по-прежнему моделируются, а не измеряются на реальных клиентах. Для реального запуска потребуются тестовые рассылки, контрольная группа и интеграция с системами оператора.
Что означают блоки экрана
| Блок | Что показывает |
|---|---|
| Кампании / контакты / бюджет | Использование лимитов; контакты и расходы включают пилоты. |
| Что меняет план | Baseline без кампаний и baseline + чистый эффект в одной симуляции. Это не сравнение с работой маркетолога. |
| Воронка гипотез | Уникальные проверенные гипотезы, отклонённые, выбранные и не вошедшие в портфель. Повторный пилот не считается новой гипотезой. |
| Куда идут ресурсы | Расходы и контакты отдельно для пилотов, финального плана и каналов. |
| Почему выбрана | Путь от исторической оценки через пилоты к осторожному решению. Кампании без пилота отмечены отдельно. |
| Не запускать | Гипотезы, отклонённые после пилота. Невыбранная из-за лимитов кампания не приравнивается к убыточной. |
| Текст кампании | Три локальных шаблонных черновика для выбранного канала; цена и пакет взяты из справочника. |
| Прогноз → факт | Журнал прогнозов и наблюдений симуляции, а не реальные результаты рассылок. |
Черновики создаются без LLM и платных API. Они не придумывают скидки, скорость или гарантии экономии. Перед использованием маркетолог должен проверить параметры и условия тарифа. Копирование не отправляет сообщение; тексты не меняют скоринг и не входят в submission.csv.
Какие данные нужны
Для демо файлы уже находятся в репозитории:
customer_profile.csv— база абонентов.data/dict_tariff.csv— справочник тарифов.data/change_tariff.csv— история переходов.
Для загрузки через интерфейс используются следующие обязательные колонки:
| Файл | Колонки |
|---|---|
| База абонентов | ID_NUMBER, current_tariff, predicted_arpu, arpu_segment, DATA_VOLUME, data_segment, call_segment |
| Справочник тарифов | tariff_plan_code, price_tariff, Data_in_PKG |
| История переходов | ID_NUMBER, tariff_plan_code_from, tariff_plan_code_to, AVG_ARPU_PREV_3M, AVG_ARPU_NEXT_3M |
Пустые CSV-шаблоны доступны в разделе «Данные». Имена файлов могут отличаться; важны колонки и содержимое.
Требования к загрузке:
- Кодировка UTF-8; разделитель — запятая или точка с запятой; десятичный разделитель — точка.
- До 15 МБ на файл, до 30 МБ суммарно.
- До 100 000 абонентов, 100 тарифов, 100 000 записей истории.
arpu_segment:LOW,MID,HIGH.data_segment:NON_USER,LITE,HEAVY.call_segment:LOW,MEDIUM,HIGH. Обратите внимание: здесьMEDIUM, а неMID.- Коды тарифов в базе и истории должны соответствовать справочнику.
Data_in_PKGинтерфейс переводит в ГБ делением на 1024.
Валидатор проверяет дубли ID в базе, тарифы, сегменты и числовые значения. Строки базы без текущего тарифа или ARPU-сегмента исключаются с предупреждением; пропуски потребления не превращаются в нулевое потребление.
Как работает агент
Публичная точка входа — Agent().act(env) в agent.py. Агент получает данные и доступ к пилотам через публичный интерфейс среды; скрытые эффекты судейства не читает.
Данные и история
→ кандидаты «текущий тариф × ARPU-сегмент → новый тариф»
→ SMS-пилоты и обновление оценки
→ отсев / повторные пилоты
→ выбор тарифа и канала с учётом риска и лимитов
→ до 10 кампаний → интерфейс и CSV
- Предварительная оценка. История переходов другой аудитории даёт сглаженную оценку изменения ARPU. Она служит отправной точкой, а не доказательством эффекта кампании.
- Пул гипотез. Агент отбирает до 16 непересекающихся групп для пилотов. Малые группы рассматриваются отдельно.
- Первичные пилоты. Для кандидатов проводятся SMS-пилоты по 150 абонентов, пока хватает ресурсов и не набрано 11 перспективных гипотез.
- Байесовское обновление. Историческая оценка и наблюдения объединяются с учётом шума. Повторные пилоты накапливают информацию, а не заменяют предыдущий результат.
- Ранний отсев. Если после первого пилота
mean + std < 0, кандидат отклоняется и не попадает в повторные пилоты или финальный план. - Адаптивные повторы. До четырёх повторных SMS-пилотов размером до 200 человек направляются туда, где дополнительная информация о запуске может оказаться полезнее стоимости пилота. Учитываются оставшиеся деньги и контакты.
- Портфель кампаний. Агент сравнивает
push,smsиdigital_ads, выбирая положительные по осторожной оценке варианты. На одну группу — не более одной финальной кампании.
Осторожная оценка кандидата:
ожидаемый эффект = сумма прогнозного ARPU охваченной группы × mean − стоимость контактов
осторожный эффект = сумма прогнозного ARPU охваченной группы × (mean − 1 × std) − стоимость контактов
Здесь mean — оценка относительного эффекта для выбранного канала, std — её модельная неопределённость. Это не 95%-доверительный интервал и не гарантия положительного результата. Для каналов, отличных от SMS, используется модельный перенос по коэффициентам среды, а не отдельный пилот канала.
Если целая группа не помещается в оставшиеся лимиты, агент оценивает доступную часть в том же порядке ID_NUMBER, что и скорер. В финальном CSV нет выдуманного поля размера аудитории: фактический охват ограничивает скорер.
Группы из 10–149 человек могут войти без отдельного пилота по осторожной исторической оценке и требуют ручной проверки. call в рабочем плане не используется: линейный перенос из SMS может быть неверен из-за потолка вероятности конверсии.
Лимиты локальной среды
| Ограничение | Значение |
|---|---|
| Финальные кампании | До 10 |
| Контакты на одну кампанию | До 5 000 |
| Общие контакты, включая пилоты | 15 000 |
| Общий бюджет, включая пилоты | 100 000 у.е. |
| Количество пилотов | До 20 |
| Размер одного пилота в среде | 10–200 абонентов |
Push бесплатен по правилам кейса, но расходует контакты. Повторные обращения также расходуют ресурсы. Итоговый скорер учитывает пилоты вместе с финальными кампаниями и устраняет двойной зачёт эффекта по одному абоненту.
Как читать результат
- Baseline — сумма
predicted_arpuбазы без кампаний. - Net ARPU gain — прирост по правилам скорера после затрат на коммуникации.
- С планом — baseline + net ARPU gain.
- Ожидаемое / осторожное значение в карточке — оценки агента для отдельной кампании, а не итог всего портфеля.
Net ARPU gain — метрика кейса, не бухгалтерская прибыль компании. Локальный мок-скорер и оценка агента различаются; на скрытом судействе будут другие эффекты переходов.
Проверка и подготовка к сдаче
Выполняйте команды из корня репозитория. Ниже python означает интерпретатор, в который установлены зависимости: для созданного окружения Windows используйте .\.venv\Scripts\python.exe, для macOS/Linux — .venv/bin/python.
# Автоматические тесты
python -m unittest discover -s tests
# Один воспроизводимый прогон, seed=42
python local_eval.py
# Десять прогонов, seed=0…9
python local_eval.py --runs 10
# Создание или перезапись submission.csv, seed=42
python make_submission.py
Тесты написаны на стандартном unittest; pytest не требуется. Проверяются логика пилотов, накопление информации, ограничения, загрузки, соответствие интерфейса агенту, отчёты и черновики.
local_eval.py --runs 10 печатает результат каждого прогона, минимум, максимум, медиану и число положительных прогонов. Это проверка механики и чувствительности к шуму на моке, а не прогноз будущего балла.
В подробном выводе одного прогона строки «Осталось бюджета / охвата» отражают состояние среды после пилотов. Итоговые расходы и контакты с учётом финальных кампаний смотрите в отчёте скорера или интерфейсе.
Для воспроизводимой сдачи подготовьте agent.py, submission.csv и requirements.txt согласно выданному формату кейса. Не редактируйте CSV вручную: он должен быть порождён текущим агентом командой выше. Скачивание CSV в интерфейсе использует выбранный сценарий; для стандартной сдачи используйте make_submission.py с фиксированным seed=42. Агент на скрытой среде может принять другие решения — это ожидаемо.
Колонки результата:
campaign_name,filter_arpu_segment,filter_data_segment,filter_call_segment,filter_current_tariff,target_tariff,channel
Текущий агент фильтрует по тарифу и ARPU-сегменту; фильтры трафика и звонков остаются пустыми.
Дополнительные стресс-проверки:
python stress_eval.py --scenarios 20 --change-sd 0.30
python stress_eval.py --scenarios 20 --change-sd 0.30 --change-bias -0.30
Они меняют синтетические эффекты переходов и шум пилотов. Флаг --risk-penalty позволяет сравнить осторожность выбора; рабочее значение — 1.0. Стресс-сценарии не воспроизводят скрытую модель судейства.
Структура проекта
| Файл / папка | Назначение |
|---|---|
agent.py | Кандидаты, пилоты, обновление оценок и финальный выбор. |
environment.py | Публичный интерфейс среды и выполнение пилотов. |
mock_environment.py | Локальная среда с синтетическими эффектами. |
scoring_core.py | Правила подсчёта результата, лимиты и дедупликация. |
dashboard_server.py | Локальный HTTP-сервер, расчёт плана и экспорты. |
dashboard/ | Интерфейс: HTML, CSS, логика плана и журнала. |
dashboard_data.py | Проверка CSV и временное хранение загруженных наборов. |
pilot_journal.py | Агрегированный журнал пилотов в SQLite. |
campaign_creatives.py | Шаблоны черновиков коммуникаций. |
local_eval.py | Локальная оценка агента по одному или нескольким seed. |
make_submission.py | Генерация сдаваемого CSV. |
tests/ | Автоматические проверки. |
model_experiment.py, ml_prior.py, compare_agents.py | Отдельный эксперимент с обучаемой начальной оценкой. |
stress_eval.py, benchmark_agents.py | Стресс-тесты и сравнение версий агента. |
Хранение данных и ограничения
- Сервер слушает только
127.0.0.1:8765. Это локальное MVP без авторизации и многопользовательского режима; не публикуйте его напрямую в интернет. - Загрузки хранятся в памяти процесса: последние четыре набора. После перезапуска их нужно загрузить заново. Исходные CSV не перезаписываются.
- Журнал хранится в
.local/pilot_history.sqlite3и переживает перезапуск. В него не записываются исходные базы, ID абонентов и имена загруженных файлов..local/исключён из Git. - Повтор одного расчёта не создаёт дубль журнала. Версия алгоритма и отпечаток данных отличают изменившиеся расчёты; интерфейс показывает 100 последних записей.
- JSON/Markdown-отчёты содержат агрегаты, seed и версию модели, но не ID абонентов, исходные имена файлов, токен загрузки или тексты черновиков.
- Журнал — аудит оценок внутри прогона, а не самообучение между запусками. Его история автоматически не подмешивается в prior агента.
- Трафик и звонки показаны как контекст для менеджера, но пока не используются для дополнительного дробления групп в рабочем агенте.
- Плохие данные пилота не обновляют оценку. Некорректный ARPU исключает затронутую группу, чтобы фильтры и порядок скорера оставались согласованными.
- На вырожденной внешней базе возможен пустой план или только малые группы без пилотов. Это безопасный отказ от сомнительных кампаний, а не гарантия выполнения must-have хакатона для любой базы.
- Для проверки реального эффекта нужны результаты контролируемых кампаний и отдельная оценка новой модели. История совершённых переходов не содержит всех отказов от предложений.
Если не запускается
| Проблема | Что сделать |
|---|---|
python / py не найден | Установите Python 3.12, откройте новый терминал и проверьте python --version или py -3.12 --version. |
ModuleNotFoundError: pandas или numpy | Установите зависимости через тот же Python: python -m pip install -r requirements.txt. |
Не найден customer_profile.csv или data/change_tariff.csv | Перейдите в корень репозитория и проверьте наличие файлов. |
| Порт 8765 занят | Возможно, сервер уже запущен: откройте его в браузере. Для перезапуска остановите прежний запуск через Ctrl+C в его терминале. |
| После обновления виден старый интерфейс | Обновите страницу; после изменений Python-кода также перезапустите сервер. |
| Загруженный набор больше недоступен | Загрузите три CSV заново: сервер перезапущен либо набор вытеснен из памяти более новыми загрузками. |
| Кнопка копирования не сработала | Используйте появившееся поле ручного копирования и Ctrl+C. |
| План пустой | Посмотрите предупреждения проверки данных, размер групп и результаты пилотов. Агент не обязан запускать убыточную кампанию ради заполнения списка. |
Сравнение и перенос улучшений из веток
Аудит шести веток, принятые решения и ограничения: BRANCH_INTEGRATION.md. Экспериментальный агент и Streamlit-интерфейс не заменяют текущий продукт; перенесены совместимые части.
python benchmark_agents.py --baseline-ref 1cb2ba6 --runs 10
# При необходимости сохранить подробный локальный отчёт:
python benchmark_agents.py --baseline-ref 1cb2ba6 --runs 20 --seed-start 2000 --output benchmark.json
Сравнивается agent.py указанной Git-ревизии с текущим рабочим файлом на одинаковых seed и неизменной среде. Отчёт содержит хеши кода/данных, Min/Mean/Median/p05, парную дельту, ошибки и нарушения. Пустые планы и неудачные прогоны не скрываются. Запускайте только доверенные ревизии: это исполнение кода.
Перенос против 1cb2ba6, seed 0–9: среднее 3 584 479 → 4 003 094, минимум 2 804 837 → 3 345 859, медиана 3 610 986 → 4 136 349 у.е.; 9/10 парных побед. Более дорогая реклама увеличивает расход бюджета. При сильном отрицательном сдвиге среднее улучшилось, но худший результат ухудшился — подробности в отчёте аудита. Это эмпирическое сравнение синтетики, не гарантия выигрыша на скрытых эффектах.
Эксперимент с обучаемым prior
Ниже — результаты отдельного эксперимента до добавления digital_ads (снимок b3706d1), а не метрики последней версии.
model_experiment.py обучает регуляризованную линейную модель на истории уже совершённых смен тарифа и проверяет её на пяти фолдах, разделённых по ID абонента. Используются только выданные участникам данные и NumPy; NVIDIA GPU для такого объёма не требуется. compare_agents.py сравнивает рабочий агент с вариантом, где обученная модель даёт предварительную оценку, а пилоты и ограничения работают как раньше.
python model_experiment.py
python compare_agents.py
Результат на отложенных клиентах: RMSE изменения ARPU 0.9917 у текущего предварительного правила против 0.9531 у ridge-модели (alpha=100). Это не точность отклика на предложение и не оценка скрытого судейства: в истории нет отказов от кампаний.
Парное сравнение на 10 seed локального мока: средний net ARPU 3,584,479 у baseline и 3,611,202 у модели; побед 5/10. На 20 синтетических сценариях со сдвигом эффекта SD=0.30 модель проиграла baseline при нулевом сдвиге (3,703,166 против 4,095,513), но выиграла при сильном отрицательном смещении -0.60 (221,146 против 116,579). Эти эффекты заданы нами для стресс-проверки, не являются моделью судейства. Из-за неоднозначного результата сдаваемый Agent() по умолчанию оставлен на проверенном prior; обученный вариант доступен только как эксперимент через Agent(prior_provider=ml_prior.learned_priors).
Авторы коммитов
История коммитов
- 2026-09-23 17:56docs: note follow-up pilot slot reserveermekov303-sudo
- 2026-09-23 17:56docs: explain follow-up pilot valueermekov303-sudo
- 2026-09-23 17:56docs: clarify free channel reach limitermekov303-sudo
- 2026-09-23 17:55docs: explain early pilot rejectionermekov303-sudo
- 2026-09-23 17:55docs: note fallback conversion choiceermekov303-sudo
- 2026-09-23 17:55docs: clarify contact reserveermekov303-sudo
- 2026-09-23 17:54docs: explain agent planning calculationsermekov303-sudo
- 2026-09-23 17:53docs: add reproducibility reminderZhanassyl-1
Все коммиты (24)
- 2026-09-23 17:52docs: note demo fallback readinessZhanassyl-1
- 2026-09-23 17:52docs: add submission safety reminderZhanassyl-1
- 2026-09-23 17:52docs: clarify synthetic demo metricsZhanassyl-1
- 2026-09-23 17:51chore: add finalization checkpoint noteZhanassyl-1
- 2026-09-23 17:45Add dashboard favicon and ignore local artifactsermekov303-sudo
- 2026-09-23 17:38Document setup, workflow, agent logic and submission processAXMEDIAR
- 2026-09-23 17:31Add campaign drafts, decision funnel and friendly demo tariff namesAXMEDIAR
- 2026-09-23 16:58Integrate reviewed branch improvements with paired benchmarks and reportsAXMEDIAR
- 2026-09-23 16:42Add persistent pilot journal and forecast outcome comparisonAXMEDIAR
- 2026-09-23 16:27Add validated CSV uploads and dataset-specific campaign plansAXMEDIAR
- 2026-09-23 16:01Add tariff campaign dashboard with pilot explanationsAXMEDIAR
- 2026-09-23 15:06Add learned prior experiment and paired evaluationAXMEDIAR
- 2026-09-23 14:46Add shifted-effect stress evaluationAXMEDIAR
- 2026-09-23 14:38Improve adaptive pilot explorationAXMEDIAR
- 2026-09-23 14:09Merge GitHub initial commit with Beeline MVPAXMEDIAR
- 2026-09-23 14:06Add Beeline tariff campaign agent MVPAXMEDIAR
Структура репозитория (44 файлов)
.gitignore BRANCH_INTEGRATION.md PARTICIPANT_GUIDE.md PARTICIPANT_GUIDE.pdf README.md agent.py agent_template.py benchmark_agents.py campaign_creatives.py compare_agents.py customer_profile.csv dashboard/app.js dashboard/favicon.svg dashboard/history.js dashboard/index.html dashboard/styles.css dashboard_data.py dashboard_server.py data/arpu_monthly.csv data/change_tariff.csv data/dict_tariff.csv data/traffic.csv environment.py feature_dictionary.csv local_eval.py make_submission.py ml_prior.py mock_environment.py model_experiment.py pilot_journal.py requirements.txt scoring_core.py stress_eval.py submission.csv tariff_dictionary.csv tests/test_agent.py tests/test_benchmark_agents.py tests/test_campaign_creatives.py tests/test_campaign_story.py tests/test_dashboard.py tests/test_dashboard_reports.py tests/test_dashboard_upload.py tests/test_model_experiment.py tests/test_pilot_journal.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.