Entity / Aggregate: DeveloperRankingRule
Дом полей правила ранжирования застройщика (контекст catalog).
Назначение
DeveloperRankingRule — настройка застройщика, задающая приоритет показа его проекта или объекта
недвижимости относительно других его же проектов/объектов (управление ранжированием в рамках своих
объектов). Правило не влияет на позицию относительно чужих застройщиков — кросс-девелоперское
продвижение решают платформенные пакеты (контекст promotion; буст — консюмер
../consumers/promotion-boost.md, ADR-0060).
Применение: значение Priority активного правила денормализуется в DeveloperPriority карточек
(catalog-property-object-card.md /
catalog-project-card.md) в той же транзакции, что мутация правила. Правило для
проекта задаёт базовый приоритет всем объектам недвижимости проекта; правило для конкретного объекта
перекрывает проектное (INV-5).
Формула ранжирования ленты (принятое решение каталога)
Ранжирование действует только в дефолтной сортировке «Рекомендованные»; явные сортировки пользователя (цена / дата добавления / популярность / окупаемость) честные и ранжированием не искажаются.
ORDER BY (сортировка «Рекомендованные», индекс ix_*_feed):
1. PromotionBoost DESC -- платформенные пакеты (кросс-девелоперское)
2. DeveloperPriority DESC -- приоритет застройщика в рамках его объектов
3. PopularityScore DESC -- органическая популярность
4. PublishedAt DESC -- свежесть
Классификация
| Поле | Значение |
|---|---|
| Контекст | catalog |
| Kind | AggregateRoot |
| Source of truth | этот контекст |
| Таблица | catalog.ranking_rules |
| Tenant isolation | TenantId = DeveloperId (данные компании-застройщика, ADR-0052 п.1) — застройщик видит/правит только свои правила |
Поля
| Поле | Тип | Обязательность | Ограничения (PK/FK/index/constraint) | Источник | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | PK | app | Идентификатор правила. |
TenantId | Guid | Да | index ix_ranking_rules_tenant_id; = DeveloperId | identity (актор) | Застройщик-владелец правила (tenant boundary). |
TargetKind | RankingTargetKind | Да | хранится как text, varchar(16) | app | Project / PropertyObject — уровень цели. |
TargetId | Guid | Да | unique composite ux_ranking_rules_target (tenant_id, target_kind, target_id) where deleted_at is null; логическая ссылка на project_cards.id / property_object_cards.id (без FK) | app | Цель правила. Одно живое правило на цель (INV-1). |
Priority | int | Да | 0..1000 (check ck_ranking_rules_priority); default 0 = нейтрально | developer | Приоритет: больше — выше в выдаче среди объектов этого застройщика. |
Status | RankingRuleStatus | Да | хранится как text, varchar(16), index ix_ranking_rules_status | app | Active — применяется; Disabled — сохранено, не применяется. |
CreatedAt | DateTimeOffset | Да | — | app | — |
CreatedByUserId | Guid? | Да (для API-мутаций) | без FK | app | Менеджер застройщика, создавший правило. |
UpdatedAt | DateTimeOffset | Да | — | app | — |
UpdatedByUserId | Guid? | Нет | — | app | — |
DeletedAt | DateTimeOffset? | Нет | partial-фильтр индексов | app | Soft-delete. |
DeletedByUserId | Guid? | Нет | — | app | — |
Enum RankingTargetKind
| Значение | Описание |
|---|---|
Project | Приоритет проекта в ленте проектов + базовый приоритет всех объектов проекта в ленте объектов. |
PropertyObject | Приоритет конкретного объекта недвижимости; перекрывает проектный приоритет для этого объекта (INV-5). |
Enum RankingRuleStatus
| Значение | Описание |
|---|---|
Active | Правило применено: Priority денормализован в карточки. |
Disabled | Правило выключено застройщиком: карточки получают приоритет по каскаду (объект → проектное правило → 0). |
Value objects
Отдельных value objects нет.
Модели JSONB (versioned)
JSONB-модели не используются.
Связи
| Связь | Тип | Ключ | Правило загрузки | Delete behavior |
|---|---|---|---|---|
CatalogProjectCard | reference (логическая) | target_id → project_cards.id при TargetKind=Project | валидация принадлежности: карточка цели + проверка DeveloperId == TenantId | делистинг проекта → правило остаётся (реактивация при восстановлении), карточек для пересчёта нет |
CatalogPropertyObjectCard | reference (логическая) | target_id → property_object_cards.id при TargetKind=PropertyObject | то же | то же |
Инварианты и переходы состояний
| Инвариант | Условие | Где проверяется | Ошибка |
|---|---|---|---|
| INV-1 SingleRulePerTarget | Одно живое правило на (TenantId, TargetKind, TargetId) | unique partial index + проверка перед созданием | CATALOG_RANKING_RULE_DUPLICATE |
| INV-2 OwnObjectsOnly | Цель принадлежит застройщику: карточка цели имеет DeveloperId == TenantId | валидатор команды: карточка цели + сравнение | CATALOG_RANKING_TARGET_NOT_OWNED |
| INV-3 PriorityRange | 0 <= Priority <= 1000 | check + проверка входа | CATALOG_RANKING_PRIORITY_OUT_OF_RANGE |
| INV-4 RecomputeInTx | Мутация правила пересчитывает DeveloperPriority затронутых карточек (проект: карточка проекта + все объекты проекта; объект: одна карточка) в той же транзакции | application handler (пересчёт в том же коммите) | нет — механика |
| INV-5 PropertyObjectOverridesProject | Эффективный DeveloperPriority объекта = Active-правило объекта ?? Active-правило его проекта ?? 0 | функция пересчёта (единственная точка расчёта) | нет — детерминированный каскад |
Доменные и интеграционные события
| Событие | Тип | Когда | Snapshot/поля |
|---|---|---|---|
RankingRuleChangedEvent | доменное | Create / Update(Priority) / Enable / Disable / SoftDelete | Id, TenantId, TargetKind, TargetId, Priority, Status |
Обработчик события в том же коммите пересчитывает DeveloperPriority карточек (INV-4).
Интеграционные события не публикуются — ранжирование потребляется только витриной каталога.
Индексы, хранение, безопасность
| Аспект | Значение |
|---|---|
| Индексы | PK pk_ranking_rules (id); ux_ranking_rules_target unique btree (tenant_id, target_kind, target_id) where deleted_at is null; ix_ranking_rules_tenant_id btree; ix_ranking_rules_status btree |
| Партиционирование / retention | none — тысячи строк максимум; история — бессрочно (soft-delete) |
| Concurrency | оптимистическая блокировка на мутациях developers API |
| Permissions / PII | Developers API (catalog/api/developers/*): catalog.ranking.view / catalog.ranking.manage в scope организации/проекта (Scoped-RBAC — identity/domain/permission.md); выборки ограничены TenantId застройщика. PII нет |
| Audit | Отметки автора + soft-delete: застройщик и ПИН видят, кто и когда менял приоритеты |
Конкурентность и деградация каскада
Мутация правила пишет только DeveloperPriority карточек; консюмеры
chessboard/invest-analytics/rewards/promotion пишут другие поля той же строки — пересечения по
данным нет. При гонке за версию строки проигравший повторяет пересчёт (оптимистическая блокировка) и
переприменяет каскад INV-5; два правила одного застройщика сериализуются блокировкой правила.
Если карточка цели ушла в Delisted (источник удалил объект/проект) — пересчёт INV-4 затрагивает 0 живых
карточек, правило сохраняется и реактивируется при восстановлении цели (INV-1 держит единственность живого
правила на цель).
Правило уровня Project пересчитывает карточку проекта и все объекты проекта без собственного
активного правила (INV-5: правило объекта перекрывает проектное); объект со своим Active-правилом от
проектного правила DeveloperPriority не получает. Переходы Enable/Disable/SoftDelete проходят тот же
каскад пересчёта.
Между коммитом правила и доездом ленты/поиска (лаг индексации) «Рекомендованные» могут секунды показывать прежний приоритет; явные сортировки пользователя приоритет не используют и не затрагиваются (прозрачность — формула «Рекомендованные» выше).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Entity / Aggregate: CatalogProjectCard, Entity / Aggregate: CatalogPropertyObjectCard, Entity / Aggregate: PostReaction + проекция PostRankingSignals (Design)
- API (10): Catalog Developers API — реестр методов и общие правила области, Catalog Public API — реестр методов и общие правила, DELETE /api/developers/v1/ranking-rules/{ruleId} — Удаление правила ранжирования, GET /api/developers/v1/ranking-rules — Список правил ранжирования застройщика, GET /api/v1/lots — Лента объектов недвижимости, GET /api/v1/projects — Лента проектов, POST /api/admin/v1/projections/rebuild — Принудительный пересчёт карточек по id (admin), POST /api/developers/v1/ranking-rules — Создание правила ранжирования, POST /api/developers/v1/ranking-rules/{ruleId}/enable — Включение правила ранжирования, PUT /api/developers/v1/ranking-rules/{ruleId}/priority — Изменение приоритета правила
- Use Cases (2): UC-CTL-001. Лента проектов (главная страница, бесконечная лента 20–30 карточек), UC-CTL-004. Сортировка проектов (цена / дата добавления / популярность / окупаемость)
- Консюмеры (2): Consumer: Chessboard Projection Sync (pin-catalog-chessboard-sync), Consumer: Promotion Boost (pin-catalog-promotion-boost)
- Фоновые задачи (2): Background Job: DelistedRetentionJob, Background Job: PopularityRecomputeJob
- index.md (1): Catalog Service (Pin.Catalog)
- registries (1): Реестр сущностей PIN
Связанные документы
- catalog-property-object-card.md,
catalog-project-card.md —
денормализованный
DeveloperPriority, ленточные индексы. - ../consumers/promotion-boost.md — кросс-девелоперский
PromotionBoost(слой выше в формуле; дом продвижения — контекстpromotion, ADR-0060). - ADR-0052 (tenancy: данные компании).