Мониторинг¶
Модуль лояльности ведёт единый журнал бонусных операций и предоставляет операторам набор сводных представлений поверх него: агрегированные метрики начислений и списаний, сигналы об аномалиях, перечень баллов с приближающимся сроком сгорания, аудит ручных начислений и оповещения по техническим и бизнес-правилам. Управление операционным (бизнес-) днём — открытие, закрытие и дневной отчёт — вынесено в отдельный раздел этой же области.
Мониторинг отвечает на вопрос «что происходит сейчас» и показывает то, что разбирают построчно. Сводные величины программы за период — пульс, баллы, акции, уровни, реферальная программа — собраны в отдельном разделе «Отчёты».
Все эндпоинты мониторинга и отчётности доступны только для чтения (за исключением закрытия бизнес-дня) и защищены ролевой моделью OAuth2/Keycloak.
Денежные величины
Суммы во всех ответах передаются в минорных единицах баллов (поля с суффиксом Minor,
целочисленные). Это исключает ошибки округления при агрегации.
Экран «Мониторинг»¶
Заголовок экрана — «Мониторинг бонусов», подзаголовок — «Начисления, списания, аномалии, сгорание — за период».
Период. Переключатель ширины окна — «7 дней», «30 дней», «90 дней» (по умолчанию 30) — и рядом выбор диапазона дат с полями «Начало» и «Конец». Любую из границ можно оставить пустой: без дат выдача строится за последние N дней, с одной датой вторая граница достраивается по правилам произвольного периода, с обеими — ширина не участвует. Даты берутся целыми сутками в часовом поясе программы. Очистка диапазона возвращает выдачу к выбранной ширине. Под подзаголовком выводится подпись «Показано за …» с фактическим окном, которое вернул сервер.
| Блок | Содержимое |
|---|---|
| Строка сводки | Пять плиток: «Начислено», «Списано», «Сгорело» — в баллах за период; «Количество активных акций» и «Ручных начислений» — счётчики. |
| «Аудит ручных начислений» | Таблица журнала ручных начислений за период (см. ниже) с подписью об усечении, если строк больше, чем показано. |
| «Топ акций по начислениям» | Таблица акций с наибольшим объёмом начислений. |
- «Списано» — только траты клиентов: сгоревшие, сторнированные и снятые корректировкой баллы сюда не входят.
- «Сгорело» — плановое сгорание по сроку и сгорание, запущенное оператором. Стоит рядом со «Списано», потому что в журнале это движение того же типа.
- «Количество активных акций» и «Топ акций по начислениям» берутся из статистики акций и от выбранного периода не зависят.
Строка сводки собрана из трёх запросов (сводка, ручные начисления, статистика акций); отказ любого из них скрывает всю строку целиком, а не только свою плитку — единая ошибка вместо частично заполненной сводки. «Повторить» перезапрашивает только упавшие запросы.
Роли и доступ¶
| Роль | Назначение |
|---|---|
LOYALTY_MONITORING_READ |
Чтение сводок, аномалий, истекающих баллов, аудита ручных начислений, журнала аудита (кто/что/когда, обращения к персональным данным); раздел «Отчёты»; чтение состояния и отчёта по бизнес-дню. |
LOYALTY_BUSINESS_DAY_WRITE |
Закрытие операционного дня. |
Действует принцип deny-by-default: при отсутствии требуемой роли запрос отклоняется.
Журнал бонусных операций¶
Все представления мониторинга строятся поверх единого журнала бонусных операций (начисления,
списания, переводы, обмены, сгорания и т. п.). У записи журнала есть тип движения — ACCRUAL
(начисление), REDEMPTION (списание), REVERSAL (сторно начисления), CORRECTION (корректировка) —
и источник. Сгорание записывается движением REDEMPTION с источником EXPIRY (плановое, по сроку)
или MANUAL_EXPIRY (запущенное оператором) — поэтому сводки отделяют его от трат клиента по
источнику. Ручное сгорание, записанное до появления источника MANUAL_EXPIRY, осталось в журнале с
источником MANUAL и от ручного списания не отличается. Каждая операция журнала несёт операционный
день, к которому она относится (см. раздел «Операционный (бизнес-) день» ниже).
Период выдачи¶
Все четыре выдачи /v1/monitoring принимают произвольный период: границы from и to (ISO-8601,
обе включительно) и ширину окна days. Ширина задаёт размер окна, границы — его положение.
| Что задано | Журнальные выдачи (summary, anomalies, manual-accruals) |
Предстоящее сгорание (expiring) |
|---|---|---|
| Ничего | Последние days дней до текущего момента (по умолчанию 30) |
Ближайшие days дней от текущего момента (по умолчанию 14) |
Только from |
От from до текущего момента |
От from на days дней вперёд |
Только to |
days дней, заканчивающиеся в to |
От текущего момента до to |
from и to |
Ровно [from, to], days не участвует |
Ровно [from, to], days не участвует |
Открытой границы не бывает намеренно: запрос по всей истории операций ничем не ограничен по объёму.
Начало позже конца отклоняется 422 invalid_period; так же отклоняется неположительная ширина, если
она участвует в расчёте окна.
Сводка по начислениям и списаниям¶
GET /v1/monitoring/summary?days=30 или GET /v1/monitoring/summary?from=…&to=…
Возвращает агрегированные метрики за период: суммарно начислено, списано клиентами и сгорело (количество и сумма), разбивку по источникам и типам операций, а также дневную динамику.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
days |
int | 30 |
Ширина окна в днях — участвует, только если не задан from |
from |
date-time | — | Начало окна включительно |
to |
date-time | — | Конец окна включительно |
| Поле | Описание |
|---|---|
from, to |
Фактическое окно выдачи |
days |
Ширина фактического окна в полных сутках — считается по окну, а не повторяет параметр запроса |
accruedMinor, accruedCount |
Сумма и число начислений за период |
redeemedMinor, redeemedCount |
Сумма и число списаний клиентами — без сгорания |
expiredMinor, expiredCount |
Сумма и число сгораний — планового и запущенного оператором |
bySource[] |
Разбивка по источнику и типу операции: source, type, totalMinor, count |
dynamics[] |
Динамика по дням: date, accruedMinor, redeemedMinor, expiredMinor |
Итоги считаются из того же разреза по источникам, который уходит в ответ, поэтому сводка всегда
сходится со своей разбивкой. Сторно начислений и корректировки не входят ни в одну из трёх величин —
они видны только в разрезе bySource[].
{
"days": 30,
"from": "2026-08-31T00:00:00+03:00",
"to": "2026-09-30T00:00:00+03:00",
"accruedMinor": 1542300,
"accruedCount": 4821,
"redeemedMinor": 689100,
"redeemedCount": 1903,
"expiredMinor": 120000,
"expiredCount": 48,
"bySource": [
{ "source": "PURCHASE", "type": "ACCRUAL", "totalMinor": 1200000, "count": 3900 },
{ "source": "EXPIRY", "type": "REDEMPTION", "totalMinor": 120000, "count": 48 }
],
"dynamics": [
{ "date": "2026-09-24", "accruedMinor": 51000, "redeemedMinor": 22000, "expiredMinor": 4000 }
]
}
Выявление аномалий¶
GET /v1/monitoring/anomalies?days=30
Возвращает операции, помеченные эвристиками контроля как потенциально аномальные: например, круглые
суммы, нетипично крупные начисления/списания, операции без привязки к покупке. Каждая запись несёт
набор меток (flags), поясняющих сработавшие признаки.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
days |
int | 30 |
Ширина окна в днях — участвует, только если не задан from |
from, to |
date-time | — | Границы окна включительно (см. «Период выдачи») |
| Поле | Описание |
|---|---|
operationId |
Идентификатор операции в журнале |
externalId |
Внешний идентификатор клиента |
type, source |
Тип и источник операции |
amountMinor |
Сумма операции |
reason, operator |
Основание и оператор (если применимо) |
createdAt |
Время операции |
flags[] |
Сработавшие признаки: NON_POSITIVE_AMOUNT, ROUND_AMOUNT, LARGE_AMOUNT, ACCRUAL_WITHOUT_PURCHASE |
Назначение
Список аномалий — инструмент сигнализации для последующего ручного разбора, а не автоматической блокировки. Решение по каждой записи принимает оператор.
Баллы с приближающимся сгоранием¶
GET /v1/monitoring/expiring?days=14
Возвращает лоты баллов, срок действия которых истекает в пределах указанного окна.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
days |
int | 14 |
Окно до сгорания в днях — участвует, только если не задан to |
from, to |
date-time | — | Границы окна сгорания включительно (см. «Период выдачи») |
Поля ответа: accountNumber, externalId, lotId, currentBalanceMinor, expiresAt.
Начало окна в прошлом допустимо: лот с наступившим сроком остаётся открытым до ближайшего прогона сгорания, и вопрос «что должно было сгореть, но ещё не сгорело» законен.
Аудит ручных начислений¶
GET /v1/monitoring/manual-accruals?days=30
Возвращает журнал начислений, выполненных вручную оператором, с указанием основания. Предназначен
для контроля и прослеживаемости ручного вмешательства. Период задаётся так же, как у остальных
выдач: days, from, to.
Поля ответа: operationId, externalId, amountMinor, reason, operator, createdAt.
Настоящий total в списках¶
BREAKING (26.3.0). Списки аномалий, истекающих баллов и ручных начислений раньше отдавались голым массивом, обрезанным потолком в 500 строк, — клиент не мог отличить обрезанную выдачу от полной, потому что общее число не передавалось. Экран вводил в заблуждение ровно тогда, когда данных много, — то есть когда на него и смотрят.
Все три ручки отдают конверт {items, page, size, total}, где total — настоящее число строк за
период, а size — потолок выдачи (500). Если строк больше, чем показано, под таблицей на экране
мониторинга выводится подпись «Показаны первые N из M — уточните период, чтобы увидеть остальные.»
Аномалии считаются по всему периоду, а не по первым 500 строкам
Раньше выдача сначала отбирала последние 500 операций и уже среди них искала аномалии — при потоке операций больше пятисот за период экран показывал считанные аномалии не потому, что их мало, а потому что выборка до них не доходила. Признак аномалии теперь один предикат в базе, а ограничение по числу строк применяется уже после фильтра.
Оповещения по техническим и бизнес-правилам¶
Модуль отдаёт метрики Prometheus и (опционально) правила оповещений PrometheusRule, чтобы о
проблеме узнавали не только те, кто догадался открыть экран мониторинга.
Бизнес-метрики:
| Метрика | Значение |
|---|---|
loyalty.monitoring.anomalies |
Число операций с признаками аномалии за сутки. |
loyalty.monitoring.manual.operations, loyalty.monitoring.manual.minor |
Число и сумма ручных операций за сутки — включая сгорание, запущенное оператором. |
loyalty.monitoring.expiring.minor |
Объём баллов к сгоранию на ближайшую неделю (информационный показатель, оповещения по нему нет). |
Значения считаются лениво и кэшируются на минуту: отказ расчёта не обнуляет показатель (ноль выглядел бы как «аномалий больше нет»), а сохраняет прежнее значение.
Правила оповещений (выключены по умолчанию, включаются на уровне развёртывания; пороги настраиваются под контур):
| Правило | Условие | Важность |
|---|---|---|
| Сервис недоступен | Цель пропала из сбора метрик дольше 5 минут. | critical |
| Очередь приёма не разбирается | Возраст самого старого необработанного сообщения приёма превышает порог дольше 5 минут. | warning |
| Команды приёма отложены в разбор | Число команд в статусе «В разборе» превышает порог дольше 15 минут. | warning |
| Сервис отвечает ошибками | Доля ответов 5xx выше порога 10 минут подряд. | critical |
| Много операций с признаками аномалии | Число аномалий за сутки выше порога дольше 30 минут. | warning |
| Всплеск ручных операций с баллами | Число ручных операций за сутки выше порога дольше 30 минут. | warning |
| Уведомления клиентам не уходят | Самое старое созревшее сообщение очереди исходящих уведомлений ждёт отправки дольше порога 10 минут подряд. | warning |
| Прогон сгорания баллов давно не отрабатывал | На контуре задано расписание сгорания, а последний успешный прогон был дольше порога назад (15 минут подряд). | warning |
Оповещения о предстоящем сгорании больше нет
Прежнее правило «Крупный объём баллов сгорает в ближайшую неделю» срабатывало на большой пачке предстоящих сгораний и молчало, когда прогон сгорания не запускался неделями. Его заменило правило «Прогон сгорания баллов давно не отрабатывал» — по факту работы прогона. Объём предстоящего сгорания остался информационным показателем.
Подробности о развёртывании и метриках — в руководстве администратора, «Наблюдаемость».
Операционный (бизнес-) день¶
Операции лояльности привязаны к операционному дню. Раздел позволяет посмотреть текущий день, закрыть его с открытием следующего и получить дневной отчёт.
Операционный день заполнен у каждой операции журнала бонусных операций: новая операция получает текущий открытый день автоматически, а операциям, записанным до появления этого признака, день проставлен по связанной проводке (где проводки нет — по календарной дате). Поэтому дневной отчёт и сверка итогов дня не теряют старые операции. Если открытого дня нет, заводится день с календарной датой в часовом поясе программы.
Статусы дня (status) отражаются в полях представления DayView: businessDate, status,
openedAt, closedAt.
Текущий и недавние дни¶
GET /v1/business-day
Возвращает текущий операционный день и список недавних дней (за последние 14 дней).
{
"current": {
"businessDate": "2026-06-25",
"status": "OPEN",
"openedAt": "2026-06-25T00:00:00Z",
"closedAt": null
},
"recent": [
{
"businessDate": "2026-06-24",
"status": "CLOSED",
"openedAt": "2026-06-24T00:00:00Z",
"closedAt": "2026-06-25T00:01:12Z"
}
]
}
Закрытие дня¶
POST /v1/business-day/close
Закрывает текущий операционный день и открывает следующий. Требует роль
LOYALTY_BUSINESS_DAY_WRITE.
stateDiagram-v2
[*] --> OPEN: открытие
OPEN --> CLOSED: close
CLOSED --> [*]
Переоткрытие дня не предусмотрено
В любой момент времени открыт ровно один операционный день — это ограничение целостности учёта. Закрытый день переоткрыть нельзя; корректировки вносятся текущим (открытым) днём.
Дневной отчёт¶
GET /v1/business-day/{date}/summary
Возвращает агрегированный отчёт по операциям за конкретную дату: суммарно начислено, списано и общее число операций.
| Поле | Описание |
|---|---|
businessDate |
Дата операционного дня |
accruedMinor |
Сумма начислений за день |
redeemedMinor |
Сумма списаний за день |
operations |
Общее число операций за день |
«Списано» в дневном отчёте считается иначе, чем в сводке
Дневной отчёт относит к списаниям все движения типа REDEMPTION — включая сгорание — и
сравнивается с итогами внешней системы при сверке. Сводка мониторинга и раздел «Отчёты»
показывают сгорание отдельно от трат клиента. Сторно начислений (REVERSAL) и корректировки
(CORRECTION) ни в «начислено», ни в «списано» дневного отчёта не входят, но учитываются в
общем числе операций.
Сводка эндпоинтов¶
| Метод и путь | Назначение | Роль |
|---|---|---|
GET /v1/monitoring/summary |
Сводка по начислениям/списаниям и динамика | LOYALTY_MONITORING_READ |
GET /v1/monitoring/anomalies |
Аномалии операций (конверт с total) |
LOYALTY_MONITORING_READ |
GET /v1/monitoring/expiring |
Баллы с приближающимся сгоранием (конверт с total) |
LOYALTY_MONITORING_READ |
GET /v1/monitoring/manual-accruals |
Аудит ручных начислений (конверт с total) |
LOYALTY_MONITORING_READ |
GET /v1/business-day |
Текущий и недавние операционные дни | LOYALTY_MONITORING_READ |
POST /v1/business-day/close |
Закрыть текущий и открыть следующий день | LOYALTY_BUSINESS_DAY_WRITE |
GET /v1/business-day/{date}/summary |
Дневной отчёт по операциям | LOYALTY_MONITORING_READ |
Эндпоинты отчётов за период (/v1/reports/**) описаны на странице «Отчёты».
Раздел «Администрирование» дополняет мониторинг двумя экранами разбора: «Очередь приёма» — застрявшие асинхронные команды, и «Журнал аудита» — кто, что и когда делал, включая обращения к персональным данным.
В дорожной карте
Выгрузка событий мониторинга во внешний поток (outbox → Kafka → SDE/RT-Flow) и витрины DWH на стороне аналитики в текущей версии модуля не реализованы и запланированы к развитию.