# Трекинг событий Shopping Assistant

Для анализа качества Shopping Assistant передавайте в Gravity Field custom events по ключевым шагам диалога. Эти события помогают понять, открывают ли пользователи чат, отправляют ли сообщения, получают ли ответы и где возникают ошибки или разрывы воронки.

Сначала определите, кто отправляет событие: готовый виджет, товарный шаблон или ваш интерфейс. История ответов сохраняется сервисом отдельно; custom events не создают диалог в кабинете.

## Готовый Web-виджет

В версии `1.41` события отправляются через `GF.API('event', ...)`, а в `properties` передаётся `threadId`. Дополнительные поля из таблицы ниже не добавляются автоматически. У `chat_dialog_end` виджет использует поле `thread_id`, а не `threadId`.

| Источник | Что уже отправляется | Что делает интегратор |
| --- | --- | --- |
| Чат | `chat_dialog_open`, `chat_dialog_close`, `assistant_message_sent`, `assistant_response_received`, `assistant_product_selection_received`, `assistant_suggestion_clicked`, `chat_dialog_end`, `chat_error`. | Не дублирует эти события в обработчиках тех же действий. |
| Встроенная кнопка | `assistant_icon_clicked`. | Для собственной кнопки отправляет этот event при клике, если он нужен в воронке. |
| Оценки | `assistant_feedback_submitted_positive`, `assistant_feedback_submitted_negative`; сохранение реакции выполняется отдельным PUT. | Проверяет успешность feedback-запроса; custom event не подтверждает запись реакции. |
| Товарный шаблон | Показы и клики в рамках настройки шаблона. | Привязывает шаблон к кампании и проверяет engagement. Не вызывает те же tracking URL повторно. |

Виджет не обрабатывает корневые tracking URL ответа генерации автоматически. Для товарных показов выберите один механизм: шаблон либо обработка URL собственным интерфейсом. `assistant_product_selection_received` означает получение блока товаров, а не их видимость.

При использовании своей кнопки:

```javascript
GF.API('event', {
  name: 'assistant_icon_clicked',
  properties: { threadId: assistant.threadId }
});
assistant.open();
```

Событие добавьте в уже существующий обработчик открытия, а не во второй обработчик той же кнопки. Для проверки смотрите запрос события в Network и [правила Web-событий](/Integration/web_integration/events.md).

!!!
В этом разделе описаны custom events ассистента и tracking URL товарных подборок.
!!!

---

## Базовая воронка

Рекомендации ниже относятся к вашему интерфейсу и обработчикам событий. В готовом Web-виджете не отправляйте повторно события, перечисленные выше.

Минимальная воронка диалога:

```text
chat_dialog_open -> assistant_message_sent -> assistant_response_received
```

Если ответ содержит товарную подборку, дополнительно фиксируйте:

```text
assistant_response_received -> assistant_product_selection_received
```

Ошибки и закрытия анализируются отдельно:

```text
chat_error
chat_dialog_close
chat_dialog_end
```

---

## События ассистента

| Событие | Когда отправлять |
| :--- | :--- |
| `chat_dialog_open` | Пользователь открыл чат ассистента. |
| `assistant_message_sent` | Пользователь отправил сообщение ассистенту. |
| `assistant_response_received` | Frontend получил и отобразил ответ ассистента. |
| `assistant_product_selection_received` | Ответ ассистента содержит товарную подборку. |
| `chat_error` | Frontend зафиксировал ошибку в сценарии ассистента. |
| `chat_dialog_close` | Пользователь закрыл чат. |
| `chat_dialog_end` | Диалог завершен. |

### Как считать ответ полученным

Событие `assistant_response_received` отправляйте после того, как ответ успешно получен и добавлен в интерфейс чата. Если API ответил, но пользователь уже закрыл чат или frontend не смог отрендерить ответ, событие отправлять не нужно.

### Как считать товарную подборку

Событие `assistant_product_selection_received` отправляйте, если в ответе ассистента есть блок:

```json
{
  "type": "products",
    "value": [{ "sku": "demo-sku", "name": "Демонстрационный товар" }]
}
```

В собственном интерфейсе не отправляйте это событие, если блок `products` отсутствует или список товаров пустой. Виджет `1.41` отправляет его при обработке каждого блока `products`, включая пустой; событие не подтверждает наличие или видимость карточек.

---

## Рекомендуемые поля

Передавайте технические идентификаторы, которые позволяют связать события одного диалога и одного запроса.
`resourceId` берите из `user.uid` Gravity Field. Его можно получить через [`POST /user`](/Integration/api_integration/v2/personalization/user.md).

| Поле | Назначение |
| :--- | :--- |
| `threadId` | ID диалога. Должен совпадать с `threadId` в `/shopping/generate`. |
| `resourceId` | `uid` пользователя Gravity Field. |
| `messageId` | ID пользовательского сообщения, если клиентская сторона его генерирует. |
| `requestId` | ID запроса к ассистенту, если клиентская сторона его генерирует. |
| `trafficType` | `real` или `test`, если это значение используется в интеграции. |
| `tenant_id` | ID секции Gravity Field. |
| `campaignId` | ID кампании, если ассистент запущен из кампании. |
| `experienceId` | ID experience, если ассистент запущен из кампании. |
| `variationId` | ID variation, если ассистент запущен из кампании. |
| `latencyMs` | Время ожидания ответа, где применимо. |
| `errorCode` | Код ошибки для `chat_error`, если он есть на стороне клиента. |

!!!
Не передавайте в event payload текст сообщений, персональные данные, чувствительные данные пользователя и другие данные, которые не нужны для аналитики воронки.
!!!

---

## Пример custom event

Ниже пример события для API V2. Дополнительные поля передаются в `customProps`.

```json
{
  "type": "assistant_message_sent",
  "name": "Assistant Message Sent",
  "customProps": {
    "threadId": "thread-8f2b1c",
    "resourceId": "demo-user",
    "messageId": "message-1001",
    "requestId": "request-7001",
    "trafficType": "test",
    "tenant_id": "demo-section",
    "campaignId": "demo-campaign",
    "experienceId": "demo-experience",
    "variationId": "demo-variation"
  }
}
```

Для передачи события используйте тот же канал, который уже применяется в вашей интеграции: Web, API или SDK.

---

## Tracking товарных подборок

В ответе Shopping Assistant tracking-события могут находиться на двух уровнях:

| Уровень | Где искать | Когда использовать |
| :--- | :--- | :--- |
| Рекомендательный блок и оценка ответа | `events[]` в корне ответа | Для tracking URL всего блока или `like`/`dislike` при нажатии оценки. |
| Товарная карточка | `response.contents[type="products"].value[].events[]` | Для `visible_impression` и `click` конкретного товара. |

При наступлении события обработайте **все** элементы `events[]` с соответствующим `type`. Для каждого элемента выполните `GET`-запрос по каждому URL из `urls[]`.

Для `like` и `dislike` это нажатие кнопки оценки, а не получение или показ ответа. Полный контракт: [Оценка ответа](reference/feedback.md).

```js
async function sendTrackingEvents(events, type) {
  const urls = events
    .filter((event) => event.type === type)
    .flatMap((event) => event.urls ?? (event.url ? [event.url] : []));

  await Promise.allSettled(
    urls.map((url) => fetch(url, { method: "GET", keepalive: true }))
  );
}

sendTrackingEvents(result.events ?? [], "WRIMP");
sendTrackingEvents(product.events ?? [], "visible_impression");
```

Отправляйте видимый показ один раз для одного фактического показа блока или карточки. Ошибка tracking-запроса не должна блокировать отображение рекомендаций или действие пользователя. Общие правила описаны в разделе [Передача взаимодействий пользователя с кампаниями](/Integration/api_integration/v2/personalization/engagement.md).

---

## Диагностика разрывов

Разрыв воронки возникает, когда пользователь отправил сообщение, но frontend не зафиксировал получение ответа:

```text
assistant_message_sent -> нет assistant_response_received
```

Чтобы такие случаи можно было анализировать, важно:

- использовать стабильный `threadId` на протяжении всего диалога;
- передавать `messageId` или `requestId` для каждого пользовательского сообщения;
- отправлять `chat_error`, если frontend получил ошибку;
- отправлять `chat_dialog_close`, если пользователь закрыл чат;
- передавать `latencyMs` в `assistant_response_received` и `chat_error`, если время ожидания известно.
