Описывает сценарий: агент, работая с активным лидом на инвестора, просматривает объекты недвижимости в
своих рабочих списках (публичный каталог, шахматка проекта, избранное, подборки) и видит поверх каждой
карточки контекст своего клиента-инвестора — сколько раз и как долго инвестор смотрел объект, лежит ли
объект в избранном (у самого агента и/или у инвестора — 3-state) и в какие подборки инвестора он входит. Это
помогает агенту вести диалог «по интересам клиента»: понимать, что инвестор уже смотрел и отбирал, и
предлагать релевантные объекты.
Ключевой принцип (ADR-0056, Часть D): каталог остаётся платформенной проекцией без персонализации —
публичные методы каталога/избранного/подборок и их карточки не меняются. Обогащение «в контексте инвестора»
живёт композицией в аутентифицированном CRM-слое: фронт агента вызывает публичные списки и параллельно —
пакетный CRM-метод контекста по списку PropertyObjectId, затем накладывает контекст оверлеем поверх уже
отрисованных карточек. Авторизация — делегированная по активному лиду (паттерн
UC-ACT-004): право «активный лид
агента на инвестора» проверяет сам crm перед вызовом internal-источников; источники доверяют переданному
userId и повторно его не перепроверяют.
crm (композиция; источники — user-activity / favorites / selections)
Тип
основной (+ A1 руководитель/Менеджер ПИН, A2 нет активного лида, A3 нет доступа к лиду, A4 объект без контекста, A5 деградация источника, A6 eventual-свежесть просмотров)
Агент-исполнитель / куратор лида (через crm); руководитель агентства и Менеджер ПИН — A1
Permissions / scope
crm.leads.view; scope — организация (агентство): TenantId = AgencyCompanyId; руководитель — весь свой TenantId; Менеджер ПИН — платформа
Tenant
контекст собирается в TenantId актора; lead.TenantId обязан совпасть
Record-level
доступ = актор ∈ активные участники агентской роли лида (ExecutorAgentUserId = actor / членство) или руководитель компании-владельца или Менеджер ПИН; иначе CRM_LEAD_FORBIDDEN (A3)
Агент авторизован в crm, имеет permission crm.leads.view, и у него есть активный лид с основным
инвестором (../domain/lead.md, INV-LEAD-7) — иначе A2/A3.
Основной инвестор лида идентифицирован по PrimaryInvestorUserId (производная пары «исполнитель ↔ основной
инвестор», INV-CRM-001).
Публичные проекции каталога/избранного/подборок доступны (объекты отрисовываются платформенно, без
персонализации).
Источники контекста (user-activity, favorites, selections) доступны; при недоступности отдельного —
деградация секции оверлея (A5), без отказа сценария.
Открытие списка объектов «в контексте лида»: каталог /lots, шахматка проекта /hotels, избранное /favorites, подборки /collection — с активным лидом инвестора в рабочем контексте
Агент выбирает активный лид как рабочий контекст и открывает список объектов (каталог/шахматка/избранное/
подборки).
Фронт агента загружает платформенную проекцию списка публичными методами каталога/избранного/подборок
(без персонализации, ADR-0056 Часть D) и получает набор карточек с их PropertyObjectId.
проверяет record-level авторизацию по лиду (актор — участник агентской роли / руководитель / Менеджер
ПИН) в рамках TenantId; иначе CRM_LEAD_FORBIDDEN (A3);
резолвит PrimaryInvestorUserId из лида (INV-LEAD-7);
по доверенному userIdделегированно и пакетно дозагружает три internal-источника по
PropertyObjectIds:
просмотры инвестора — user-activity: { Count, TotalDurationSeconds, LastViewedAt } по объекту
(только закрытые просмотры; открытые не входят);
3-state избранного — favorites по паре владельцев [ActorUserId, PrimaryInvestorUserId]:
FavoritedByAgent / FavoritedByInvestor — независимые флаги как наличие строки Favorite
соответствующего владельца (Note не запрашивается и не отдаётся кросс-владельца);
подборки инвестора — selections: список подборок (SelectionId, Title), в которые объект
входит.
crm сшивает три источника по PropertyObjectId и возвращает PropertyObjectInvestorContextListModel —
по одному элементу на каждый запрошенный (после дедупликации) объект, включая объекты с пустым контекстом
(нули/false/пустой список). Состояние не меняется — доменных/интеграционных событий не поднимается;
побочный эффект — read-sensitive access-audit (actor, PrimaryInvestorUserId, без PII).
Фронт агента накладывает вернувшийся контекст оверлеем поверх ранее отрисованных платформенных
карточек, сопоставляя по PropertyObjectId:
бейдж «Инвестор смотрел N раз · Mм» (из InvestorViews; нет просмотров — бейдж не показывается);
индикатор избранного 3-state: «не в избранном» (нет ни у кого) / «у меня» (FavoritedByAgent) /
«у инвестора» (FavoritedByInvestor) — флаги независимы, могут гореть оба;
метки подборок инвестора, в которые входит объект (InInvestorSelections).
Агент при скролле/пагинации/смене фильтров публичного списка повторяет шаги 2–6 для новой порции видимых
PropertyObjectId — контекст дозагружается для актуального набора карточек.
Актор — руководитель компании-владельца лида (весь свой TenantId) или Менеджер ПИН (platform-scope).
Record-level по лиду проходит по членству/руководству/платформе; целевой инвестор — тот же
PrimaryInvestorUserId лида. Read-sensitive audit фиксирует PrimaryInvestorUserId. Возврат к основному
потоку с шага 4.
A2. Нет активного лида в контексте
Агент открывает каталог без выбранного активного лида. Фронт показывает только платформенную
проекцию (каталог/избранное/подборки без персонализации) и не вызывает CRM-метод контекста —
обогащение недоступно, каталог работает как обычная площадка. Сценарий завершается без контекста инвестора.
A3. Нет доступа к лиду
Актор не участник агентской роли лида / не руководитель / не Менеджер ПИН, либо лид не в его TenantId.
crm отклоняет запрос контекста — CRM_LEAD_FORBIDDEN (403) / CRM_LEAD_NOT_FOUND (404) — и не
вызывает internal-источники. Платформенные карточки остаются видны, оверлей контекста не накладывается.
A4. Объект без контекста инвестора
Часть объектов списка инвестор не смотрел, не отмечал в избранном и не добавлял в подборки. Для них ответ
содержит элемент с пустым контекстом (Count = 0, LastViewedAt = null, оба флага false,
InInvestorSelections = []). Фронт не показывает по ним бейджей — карточка выглядит как обычная
платформенная. Индикатор избранного — состояние «не в избранном».
A5. Деградация источника контекста
Один из internal-источников (user-activity/favorites/selections) недоступен. CRM-метод возвращает
200 с «пустой» секцией недоступного источника (просмотры — нули, флаги — false, подборки — пусто), а
остальные секции — заполнены (graceful degradation, как в
UC-ACT-004 A5). Фронт
накладывает оверлей по пришедшим данным. Полная недоступность лида/CRM — не деградация, а отказ (CRM_LEAD_*).
A6. Свежесть просмотров (eventual)
Инвестор только что открыл объект — просмотр ещё не закрыт polling-циклом worker-а user-activity (10–15
мин, ADR-0056). Такой открытый просмотр в InvestorViews не учитывается (Count/TotalDurationSeconds
отстают) — не ошибка; при следующем открытии списка бейдж догонит фактическую активность. Избранное и
подборки — актуальны на момент чтения соответствующих источников.
Состояние не изменилось (read-only: ни мутаций, ни доменных, ни интеграционных событий). Публичные проекции
каталога/избранного/подборок не персонализированы — обогащение существует только оверлеем в агентском
UI (ADR-0056 Часть D).
Агент увидел контекст только основного инвестора данного активного лида; чужой контекст недоступен ни
при каких параметрах запроса.
3-state избранного выведено из владельца строки Favorite (нет строки → «не в избранном»; агент →
«у меня»; инвестор → «у инвестора»; флаги независимы); личная заметка (Note) кросс-владельца не раскрыта.
Свежесть eventual: просмотры отстают на polling-цикл worker-а; избранное/подборки — на момент чтения.
Зафиксирован read-sensitive access-audit (actor, PrimaryInvestorUserId, без PII) на вызов контекста.
Сценарий read-only: доменных и интеграционных событий не поднимает. Просмотры/избранное/подборки инвестора
наполняются в других UC (просмотр объекта инвестором, добавление в избранное, сборка подборки) —
здесь лишь читаются композицией.
Кросс-сервисные контракты (crm ↔ источники контекста)
публичные методы/карточки каталога/избранного/подборок не меняются; обогащение — только оверлей в CRM-слое (ADR-0056 Часть D)
Соответствие 1:1
на каждый запрошенный (после дедупликации) PropertyObjectId — ровно один элемент контекста (в т.ч. пустой), для строгого оверлея по id
Делегированная авторизация
«активный лид агента на инвестора» проверяет crm; user-activity/favorites/selections доверяют userId и не перепроверяют
3-state избранного
вывод из владельца строки Favorite: нет строки → «не в избранном»; агент → «у меня»; инвестор → «у инвестора»; флаги независимы; Note не отдаётся кросс-владельца
Изоляция
контекст — только основного инвестора данного лида; чужой недоступен при любых параметрах
При активном лиде агент видит по объекту просмотры инвестора (Count/TotalDurationSeconds/LastViewedAt) оверлеем; каталог загружен платформенной проекцией без персонализации
тест композиции: публичный список + контекст-метод
AC2
Индикатор избранного 3-state: «не в избранном» / «у меня» / «у инвестора»; флаги независимы (могут гореть оба); Note инвестора агенту не отдаётся
тест 3-state независимости + отсутствие Note
AC3
По объекту показываются подборки инвестора (InInvestorSelections), в которые он входит
контракт-тест подборок
AC4
Контекст — только основного инвестора данного лида; чужой контекст не отдаётся ни при каких параметрах
тест изоляции record-level (обязателен)
AC5
Нет активного лида в контексте → фронт не вызывает контекст-метод, показывает чистую платформенную проекцию (A2)
тест «каталог без лида»
AC6
Актор без доступа к лиду → 403 CRM_LEAD_FORBIDDEN / 404 CRM_LEAD_NOT_FOUND, источники не вызываются, оверлей не накладывается (A3)
тест delegated-авторизации
AC7
Объект без активности/избранного/подборок → элемент с пустым контекстом (нули/false/[]), бейджей нет (A4)