Расскажу, как я автоматизировала регулярную отправку графиков из BI в мессенджер. Я опишу базовые шаги, чтобы в целом дать понимание и рассказать про такую возможность, конечно, код должен дорабатываться и персонализироваться исходя из ваших задач
Расскажу, как я автоматизировала регулярную отправку графиков из BI в мессенджер.
Задача была довольно типичная: есть дашборд в redash, на который смотрят каждый день. Данные иногда приходят с задержками и нельзя быть уверенным, что в 9 утра все "доедет", плюс зайти руками и прокликать несколько разрезов - долго и неудобно, хочется сразу все видеть в мессенджере как только данные обновились.
Я опишу базовые шаги, чтобы в целом дать понимание и рассказать про такую возможность, конечно, код должен дорабатываться и персонализироваться исходя из ваших задач
В итоге я собрала небольшой пайплайн, который:
Забирает данные из Redash по API
Строит аналогичные графики в matplotlib
Склеивает их в одну картинку
Отправляет в Mattermost
Сразу скажу, что в планах добавить возможность просто скринить нужный гарафик, но здесь есть несколько минусов и проблем, которые находятся в проработке
Что в итоге получилосьКаждый день пользователи получают вот такое сообщение:
набор графиков по разным метрикам
разбивка по категориям
подписи с ключевыми цифрами
Но самое главное, эти графики можно посмотреть с утра, пока ты даже не сел за компьютер.
Шаг 1. Забираем данные из RedashУ Redash есть API, через который можно дергать запросы.
По сути, нам нужно:
взять query_id
передать параметры
дождаться результата
Упрощенно это выглядит так:
import requests
BASE_URL = 'https://your-redash-instance'
HEADERS = {'Authorization': 'Key YOUR_API_KEY'}
def fetch_query(query_id, params):
response = requests.post(
f'{BASE_URL}/api/queries/{query_id}/results',
headers=HEADERS,
json={'parameters': params, 'max_age': 0},
)
return response.json()Но есть нюанс: если результат не готов сразу, Redash вернет job, и нужно будет подождать. Поэтому в реальности добавляется polling (ожидание выполнения запроса).
Если вы дергаете не просто query, а виджет с дашборда, становится чуть сложнее.
Нужно:
распарсить параметры из URL
сопоставить их с параметрами запроса (это была сама сложная часть, так как нужно было учесь все типы параметров)
корректно привести типы (даты, списки и т.д.)
Пример:
from urllib.parse import parse_qs, urlparse
def parse_dashboard_url(url):
parsed = urlparse(url)
params = parse_qs(parsed.query)
return {
key: value[0]
for key, value in params.items()
}Отдельная боль это динамические даты (last 7 days, this month и т.д.).
Их приходится разворачивать руками:
from datetime import datetime, timedelta
def last_7_days():
today = datetime.utcnow()
return {
'start': (today - timedelta(days=7)).strftime('%Y-%m-%d'),
'end': today.strftime('%Y-%m-%d')
}Шаг 3. Строим графикиДальше используем обычный matplotlib.
Графики максимально приближенные к оригиналам ( к тому как они выглядят в redash) все признаки, цвета и типы линий тоже можно забрать по API
Пример:
import matplotlib.pyplot as plt
def build_chart(df):
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(df['date'], df['value'], linewidth=2)
ax.plot(df['date'], df['plan'], linewidth=2)
ax.set_title('Metric dynamics')
return figШаг 4. Добавляем полезные подписиОчень важный момент, график должен быть читаем без наведения мышки, так как это картинка, а не дажшборд
Поэтому я добавляю:
последнее значение
WoW изменение
отклонение от плана
Пример:
ax.annotate(
f"value: {last_value}",
xy=(last_date, last_value),
xytext=(10, 10),
textcoords='offset points'
)Это сильно упрощает восприятие, особенно когда график просто прислали в чат.
Шаг 5. Склеиваем графики в один дашбордОтправлять 30 отдельных картинок явно плохое решение с точки зрения UX/UI, их можно объединить, например, по отделам
Для этого я собирала их в один layout с помощью PIL:
from PIL import Image
def merge_images(images, output_path):
base = Image.new('RGB', (1200, 800), 'white')
for img, pos in images:
base.paste(img, pos)
base.save(output_path)Плюс добавляю отдельный блок с заголовком категории чтобы сразу было понятно, что за данные.
Шаг 6. Проверка, что данные доехалиСам код у меня стоит на кроне и проверят есть ли уже на графиках нужная мне дата, как только проверка успешна - то только тогда запускается весь скрипт выше
Пример проверки:
if df['date'].max() != target_date:
raise Exception('Данные еще не обновились')Шаг 7. Отправка в MattermostУ Mattermost есть Python SDK, через который можно:
загрузить файл
отправить сообщение
создать личный канал
Пример:
from mattermostdriver import Driver
mm = Driver({
'url': 'your-mattermost',
'token': 'BOT_TOKEN',
})
mm.login()
mm.posts.create_post({
'channel_id': channel_id,
'message': 'Отчет готов',
'file_ids': [file_id],
})Шаг 8. Защита от повторной отправкиЧтобы скрипт не отправлял один и тот же отчет несколько раз, я сохраняю дату последней отправки, и в самом начале кода проверяю существует ли уже эта запись
from pathlib import Path
STATE_FILE = Path('last_sent.txt')
def was_sent(date):
return STATE_FILE.exists() and STATE_FILE.read_text() == str(date)ЗаключениеВ итоге я автоматизировала получение данных в самый удобный канал получения информации на работе - в рабочий мессенджер. Конечно, в отправленном сообщении с графикам я добавляю и ссылку на исходный дашборд, на который можно сразу перейти и уточнить детали.
Схематичный пример, как это выглядит на мобильном приложении
Об автореМеня зовут Валерия Смирнова, я уже более 6 лет работаю в продуктовой и в BI-аналитике. Веду канал про BI и визуализацию данных и аналитике.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Визуализация данных в Python с помощью библиотеки Plotly: база для новичков | 0 | 6.12 | 07-02-2026 |
| 2 | api2app: Использование совместно с FastAPI | 0 | 20.51 | 25-04-2026 |
| 3 | Как я научила Telegram-бота наводить порядок в чате с мемами: пересылка по хештегам в соответствующую тему | 0 | 7.64 | 01-06-2026 |
| 4 | Пошаговые диалоги в Python без боли: описываем визарды в JSON, а не в if-ах | 0 | 10.65 | 21-04-2026 |
| 5 | От сложности к простоте: как я переписал систему логирования Discord-серверов и сделал её в 10 раз проще | 0 | 12.86 | 18-07-2026 |
| 6 | Как красиво разложить граф на экране: метод Сугиямы по шагам | 0 | 5.52 | 27-07-2026 |
| 7 | Airflow TaskFlow API: внутреннее устройство современного способа писать DAG-и | 0 | 10.41 | 14-05-2026 |
| 8 | Как конечные автоматы помогают сделать агента надежнее и при чем тут pydantic-graph? | 0 | 8.61 | 07-06-2026 |
| 9 | obabot: пишем бота один раз — запускаем в Telegram и Max | 0 | 8.9 | 09-04-2026 |
| 10 | Как отправлять email через LoRa mesh-сеть (Meshtastic + Python) | 0 | 9.68 | 21-03-2026 |