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

Catalog Service (Pin.Catalog)

Назначение

catalog владеет публичным каталогом объектов недвижимости: денормализованными витринными проекциями проектов и объектов недвижимости, оверрайдами отображения и ранжированием застройщика. Продвижение (промо-пакеты и покупки) вынесено в отдельный контекст promotioncatalog выступает консюмером буста (ADR-0060): наполняет денормализованное PromotionBoost карточек по событию pin.promotion.purchase-changed.v1, но не владеет пакетами/покупками. Предоставляет ленту /lots и /hotels (бесконечная лента 20–30 карточек, гейт: токен или реферальная cookie), поиск по названию/адресу/девелоперу (Elasticsearch, ADR-0059), фильтры (тип/класс/регион/город/статус строительства/цена), сортировки (цена/дата/популярность/окупаемость) и показ комиссий агентам.

Metadata

ПолеЗначение
Статусdraft
Owning teamPIN Marketplace
DomainPublic Catalog & Merchandising
Data classificationPublic/Internal (витрина публична; комиссии — internal для агентов; PII нет)
Runtime criticalityhigh (лента и карточки — главный публичный трафик платформы; вход воронки UC-1/2/3)

Ownership

ArtifactOwner/Rule
PostgreSQL schemacatalog
API areaspublic (лента/поиск/карточки), developers (ранжирование), admin (оверрайды — m2m, ADR-0053)
Owned entitiesCatalogProjectCard, CatalogPropertyObjectCard, PropertyObjectDisplayOverride, BuildingDisplayOverride, DeveloperRankingRule; PromotionContribution — лёгкая реплика вкладов продвижения (consumer, consumers/promotion-boost.md)
Produced eventsnone (продвижение владеет контекст promotion; карточки — конечная точка данных, ADR-0056)
Consumed eventspin.chessboard.projects.v1, pin.chessboard.buildings.v1, pin.chessboard.property-objects.v1, pin.invest-analytics.property-object-metrics.v1, pin.rewards.commission-rules.v1, pin.user-activity.property-object-view-ended.v1, pin.promotion.purchase-changed.v1 (буст → PromotionBoost)
Search indexesElasticsearch-проекции catalog-property-objects, catalog-projects (ADR-0059; search/property-objects.md, search/projects.md); PostgreSQL-FTS не используется
Analytics factsnone (просмотры — user-activitydev-stats, ADR-0054/0057)

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

  • Делает: денормализованные карточки проектов/объектов недвижимости (лента одной строкой без джойнов), поиск (Elasticsearch)/фильтры/сортировки, эффективные значения после оверрайдов (Effective = Override ?? Source), приоритеты застройщика в рамках его объектов, денормализация комиссии агента из rewards и буста продвижения из promotion (PromotionBoost — consumer, ADR-0060), маскирование точных цен/статусов для гостей.
  • НЕ делает: source of truth структуры недвижимости и шахматки (chessboard); расчёт метрик доходности/истории цен (invest-analytics); правила комиссий (rewards); продвижение — промо-пакеты и покупки (контекст promotion, ADR-0060; catalog лишь консюмер буста; оплата отложена — решение №10); заявки на консультацию/бронь — формы каталога отправляются в crm (service-boundaries, поток «Заявки»); запись просмотров/поисков/фильтров — фронт шлёт события в user-activity (ADR-0054), статистика застройщика — dev-stats (ADR-0057); избранное/подборки (favorites/selections); сохранение фильтров: URL-параметры (шаринг ссылки) — фронт, «фильтры сессии» — ActionEvent в user-activity.
  • Source of truth: оверрайды, ранжирование, эффективные display-значения и агрегаты витрины (Price-диапазон/PropertyObjectsAvailable/PopularityScore).
  • Реплики/проекции: карточки — реплики chessboard/invest-analytics/rewards/identity (снапшоты источников — typed jsonb SourceSnapshot с guard-ами монотонности версий); PromotionBoost — проекция активных покупок promotion (реплика вкладов PromotionContribution); Elasticsearch — rebuildable search projection карточек.

Зависимости

ЗависимостьТипЗачем НужнаFailure mode
PostgreSQL (schema catalog)dbПроекции, оверрайды, ранжирование, продвижение, outboxПолный отказ каталога
Elasticsearchsearch projectionПолнотекстовый поиск объектов/проектов (ADR-0059)Поиск деградирует; лента/карточки по id работают из PostgreSQL
KafkaqueueКонсюмеры источников (in), в т.ч. pin.promotion.purchase-changed.v1 (буст)Витрина работает на последних данных; свежесть деградирует (лаг-алерты)
chessboardservice (events)Структура проектов/корпусов/объектов, статусы объектовКарточки устаревают; новые объекты не появляются
invest-analyticsservice (events)Метрики: окупаемость, рост, аренда, рейтингМетрики устаревают; карточки работают
rewardsservice (events)Эффективные комиссии для агентовКомиссии устаревают/не показываются
promotionservice (events)Буст продвижения → PromotionBoost карточек (ADR-0060)Сортировка «Рекомендованные» устаревает; карточки работают
user-activityservice (events)property-object-view-ended → популярностьСортировка «по популярности» устаревает
identityservice (JWT/permissions)Авторизация акторов, permission catalog.commissions.view, tenant застройщикаPublic-чтение с реф-cookie живёт; персонализированные области недоступны
Администратор платформы (admin API)consumer (m2m, ADR-0053)ОверрайдыНедоступны admin-правки

Проекты

src/services/catalog/
Pin.Catalog.Api/ # public + developers + admin API areas
Pin.Catalog.Application/ # обработка операций, пересчёты проекций
Pin.Catalog.Domain/ # агрегаты и проекции domain/*.md
Pin.Catalog.Infrastructure/ # PostgreSQL-хранилище, Elasticsearch-проекции, outbox
Pin.Catalog.Background/ # consumers источников (в т.ч. promotion-boost), jobs: popularity-recompute, delisted-retention, search-rebuild
Pin.Catalog.Migrator/ # миграции схемы
Pin.Catalog.Contracts/ # контракты потребляемых событий (промо-контракт — pin.promotion.*)

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

EntityНазначениеSource Of TruthДокумент
CatalogProjectCardДенормализованная карточка проекта: лента, поиск, фильтры, агрегаты цен/объектовcatalog (эффективные значения) / chessboard, invest-analytics (источники)domain/catalog-project-card.md
CatalogPropertyObjectCardДенормализованная карточка объекта недвижимости, комиссия, ранжированието же + rewardsdomain/catalog-property-object-card.md
PropertyObjectDisplayOverride / BuildingDisplayOverrideПравки витрины поверх источникаcatalogdomain/display-override.md
DeveloperRankingRuleПриоритет застройщика в рамках своих объектовcatalogdomain/ranking-rule.md
PromotionContributionРеплика вкладов активных покупок продвижения → пересчёт PromotionBoostcatalog (consumer; SoT — promotion)consumers/promotion-boost.md

ER-обзор

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

  • Public (гейт ленты: JWT или реф-cookie referralUrl; ограниченная карточка проекта — без гейта; маскирование точных цен/статусов для гостя): GET /api/v1/property-objects (лента 20–30, keyset-пагинация, поиск searchTerm, фильтры type/class/region/constructionStatus/price/rooms, сортировки recommended/price/date/popularity/payback — состояние фильтров в URL-параметрах для шаринга), GET /api/v1/property-objects/{id}, GET /api/v1/projects (лента /hotels и главная), GET /api/v1/projects/{id}, GET /api/v1/projects/{id}/property-objects (assortment с фильтрами этаж/комнатность/цена), GET /api/v1/catalog/filters (lookup справочников значений фильтров).
  • Developers (catalog.ranking.*): CRUD DeveloperRankingRule (list/create/update-priority/enable/disable/delete). Продвижение (покупка пакетов) — контекст promotion (developers-область promotion, ADR-0060).
  • Admin (m2m, ADR-0053; catalog.overrides.*): CRUD оверрайдов объектов и корпусов, POST /admin/projections/rebuild (принудительный пересчёт карточек по id — runbook расхождений). CRUD пакетов продвижения — admin-область контекста promotion.
  • Internal: not applicable (потребители читают public API или события).

Events И Consumers

TypeNameTopicНазначениеLink
ConsumedPromotionPurchaseChangedpin.promotion.purchase-changed.v1Пересчёт PromotionBoost карточек Project/PropertyObject (буст продвижения, ADR-0060)consumers/promotion-boost.md
ConsumedProjectUpserted/Deletedpin.chessboard.projects.v1Upsert/делистинг CatalogProjectCarddomain/catalog-project-card.md
ConsumedBuildingUpserted/Deletedpin.chessboard.buildings.v1Корпусные поля карточек (DeliveryDate/Readiness/Stars)domain/catalog-project-card.md
ConsumedPropertyObjectUpserted/StatusChanged/Deletedpin.chessboard.property-objects.v1Upsert CatalogPropertyObjectCard, статусы Резерв/Бронь/Продан (поток crm → chessboard → catalog)domain/catalog-property-object-card.md
ConsumedPropertyObjectMetricsUpdatedpin.invest-analytics.property-object-metrics.v1Payback/Growth/RentalIncome/Rating/Reliabilitydomain/catalog-property-object-card.md
ConsumedCommissionRulesChangedpin.rewards.commission-rules.v1Per-developer снапшот набора правил → батч-пересчёт диапазона PropertyObjectAgentCommission (PercentMin..PercentMax) объектов застройщикаconsumers/rewards-commission.md
ConsumedPropertyObjectViewEndedpin.user-activity.property-object-view-ended.v1Инкремент PopularityViews объекта/проекта (ADR-0056 п.3)domain/catalog-property-object-card.md

Consumer-документы — в consumers/; имена чужих топиков — конвенция pin.<context>.<event>.v1, подтверждаются при написании ТЗ контекстов-владельцев.

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

ОбластьТребование
Health checks/health/live, /health/ready (PostgreSQL, Elasticsearch, Kafka), /health/startup (миграции применены)
SLO/SLAлента p99 < 300 мс, карточка p99 < 200 мс (одна строка + индексы); поиск p99 < 400 мс (Elasticsearch); лаг консюмеров источников < 1 мин (цены/статусы), < 15 мин (метрики/комиссии)
Dashboardcatalog-overview (RPS ленты/поиска, latency, лаг консюмеров по топикам, DLQ, freshness search-проекций, размер Delisted)
Alertsлаг pin.chessboard.property-objects.v1 > 5 мин (статусы брони!); лаг pin.promotion.purchase-changed.v1 > 1 мин (буст); DLQ > 0; ошибки пересчёта проекций; расхождение search-проекции с source
Runbooksrebuild проекций по id (admin-API); rebuild/переключение alias search-индексов; replay DLQ консюмеров (в т.ч. promotion-boost); сверка карточек с chessboard (расхождение SourceSnapshot.ChessboardVersion)
Feature flagscatalog.promotions.enabled (гейт консюмера буста promotion-boost — не применять PromotionBoost до готовности), catalog.commissions.enabled

Jobs (Pin.Catalog.Background): popularity-recompute (ночной пересчёт PopularityScore, окно 30 дней), delisted-retention (очистка Delisted-карточек старше 30 дней), search-rebuild (bootstrap/восстановление Elasticsearch). Истечение покупок продвижения (expire-promotions) — job контекста promotion (ADR-0060).

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

  • Actor types: guest (лента с реф-cookie, ограниченные карточки), investor/agent (полные карточки; агент — + комиссии), developer-manager (developers-область в scope компании), администратор платформы (admin API, m2m; admin-область), system (consumers/jobs).
  • Permission model: identity/domain/permission.mdcatalog.commissions.view, catalog.ranking.view/manage, catalog.overrides.view/manage (permissions продвижения promotion.* — контекст promotion).
  • Sensitive data classes: комиссии агентов (internal — только по permission); Comment оверрайдов (internal); PII в контексте нет. Search-проекции PII не индексируют, security-trimming — до query execution (ADR-0059).
  • Audit: оверрайды/ранжирование — полный actor-stamping + Version + soft-delete; проекции (включая PromotionBoost/PromotionContribution) — без аудита (trace через SourceSnapshot/событие promotion).
  • Tenant isolation: ADR-0052 — витрина platform-level (не tenanted); DeveloperRankingRule — tenanted (TenantId = DeveloperId); гость — маскирование цен/статусов на уровне ответа API. PromotionContribution — не tenanted проекция (карточки платформенные).

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

  • Контракты событий chessboard/invest-analytics/rewards — assumption (конвенция pin.<context>.<event>.v1); финализируются в ТЗ этих контекстов (chessboard — Батч 1, invest-analytics/rewards — этот батч).
  • Контракт порядка событий источников: каталог полагается на партиционирование каждого топика по id агрегата (проект/корпус/объект) — тогда события одной сущности приходят по порядку и per-entity консистентность сохраняется. Кросс-топиковый и кросс-сущностный порядок не гарантирован; защита — guard-ы монотонности версий (событие со SourceVersion <= уже применённой отбрасывается как устаревшее) + INV-2 «ProjectFirst» (карточка объекта, пришедшая раньше карточки своего проекта, ждёт в Hidden и становится видимой, когда проект появится). Что видит пользователь в промежутке: новый объект недвижимости может некоторое время не показываться в ленте, пока не доедет его проект.
  • Продвижение вынесено в контекст promotion (ADR-0060); catalog — консюмер буста (PromotionBoost наполняется по pin.promotion.purchase-changed.v1). Оплата не реализуется (решение №10) — решается в контексте promotion.
  • Ранжирование действует только в сортировке «Рекомендованные» (принятое решение — domain/ranking-rule.md); прозрачность для явных сортировок.
  • Свежесть витрины и search-проекции — eventual, с разными окнами по классу данных: цена/статус (chessboard) — цель < 1 мин; метрики доходности (invest-analytics) и комиссии (rewards) — цель < 15 мин; буст продвижения (promotion) — < 1 мин; популярность — лаг закрытия просмотра 10–15 мин (ADR-0056). Что видит пользователь в окне лага: забронированный объект может ещё несколько десятков секунд показываться как доступный, метрика/комиссия — с прежним значением. Это безопасно: критичные для брони проверки статуса объекта выполняет crm в источнике на шаге бронирования, поэтому устаревший статус в витрине не приводит к переспродаже; каталог — только отображение.
  • PopularityScore считается по завершённым просмотрам (property-object-view-ended, лаг закрытия 10–15 мин — ADR-0056), т.е. популярность отстаёт от реального времени — приемлемо для сортировки.
  • Каталог — personalization-free (ADR-0056): карточки и публичные методы возвращают одинаковую платформенную проекцию для всех акторов; per-user поля (просмотры лида, статус избранного, членство в подборках, заметки) в публичные карточки/методы не добавляются. Обогащение «в контексте инвестора» собирается вне каталога — в аутентифицированном agent/CRM-слое, пакетно по id объектов из ответа каталога (просмотры лида + 3-state избранное «нет/у меня/у инвестора» + подборки), с делегированной авторизацией по активному лиду. Дом обогащения — crm/api/agent/get-property-object-investor-context.md; каталог остаётся источником карточек и не участвует в персонализации.

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