Панель Метрик Застройщика: dev-stats-overview (аналитика внимания)
Продуктовая ЛК-панель застройщика над DevStats.Api (ADR-0057): обзор внимания к его недвижимости —
просмотры, уникальные зрители, длительность, повторность, топы по иерархии и воронка конверсии за период.
Это не ops-борд ingestion (здоровье консюмеров/лаги/пакетной вставки — отдельно, см.
dev-stats-ingest-overview.md): здесь только бизнес-метрики, отдаваемые
read-only через ClickHouse pin_dev_stats (dedup-tolerant argMax/*Merge, без FINAL). Дом мер —
../facts/property-object-view-daily-rollup.md,
../facts/funnel-daily-rollup.md,
../facts/session-facts.md; фильтр — DevStatsFilterModel
(../domain/dev-stats-filter.md).
Назначение
Отвечает на бизнес-вопрос застройщика: кто, сколько, как долго и как часто смотрит мои проекты/здания/
группы планировок/планировки/объекты и как это внимание конвертируется в лиды и сделки за выбранный
период и срез иерархии. Панель — клиентская композиция ключевых метрик области developers (сводка,
динамика, топы, сегменты аудитории, воронка): фронт вызывает нужные из 12 read-only методов и собирает
дашборд (backend-эндпоинта «get-dashboard» нет — каждая плитка = отдельный метод, ADR-0056). Owning team —
PIN Analytics (dev-stats); аудитория — застройщик. Панель не показывает операционные данные
(user-activity), историю цен (invest-analytics) и персональные данные зрителей.
Область Действия
| Поле | Значение |
|---|---|
| Инструмент | Продуктовая ЛК-панель застройщика поверх DevStats.Api (read-only), не Grafana |
| Источник данных | ClickHouse pin_dev_stats (property_object_view_daily, funnel_daily, property_object_view_facts, session_facts) |
| Раздел | ЛК застройщика → «Аналитика внимания» |
| Сервисы | dev-stats (owner); upstream — user-activity, chessboard, favorites/selections/crm (через факты/воронку) |
| Аудитория | застройщик (роль developer, permission dev-stats.view) |
| Primary SLO | Freshness фактов < 5 мин; API p95 ≤ 300 мс (rollup-панели), ≤ 600 мс (сырые факты) |
| Статус | draft |
Переменные
Переменные панели = поля DevStatsFilterModel (дом — ../domain/dev-stats-filter.md).
DeveloperId не выбирается пользователем — он жёстко проставляется из claims актора (tenant isolation,
ADR-0052) и подставляется в каждый запрос как TenantId = DeveloperId. DateFrom/DateTo обязательны и
режут партиции toYYYYMM до сканирования (см. подсказку по индексам в
property-object-view-daily-rollup.md).
| Переменная | Тип | Источник | Значение по умолчанию | Безопасность |
|---|---|---|---|---|
tenantId (DeveloperId) | TenantId (UUID) | claims актора (не из UI) | = актор | Обязателен, = актор; fail-closed при чужом id |
DateFrom | Date | UI (обязателен) | today − 30d | Обязателен; ограничивает партиции |
DateTo | Date | UI (обязателен) | today | Обязателен; DateTo ≥ DateFrom |
ProjectId…PropertyObjectId | UUID? | UI (dim_* актора) | * | Префикс иерархии; только свой DeveloperId |
ViewerKind | enum? (Guest/Agent/Investor) | UI | * | Bounded (3 значения) |
ViewSource | enum? (Catalog/Search/Selection/Favorites/News/DirectLink/Unknown) | UI | * | Bounded (7 значений) |
granularity | enum (day/week/month) | UI | day | Bounded; шаг рядов динамики |
groupBy | enum (уровень иерархии / ViewerKind / ViewSource) | UI | project | Bounded; разбивка топов/сегментов |
Панели
Каждая панель питается отдельным read-only методом области developers (*Merge/argMax без FINAL).
Пороги — не алерты (это продуктовый борд), а справочные ориентиры «нормы внимания».
| Panel | Метод | Агрегация | Факт/роллап |
|---|---|---|---|
kpi-summary | get-views-summary | uniqMerge(Views), uniqMerge(UniqViewers), sumMerge(DurationSum)/uniqMerge(Views) | property_object_view_daily |
views-trend | get-views-trend | uniqMerge(Views) по бакету (granularity) + сравнение периодов | property_object_view_daily |
unique-viewers | get-unique-viewers | uniqMerge(UniqViewers) + разрез ViewerKind + ряд | property_object_view_daily (uniqState) |
repeat-frequency | get-repeat-frequency | распределение визитов на пару «зритель × объект» | property_object_view_facts |
duration-histogram | get-duration-histogram | бины длительностей + avg/median | property_object_view_facts |
hierarchy-funnel | get-hierarchy-funnel | стадии по иерархии + top-N детей (dim_*) | property_object_view_daily |
top-objects | get-top-objects | топ объектов уровня по Views/DurationSum (постранично) | property_object_view_daily / dim_* |
conversion-funnel | get-conversion-funnel | uniqMerge этапов + конверсии | funnel_daily |
session-depth | get-session-depth | объектов за сессию (avg/median/p90 + распределение) | property_object_view_facts |
activity-heatmap | get-activity-heatmap | час × день недели (таймзона) | property_object_view_facts |
bounce-signal | get-bounce-signal | доля коротких просмотров по объекту | property_object_view_facts |
time-to-first-view | get-time-to-first-view | TTFV опубликованных объектов | dim_property_object + property_object_view_facts |
Топы (top-objects) и bounce-signal/time-to-first-view пагинируются постранично;
воронка следует флагу dev-stats.funnel.enabled (нижние этапы активны по мере готовности selections/crm).
Метрики
Смысл мер — из property-object-view-daily-rollup.md
(§ «Определение measures»), funnel-daily-rollup.md,
session-facts.md. Все меры dedup-tolerant: uniq-состояния устойчивы к
open/closed-склейке (по PropertyObjectViewId) и at-least-once.
| Метрика | Тип (CH) | Чтение (без FINAL) | Смысл |
|---|---|---|---|
Views | AggregateFunction(uniq, UUID) | uniqMerge(Views) | Число просмотров (= uniqState(PropertyObjectViewId), open+closed = 1). |
UniqViewers | AggregateFunction(uniq, UUID) | uniqMerge(UniqViewers) | Уникальные зрители (coalesce(UserId, AnonymousId)). |
RepeatViews | производная | uniqMerge(Views) − uniqMerge(UniqViewers) | Просмотры сверх первого на зрителя. |
DurationSum | AggregateFunction(sum, Int32) | sumMerge(DurationSum) | Суммарная длительность (open-строки вносят 0). |
AvgDuration | производная/avg | sumMerge(DurationSum)/uniqMerge(Views) | Средняя длительность на просмотр (нуль-смещение учтено). |
SessionDuration | Int32 (session_facts) | argMax(DurationSeconds, IngestionTime) по SessionId | Длительность сессий, где смотрели объекты актора. |
Funnel.* | AggregateFunction(uniq, UUID) | uniqMerge(<этап>) | Views/Favorites/Selections/Leads/Bookings/BookingsPaid/Deals/DealsCompleted. |
Разбивки задаёт groupBy: по уровню иерархии — удлинение префикса order key; по аудитории (ViewerKind) —
хвостовой компонент property_object_view_daily; по источнику (ViewSource) — из property_object_view_facts.
Сигналы Logs И Traces
Продуктовый борд не поднимает alert-ы — операционное здоровье (ingest-lag, пакетная вставка, 5xx, freshness)
живёт в ops-борде dev-stats-ingest-overview.md. Здесь фиксируется только
аудит чтения и корреляция запросов метрик.
| Сигнал | Query | Correlation |
|---|---|---|
| Logs (чтение метрик) | service=dev-stats area=developers operation=~"get-.*" outcome=ok | correlationId, tenantId (= DeveloperId) |
| Logs (отказ доступа) | service=dev-stats outcome=forbidden reason=scope-mismatch | correlationId, tenantId, requestedDeveloperId |
| Traces (панель) | service=dev-stats span=~"get-.*" (per-metric CH-запросы) | correlationId, causationId |
Экспорт панели по фильтрам аудируется отдельно (permission dev-stats.export) — см.
../reports/dev-stats-export.md.
Маскирование Sensitive Data
- Панель показывает только агрегаты: счётчики (
Views/UniqViewers/RepeatViews), длительности, доли поViewerKind/ViewSource, этапы воронки. ФИО, телефоны,UserId/AnonymousId/SessionIdзрителей наружу не выводятся (ADR-0057 §6). DeveloperIdподставляется из claims, а не из UI: застройщик не может запросить чужой scope (fail-closed).- Сессионные метрики отдаются только в пересечении с просмотрами объектов актора
(
SessionId ∈ property_object_view_facts WHERE DeveloperId = актор). - Скриншоты панели для инцидентов/презентаций маскируют
tenantIdи любые id в аннотациях.
Промежуточные состояния и eventual consistency
Панель читает аналитическое хранилище с окном свежести ≤ 5 мин — застройщик в переходные моменты может видеть не финальную картину. Это ожидаемое поведение, а не ошибка данных:
- Окно свежести (≤ 5 мин). Свежий просмотр/только что опубликованный объект появляется в панелях с
задержкой batch-ingestion. До этого панель показывает прежние значения (для нового объекта —
0), без ошибки. - Open/closed длительность. Пока closed-факт просмотра не пришёл, просмотр учтён по open-строке
(
DurationSeconds = 0) —Viewsуже корректен, ноAvgDuration/гистограмма могут кратко занижаться; при чтении склейка поPropertyObjectViewId(argMax(*, IngestionTime)) выбирает closed-строку. Число просмотров не задваивается. - Отставание воронки. Нижние этапы (
Favorites/Selections/Leads/Bookings/Deals) питаются из разных источников и могут отставать отViews— конверсия временно занижается, затем догоняется. Флагdev-stats.funnel.enabledуправляет активностью нижних этапов. - Деградация подписи иерархии. Если строка измерения (
dim_project/dim_building/…) ещё не долетела, панель резолвит метрику по id и показывает id вместо имени (плейсхолдер), затем подтягиваетName. Метрика при этом корректна (иерархия факта денормализована). - Dimension gap объекта. Просмотры по объекту, чья строка
dim_property_objectне долетела, временно отсутствуют в фактах (парковка на late-dimension) и появляются после backfill измерения; видно в ops-борде (dev-stats-ingest-overview.md, панельdim-miss).
Проверка
| Проверка | Ожидаемый Результат |
|---|---|
| Empty state | При отсутствии просмотров панели показывают 0/нет данных без ошибок запроса. |
| Tenant isolation | Все панели содержат только данные DeveloperId = актор; подмена id → 403. |
| Обязательные фильтры | Запрос без DateFrom/DateTo отклоняется валидацией (400); DateTo ≥ DateFrom. |
| Дедуп | Повтор доставки события (open+closed, at-least-once) не задваивает Views/этапы воронки. |
| Time range | Панели корректны на 7d и 1y (до TTL 3 года) диапазонах. |
| Funnel flag | При dev-stats.funnel.enabled = false панель conversion-funnel скрыта/показывает только views. |
| Свежесть | Просмотр виден в панелях в пределах окна ≤ 5 мин; до этого — прежние значения без ошибки. |
| Деградация подписи | При отсутствующей строке dim_* метрика корректна, подпись показывает id вместо имени. |
| Отставание воронки | При лаге источников нижние этапы < Views; конверсия догоняется, ошибок нет. |
No FINAL | Все панельные запросы используют argMax/*Merge без FINAL (dedup-tolerant чтение). |
Топология панели
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): DevStatsFilterModel (общий вход метрик dev-stats)
- API (1): GET /api/v1/dev-stats/views/summary — Сводка просмотров (кто/сколько/как долго/как часто)
- Use Cases (3): UC-STA-001. Застройщик смотрит статистику проекта (период + проект), UC-STA-003. Сравнение периодов и тренд просмотров, UC-STA-005. Экспорт отчёта статистики (Excel / PDF) с аудитом
- Отчёты (1): Отчет/Export: dev-stats-export (выгрузка аналитики застройщика)
- Дашборды (1): Ops-борд: dev-stats-ingest-overview (здоровье ingestion)
- index.md (1): DevStats Service (Pin.DevStats)
Связанные Документы
- Сервис: README (Каталог метрик, Runtime И Эксплуатация, Безопасность); ops-борд ingest —
dev-stats-ingest-overview.md. - Методы API (area
developers, 12 read-only):../api/developers/. - Экспорт панели:
../reports/dev-stats-export.md(permissiondev-stats.export, аудит). - Факты/роллапы:
property-object-view-facts,property-object-view-daily-rollup,funnel-daily-rollup,session-facts; фильтр —../domain/dev-stats-filter.md; классификация зрителя —../domain/viewer-classification.md. - Эталон панели:
../../news/dashboards/news-overview.md. - ADR: ADR-0057 (dev-stats), ADR-0056 (границы CH), ADR-0052 (tenancy).