GET /api/v1/dev-stats/top-objects — Топ объектов по просмотрам и времени
Назначение
Рейтинг объектов застройщика выбранного уровня (property-object/layout/layoutGroup/building/project) —
топ по числу просмотров и по суммарному времени внимания. Отвечает на вопрос «что смотрят больше и
дольше всего». Источник виджетов «Топ объектов недвижимости / планировок / проектов» на дашборде застройщика
(../../README.md, метрики «По объектам/планировкам/проектам»). Считается из дневного rollup-а
property_object_view_daily (AggregatingMergeTree, *Merge, без FINAL),
имена — из реплик dim_*; рейтинг пагинируется постранично.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | dev-stats |
| API area | developers |
| Feature group | top-objects |
| Статус | draft |
| Документ | docs/06-services/dev-stats/api/developers/get-top-objects.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Bearer JWT; роль застройщика — developer) |
| Auth policy / Permissions | permission dev-stats.view — identity/domain/permission.md |
| Scope (RBAC) | организация застройщика; DeveloperId строго = актор (fail-closed) |
| Record-level | не применяется (агрегаты) |
| Tenant isolation | TenantId = DeveloperId обязателен и = актору |
| Audit | none (read-агрегаты; персональные данные зрителей не выдаются) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/dev-stats/top-objects |
| Success status | 200 |
| Idempotency header | не применяется (safe GET) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
Общий вход — DevStatsFilterModel (../../domain/dev-stats-filter.md) +
уровень рейтинга, сортировка и пагинация PagingQuery.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
DeveloperId | identity claims | Guid | Да | != Guid.Empty; = актор (иначе Forbidden) | Tenant isolation |
DateFrom | query dateFrom | DateOnly | Да | ISO; DateFrom <= DateTo | — |
DateTo | query dateTo | DateOnly | Да | ISO; ≤ сегодня (UTC) | — |
ObjectType | query objectType | TopObjectType (enum) | Да | property-object/layout/layoutGroup/building/project; неизвестное → ValidationError | — |
SortBy | query sortBy | TopObjectSort (enum) | Нет | views (default) / durationSum; определяет ключ ORDER BY … DESC | — |
ProjectId | query projectId | Guid? | Нет | ограничивает рейтинг под-деревом (существование не проверяется) | — |
BuildingId | query buildingId | Guid? | Нет | требует ProjectId | — |
LayoutGroupId | query layoutGroupId | Guid? | Нет | — | — |
LayoutId | query layoutId | Guid? | Нет | — | — |
ViewerKind | query viewerKind | ViewerKind? (enum) | Нет | Guest/Agent/Investor (dimension rollup-а) | — |
Paging | query page/pageSize | PagingQuery | Нет | pageSize 1..100 (default 20); page ≥ 1 | — |
Enum TopObjectType: PropertyObject/Layout/LayoutGroup/Building/Project; TopObjectSort: Views/
DurationSum (wire — нижний регистр; хранение — .HasConversion<string>()).
Модель ответа — постраничный рейтинг
Постраничная выдача: items, totalCount, page, pageSize, hasMore.
TopObjectRow
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
rank | int | Да | позиция в отсортированном ряду (с учётом страницы) | — | Место в рейтинге, 1-based. |
objectType | string (enum wire) | Да | echo ObjectType | — | Уровень объекта. |
id | Guid | Да | GROUP BY <levelId> | — | Идентификатор объекта. |
name | string | Да | dim_* по id | — | Отображаемое имя (пусто при отсутствии измерения). |
parentPath | string? | Нет | dim_* вверх (проект / проект→здание …) | — | Контекст в иерархии (для объекта недвижимости/планировки). |
views | long | Да | uniqMerge(Views) | — | Число просмотров. |
uniqViewers | long | Да | uniqMerge(UniqViewers) | — | Уникальные зрители. |
durationSum | long | Да | sumMerge(DurationSum) | — | Суммарное время внимания, сек. |
avgDuration | int | Да | durationSum / views (0 при views=0) | — | Средняя длительность на просмотр, сек. |
Предусловия и согласованность данных
- Пользователь застройщика авторизован; есть permission
dev-stats.view; scopeDeveloperId= актор (INV-STA-1, fail-closed — ../../README.md). - Рейтинг считается из rollup
property_object_view_daily(окно свежести ≤ 5 мин, INV-STA-5); имена иparentPath— из репликdim_*. При отставанииchessboard-консюмераname/parentPathмогут быть пустыми, но метрики и порядок рейтинга корректны (INV-STA-6). totalCount(число объектов уровня с просмотрами) считается тем же условием, что и страница, поэтому пагинация согласована с общим числом.- Операция read-only: состояние не меняется, события не публикуются.
Алгоритм
- Контекст и доступ. Операцию выполняет пользователь застройщика (роль developer) с правом
dev-stats.view.DeveloperIdберётся из identity-claims актора; если клиент прислал иной id → отказForbidden(403). - Проверка входа.
ObjectType/SortBy∈ enum (неизвестное →ValidationError);DateFrom <= DateTo;pageSize∈ 1..100. Существование иерархических id не проверяется. - Что читаем. Дневной rollup
property_object_view_dailyза период,GROUP BY <levelId>(колонка уровняObjectType:PropertyObjectId/LayoutId/LayoutGroupId/BuildingId/ProjectId). Scope-фильтр — префикс-скан по ключу(DeveloperId, ProjectId, BuildingId, LayoutGroupId, LayoutId, PropertyObjectId, …); диапазон дат режет партицииtoYYYYMM(EventDate). Сортировка поSortBy:uniqMerge(Views) DESCилиsumMerge(DurationSum) DESC; страница —LIMIT pageSize OFFSET (page−1)×pageSize;totalCount—uniqExact(<levelId>)тем же условием (число объектов с просмотрами). - Расчёты, маппинг и имена. Меры dedup-tolerant —
uniqMerge(Views),uniqMerge(UniqViewers),sumMerge(DurationSum)— безFINAL;avgDuration = durationSum / views(guardviews>0). Имена иparentPath— из реплик-измеренийdim_property_object/dim_layout/dim_layout_group/dim_building/dim_projectпоid(реплики chessboard в той же БД).rank= глобальная позиция с учётомOFFSET. - Результат. Возвращается постраничный рейтинг
TopObjectRow(200):items+totalCount/page/pageSize/hasMore. Freshness ≤ 5 мин; недоступность ClickHouse →DEV_STATS_UNAVAILABLE(503).
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Невалидные objectType/sortBy/диапазон/pageSize вне 1..100 | Ошибка валидации запроса | no retry |
UNAUTHORIZED | 401 | Нет JWT | Требуется авторизация | no retry |
Forbidden | 403 | DeveloperId != актор или нет dev-stats.view | Доступ запрещён | no retry |
DEV_STATS_UNAVAILABLE | 503 | ClickHouse недоступен / timeout | Аналитика временно недоступна | retry с backoff |
Пустой рейтинг (нет просмотров) — не ошибка: 200 с items = [], totalCount = 0.
Acceptance Criteria
- Рейтинг по просмотрам. Дано авторизованный застройщик,
objectType=property-objectи период. КогдаGET /top-objectsбезsortBy. Тогда200, строки по убываниюviews,rank1-based сквозной с учётом страницы,totalCount= число объектов уровня с просмотрами. - Сортировка по времени. Дано
sortBy=durationSum. Когда запрос. Тогда порядок — по убыванию суммарного времени внимания,avgDuration = durationSum / views(guardviews>0). - Пагинация. Дано
page=2&pageSize=20. Когда запрос. Тогдаitems— вторая страница,rankпродолжает нумерацию (21…),hasMoreотражает наличие следующих. - Имя из реплики отстаёт. Дано объект ещё не в
dim_*. Когда строка возвращается. Тогдаname/parentPathпустые, ноviews/durationSumи место в рейтинге верны (INV-STA-6). - Обязательный уровень. Дано
objectTypeне задан или неизвестен. Когда запрос. Тогда400 ValidationError. - Границы пагинации. Дано
pageSize=0или > 100. Когда запрос. Тогда400 ValidationError. - Пустой рейтинг. Дано период без просмотров. Когда запрос. Тогда
200,items = [],totalCount = 0(не ошибка). - Чужой scope / нет права. Дано чужой
DeveloperIdили нетdev-stats.view. Когда запрос. Тогда403 Forbidden(INV-STA-1). - Недоступность витрины. Дано ClickHouse недоступен. Когда запрос. Тогда
503 DEV_STATS_UNAVAILABLE(retry с backoff).
Совместимость и наблюдаемость
- Новые optional поля строки — additive; удаление/переименование/смена wire-значений
objectType/sortBy— breaking (версия метода). Unknown enum →ValidationError. - Logs:
top-objects-read(developerId, objectType, sortBy, page, pageSize, elapsed; PII нет). Metrics:dev_stats_top_objects_requests_total{objectType,sortBy}, latency histogram (SLO p95 ≤ 300 мс). Traces: span приёма → span обработки → span чтения ClickHouse; correlation — сквозной tracetraceparent.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Модели ответа dev-stats (типизированные ряды / точки / сводки)
- API (2): GET /api/v1/dev-stats/views/hierarchy-funnel — Воронка просмотров по иерархии, GET /api/v1/dev-stats/views/trend — Динамика просмотров + сравнение периодов
- Use Cases (1): UC-STA-002. Детализация просмотров до здания / группы планировок / планировки
- Дашборды (1): Панель Метрик Застройщика: dev-stats-overview (аналитика внимания)
- index.md (1): DevStats Service (Pin.DevStats)
- registries (1): Реестр API-методов PIN
Связанные документы
- Rollup-источник: ../../facts/property-object-view-daily-rollup.md (меры/ключ/партиции).
- Измерения (имена/
parentPath): ../../dimensions/ (dim_property_object…dim_project). - Фильтр/модели: ../../domain/dev-stats-filter.md, ../../domain/response-models.md.
- Соседние метрики: get-views-trend.md, get-hierarchy-funnel.md.