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

UC-UAC-001. Агрегированная активность нескольких инвесторов-участников лида

Назначение

Описывает сценарий: у лида в CRM может быть несколько инвесторов-участников — основной инвестор и со-инвесторы/созаёмщики (InvestorPrimary + сколько угодно InvestorSecondary). Агент (или менеджер ПИН) открывает карточку лида и хочет видеть поведенческую активность всех инвесторов лида как единую картину: какие объекты недвижимости смотрела «сторона покупателя» в совокупности, кто из со-инвесторов чем интересовался, и сводную хронологию действий. Активность каждого инвестора живёт в user-activity и изолирована по владельцу (record-level по UserId + его сессии), поэтому карточка собирается на стороне crm: CRM резолвит множество инвесторов-участников лида, делает fan-out по m2m internal API user-activity (по одному вызову на инвестора), затем агрегирует и атрибутирует результат по участникам. Связан с контекстом user-activity (модули views/actions); потребитель и владелец авторизации лида — crm.

Префикс ID: в реестре контекст user-activity использует ACT; данный документ заведён под явным ID UC-UAC-001 и опирается на те же internal-контракты, что и одиночный UC-ACT-004 (активность одного инвестора).

Metadata

ПолеЗначение
IDUC-UAC-001
Контекстuser-activity (см. docs/06-services/user-activity/); чтение из crm (карточка лида)
Типосновной (+ A1 менеджер ПИН, A2 нет активного лида, A3 инвесторы без активности, A4 пересечение интересов, A5 частичная недоступность, A6 участник добавлен/снят)
Статусdraft

Актор

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

Предусловие

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

Триггер

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

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

  1. Агент открывает карточку лида и вкладку активности.
  2. Backend crm резолвит множество целевых инвесторов лида (InvestorPrimary + все InvestorSecondary) и проверяет record-level авторизацию: лид активен и принадлежит агенту (проверка целиком на стороне crm; user-activity доверяет userId из маршрута в m2m-контексте). Инвесторы вне состава лида в множество не попадают.
  3. crm делает fan-out — для каждого инвестора параллельно вызывает два internal-endpoint-а user-activity (по одному набору на инвестора):
  4. user-activity на каждый вызов независимо: проверяет m2m scope internal + permission; применяет «Правило чтения истории» (множество сессий инвестора по его UserId, чтобы гостевые просмотры/действия до входа попали в историю); отдаёт агрегаты просмотров (ViewsCount/TotalDurationSeconds/AvgDurationSeconds/ LastViewedAt per объект) и страницу действий; record-level гарантирован фильтром по UserId/SessionId каждого инвестора по отдельности; вне tenant. Каждая операция read-only; побочный эффект — read-sensitive access-audit (actor, targetUserId, без PII) на каждый вызов.
  5. crm агрегирует ответы по инвесторам в единую картину карточки лида:
    • объединяет активность в совокупный интерес «стороны покупателя» и одновременно сохраняет атрибуцию по участнику (какой инвестор смотрел/искал) — карточка показывает и сводку, и разбивку по со-инвесторам;
    • по объектам недвижимости, которые смотрели несколько инвесторов (A4), сводит per-объект показатели (например, суммарные просмотры стороны покупателя) с раскрытием вклада каждого инвестора; дедуп записей ленты — по ActionEventId (одна запись из pull и push-ленты не задваивается);
    • мёржит fan-out-выдачу со своей накопленной push-лентой (консюмер ../../crm/consumers/user-activity-actions.md событий action-tracked/property-object-view-ended) — по каждому инвестору;
    • display-данные объектов недвижимости добирает батчем в catalog.
  6. Операции user-activity состояние не меняют — доменных/интеграционных событий не поднимается.
  7. Фронт crm рендерит карточку лида: сводный блок «Активность инвесторов по объектам» (совокупно + по участникам) и общую хронологию действий с отметкой автора-инвестора — «что смотрела и искала сторона покупателя, и кто именно».

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

A1. Менеджер ПИН — прямой доступ

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

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

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

A3. Инвесторы без активности

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

A4. Пересечение интересов со-инвесторов

  1. Один и тот же объект недвижимости смотрели несколько инвесторов лида. user-activity отдаёт per-инвестор агрегаты независимо; сведение per-объект (совокупные просмотры стороны покупателя + вклад каждого) делает crm на агрегации (шаг 5). Задвоения записей ленты нет — дедуп по ActionEventId.

A5. Частичная недоступность user-activity

  1. Часть fan-out-вызовов вернула 503/500; crm рендерит карточку по успешно полученным инвесторам и своей накопленной push-ленте (graceful degradation), помечая, что данные части инвесторов временно недоступны. Полная недоступность — карточка без pull-блока (только push-лента). Возврат к полной картине при повторном открытии.

A6. Состав инвесторов лида изменился

  1. Инвестор добавлен/снят в составе лида (../../crm/domain/lead.md) → при следующем открытии карточки crm резолвит актуальное множество инвесторов и делает fan-out по нему: активность добавленного включается, снятого — исключается из новой сборки (карточка отражает текущий состав лида, а не исторический). Историческая накопленная push-лента по снятому участнику в новой сводке не показывается.

Постусловие

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

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

КодHTTPУсловиеПоведение клиента
VALIDATION_FAILED400UserId = Guid.Empty, From > To, PageSize ∉ 1..100 в одном из вызововисправить параметры запроса crm
UNAUTHENTICATED401нет валидного m2m service-tokenобновить service-token; повтор
USER_ACTIVITY_FORBIDDEN403нет permission; агент без активного лида — отклоняет crm до вызовов (A2)скрыть блок активности
— (не ошибка)200инвестор без активности / неизвестный userId (A3)empty-state по этому инвестору
USER_ACTIVITY_UNAVAILABLE503часть/все вызовы недоступны (A5)карточка по доступным инвесторам + push-лента; 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, per-инвестор; дом контрактов — api-доки):

АспектГарантия
Гранулярностьэндпоинты — per single userId; агрегация по нескольким инвесторам лида выполняется crm fan-out-ом (нет мульти-user-эндпоинта — record-level изоляция сохраняется на каждом вызове)
Пустой результатнеизвестный/безактивный userId200, Items = [] (не 404, A3)
Изоляцияrecord-level по целевому UserId + его сессии на каждый вызов; активность вне запрошенного инвестора недоступна
Порядок / пагинацияtimelineOccurredAt desc; property-object-activityLastViewedAt desc; стабильная страничная выборка
Полнота просмотровв property-object-activity — только закрытые (Inactive) просмотры; открытые (Active) — с ViewDurationSeconds = null в timeline
Аудитread-sensitive access-audit на каждый вызов (actor, targetUserId, без PII)

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

Fan-out + агрегация (на стороне crm). Единая картина лида собирается crm: N пар вызовов (по инвестору) + merge с push-лентой (дедуп по ActionEventId) + сведение per-объект и атрибуция по участнику. При частичной недоступности (A5) — деградация до доступных инвесторов и push-ленты без блокировки карточки.

Acceptance Criteria

#КритерийПроверка
AC1Для лида с несколькими инвесторами crm делает fan-out по каждому и собирает совокупную активность + разбивку по участникамтест fan-out + агрегации (шаги 3–5)
AC2Каждый per-инвестор вызов изолирован record-level (UserId + сессии); активность инвестора вне состава лида недоступна ни при каких параметрахтест изоляции на каждом вызове (обязателен)
AC3Объект, просмотренный несколькими со-инвесторами, сводится per-объект с раскрытием вклада каждого; записи ленты не задваиваются (дедуп ActionEventId) (A4)тест пересечения интересов
AC4История каждого инвестора включает гостевые просмотры/действия до входа (через связанные сессии — «Правило чтения»)фикстура: гость → вход в той же сессии, на одном из инвесторов
AC5Агент без активного лида не получает данные — crm отдаёт 403 и не вызывает user-activity ни по одному инвестору (A2)тест delegated-авторизации в crm
AC6Инвесторы без активности / неизвестные userId200, Items = []; карточка показывает частичный empty-state, не 404 (A3)тест пустого состояния
AC7Частичная недоступность → карточка по доступным инвесторам + push-лента, без блокировки (A5)тест graceful degradation
AC8Изменение состава инвесторов лида отражается при следующем открытии: добавленный включён, снятый исключён (A6)тест актуального состава
AC9Read-sensitive access-audit записан на каждый инвесторский вызов; PII в ответах нетпроверка ответов и логов

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

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

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