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

Сервис Инвестор-Собственник (investor-owner)

Назначение

investor-owner владеет портфелем объектов инвестора («Мои объекты недвижимости», /profile/my-lots) и сопоставлением Собственник ↔ Инвестор. Инвестор видит свои объекты: за сколько купил/продал, сколько и когда заработал, историю изменения цен объекта (ссылка на invest-analytics). Объекты появляются двумя путями: после завершения сделки в CRM (pin.crm.deal-completed.v1) и синхронизацией собственников и владений из внешней системы Talent Owners — с сопоставлением по PhoneNumber и созданием пользователя-инвестора в identity, если его ещё нет (id собственника = id пользователя-инвестора, правило INV-OWN-001).

Metadata

ПолеЗначение
Статусdraft
Owning teamPIN Investor Experience
DomainInvestor Portfolio & Ownership
Data classificationSensitive (PII собственников: телефон/ФИО; финансовые данные: цены покупки/продажи, доход)
Runtime criticalitymedium (недоступность не блокирует вход/каталог/сделки; страдает страница «Мои объекты недвижимости» и синк)

Ownership

ArtifactOwner/Rule
PostgreSQL schemainvestor_owner
API areaspublic (портфель инвестора, self-only), admin (разрешение конфликтов связи, исправление записей — Служба заботы)
Owned entitiesOwnerMappingEntity, InvestorPropertyEntity
Produced eventspin.investor-owner.owner-linked.v1, pin.investor-owner.property-added.v1, pin.investor-owner.property-sold.v1
Consumed eventstalent.owners.owners.v1, talent.owners.ownerships.v1 (assumption — контракты Talent Owners), pin.crm.deal-completed.v1, pin.identity.users.v1 (смена телефона инвестора — контроль расхождений связи)
Search indexesnone (портфель — единицы записей на инвестора; btree-индексов PostgreSQL достаточно, ADR-0056)
Analytics factsnone (история цен — invest-analytics; статистика просмотров — dev-stats)

Ответственность И Границы

  • Делает: реестр связей Owner↔Investor (по телефону, INV-OWN-001) c конфликтами и ручным разрешением; инициирует создание инвестора в identity (internal-API) при отсутствии; ведёт портфель объектов (покупка/продажа/реализованный доход, статусы Active/Sold); отдаёт API «Мои объекты недвижимости» и сводку доходности; публикует события портфеля.
  • НЕ делает: не хранит учётки/секреты (создание пользователя — identity); не хранит и не считает историю цен и прогнозы доходности (invest-analytics); не владеет структурой объектов недвижимости (chessboard) и витриной (catalog); не ведёт сделки (crm); не считает комиссии (rewards); нереализованный доход (текущая оценка − покупка) — read-проекция по текущей цене из invest-analytics/catalog, не хранится.
  • Source of truth: связь Owner↔Investor и её статус; записи портфеля (цены покупки/продажи, RealizedProfit, статус владения).
  • Реплики/проекции: снапшот ФИО/телефона собственника (owner — Talent Owners); иммутабельный PropertyObjectSnapshot объекта недвижимости (owner — chessboard); InvestorUserId — ссылка на identity без FK.

Зависимости

ЗависимостьТипЗачем НужнаFailure mode
PostgreSQL (schema investor_owner)dbОба агрегата + outboxПолный отказ сервиса
KafkaqueueConsumers (Talent Owners, crm, identity) + produced события через outboxAPI портфеля работает по имеющимся данным; синк/появление объектов отстаёт (лаг-алерты)
identity internal-API (provision-investor, будущий путь docs/06-services/identity/api/provision-investor.md)serviceПоиск инвестора по телефону и создание с заданным Id = OwnerIdСвязи остаются в PendingLink; consumer ретраит; конфликты — в admin-очередь
Talent Ownersexternal systemИсточник собственников и владений (Kafka)Синк отстаёт; локальный портфель из сделок PIN не затронут
crm (pin.crm.deal-completed.v1, будущий путь docs/06-services/crm/events/deal-completed.md)queueПоявление купленного объекта в портфелеОбъект появится после восстановления консюмера (идемпотентно по DealId)
chessboard internal-API (get-property-objects-by-ids, будущий путь docs/06-services/chessboard/api/internal/property-objects/get-property-objects-by-ids.md)serviceДозаполнение PropertyObjectSnapshot, если payload события неполонСнапшот создаётся из payload; недостающие поля — retry-дозаполнение
invest-analyticsserviceСсылка «история изменения цен» в карточке объекта (по PropertyObjectId)Карточка без графика цен; портфель работает

Основные Сущности

EntityНазначениеSource Of TruthДокумент
OwnerMappingEntityСвязь Owner (Talent) ↔ Investor (PIN) по телефону; конфликты; INV-OWN-001 (Id = OwnerId = InvestorUserId при создании)investor-owner (связь); Talent Owners (снапшот собственника)domain/owner-mapping.md
InvestorPropertyEntityОбъект портфеля: объект недвижимости, цены покупки/продажи, даты, реализованный доход, статус Active/Sold, источник (сделка PIN / синк Talent)investor-ownerdomain/investor-property.md

ER-обзор

API (карта областей; детали — в api/)

  • Public (инвестор, self-only, record-level InvestorUserId = actor):
    • get-my-properties — список «Мои объекты недвижимости» с фильтром по OwnershipStatus, пагинация Filter;
    • get-my-property — карточка объекта: цены/даты/доход + ссылка на историю цен invest-analytics по PropertyObjectId + актуальные данные объекта недвижимости из catalog;
    • get-my-portfolio-summary — сводка: всего вложено, всего заработано (Σ RealizedProfit по Sold), активных/проданных объектов.
  • Admin (Служба заботы, permissions investor-owner.mappings.manage / investor-owner.portfolio.manage):
    • list-owner-mapping-conflicts — очередь связей Status = Conflict;
    • resolve-owner-mapping-conflict — ручная привязка InvestorUserId (переход Conflict → Linked);
    • archive-investor-property / create-investor-property — компенсирующие исправления записей портфеля (soft-delete + новая запись, с аудитом).
  • Internal / Callbacks: not applicable (межсервисное взаимодействие — только события Kafka).

Events И Consumers

TypeNameTopicНазначениеLink
ProducedInvestorOwnerLinkedKafkaEventpin.investor-owner.owner-linked.v1Связь установлена: profile создаёт профиль, notifications приветствуетevents/owner-linked.md
ProducedInvestorPropertyAddedKafkaEventpin.investor-owner.property-added.v1Объект появился в портфеле (notifications, user-activity)events/property-added.md
ProducedInvestorPropertySoldKafkaEventpin.investor-owner.property-sold.v1Продажа зафиксирована, доход рассчитанevents/property-sold.md
Consumedowner-upserted/owner-deleted (Talent)talent.owners.owners.v1 (assumption)Создание/upsert OwnerMappingEntity, provision инвестора (INV-OWN-001)consumers/talent-owners-sync.md (будущий путь)
Consumedownership-upserted (Talent)talent.owners.ownerships.v1 (assumption)Объекты владения → InvestorPropertyEntity (Source = TalentOwnersSync, идемпотентно по ExternalOwnershipId)consumers/talent-owners-sync.md (будущий путь)
ConsumedDealCompletedKafkaEventpin.crm.deal-completed.v1Купленный объект → портфель (Source = PinCrmDeal, идемпотентно по DealId)consumers/crm-deal-completed.md (будущий путь)
ConsumedUserUpsertedKafkaEventpin.identity.users.v1Контроль смены телефона связанного инвестора (алерт расхождения связи, без авторазрыва)consumers/identity-users.md (будущий путь)

Сквозные Потоки И Окна Согласованности

Два пути наполнения портфеля и что видит пользователь в промежуточных состояниях (механика — в consumer-доках; участники — бизнес/доменные роли и соседние сервисы):

ПотокПоследовательность (кто → кому; sync/async)Окно согласованностиПромежуточное состояние для пользователя
Покупка через PINcrm завершает сделку → pin.crm.deal-completed.v1 (async) → consumer создаёт запись портфеля → pin.investor-owner.property-added.v1 (async) → notifications / user-activityпоявление объекта < 1 мин после завершения сделки (SLO)до обработки события объект ещё не виден в «Мои объекты недвижимости»; уведомление приходит после property-added
Синхронизация Talent OwnersTalent owners (async) → provision в identity (sync, internal-API, INV-OWN-001) → связь Linkedpin.investor-owner.owner-linked.v1 (async); затем Talent ownerships (async) → записи портфеля → property-added / property-soldлаг синка < 15 мин; при недоступности identity связь ждёт в PendingLinkпока связь не Linked, объекты собственника не появляются; при Conflict — ждут ручного разрешения Службой заботы
Установление связи вручнуюСлужба заботы resolve-owner-mapping-conflict (sync) → Conflict → Linkedowner-linked (async); оператор инициирует replay зависших владений из DLQзависит от разбора оператором; после Linked владения проходят INV-8до разрешения объекты собственника не появляются; конфликт виден только в admin-очереди
Текущая оценка портфеляinvest-analytics ← best-effort GET на чтении портфеля (sync, timeout 500 мс)freshness ленты цен ≤ 5 минпри деградации показывается available = false; реализованный доход не затронут

Кросс-топиковый порядок owner → ownership НЕ гарантирован: событие владения, пришедшее раньше связи, ретраится до появления/линковки собственника (INV-8, INV-OWN-001) — см. consumers/talent-owners-sync.md. Все исходящие pin.investor-owner.* события публикуются транзакционно со сменой состояния (outbox) и партиционируются по Id агрегата; телефон/ФИО в них не попадают.

Runtime И Эксплуатация

ОбластьТребование
Health checks/health/live; /health/ready (PostgreSQL, Kafka); /health/startup (миграции применены)
SLO/SLAAPI портфеля p99 < 300 мс; появление объекта после deal-completed < 1 мин; лаг синка Talent Owners < 15 мин
Dashboardinvestor-owner-overview: лаг consumers, конфликты связей, DLQ, созданные инвесторы/объекты в день
AlertsDLQ > 0; лаг любого consumer > 15 мин; рост Status = Conflict (> 10/день); ошибки identity provision-API
Runbooksreplay DLQ синка; разбор конфликтов связи (admin-очередь); компенсирующее исправление записи портфеля
Feature flagsnone (simplicity-first, ADR-0056; включение синка — конфигурация consumer-а, не флаг)

Безопасность

  • Actor types: user-инвестор (self-only портфель), platform (Служба заботы / Менеджер ПИН — конфликты, просмотр с аудитом), system (consumers, m2m identity/chessboard).
  • Permission model: investor-owner.portfolio.view-self (self), investor-owner.portfolio.view / investor-owner.portfolio.manage, investor-owner.mappings.manage (платформенный scope) — каталог permissions в ../identity/domain/permission.md.
  • Sensitive data: телефоны/ФИО собственников (PII, маскирование в логах +7***1234), цены и доход (финансовые данные — только владелец и платформенные роли с аудитом); телефон не публикуется в pin.investor-owner.* событиях.
  • Audit: Version + actor-stamping на обоих агрегатах; ручные разрешения/исправления — только через admin-операции c доменными событиями; физическое удаление отсутствует (soft-delete).
  • Tenant isolation: ADR-0052 — данные инвестора вне tenant (TenantId отсутствует); обязательный record-level фильтр по InvestorUserId через repository-интерсепторы; тесты изоляции обязательны.

Известные Ограничения И Assumptions

  • Имена топиков/контракты Talent Owners (talent.owners.{owners,ownerships}.v1) — assumption до подтверждения schema registry Talent; поля payload фиксируются в consumer-доке.
  • Internal-API identity provision-investor (поиск по телефону + создание с заданным Id) — новая операция identity, документируется по будущему пути docs/06-services/identity/api/provision-investor.md.
  • Смена телефона инвестора в PIN после установки связи не разрывает связь (сопоставление по телефону — только на момент линковки); расхождения мониторятся consumer-ом pin.identity.users.v1.
  • Продажа объекта «через PIN» (сделка продажи в CRM) в текущем объёме не проектируется: MarkSold наполняется синком Talent Owners или admin-операцией; при появлении сделок продажи в crm добавится consumer без изменения модели.
  • Объекты Talent Owners могут не сопоставляться с шахматкой PIN (ProjectId/BuildingId/DeveloperId = null, история цен недоступна) — карточка показывает только данные снапшота.
  • Событие pin.crm.deal-completed.v1 должно нести снапшот объекта и итоговую цену сделки — требование к контракту crm (будущий путь docs/06-services/crm/events/deal-completed.md).

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

  • Домены: owner-mapping · investor-property.
  • Смежные контексты: ../identity/ (учётки, provision), ../crm/ (deal-completed), ../chessboard/ (объекты недвижимости), ../invest-analytics/ (история цен), ../notifications/, ../profile/.
  • Решения: ADR-0052 (tenancy: инвестор вне tenant), ADR-0056 (simplicity-first: PostgreSQL-only, без Redis), technical-assignment.md (контекст investor-owner), service-boundaries.md, glossary.md («Собственник», «Объект инвестора»).