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

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

ПолеТипИсточникОбязательностьПравило
DeveloperIdGuididentity claims актораДа!= Guid.Empty; = актор (fail-closed; клиентское значение отвергается → 403). TenantId = DeveloperId (ADR-0052)
PeriodNullableRange<DateOnly>wire dateFrom/dateToДаFrom <= To; окно ≤ 3 года (= TTL фактов). Границы режут партиции toYYYYMM
HierarchyHierarchyFilterModelwire projectIdpropertyObjectIdНет«сверху вниз без дыр» (нижний уровень требует заданного верхнего)
ViewerKindViewerKind?wire viewerKindНетguest/agent/investor; unknown → ValidationError
ViewSourceViewSource?wire viewSourceНетзначения ViewSource; при задании метод читает сырьё property_object_view_facts

Диапазон дат моделируется как NullableRange<DateOnly> (см. response-models.mdNullableRange<T>), а не парой скалярных DateFrom/DateTo: один value object, одна валидация From <= To.

HierarchyFilterModel (вложенный срез иерархии)

ПолеТипОбязательностьПравило
ProjectIdGuid?Нетуровень 2; сужает scope
BuildingIdGuid?Неттребует ProjectId
LayoutGroupIdGuid?Неттребует BuildingId
LayoutIdGuid?Неттребует LayoutGroupId
PropertyObjectIdGuid?Нетсамый глубокий уровень (лист)

Level (самый глубокий заданный уровень: developer/project/building/layout-group/layout/ property-object) — вычисляемое свойство, используется методами для echo и выбора уровня «детей». Существование id в иерархии не проверяется (деградационная устойчивость: неизвестный id → пустой результат, а не ошибка).

Method-specific расширения

Фильтр — общая база; отдельные методы добавляют собственные типизированные параметры (дом — в doc метода):

ПараметрТипМетодыДом
GranularitySeriesGranularity (day/week/month)trend, unique-viewersget-views-trend
GroupByenum (уровень иерархии / viewerKind / viewSource)summary, session-depthdashboards/dev-stats-overview
PagingPagingQuerytop-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.viewDEV_STATS_FORBIDDEN (403)
3Scope-guardклиентский DeveloperId отсутствует или равен акторуDEV_STATS_FORBIDDEN (403) — до чтения ClickHouse
4Период заданPeriod.From и Period.To присутствуютValidationError (400)
5Порядок границPeriod.From <= Period.ToValidationError (400)
6Ширина окнаTo − From ≤ 3 года (= TTL фактов)ValidationError (400)
7Иерархия «без дыр»каждый заданный уровень имеет заданным свой родитель (BuildingIdProjectId, LayoutGroupIdBuildingId, LayoutIdLayoutGroupId, PropertyObjectIdLayoutId)ValidationError (400)
8Enum сегментаViewerKind (если задан) ∈ {guest,agent,investor}; ViewSource (если задан) ∈ множестве ViewSourceValidationError (400) — молчаливый дефолт запрещён
9Method-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 PeriodBoundedFrom <= To и ширина окна ≤ TTL (3 года)Запрос не сканирует за пределы глубины хранения
INV-DSF-4 EnumClosedViewerKind/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 = актор; мутаций/событий/аудита нет (чтение; аудит — только экспорт).

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

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

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