Перейти к основному содержимому

UC-ACT-004. Агент/менеджер видит активность инвестора (для карточки лида)

Назначение

Описывает сценарий: агент (или Менеджер ПИН) открывает карточку лида инвестора в CRM и видит его поведенческую активность на платформе PIN — какие объекты недвижимости и как активно инвестор смотрел (сколько раз, сколько суммарно/в среднем времени, когда в последний раз) и хронологию действий (применённые фильтры, поиск, избранное, подборки). Это pull-режим: при открытии карточки лида CRM по m2m internal API дозагружает историю из user-activity и мёржит её со своей накопленной push-лентой. Связан с контекстом user-activity (модули views/actions); потребитель и владелец авторизации лида — crm.

Metadata

ПолеЗначение
IDUC-ACT-004
Контекстuser-activity (см. docs/06-services/user-activity/); чтение из crm (карточка лида)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольАгент (через crm) / МенеджерПИН (прямой доступ, A1)
Permissions / scopem2m crmuser-activity.activity.read-internal (просмотры) + user-activity.history.view-by-user (лента); авторизацию «активный лид агента на инвестора» делает crm (delegated). МенеджерПИН — user-activity.history.view-by-user напрямую (A1)
Tenantuser-activity вне tenant (ADR-0052 п.2)
Record-levelизоляция по целевому UserId инвестора + его SessionId («Правило чтения истории», ../domain/property-object-view.md); чужая активность недоступна

Предусловие

  • Агент авторизован в crm; у него есть активный лид на инвестора (иначе A2). Лид хранит логическую ссылку InvestorUserId (../../crm/domain/lead.md, ../../crm/domain/client.md).
  • Инвестор идентифицирован по UserId (identity); его существование user-activity синхронно не проверяет (чужой контекст — нет активности ⇒ пустой результат, а не 404).
  • Сервис user-activity доступен (PostgreSQL up); иначе деградация карточки — A5.

Триггер

ПолеЗначение
ИсточникUI navigation (в crm) → m2m API call (в user-activity)
Триггерящий элементОткрытие карточки лида /crm/clients/[clientId] (вкладка «Активность»/«События»)

Основной Поток

  1. Агент открывает карточку лида инвестора и вкладку активности.
  2. Backend crm резолвит целевой investorUserId из лида и проверяет record-level авторизацию: у агента есть активный лид на этого инвестора (проверка — на стороне crm; user-activity доверяет userId из маршрута в m2m-контексте).
  3. crm (m2m, service-token) параллельно вызывает два internal-endpoint-а user-activity:
  4. user-activity: проверяет m2m scope internal + permission (user-activity.activity.read-internal / user-activity.history.view-by-user); каждая операция read-only (дом — api-доки, здесь дельта):
    • Правило чтения истории: определяет множество сессий инвестора (сессии с этим UserId, индекс ix_sessions_user_id) — так гостевые просмотры/действия до входа (в позже связанной сессии) попадают в историю без backfill UserId.
    • property-object-activity: группирует Inactive-PropertyObjectView по PropertyObjectId (для просмотров, где UserId == userId или SessionId ∈ сессии инвестора) → { PropertyObjectId, ViewsCount, TotalDurationSeconds, AvgDurationSeconds (derived), LastViewedAt }, сортировка по LastViewedAt desc. Open (Active) просмотры (Duration == null) — не входят.
    • timeline: страница ActionEvent (../domain/action-event.md) с предикатом UserId∨SessionId + From/To + ActionTypes, сортировка OccurredAt desc; для ViewPropertyObject- элементов дочитывается ViewDurationSeconds из связанного PropertyObjectView; FilterSnapshot — typed AppliedFilterModel (ContractVersion = 1).
    • Record-level гарантирован фильтром по UserId/SessionId; вне tenant (ADR-0052).
  5. Операции read-only: состояние не меняется, доменных/интеграционных событий не поднимается. Побочный эффект — read-sensitive access-audit (actor, targetUserId, без PII).
  6. user-activity возвращает страницы UserPropertyObjectActivityItem и UserActionTimelineItemModel → HTTP 200 (пусто → Items = [], TotalCount = 0, не 404).
  7. crm мёржит pull-выдачу со своей накопленной push-лентой (консюмер ../../crm/consumers/user-activity-actions.md событий action-tracked/property-object-view-ended), дедуп по ActionEventId; display-данные объектов недвижимости (PropertyObjectId) добирает батчем в catalog.
  8. Фронт crm рендерит в карточке лида блок «Активность по объектам недвижимости» (сколько раз / сколько времени / когда смотрел) и хронологию действий (фильтры/поиск/избранное/подборки) — «что смотрел и искал клиент».

Альтернативные Потоки

A1. Менеджер ПИН — прямой доступ (не через активный лид)

  1. Actor — Operator(Admin) с permission user-activity.history.view-by-user; те же endpoints, платформенный scope (без проверки «активного лида»). Read-sensitive audit фиксирует targetUserId.
  2. Возврат к основному потоку с шага 4.

A2. У агента нет активного лида на инвестора

  1. crm не находит активный лид (record-level) и не вызывает user-activity → отдаёт 403 в свой фронт. Авторизация — целиком на стороне crm (delegated); internal-endpoint не затрагивается.

A3. Пустая активность / инвестор без просмотров

  1. Оба endpoint-а → 200 с Items = [], TotalCount = 0 (несуществующий userId — тоже пустой результат, не 404). Фронт crm показывает empty-state «Активности пока нет». Сценарий завершается успешно.

A4. Свежесть: незакрытые просмотры и лаг консюмеров

  1. Недавно открытые просмотры (Active, Duration == null) в property-object-activity не входят; в timeline приходят с ViewDurationSeconds = null — заполнятся после закрытия worker-ом activity-closer (10–15 мин, ADR-0056).
  2. Записи AddToFavorites*/AddToSelection появляются после обработки событий favorites/selections (eventual). Поток не меняется — карточка дозагрузится при обновлении.

A5. user-activity недоступен

  1. Endpoint-ы → 503/500; crm рендерит карточку лида без блока pull-активности (graceful degradation) или только со своей накопленной push-лентой. Возврат к основному потоку при повторном открытии.

Постусловие

  • Состояние user-activity не изменилось (read-only: ни мутаций, ни доменных, ни интеграционных событий).
  • Агент/менеджер увидел активность только целевого инвестора (record-level UserId + его сессии); чужая активность недоступна ни при каких параметрах.
  • Свежесть eventual: закрытые просмотры видны после polling-цикла worker-а; consumer-записи — после обработки событий; горизонт данных — retention 12 мес сырых записей (ADR-0056; старше — dev-stats).
  • Зафиксирован read-sensitive access-audit (actor service/operator + targetUserId, без PII).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
VALIDATION_FAILED400UserId = Guid.Empty, From > To, PageSize ∉ 1..100исправить параметры запроса crm
UNAUTHENTICATED401нет валидного m2m service-tokenобновить service-token; повтор
USER_ACTIVITY_FORBIDDEN403нет permission (activity.read-internal/history.view-by-user); агент без активного лида — отклоняет crm до вызова (A2)скрыть блок активности
— (не ошибка)200неизвестный UserId / нет активности (A3)empty-state «Активности пока нет»
USER_ACTIVITY_UNAVAILABLE503PostgreSQL недоступен (A5)карточка без блока активности; retry

События

Сценарий read-only: доменных и интеграционных событий не поднимает. CRM параллельно накапливает push-ленту из событий, эмитируемых в других UC:

СобытиеTopicConsumers
ActionTrackedKafkaEvent (push-двойник, при записи действий)pin.user-activity.action-tracked.v1crm (накопление ленты лида), news
PropertyObjectViewEndedKafkaEvent (push-двойник, при закрытии просмотра)pin.user-activity.property-object-view-ended.v1crm (лента лида), catalog, dev-stats

Кросс-сервисные контракты (crmuser-activity)

user-activity предоставляет (internal m2m, read-only; дом контрактов — api-доки):

АспектГарантия
Пустой результатнеизвестный / безактивный userId200, Items = [], TotalCount = 0 (не 404, A3)
Изоляцияrecord-level по целевому UserId + его сессии («Правило чтения»); чужие записи недоступны при любых параметрах запроса
Порядок / пагинацияtimelineOccurredAt desc; property-object-activityLastViewedAt desc; стабильная страничная выборка (page/pageSize ∈ 1..100)
Полнота просмотровв property-object-activity — только закрытые (Inactive) просмотры; Active (Duration == null) исключены; в timeline открытый просмотр отдаётся с ViewDurationSeconds = null (A4)
Горизонт данныхсырые записи — retention 12 мес (ADR-0056); глубже — многомерная аналитика dev-stats/ClickHouse (ADR-0057)
Аудитread-sensitive access-audit (actor, targetUserId, без PII) на каждый internal-вызов

user-activity ожидает от crm: валидный m2m service-token + permission (activity.read-internal / history.view-by-user); доверенный userId в маршруте — record-level «активный лид агента на инвестора» проверяет сам crm (delegated, A2), user-activity его не перепроверяет.

Pull + push (сборка на стороне crm). Карточка мёржит pull-выдачу этого UC с накопленной push-лентой консюмера action-tracked/property-object-view-ended (user-activity-actions.md); дедуп по ActionEventId — одна и та же запись из обоих источников не задваивается. При недоступности user-activity (A5) — деградация до push-ленты без блокировки карточки.

Acceptance Criteria

#КритерийПроверка
AC1Агент видит по каждому объекту недвижимости ViewsCount/TotalDurationSeconds/AvgDurationSeconds/LastViewedAtконтракт-тест property-object-activity
AC2Хронология действий содержит ViewPropertyObject/ApplyFilter/Search/*Favorites/AddToSelection с OccurredAt desc, FilterSnapshot/SearchQuery — где применимоконтракт-тест timeline
AC3Возвращается активность только целевого инвестора; запрос по чужому userId не отдаёт данные другоготест изоляции record-level (обязателен)
AC4История включает гостевые просмотры/действия до входа (через связанные сессии — «Правило чтения»)фикстура: гость → вход в той же сессии
AC5Агент без активного лида не получает данные — crm отдаёт 403 и не вызывает user-activity (A2)тест delegated-авторизации в crm
AC6Незакрытые просмотры (Active) не входят в property-object-activity; в timeline ViewDurationSeconds = null (A4)тест открытого просмотра
AC7Пустая активность / несуществующий userId200, Items = [] (не 404) (A3)тест пустого состояния
AC8PII отсутствует в ответе; SearchQuery в логах усечён (Internal); access-audit targetUserId записанпроверка ответа и логов

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные Документы