DevStatsFilterModel (общий вход метрик dev-stats)
Дом типизированного контракта фильтра, общего для всех read-only методов области developers
(ADR-0057). Каждый метод принимает DevStatsFilterModel (плюс собственные method-specific параметры —
грануляция, пороги, пагинация) и жёстко фильтрует ClickHouse pin_dev_stats по нему.
Назначение
DevStatsFilterModel — единый строго типизированный фильтр внимания к недвижимости застройщика: чей
scope (DeveloperId), за какой период (Period), по какому срезу иерархии
(Hierarchy: проект → здание → группа планировок → планировка → объект недвижимости) и с каким сегментом
аудитории/источника (ViewerKind/ViewSource). Порядок полей иерархии дословно повторяет order key
фактов/rollup-ов (property_object_view_facts/property_object_view_daily), поэтому любой заданный срез —
префикс-скан по первичному индексу (см. rollup).
Все поля строго типизированы, enum'ы — из конечных множеств (viewer-classification.md).
Модель DevStatsFilterModel
| Поле | Тип | Источник | Обязательность | Правило |
|---|---|---|---|---|
DeveloperId | Guid | identity claims актора | Да | != Guid.Empty; = актор (fail-closed; клиентское значение отвергается → 403). TenantId = DeveloperId (ADR-0052) |
Period | NullableRange<DateOnly> | wire dateFrom/dateTo | Да | From <= To; окно ≤ 3 года (= TTL фактов). Границы режут партиции toYYYYMM |
Hierarchy | HierarchyFilterModel | wire projectId…propertyObjectId | Нет | «сверху вниз без дыр» (нижний уровень требует заданного верхнего) |
ViewerKind | ViewerKind? | wire viewerKind | Нет | guest/agent/investor; unknown → ValidationError |
ViewSource | ViewSource? | wire viewSource | Нет | значения ViewSource; при задании метод читает сырьё property_object_view_facts |
Диапазон дат моделируется как NullableRange<DateOnly> (см. response-models.md —
NullableRange<T>), а не парой скалярных DateFrom/DateTo: один value object, одна валидация From <= To.
HierarchyFilterModel (вложенный срез иерархии)
| Поле | Тип | Обязательность | Правило |
|---|---|---|---|
ProjectId | Guid? | Нет | уровень 2; сужает scope |
BuildingId | Guid? | Нет | требует ProjectId |
LayoutGroupId | Guid? | Нет | требует BuildingId |
LayoutId | Guid? | Нет | требует LayoutGroupId |
PropertyObjectId | Guid? | Нет | самый глубокий уровень (лист) |
Level (самый глубокий заданный уровень: developer/project/building/layout-group/layout/
property-object) — вычисляемое свойство, используется методами для echo и выбора уровня «детей».
Существование id в иерархии не проверяется (деградационная устойчивость: неизвестный id → пустой
результат, а не ошибка).
Method-specific расширения
Фильтр — общая база; отдельные методы добавляют собственные типизированные параметры (дом — в doc метода):
| Параметр | Тип | Методы | Дом |
|---|---|---|---|
Granularity | SeriesGranularity (day/week/month) | trend, unique-viewers | get-views-trend |
GroupBy | enum (уровень иерархии / viewerKind / viewSource) | summary, session-depth | dashboards/dev-stats-overview |
Paging | PagingQuery | top-objects, bounce-signal, time-to-first-view | стандартная пагинация |
пороги (BinEdgesSeconds, ThresholdSeconds, VisitBucketEdges, TimeZone, Unit) | типизированные | histogram/bounce/repeat/heatmap/ttfv | соответствующие doc'и |
TimeZone — IANA-строка (например Europe/Moscow); timezone-сущности в справочнике pin.handbook нет
(ADR-0063).
Правила валидации (полный перечень, порядок проверок)
Каждый метод применяет проверки фильтра в фиксированном порядке; первый нарушенный пункт возвращает свой код и прекращает обработку до чтения витрины. Порядок важен: доступ (fail-closed) — раньше валидации формата, чтобы попытка чужого scope не приводила даже к разбору остального ввода.
| # | Проверка | Условие прохождения | Исход при нарушении |
|---|---|---|---|
| 1 | Аутентификация | JWT валиден, извлечён DeveloperId актора | UNAUTHORIZED (401) |
| 2 | Право доступа | у актора есть dev-stats.view | DEV_STATS_FORBIDDEN (403) |
| 3 | Scope-guard | клиентский DeveloperId отсутствует или равен актору | DEV_STATS_FORBIDDEN (403) — до чтения ClickHouse |
| 4 | Период задан | Period.From и Period.To присутствуют | ValidationError (400) |
| 5 | Порядок границ | Period.From <= Period.To | ValidationError (400) |
| 6 | Ширина окна | To − From ≤ 3 года (= TTL фактов) | ValidationError (400) |
| 7 | Иерархия «без дыр» | каждый заданный уровень имеет заданным свой родитель (BuildingId ⇒ ProjectId, LayoutGroupId ⇒ BuildingId, LayoutId ⇒ LayoutGroupId, PropertyObjectId ⇒ LayoutId) | ValidationError (400) |
| 8 | Enum сегмента | ViewerKind (если задан) ∈ {guest,agent,investor}; ViewSource (если задан) ∈ множестве ViewSource | ValidationError (400) — молчаливый дефолт запрещён |
| 9 | Method-specific | грануляция/пороги/пагинация в допустимых границах (дом — doc метода) | ValidationError (400) |
Существование id иерархии не проверяется (шаг 7 контролирует только связность уровней, не наличие узла в
dim_*). Неизвестный/удалённый ProjectId/BuildingId/… — это не ошибка: срез просто не совпадёт ни с
одной строкой витрины, метод вернёт пустой результат (200). Это осознанная деградационная устойчивость
(dim_* — реплики с лагом; см. response-models.md, ref-модели).
Поведение на граничных/предельных входах
| Вход | Поведение |
|---|---|
From == To (один день) | Валиден; срез — одна дневная партиция |
| Пустой срез (нет строк за период/иерархию) | 200 с нулевыми мерами / пустым рядом — не ошибка |
| Неизвестный id уровня иерархии | 200, пустой результат (существование не проверяется) |
| Окно частично выходит за TTL (старше 3 лет) | Валидно, если ширина ≤ 3 года; факты старше TTL уже вычищены → в них нулевые меры |
ViewSource задан | Метод переключается на сырьё property_object_view_facts (rollup его не хранит) → SLO чтения p95 ≤ 600 мс вместо ≤ 300 мс |
ViewerKind задан | Rollup хранит ViewerKind в order key — остаётся быстрым префикс-скан (p95 ≤ 300 мс) |
Инварианты
| Код | Инвариант | Смысл |
|---|---|---|
| INV-DSF-1 TenantScoped | Каждое чтение жёстко фильтрует DeveloperId = актор (= TenantId, ADR-0052) | Изоляция застройщика; данные чужого tenant недостижимы (fail-closed) |
| INV-DSF-2 HierarchyPrefix | Заданные поля иерархии образуют связный префикс «сверху вниз» и дословно повторяют order key фактов/rollup | Любой срез — префикс-скан по первичному индексу, без чтения хвостовых уровней |
| INV-DSF-3 PeriodBounded | From <= To и ширина окна ≤ TTL (3 года) | Запрос не сканирует за пределы глубины хранения |
| INV-DSF-4 EnumClosed | ViewerKind/ViewSource — из конечных множеств; unknown на входе → ValidationError | Нет молчаливого дефолта, визуализатор всегда получает известный сегмент |
| INV-DSF-5 ExistenceNotChecked | Наличие id иерархии в dim_* не проверяется | Лаг/чистка измерений не превращает валидный запрос в ошибку — деградация до пустого результата |
Scope-guard (обязательный инвариант, INV-DSF-1)
DeveloperId инжектится из identity-claims, не принимается от клиента. Каждый метод проверяет (шаг 3
выше): если клиент прислал DeveloperId ≠ актор → отказ DEV_STATS_FORBIDDEN (403) до чтения ClickHouse
и до валидации остального ввода (fail-closed). Доступ к чужому застройщику невозможен; tenant-isolation-тесты
обязательны. Постусловие любого метода: возвращённые данные принадлежат только DeveloperId = актор;
мутаций/событий/аудита нет (чтение; аудит — только экспорт).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Классификация зрителя и источника (ViewerKind / ViewSource), Модели ответа dev-stats (типизированные ряды / точки / сводки)
- API (12): GET /api/v1/dev-stats/activity-heatmap — Хитмап активности (час × день недели), GET /api/v1/dev-stats/bounce-signal — Сигнал отказа (короткие просмотры по объекту), GET /api/v1/dev-stats/conversion-funnel — Воронка конверсии (просмотр→сделка), GET /api/v1/dev-stats/duration/histogram — Гистограмма длительностей просмотра, GET /api/v1/dev-stats/repeat-frequency — Повторные просмотры и частота возврата на объект, GET /api/v1/dev-stats/session-depth — Глубина сессии (объектов за сессию), GET /api/v1/dev-stats/time-to-first-view — Время до первого просмотра (TTFV), GET /api/v1/dev-stats/top-objects — Топ объектов по просмотрам и времени, GET /api/v1/dev-stats/viewers/unique — Уникальные зрители (гость/агент/инвестор), GET /api/v1/dev-stats/views/hierarchy-funnel — Воронка просмотров по иерархии, GET /api/v1/dev-stats/views/summary — Сводка просмотров (кто/сколько/как долго/как часто), GET /api/v1/dev-stats/views/trend — Динамика просмотров + сравнение периодов
- Use Cases (5): UC-STA-001. Застройщик смотрит статистику проекта (период + проект), UC-STA-002. Детализация просмотров до здания / группы планировок / планировки, UC-STA-003. Сравнение периодов и тренд просмотров, UC-STA-004. Воронка конверсии «просмотр → сделка», UC-STA-005. Экспорт отчёта статистики (Excel / PDF) с аудитом
- Отчёты (1): Отчет/Export: dev-stats-export (выгрузка аналитики застройщика)
- Дашборды (1): Панель Метрик Застройщика: dev-stats-overview (аналитика внимания)
- index.md (1): DevStats Service (Pin.DevStats)
- registries (1): Реестр сущностей PIN
Связанные документы
- Классификация зрителя/источника: viewer-classification.md
(
ViewerKind/ViewSource). - Типизированные модели ответа и
NullableRange<T>/ref-модели: response-models.md. - Индекс/order key (иерархия = префикс): property-object-view-daily-rollup.md, property-object-view-facts.md.
- Методы-потребители: ../api/developers/ (все 12 read-only метрик).
- ADR: ADR-0057, ADR-0056, ADR-0052 (tenancy).