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

UC-CTL-004. Сортировка проектов (цена / дата добавления / популярность / окупаемость)

Назначение

Пользователь сортирует ленту проектов/объектов недвижимости по цене, дате добавления, популярности или окупаемости. Все ключи сортировки — денормализованные поля витрины (MinPrice/Price, PublishedAt, PopularityScore, Payback). По умолчанию действует порядок «Рекомендованные» (ранжирование застройщика и пакеты продвижения — ranking-rule.md); явные сортировки пользователя — честные, ранжированием не искажаются. Выбранная сортировка запоминается на сессию через user-activity.

Metadata

ПолеЗначение
IDUC-CTL-004
Контекстcatalog (см. docs/06-services/catalog/)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольГость (с реф-cookie) / Инвестор / Агент / Девелопер / МенеджерПИН
Permissions / scopeкак UC-CTL-001: гейт ленты; permissions не требуются
Tenantне применяется — платформенная витрина (ADR-0052 п.3)
Record-levelне применяется

Предусловие

  • Выполняются предусловия UC-CTL-001; лента открыта (возможно, с поиском/фильтрами).

Триггер

ПолеЗначение
ИсточникUI select
Триггерящий элементВыпадающий список сортировки ленты

Основной Поток

  1. Пользователь выбирает вариант сортировки из выпадающего списка: «по цене», «по дате добавления», «по популярности», «по окупаемости» и, при поддержке варианта, порядок возрастание/убывание.

  2. Интерфейс кладёт sort в URL-параметры (восстановление/шаринг вместе с фильтрами — как UC-CTL-003) и запрашивает ленту с сохранением текущих searchTerm/фильтров. Значение сортировки — enum CatalogSort (по умолчанию recommended):

    ЗначениеКлючи ORDER BY (карточка проекта)
    recommendedPromotionBoost DESC, DeveloperPriority DESC, PopularityScore DESC, PublishedAt DESC, Id DESC
    priceAsc / priceDescMinPrice ASC NULLS LAST / DESC NULLS LAST, Id
    dateDescPublishedAt DESC, Id DESC («сначала новые»)
    popularityPopularityScore DESC, Id
    paybackнормализованная окупаемость в годах ASC NULLS LAST (payback_value при Period=Years, payback_value/12 при Months — правило catalog-property-object-card.md), Id
  3. Каталог применяет сортировку:

    • Проверка входа. sort — известное значение CatalogSort; направление допустимо только для цены (priceAsc/priceDesc — отдельные значения enum); иначе ValidationError (400). Keyset-cursor версионируется значением sort: cursor от другой сортировки отклоняется валидацией.
    • Выборка. Карточки Status = Published с активными фильтрами и поиском, порядок — по ключам выбранной сортировки (таблица шага 2).
    • Ранжирование. PromotionBoost/DeveloperPriority участвуют только в recommended — прозрачность явных сортировок (решение — ranking-rule.md); «по популярности» использует PopularityScore, рассчитанный по завершённым просмотрам (pin.user-activity.property-object-view-ended.v1, ADR-0056 п.3, ночной job popularity-recompute).
    • Состав карточек и гостевое маскирование — как UC-CTL-001.
    • Результат. Отсортированный список.
  4. Интерфейс обновляет ленту в новом порядке.

  5. Пагинация продолжает выбранную сортировку (cursor несёт её ключи).

  6. Интерфейс запоминает сортировку для текущей сессии: локально (sessionStorage + URL) и действием SortChanged в user-activity (ADR-0054) — при возврате в ленту в рамках сессии применяется сохранённое значение.

Альтернативные Потоки

A1. Сортировка ленты объектов недвижимости /lots

  1. Тот же сценарий для ленты объектов недвижимости /lots; ключи уровня объекта: priceAsc/priceDesc — эффективная Price, dateDescPublishedAt, popularityPopularityScore, payback — нормализация PropertyObjectPayback в годы, recommended — те же ключи ранжирования. Завершается как основной поток.

A2. Смена сортировки при активном cursor-е

  1. Пользователь меняет сортировку после подгрузки нескольких страниц.
  2. Интерфейс сбрасывает cursor и запрашивает первую страницу новой сортировки (cursor старой сортировки каталогом отклоняется — шаг 3). Возврат к шагу 3.

A3. Карточки без значения ключа (MinPrice = null, Payback = null)

  1. NULLS LAST: карточки без цены/окупаемости попадают в конец выдачи независимо от направления; из сортировки не исключаются (остаются доступными). Поток не меняется.

A4. Сортировка «по популярности» и ночной пересчёт

  1. Ключ PopularityScore пересчитывает ночной job popularity-recompute по завершённым просмотрам за окно 30 дней с экспоненциальным затуханием; сырой счётчик PopularityViews растёт в течение дня по событиям pin.user-activity.property-object-view-ended.v1, но ключ сортировки от него в пределах суток не меняется.
  2. Поэтому порядок «по популярности» стабилен внутри суток и сдвигается после ночного пересчёта: пользователь видит согласованный порядок в рамках сессии — это ожидаемое поведение, а не рассинхронизация. Поток не меняется.

Постусловие

  • Лента упорядочена по выбранному ключу; порядок стабилен (tie-break Id) и воспроизводим по URL.
  • Выбранная сортировка сохранена для сессии (sessionStorage + ActionEvent (SortChanged) в user-activity).
  • Состояние каталога не изменилось (read-only).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
ValidationError400Неизвестное значение sort; cursor не соответствует sortСбросить cursor, повторить первую страницу
CATALOG_FEED_GATE_REQUIRED401Гейт ленты не пройденAuth-модалка (UC-CTL-001 A1)

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
ActionEventRecorded (производит user-activity)pin.user-activity.actions.v1после приёма события смены сортировки (шаг 6)crm (контекст лида)
PropertyObjectViewEnded (производит user-activity)pin.user-activity.property-object-view-ended.v1вход данных сортировки «по популярности» (consumer каталога инкрементирует PopularityViews)catalog, dev-stats

Acceptance Criteria

#КритерийПроверка
AC1Все четыре сортировки работают: цена (двунаправленно), дата, популярность, окупаемостьтесты каждого значения CatalogSort
AC2Сортировка применяется поверх активных фильтров и поискакомбинированный тест
AC3В явных сортировках PromotionBoost/DeveloperPriority не участвуют (честная выдача)карточка с максимальным boost не всплывает в priceAsc
AC4payback нормализуется в годы: Value/12 при Period=Monthsкарточки Years=2 и Months=18 упорядочены 1.5 < 2
AC5null-ключи — в конце выдачи (NULLS LAST), карточки не пропадаюттест с MinPrice = null
AC6Сортировка запоминается на сессию: возврат в ленту восстанавливает её; создан ActionEvent (SortChanged)e2e + проверка записи
AC7Keyset-пагинация стабильна внутри каждой сортировки (tie-break Id)тест дубликатов на границах страниц
AC8«По популярности» использует PopularityScore (ночной пересчёт), а не сырой PopularityViews: порядок стабилен внутри суток, дневной рост просмотров его не тасуеттест инкремента просмотров без ночного job-а

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

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

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