Сервис Инвестор-Собственник (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 team | PIN Investor Experience |
| Domain | Investor Portfolio & Ownership |
| Data classification | Sensitive (PII собственников: телефон/ФИО; финансовые данные: цены покупки/продажи, доход) |
| Runtime criticality | medium (недоступность не блокирует вход/каталог/сделки; страдает страница «Мои объекты недвижимости» и синк) |
Ownership
| Artifact | Owner/Rule |
|---|---|
| PostgreSQL schema | investor_owner |
| API areas | public (портфель инвестора, self-only), admin (разрешение конфликтов связи, исправление записей — Служба заботы) |
| Owned entities | OwnerMappingEntity, InvestorPropertyEntity |
| Produced events | pin.investor-owner.owner-linked.v1, pin.investor-owner.property-added.v1, pin.investor-owner.property-sold.v1 |
| Consumed events | talent.owners.owners.v1, talent.owners.ownerships.v1 (assumption — контракты Talent Owners), pin.crm.deal-completed.v1, pin.identity.users.v1 (смена телефона инвестора — контроль расхождений связи) |
| Search indexes | none (портфель — единицы записей на инвестора; btree-индексов PostgreSQL достаточно, ADR-0056) |
| Analytics facts | none (история цен — 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 | Полный отказ сервиса |
| Kafka | queue | Consumers (Talent Owners, crm, identity) + produced события через outbox | API портфеля работает по имеющимся данным; синк/появление объектов отстаёт (лаг-алерты) |
identity internal-API (provision-investor, будущий путь docs/06-services/identity/api/provision-investor.md) | service | Поиск инвестора по телефону и создание с заданным Id = OwnerId | Связи остаются в PendingLink; consumer ретраит; конфликты — в admin-очередь |
| Talent Owners | external 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-analytics | service | Ссылка «история изменения цен» в карточке объекта (по 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-owner | domain/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
| Type | Name | Topic | Назначение | Link |
|---|---|---|---|---|
| Produced | InvestorOwnerLinkedKafkaEvent | pin.investor-owner.owner-linked.v1 | Связь установлена: profile создаёт профиль, notifications приветствует | events/owner-linked.md |
| Produced | InvestorPropertyAddedKafkaEvent | pin.investor-owner.property-added.v1 | Объект появился в портфеле (notifications, user-activity) | events/property-added.md |
| Produced | InvestorPropertySoldKafkaEvent | pin.investor-owner.property-sold.v1 | Продажа зафиксирована, доход рассчитан | events/property-sold.md |
| Consumed | owner-upserted/owner-deleted (Talent) | talent.owners.owners.v1 (assumption) | Создание/upsert OwnerMappingEntity, provision инвестора (INV-OWN-001) | consumers/talent-owners-sync.md (будущий путь) |
| Consumed | ownership-upserted (Talent) | talent.owners.ownerships.v1 (assumption) | Объекты владения → InvestorPropertyEntity (Source = TalentOwnersSync, идемпотентно по ExternalOwnershipId) | consumers/talent-owners-sync.md (будущий путь) |
| Consumed | DealCompletedKafkaEvent | pin.crm.deal-completed.v1 | Купленный объект → портфель (Source = PinCrmDeal, идемпотентно по DealId) | consumers/crm-deal-completed.md (будущий путь) |
| Consumed | UserUpsertedKafkaEvent | pin.identity.users.v1 | Контроль смены телефона связанного инвестора (алерт расхождения связи, без авторазрыва) | consumers/identity-users.md (будущий путь) |
Сквозные Потоки И Окна Согласованности
Два пути наполнения портфеля и что видит пользователь в промежуточных состояниях (механика — в consumer-доках; участники — бизнес/доменные роли и соседние сервисы):
| Поток | Последовательность (кто → кому; sync/async) | Окно согласованности | Промежуточное состояние для пользователя |
|---|---|---|---|
| Покупка через PIN | crm завершает сделку → pin.crm.deal-completed.v1 (async) → consumer создаёт запись портфеля → pin.investor-owner.property-added.v1 (async) → notifications / user-activity | появление объекта < 1 мин после завершения сделки (SLO) | до обработки события объект ещё не виден в «Мои объекты недвижимости»; уведомление приходит после property-added |
| Синхронизация Talent Owners | Talent owners (async) → provision в identity (sync, internal-API, INV-OWN-001) → связь Linked → pin.investor-owner.owner-linked.v1 (async); затем Talent ownerships (async) → записи портфеля → property-added / property-sold | лаг синка < 15 мин; при недоступности identity связь ждёт в PendingLink | пока связь не Linked, объекты собственника не появляются; при Conflict — ждут ручного разрешения Службой заботы |
| Установление связи вручную | Служба заботы resolve-owner-mapping-conflict (sync) → Conflict → Linked → owner-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/SLA | API портфеля p99 < 300 мс; появление объекта после deal-completed < 1 мин; лаг синка Talent Owners < 15 мин |
| Dashboard | investor-owner-overview: лаг consumers, конфликты связей, DLQ, созданные инвесторы/объекты в день |
| Alerts | DLQ > 0; лаг любого consumer > 15 мин; рост Status = Conflict (> 10/день); ошибки identity provision-API |
| Runbooks | replay DLQ синка; разбор конфликтов связи (admin-очередь); компенсирующее исправление записи портфеля |
| Feature flags | none (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
identityprovision-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 («Собственник», «Объект инвестора»).