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

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
KindAggregateRoot
Source of truthэтот контекст
Таблицаcatalog.ranking_rules
Tenant isolationTenantId = DeveloperId (данные компании-застройщика, ADR-0052 п.1) — застройщик видит/правит только свои правила

Поля

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
IdGuidДаPKappИдентификатор правила.
TenantIdGuidДаindex ix_ranking_rules_tenant_id; = DeveloperIdidentity (актор)Застройщик-владелец правила (tenant boundary).
TargetKindRankingTargetKindДахранится как text, varchar(16)appProject / PropertyObject — уровень цели.
TargetIdGuidДа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).
PriorityintДа0..1000 (check ck_ranking_rules_priority); default 0 = нейтральноdeveloperПриоритет: больше — выше в выдаче среди объектов этого застройщика.
StatusRankingRuleStatusДахранится как text, varchar(16), index ix_ranking_rules_statusappActive — применяется; Disabled — сохранено, не применяется.
CreatedAtDateTimeOffsetДаapp
CreatedByUserIdGuid?Да (для API-мутаций)без FKappМенеджер застройщика, создавший правило.
UpdatedAtDateTimeOffsetДаapp
UpdatedByUserIdGuid?Нетapp
DeletedAtDateTimeOffset?Нетpartial-фильтр индексовappSoft-delete.
DeletedByUserIdGuid?Нетapp

Enum RankingTargetKind

ЗначениеОписание
ProjectПриоритет проекта в ленте проектов + базовый приоритет всех объектов проекта в ленте объектов.
PropertyObjectПриоритет конкретного объекта недвижимости; перекрывает проектный приоритет для этого объекта (INV-5).

Enum RankingRuleStatus

ЗначениеОписание
ActiveПравило применено: Priority денормализован в карточки.
DisabledПравило выключено застройщиком: карточки получают приоритет по каскаду (объект → проектное правило → 0).

Value objects

Отдельных value objects нет.

Модели JSONB (versioned)

JSONB-модели не используются.

Связи

СвязьТипКлючПравило загрузкиDelete behavior
CatalogProjectCardreference (логическая)target_id → project_cards.id при TargetKind=Projectвалидация принадлежности: карточка цели + проверка DeveloperId == TenantIdделистинг проекта → правило остаётся (реактивация при восстановлении), карточек для пересчёта нет
CatalogPropertyObjectCardreference (логическая)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 PriorityRange0 <= Priority <= 1000check + проверка входа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 / SoftDeleteId, 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
Партиционирование / retentionnone — тысячи строк максимум; история — бессрочно (soft-delete)
Concurrencyоптимистическая блокировка на мутациях developers API
Permissions / PIIDevelopers API (catalog/api/developers/*): catalog.ranking.view / catalog.ranking.manage в scope организации/проекта (Scoped-RBAC — identity/domain/permission.md); выборки ограничены TenantId застройщика. PII нет
AuditОтметки автора + soft-delete: застройщик и ПИН видят, кто и когда менял приоритеты

Конкурентность и деградация каскада

Пересчёт vs консюмеры карточки

Мутация правила пишет только DeveloperPriority карточек; консюмеры chessboard/invest-analytics/rewards/promotion пишут другие поля той же строки — пересечения по данным нет. При гонке за версию строки проигравший повторяет пересчёт (оптимистическая блокировка) и переприменяет каскад INV-5; два правила одного застройщика сериализуются блокировкой правила.

Цель делистнута между проверкой и пересчётом

Если карточка цели ушла в Delisted (источник удалил объект/проект) — пересчёт INV-4 затрагивает 0 живых карточек, правило сохраняется и реактивируется при восстановлении цели (INV-1 держит единственность живого правила на цель).

Порядок каскада проекта

Правило уровня Project пересчитывает карточку проекта и все объекты проекта без собственного активного правила (INV-5: правило объекта перекрывает проектное); объект со своим Active-правилом от проектного правила DeveloperPriority не получает. Переходы Enable/Disable/SoftDelete проходят тот же каскад пересчёта.

Видимость промежуточного состояния

Между коммитом правила и доездом ленты/поиска (лаг индексации) «Рекомендованные» могут секунды показывать прежний приоритет; явные сортировки пользователя приоритет не используют и не затрагиваются (прозрачность — формула «Рекомендованные» выше).

Обратные ссылки

Автоссылки: где используется этот документ.

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