На работе в моём ведении оказались сразу две связанные области: дата-инженерия и отчётность. Я отвечаю и за то, чтобы данные доехали до витрин, и за то, чтобы поверх них появились понятные дашборды. Для визуализации у нас два инструмента — Power BI и Superset. И если Superset живёт в мире кода и конфигов, то Power BI — это .pbix, бинарный файл, который исторически делается руками в Desktop.

Чем больше отчётов, тем больнее ручной процесс. Новый дашборд под запрос бизнеса, правка проверки перед публикацией, обновление схемы lineage на Confluence, аудит соответствия брендбуку перед показом руководству — всё это либо делалось вручную, либо висело на одном человеке. В рамках общей линии на автоматизацию и агентизацию я собрал для Power BI отдельный фреймворк.

Почему с Power BI всё сложнее, чем с Superset

.pbix — это обычный ZIP-контейнер, но внутри два принципиально разных слоя:

  • DataModel — бинарная VertiPaq-модель. Её нельзя корректно написать

байт за байтом на Python: формат закрыт, внутри XPress9-компрессия.

  • Report/Layout — текстовый JSON с описанием страниц и визуалов. Вот

его генерировать можно — при условии, что кодировка UTF-16LE без BOM, иначе Desktop просто не распарсит файл.

Отсюда главный приём: модель берётся от рабочего донора, а собранный нами layout вкладывается поверх неё. Визуалы ссылаются на объекты модели по имени, поэтому пока имена совпадают — отчёт открывается и показывает данные. Второй, канонический путь — дать Desktop RS-движку (msmdsrv.exe) самому сериализовать модель, но это требует Windows и установленного Desktop RS. Фреймворк прячет оба пути за один API.

Что умеет фреймворк

Всё построено вокруг пяти стадий жизненного цикла: develop → test → deploy → control → support.

  • develop — из декларативного spec (YAML/JSON) или программного

Board собирается layout: KPI-карточки, графики, таблицы, фильтры, многостраничные борды с авторазметкой. Дальше layout свёртывается в бинарный .pbix через донора или собирается движком прямо из данных.

  • test — validate_pbix сверяет layout с моделью донора и падает, если

визуал ссылается на несуществующую таблицу, колонку или меру. Отдельно engine_verify делает обратный самотест: читает собранный .pbix обратно и подтверждает, что страницы, привязки и данные на месте. Ещё один слой — antipatterns: сканирование M-кода и DAX на хардкод путей, фикс-даты, SUMX/EARLIER, бизнес-логику в вычисляемых колонках и прочие грабли.

  • deploy — публикация и обновление отчёта на Power BI Report Server

через REST /api/v2.0/CatalogItems с Windows SSO. Повторная публикация (409 Conflict) автоматически превращается в PUT.

  • control — проверка, что отчёт действительно долетел: GET по пути и

сверка Id/Size/ModifiedDate, плюс диагностика каталога Report Server.

  • support — единый каталог порогов, объяснение правил и triage.

Быстрый каркас, в котором остаётся только дизайн

Отдельная ценность — генератор скелета отчёта. По описанию таблицы, колонок и мер фреймворк собирает готовый PBIP-проект: KPI-карточка, график, таблица и фильтры на странице. Это не пустой шаблон, а отчёт, который уже открывается в Desktop и показывает данные по контракту колонок — с корректными привязками, layout в UTF-16LE без BOM и фильтрами через slicer, а не через ломающий drill IN.

То есть по правилам собирается рабочий каркас, и от него остаётся только реализация дизайна: цвета, шрифты, композиция, акценты. Аналитику больше не надо каждый раз вручную воспроизводить одну и ту же структуру страницы, а дизайнер получает не сырой холст, а осмысленный макет, который уже считает метрики.

Что находил аудит антипаттернов

Сканирование M-кода и DAX даёт не абстрактное «стиль кода не очень», а конкретный список вида «мера X, таблица Y, вот в чём проблема». В реальных бордах регулярно всплывает:

  • хардкод путей и фиксированные даты вместо параметров — отчёт ломается при

первом переезде источника;

  • SELECT * и хрупкое разворачивание JSON в Power Query;
  • бизнес-логика, зашитая в вычисляемую колонку, вместо меры;
  • по десятку разных шрифтов на одном листе: у части визуалов одна гарнитура,

у части — другая, и уже никто не помнит, почему так;

  • 25 визуальных элементов, бережно спрятанных друг под другом: их

положили, потом перекрыли новыми, а старые не удалили. Глазами их не видно, но они живут в layout, тянут данные и раздувают файл.

Последние два примера — не про «плохой код», а про то, что дашборд визуально выглядит нормально, хотя внутри уже хаос. Ручной глаз такое не поймает.

Отдельно стоят инструменты, которые в работе оказались нужнее всего.

Lineage. Класс PbiLineage разбирает любой .pbix без установленного Power BI и вытаскивает полный след: происхождение → источник → Power Query → таблицы модели → семантику → страницы → визуалы. На выходе — JSON, Excel, Markdown и PlantUML-схема, которую скрипт сам публикует на Confluence. Причём перед публикацией схема валидируется, а публикация блокируется, если число листов вдруг упало — защита от «не того» файла. Плюс к каждому узлу считается refs — сколько раз он реально используется в визуалах. Это отвечает на вопрос «можно ли безболезненно удалить эту меру», на который обычно отвечают наугад.

Аудит брендбука. Дашборд, который не соответствует фирменному стилю, нельзя показывать. Проверка идёт по реально используемым в визуалах шрифтам и цветам, с допуском на искажения RGB, и локализует нарушение вплоть до конкретного визуала на конкретном листе. Для CI есть код возврата: есть листы-нарушения — выходной код 1.

Где здесь агенты

Агенты — не обязательный шаг конвейера, а опциональный умный слой. Конвейер прекрасно работает и без них. Каждый агент — декларативный манифест, привязанный к одной стадии: agent.builder (сборка), agent.tester (проверка), agent.deployer (публикация), agent.watchdog (контроль), agent.supporter (разбор инцидентов). Включаются, выключаются и переопределяются они прямо в spec. Это позволяет поручить агенту, например, весь путь «запрос → скелет → сборка → валидация → публикация → проверка», оставив за человеком только постановку задачи и приёмку.

Как агент отвечает на вопрос пользователя

Самое заметное на практике — не сборка отчётов, а скорость расследований. Вот несколько сценариев, которые раньше занимали полдня переписки, а теперь закрываются за один прогон.

«В первичке строк больше, чем в KPI на дашборде»

Пользователь приходит с расхождением: в таблице-источнике, скажем, 52 000 строк, а KPI на дашборде показывает 48 300. Классический вопрос «почему не сходится», который обычно уходит в аналитика на день.

Агент действует по фреймворку и не гадает:

  1. По lineage находит, какой именно источник и какая мера стоят за KPI, и

вытаскивает её DAX-выражение.

  1. Видит, что KPI использует не COUNTROWS, а DISTINCTCOUNT по

идентификатору.

  1. Идёт в исходник и считает: сколько строк всего и сколько уникальных

идентификаторов. Если всего 52 000, а уникальных 48 300 — картина сходится, и дело не в дашборде.

  1. Ищет сами дубли и показывает их: вот строки с одинаковым идентификатором,

у которых различаются атрибуты A и B.

Ответ пользователю получается конкретным:

Расхождение не в дашборде: KPI считает уникальные идентификаторы, а в
источнике есть дубли. Например, идентификатор X-1042 встречается три раза —
с разными значениями статус и дата_загрузки. Таких записей 3 700, что
ровно и даёт разницу 52 000 − 48 300. Причины дублей: перезагрузка источника
без дедупликации и два разных написания одного и того же атрибута.

Здесь срабатывает связка из статьи: lineage отвечает на «что за KPI и откуда он», а антипаттерны и разбор модели — на «почему цифра ведёт себя именно так».

Смысл не в том, что агент «умный». Смысл в том, что это закрывает заявку в поддержку за минуты вместо часов работы человека. Такой тикет раньше уходил в очередь к дата-инженеру, лежал до следующего окна, потом аналитик вручную открывал .pbix, искал меру, сверял со источником, писал ответ. Фреймворк превращает это в один прогон скриптов с готовым ответом и доказательством: вот мера, вот дубли, вот строки, вот причина. Человек остаётся там, где он действительно нужен — решить, что делать с дублями в источнике.

«Откуда взялась эта цифра»

К цифре в отчёте нужно объяснить путь: источник → Power Query → таблица модели → мера → визуал. Вместо ручного разбора .pbix агент строит lineage и отдаёт цепочку и саму формулу меры. Если мера используется ещё в десяти визуалах — это тоже видно по refs, так что сразу понятно, что менять её безопасно только проверив все места.

«Почему дашборд не проходит по брендбуку»

Агент запускает аудит и локализует нарушение до конкретного элемента: не «где-то не тот шрифт», а «лист 4, визуал „Выручка по месяцам“, шрифт X вместо фирменного, доля фирменных цветов 40%». Инженеру и дизайнеру не нужно просматривать весь отчёт глазами.

Чему я научился

Главный урок — инструмент для данных должен быть проверяемым. Красиво собрать отчёт мало; нужно, чтобы перед публикацией что-то независимо убедилось, что он не пустой и не сломанный. Отсюда validate_pbix перед публикацией и обратный самотест после сборки.

Второй урок — lineage окупается быстрее, чем кажется. Пока его нет, вопрос «а откуда взялась эта цифра» превращается в расследование. Когда схема строится автоматически из самого файла, ответ находится за минуты.

Третий — не надо бороться с закрытыми форматами. Бинарную модель VertiPaq лучше не реверсить, а аккуратно брать у Desktop. Это честнее и переживает обновления продукта.

Где посмотреть

Фреймворк лежит в открытом виде — с примерами, тестами и документацией по всем пяти стадиям: github.com/Bagilov/agentic_bi_framework. Там же разобраны конкретные приёмы: контейнер .pbix, кодировка layout, донорский перенос, движок-сборка, публикация и аудит брендбука.

Это не попытка заменить Power BI Desktop. Это попытка убрать из работы всё, что не является thinking-задачей: ручную сборку, ручную проверку, ручную публикацию и ручной поиск того, откуда взялась цифра.