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

UC-STA-002. Детализация просмотров до здания / группы планировок / планировки

Назначение

Застройщик из сводки по проекту последовательно проваливается вниз по иерархии — проект → здание → группа планировок → планировка (→ объект недвижимости) — и на каждом уровне видит разбивку просмотров (top-N с постраничной выдачей). Каждый уровень детализации сужает срез до выбранной ветки иерархии. Сценарий только читает данные. Контракты операций — get-hierarchy-funnel.md, get-top-objects.md; модель фильтра — dev-stats-filter.md; источник — property_object_view_daily. Контекст — dev-stats (ADR-0057).

Metadata

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

Актор

РольPermissions / scopeTenantRecord-level
Девелопер (застройщик)dev-stats.view; scope строго = свой DeveloperIdTenantId = DeveloperId = CompanyEntity.Id (ADR-0052)не применяется (агрегаты)

Каждый уровень детализации остаётся в scope застройщика: DeveloperId = актор — обязательный префикс любого запроса; провал вниз лишь добавляет фильтры дочерних уровней.

Предусловие

  • Выполнен UC-STA-001 (застройщик уже на дашборде проекта).
  • Агрегат property_object_view_daily наполнен; измерения dim_building/dim_layout_group/dim_layout/dim_property_object наполнены (для подписей уровней в UI).
  • Токен застройщика содержит право dev-stats.view.

Триггер

ПолеЗначение
ИсточникUI (клик по строке разбивки — «провалиться в здание/группу/планировку») → API call
Триггерящий элементтаблица разбивки дашборда → GET /api/v1/dev-stats/views/hierarchy-funnel.../hierarchy-funnel(layout)top-objects

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

Полный контракт операций — get-hierarchy-funnel.md / get-top-objects.md; ниже — сценарий drill-down.

  1. Ввод. На дашборде проекта (ProjectId зафиксирован из UC-STA-001) застройщик кликает строку здания — запрашивает разбивку по зданиям проекта; затем провал в конкретное BuildingId — разбивку по группам планировок; далее LayoutGroupIdпо планировкам; глубже LayoutIdтоп объектов недвижимости.
  2. Запрос. Каждый уровень — свой запрос с накопленным фильтром и постраничной выдачей: разбивка уровня за период/проект; при провале добавляется buildingId, затем layoutId и т. д. DeveloperId — из токена.
  3. Проверка входа и доступа. DateFrom/DateTo обязательны (DateFrom ≤ DateTo); заданные уровни-фильтры — валидные идентификаторы; DeveloperId = актор (fail-closed); параметры страницы — размер страницы PageSize ≤ 100, сортировка по мере (по умолчанию Views по убыванию). Нарушение — VALIDATION_ERROR; чужой DeveloperId/нет права — FORBIDDEN.
  4. Чтение разбивки уровня. Данные читаются из property_object_view_daily в срезе, удлинённом на выбранный уровень: разбивка по зданиям — срез (DeveloperId, ProjectId) с группировкой по BuildingId; по группам — (DeveloperId, ProjectId, BuildingId) с группировкой по LayoutGroupId; по планировкам — + LayoutGroupId с группировкой по LayoutId; топ объектов — + LayoutId с группировкой по PropertyObjectId. Каждый уровень читает только выбранную ветку (без соседних) и только затронутые месячные партиции по дате.
  5. Расчёт мер (устойчивый к дублям). На каждую группу — Views, UniqViewers, средняя длительность = DurationSum / Views; сортировка top-N по Views по убыванию + смещение страницы; общее число групп уровня — для счётчика Total постраничной выдачи. Подсчёт устойчив к дублям и at-least-once-доставке.
  6. Результат. Возвращается постраничная разбивка HierarchyBreakdownRow (LevelId, LevelName — из dim_*, Views, UniqViewers, AvgDurationSeconds) с признаком «есть ещё» и общим счётчиком Total. Интерфейс показывает таблицу уровня с «показать ещё»; клик по строке — запрос глубже (шаг 2 с добавленным фильтром); «наверх» — возврат к родительскому уровню.

Диаграмма

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

A1. Уровень без просмотров

  1. На выбранном срезе нет строк агрегата.
  2. Возвращается пустая разбивка (Total = 0, «есть ещё» = false) — HTTP 200 (не ошибка).

Завершается: фронт показывает пустую таблицу уровня.

A2. Прямой глубокий фильтр (без пошагового провала)

  1. Застройщик сразу задаёт BuildingId + LayoutGroupId в фильтре (deep-link/сохранённый вид).
  2. Срез = (DeveloperId, ProjectId, BuildingId, LayoutGroupId) с группировкой по LayoutId. Результат идентичен пошаговому drill-down (определяется срезом, а не порядком кликов).

Возвращается к основному потоку на шаг 5.

A3. Отсутствует подпись уровня в dim_*

  1. dim_layout/dim_property_object ещё не догнали chessboard (лаг наполнения измерений).
  2. Строка отдаётся с LevelId, но LevelName = null/last-known (деградация подписи, не ошибка — метрика считается независимо от подписи).

A4. Страница за пределом выдачи / устойчивость сортировки

  1. Застройщик запрашивает страницу с номером больше числа страниц уровня или на уровне много групп с равной мерой.
  2. Страница за пределом → 200 с пустым Items, прежним Total, HasMore = false (не ошибка). При равенстве меры порядок доопределяется стабильным вторичным ключом (id узла) — одна и та же страница между вызовами детерминирована (нет «прыгающих» строк при пагинации).

Завершается: фронт показывает пустую страницу/стабильный список.

A5. Несуществующий id уровня (deep-link/устаревший вид)

  1. В накопленном фильтре задан BuildingId/LayoutId, которого нет (удалён или ещё не реплицирован в dim_*).
  2. Существование id не проверяется — срез не совпадает ни с одной строкой → 200 с пустой разбивкой (Total = 0), не 404/ошибка (деградационная устойчивость, см. dev-stats-filter.md).

Постусловие

  • Мутаций нет (read-only): агрегаты/факты/измерения не изменяются, доменные и интеграционные (Kafka) события не публикуются, аудит не пишется.
  • Клиент получил постраничную разбивку выбранного уровня иерархии в scope своего DeveloperId.

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

КодHTTPУсловиеПоведение клиента
VALIDATION_ERROR400Нет дат / DateFrom > DateTo; невалидный BuildingId/LayoutGroupId/LayoutId; PageSize > 100Показать ошибку параметров
FORBIDDEN403DeveloperId ≠ актор; нет dev-stats.viewСкрыть данные
— (не ошибка)200Уровень без просмотров (A1)Пустая таблица уровня
5xx (ProblemDetails)500/503Отказ аналитической витрины pin_dev_statsРетрай позже

Acceptance Criteria

#КритерийПроверка
AC1Каждый уровень детализации сужает срез до выбранной ветки (без чтения соседних веток)Тест плана: разбивка при заданном buildingId сканирует только срез (…, BuildingId)
AC2Результат определяется фильтром, а не порядком кликов (A2 == пошаговый drill)Тест: deep-фильтр и пошаговый провал дают идентичную разбивку
AC3Постраничная выдача: размер страницы, признак «есть ещё», Total = число групп уровняТест: PageSize, «есть ещё», корректный Total
AC4Сортировка top-N по Views по убываниюТест: порядок строк разбивки
AC5DeveloperId = актор на всех уровнях (чужой → 403)Изоляционный тест по каждому уровню
AC6Лаг dim_* → строка с LevelId и деградированной подписью (не ошибка)Тест A3: метрика есть, LevelName пустой
AC7Ответ типизирован (HierarchyBreakdownRow)Ревью контракта ответа
AC8Страница за пределом → 200 пустая (не ошибка); сортировка стабильна при равной мереТест A4: страница > последней и tie-break по id
AC9Несуществующий id уровня → 200 пустая разбивка (существование не проверяется)Тест A5: deep-фильтр с неизвестным BuildingId

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

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

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