Investor-Owner API — реестр операций и общие контрактные модели
Индекс HTTP-операций сервиса investor-owner (портфель инвестора, страница /profile/my-lots —
«Мои объекты недвижимости») и общий дом контрактных моделей ответа портфеля (MoneyModel,
PropertyObjectSnapshotModel, CurrentValuationModel) и правила деградации текущей оценки. Файлы операций
описывают только свою дельту и ссылаются сюда.
| Поле | Значение |
|---|
| Сервис/контекст | investor-owner |
| Статус | draft |
| Owning team | PIN Investor Experience |
| Область действия | Public API area (портфель инвестора, self-only); admin-операции — отдельные документы |
| Assumptions | Текущая оценка объекта недвижимости — internal-API invest-analytics (см. «Текущая оценка» ниже, assumption до фиксации контракта); история цен — public API invest-analytics по PropertyObjectId |
| Зависимости | ../domain/investor-property.md, ../domain/owner-mapping.md, invest-analytics (текущая оценка + история цен) |
Реестр операций (public, self-only)
Admin-операции (Служба заботы: очередь конфликтов связи, ручное разрешение, компенсирующие исправления
портфеля) — карта в ../README.md, документы api/admin/:
admin/list-owner-mapping-conflicts.md,
admin/resolve-owner-mapping-conflict.md,
admin/archive-investor-property.md,
admin/create-investor-property.md (permissions investor-owner.mappings.manage /
investor-owner.portfolio.manage). Internal-API и callbacks отсутствуют — межсервисное взаимодействие
контекста только через Kafka (см. ../README.md).
Общие правила доступа (все public-операции)
| Правило | Значение |
|---|
| Actor type | User (инвестор, Bearer JWT; InvestorUserId = claim sub) |
| Permission | investor-owner.portfolio.view-self (каталог — ../../identity/domain/permission.md) |
| Tenant isolation | не применяется: данные инвестора вне tenant (ADR-0052) |
| Record-level | обязательный фильтр InvestorUserId = actor; условие владельца дублируется явно в запросе — defence-in-depth |
| Enumeration | чужая/несуществующая запись портфеля неразличимы: единый INVESTOR_OWNER_PROPERTY_NOT_FOUND (404) |
| Masking | суммы (цены, доход) — финансовые данные владельца: в ответ отдаются без маскирования только владельцу; в логи суммы не пишутся (только PropertyId/PropertyObjectId/количества) |
| Audit | read-sensitive: чтение своего портфеля — без audit-записи; платформенный просмотр чужого портфеля (investor-owner.portfolio.view) — только admin API с deep audit (ADR-0053), в этом реестре не описывается |
Общие контрактные модели портфеля
| Поле | Тип | Обязательность | Ограничения | Описание |
|---|
amount | decimal | Да | numeric(18,2); отрицательные значения допустимы только для профитов | Сумма. |
currencyId | short | Да | ссылка на справочник валют handbook (../../handbook/domain/currency.md); v1 — всегда RUB (ADR-0048) | Валюта — типизированная ссылка. |
Display-код валюты в MoneyModel не передаётся: единая валюта записи (INV-5) отдаётся один раз —
currencyIsoCode в сводке (public/get-my-portfolio-summary.md,
денормализованный CurrencyIsoCode записи); полный display-набор валюты — публичный lookup справочника
(../../handbook/api/public/lookup-references.md).
PropertyObjectSnapshotModel (проекция VO PropertyObjectSnapshot — дом полей ../domain/investor-property.md)
Пары id + name проекта и корпуса свёрнуты во вложенные ref-модели (strict-typed —
strict-typed-documentation-rules.md),
а не в плоские поля *Name.
| Поле | Тип | Обязательность | Источник | Описание |
|---|
title | string | Да | PropertyObjectSnapshot.Title | Название объекта недвижимости на момент покупки. |
number | string? | Нет | PropertyObjectSnapshot.Number | Номер объекта недвижимости. |
project | ProjectRefModel? | Нет | PropertyObjectSnapshot.Project | Проект (ЖК/отель) — ref-модель ниже; null, если объект недвижимости не сопоставлен с шахматкой PIN. |
building | BuildingRefModel? | Нет | PropertyObjectSnapshot.Building | Здание/литер — ref-модель ниже; null при отсутствии сопоставления. |
areaTotal | decimal? | Нет | PropertyObjectSnapshot.AreaTotal | Площадь, м². |
floor | int? | Нет | PropertyObjectSnapshot.Floor | Этаж. |
snapshotAt | string (ISO 8601) | Да | PropertyObjectSnapshot.SnapshotAt | Момент фиксации снапшота (иммутабелен — «что купили»). |
ProjectRefModel (проект — ref-модель)
| Поле | Тип | Обязательность | Описание |
|---|
projectId | Guid | Да | ProjectId (снапшот на момент покупки; совпадает с денормализованным projectId записи). |
projectName | string | Да | ProjectName (название проекта на момент покупки). |
BuildingRefModel (корпус — ref-модель)
| Поле | Тип | Обязательность | Описание |
|---|
buildingId | Guid | Да | BuildingId (снапшот на момент покупки). |
buildingName | string | Да | BuildingName (здание/литер). |
CurrentValuationModel (read-проекция текущей оценки; в БД investor-owner не хранится)
| Поле | Тип | Обязательность | Источник | Описание |
|---|
available | bool | Да | computed | true, если текущая цена получена от invest-analytics (см. правило деградации ниже). |
currentPrice | MoneyModel? | Нет (обязателен при available = true) | invest-analytics (NewPrice последнего факта цены объекта недвижимости) | Текущая полная цена объекта недвижимости. |
pricedAt | string (ISO 8601)? | Нет | invest-analytics (EventTime последнего факта) | Момент последнего изменения цены. |
unrealizedProfit | MoneyModel? | Нет | computed: currentPrice.amount − PurchasePrice.Amount; только при available = true, Status = Active и совпадении валют оценки и записи (currentPrice.currencyId = PurchasePrice.CurrencyId) | Нереализованный доход (может быть отрицательным). Не хранится (см. ../domain/investor-property.md). |
Текущая оценка: источник и правило деградации
- Источник — internal-API
invest-analytics getLotsCurrentPrices (батч по PropertyObjectId[], последняя
цена из ленты lot_price_history_facts;
../../invest-analytics/api/get-lots-current-prices.md,
assumption до фиксации контракта; валюта цены — CurrencyId справочника handbook: события ленты цен
chessboard несут CurrencyId). Операционный source of truth текущей цены — chessboard
(../../invest-analytics/domain/price-history-fact.md);
для портфеля достаточно аналитической ленты (freshness ≤ 5 мин).
- Оценка запрашивается только для записей
Status = Active c ProjectId != null (объект сопоставлен
с шахматкой PIN). Для ProjectId = null (несопоставленный объект Talent Owners) и для Sold
оценка не запрашивается: available = false.
- Вызов — best-effort: timeout 500 мс, retry отсутствует (простой GET на горячем пути списка). Любой
сбой/таймаут/отсутствие цены по объекту недвижимости →
available = false, currentPrice = null, операция
завершается 200 (портфель работает без оценки — failure mode из ../README.md).
Метрика деградации: investor_owner_current_valuation_degraded_total.
- История изменения цен объекта в этом API не отдаётся: карточка возвращает признак
priceHistoryAvailable, фронт запрашивает график напрямую у public API invest-analytics по PropertyObjectId
(../../invest-analytics/api/public/get-property-object-price-history.md).
Согласованность и свежесть (операции чтения)
- Записи портфеля (
InvestorPropertyEntity) — сильная согласованность внутри сервиса: после появления
объекта (consumer deal-completed / синк Talent Owners) или admin-правки чтение видит результат немедленно
(read-your-writes). Порядок появления и лаги источников — SLO ../README.md (объект после
deal-completed < 1 мин; лаг синка Talent Owners < 15 мин).
- Текущая оценка (
CurrentValuationModel) — eventual: свежесть аналитической ленты цен invest-analytics
≤ 5 мин; при недоступности/таймауте источника — деградация до available = false без ошибки операции.
Реализованные показатели (RealizedProfit и суммы по Sold) от оценки не зависят и всегда полны.
- Промежуточные состояния, видимые пользователю: пока связь Собственник ↔ Инвестор не установлена
(
PendingLink / Conflict), объекты этого собственника ещё не появляются в портфеле; после перехода в
Linked и обработки владений они появляются в течение окна лага синка.
Совместимость (все операции)
- Новые optional поля — additive; удаление/переименование/смена типа — breaking
(migration + синхронный rollout фронта
/profile/my-lots).
- Enum-значения (
OwnershipStatus, PropertySource) сериализуются строками; клиент обязан
игнорировать неизвестные значения (unknown enum handling: показывать запись без бейджа статуса,
не падать).
Связанные документы