Предиктека Войти

Аналитика продукта

Предиктека считает, чем её пользуются, и отправляет эти события на свой хост Porcupine — самостоятельный сервис продуктовой аналитики: принимает события, кладёт их в SQLite и проецирует в DuckLake, где к ним пускают SQL.

Внешних сервисов нет: адрес задаёт оператор установки, а если он не задан — не отправляется ничего. Установка в закрытом контуре работает ровно так же, только без событий.

Что отправляется

Событие Когда Свойства
signed_in успешный вход способ входа
template_installed установлен шаблон шаблон, версия, движок, форматы, число полей
document_printed напечатан документ шаблон, версия, движок, формат, приложений, страниц, байт
document_refused документ отклонён до печати шаблон, причина (данные не по схеме, приложение не включилось)
batch_started запущен пакет шаблон, строк, приложений
import_started запущено сопоставление классификатор
licence_installed поставлена лицензия откуда
mcp_token_issued выпущен ключ ассистента —
mcp_tool_called вызов инструмента ассистентом инструмент, успех, миллисекунды, ошибка
К каждому событию добавляются workspace, plan, service и build.

Ассистент считается так же

Каждый вызов MCP-инструмента — событие mcp_tool_called: какой инструмент, сработал ли, сколько занял и текст ошибки, если не сработал. Это самая полезная часть аналитики, потому что ассистент — самый активный пользователь продукта, а его отказ («шаблона нет», «данные не по схеме») — это готовый список того, что людям мешает.

Пишется вызов, а не его содержимое: имя инструмента, workspace, тариф, длительность. Данные документа — тайна клиента; то, что ассистент напечатал договор, — метрика. По той же причине журнал действий не хранит тела документов.

Актор — хеш токена: один ассистент считается одним актором, два разных различаются, сам токен никуда не уходит.

Чего не отправляется

Содержимого документов, имён файлов, адресов почты, названий контор и workspace. Человек превращается в псевдоним — хеш его идентификатора записи (u_9f2c…), workspace — в свой случайный идентификатор. Печатать договор — рабочая тайна; то, что его напечатали, — метрика.

Где смотреть

Кабинет: страница /analytics — доступна владельцу workspace, потому что показывает цифры всей установки. Там же таблица событий, поле SQL и четыре готовых вопроса: печати по дням, какие шаблоны, вызовы ассистента, ошибки ассистента. Результат запроса — таблица, столбцы в том порядке, в котором сервер их вернул.

Чтение настраивается отдельно от записи: ключ записи умеет только добавлять события, а странице нужен сервисный ключ с правом events:read.

export PORCUPINE_PROJECT=d3346824-…              # id проекта
export PORCUPINE_API_TOKEN=pc_sk_…               # сервисный ключ, право events:read

Кнопка «Проверить связь» отправляет пустой пакет и отвечает на первый вопрос оператора: неверный ключ или недоступный хост. Счётчики те же, что отдаёт GET /api/analytics в JSON — этого достаточно для мониторинга.

Как включить

export PORCUPINE_URL=http://analytics.example.com
export PORCUPINE_WRITE_KEY=pk_...        # публичный ключ записи проекта

Перезапустите кабинет. Проверить, что связь есть: GET /api/analytics отвечает счётчиками отправленных, отброшенных и неудачных событий и последней ошибкой — по ним видно разницу между «никто не пользуется», «не настроено» и «хост отказывает».

Почему это не может сломать печать

Клиент internal/analytics отправляет события в фоне: у него ограниченная очередь, и при переполнении событие отбрасывается и считается, а не ждёт. Ошибка аналитики не влияет на печать документа, установку шаблона или запуск прогона — это проверено тестом TestUnreachableHostNeverBlocks.

Неудачи делятся на два вида: 429 и 5xx повторяются с тем же message_id (дедупликация на стороне Porcupine, поэтому повтор не превращается в двойной счёт), а 4xx не повторяется вовсе — неправильное событие не станет правильным от второй попытки.

Запросы

События лежат в таблице events проекта. Число напечатанных документов по дням:

SELECT CAST(event_time AS DATE) AS day, count(*) AS printed
FROM events
WHERE event = 'document_printed'
GROUP BY day ORDER BY day

Какие шаблоны печатают, по убыванию:

SELECT json_extract_string(properties, '$.template') AS template,
       count(*) AS prints,
       sum(CAST(json_extract_string(properties, '$.pages') AS INTEGER)) AS pages
FROM events
WHERE event = 'document_printed'
GROUP BY template ORDER BY prints DESC

Доля документов, напечатанных вместе с приложениями:

SELECT count(*) FILTER (WHERE CAST(json_extract_string(properties, '$.appendices') AS INTEGER) > 0) * 100.0
       / count(*) AS with_appendix_percent
FROM events
WHERE event = 'document_printed'

Воронка: вошли → установили шаблон → напечатали:

SELECT
  count(DISTINCT person_id) FILTER (WHERE event = 'signed_in') AS signed_in,
  count(DISTINCT person_id) FILTER (WHERE event = 'template_installed') AS installed,
  count(DISTINCT person_id) FILTER (WHERE event = 'document_printed') AS printed
FROM events
WHERE event_time > now() - INTERVAL 30 DAY

Свой экземпляр для разработки

git clone https://github.com/olup/porcupine && cd porcupine
# README → quickstart: создаёт .env со случайным bootstrap-секретом
docker compose -f compose.yaml -f compose.oauth.yaml -f compose.release.yaml up -d
curl --fail --silent --retry 30 --retry-all-errors --retry-delay 1 http://localhost:8080/readyz

Экземпляр поднимается на http://localhost:8080. Проект и ключ записи выдаются в его интерфейсе; дальше PORCUPINE_URL и PORCUPINE_WRITE_KEY выше — и события из кабинета поедут туда.

У Porcupine нет своих экранов: это API, MCP и CLI. Поэтому админка живёт в кабинете (страница /analytics), а не в отдельном приложении.

Внутренний домен

Если установка опубликована в tailnet, Porcupine удобно держать там же — наружу он не смотрит:

tailscale serve --bg --https=8443 18080
tailscale serve status
# https://<host>.<tailnet>.ts.net:8443 -> http://127.0.0.1:18080

Порт 8443, а не 443: 443 на сервере обычно занят обратным прокси, и tailscale serve в этом случае падает с bind: address already in use. PORCUPINE_OAUTH_ISSUER должен совпадать с этим адресом символ в символ, иначе OAuth-клиенты получат токен не для того ресурса.

← Все страницы документации