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

UC-STA-001. Застройщик смотрит статистику проекта (период + проект)

Назначение

Застройщик открывает в личном кабинете аналитику внимания к своей недвижимости: задаёт период (DateFrom/DateTo) и проект (ProjectId) и получает сводку (просмотры / уникальные зрители / средняя длительность / повторные просмотры) и временной ряд просмотров. Сценарий только читает данные — ничего не меняет. Контракты операций — get-views-summary.md и get-views-trend.md; состав дашборда — dev-stats-overview.md; модель фильтра — dev-stats-filter.md; источник данных — суточный агрегат просмотров property_object_view_daily. Контекст — dev-stats (ADR-0057).

Metadata

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

Актор

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

Запрос жёстко фильтруется по DeveloperId = актор (обязательный компонент DevStatsFilterModel); DeveloperId берётся из контекста безопасности, не из параметров запроса; доступ к чужому застройщику невозможен (fail-closed).

Предусловие

  • Суточный агрегат property_object_view_daily наполнен: факты просмотров обогащены иерархией из измерений dim_*; свежесть ≤ 5 мин от события.
  • Реплики-измерения dim_developer/dim_project наполнены (для выбора проекта в UI и валидности иерархии).
  • Токен застройщика содержит право dev-stats.view и разрешённый DeveloperId (роль developer).

Триггер

ПолеЗначение
ИсточникUI (страница ЛК-аналитики застройщика) → API call
Триггерящий элементдашборд /developer/stats (выбор периода + проекта) → GET /api/v1/dev-stats/views/summary (+ .../timeseries)

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

Полный контракт операций — get-views-summary.md / get-views-trend.md; ниже — сценарий и особенности чтения витрины.

  1. Ввод. Застройщик выбирает период (DateFrom/DateTo) и проект (ProjectId); опционально — тип зрителя (ViewerKind) / источник (ViewSource) и гранулярность ряда (day/week/month).
  2. Запрос. Интерфейс запрашивает сводку и временной ряд за выбранные период/проект. DeveloperId в запросе не передаётся — он берётся из контекста безопасности (токена).
  3. Проверка входа и доступа. DateFrom/DateTo обязательны и DateFrom ≤ DateTo, диапазон ≤ 3 лет (глубина хранения); ProjectId — валидный идентификатор. DeveloperId подставляется из контекста безопасности; если клиент прислал DeveloperId, отличный от актора, либо у актора нет права dev-stats.view — отказ FORBIDDEN (A3) до чтения данных. Нарушение формата/диапазона входа — VALIDATION_ERROR.
  4. Чтение метрик. Данные читаются из суточного агрегата property_object_view_daily (не из сырых фактов), строго в срезе DeveloperId = актор ∧ ProjectId ∧ EventDate ∈ [DateFrom; DateTo]. Сканируется только нужная ветка иерархии (префикс (DeveloperId, ProjectId)) и только затронутые месячные партиции по дате; уровни ниже проекта (BuildingIdPropertyObjectId) не читаются. ViewerKind/ViewSource, если заданы, дополнительно сужают срез.
  5. Расчёт мер (устойчивый к дублям). Подсчёт устойчив к повторам и at-least-once-доставке: дубликат факта и пара open/closed одной сессии не задваивают меры. Сводка: Views, UniqViewers, средняя длительность = DurationSum / Views, повторные просмотры = Views − UniqViewers. Временной ряд — те же меры с группировкой по дате (или по началу недели/месяца согласно granularity).
  6. Результат. Возвращается типизированная сводка ViewsSummary (Views, UniqViewers, AvgDurationSeconds, RepeatViews) и ряд ViewsTimeseries (список точек ViewsTimeseriesPoint с Date, Views, UniqViewers). Интерфейс рисует KPI-плитки сводки и линейный график ряда; пустой период → A1; смена гранулярности — повторный запрос ряда.

Диаграмма

Интеграция и согласованность

Сценарий не делает синхронных вызовов соседних сервисов: он читает только собственную витрину property_object_view_daily. Наполнение витрины — асинхронное (Kafka-ingestion user-activity → факты → MV), окно eventual-consistency ≤ 5 мин (freshness SLA). Практические следствия для читателя:

  • Просмотры последних минут могут ещё не отразиться (в пределах окна freshness) — не ошибка, догоняется.
  • Текущий день «доуточняется»: длительности дорастают по мере прихода closed-фактов, счётчики прошедших дней могут вырасти при догоне late-dimension (идемпотентно). Ряд за завершённые прошлые дни стабилен.

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

A1. Пустой период (нет просмотров)

  1. Срез не совпал ни с одной строкой агрегата за период/проект.
  2. Возвращается сводка из нулей (Views=0, UniqViewers=0, AvgDurationSeconds=0, RepeatViews=0) и пустой ряд — HTTP 200 (отсутствие данных — валидный исход, не ошибка).

Завершается: фронт рендерит пустое состояние дашборда.

A2. Разбивка по типу зрителя (groupBy = viewerKind)

  1. Застройщик включает разбивку Guest/Agent/Investor.
  2. К мерам добавляется группировка по ViewerKind — доли по типам зрителя за тот же срез (DeveloperId, ProjectId).

Возвращается к основному потоку на шаг 6 (сборка разбивки).

A3. Попытка чужого DeveloperId

  1. Клиент передал DeveloperId ≠ актор (или у актора нет права dev-stats.view).
  2. Операция завершается FORBIDDEN (403) до чтения витрины (fail-closed, изоляция tenant).

A4. Свежие данные ещё догружаются (окно freshness)

  1. Застройщик запросил статистику за период, включающий текущий день/последние минуты.
  2. Часть просмотров ещё не доехала в витрину (в пределах ≤ 5 мин) — возвращается 200 с текущими (частичными по последнему интервалу) значениями; прошлые завершённые дни полны.
  3. Повторный запрос через несколько минут отражает догнавшие данные (значения монотонно дорастают, идемпотентно — без задвоения).

Завершается: фронт показывает данные с пометкой актуальности «данные за последние минуты могут уточняться».

Постусловие

  • Мутаций нет (read-only): факты/агрегаты/измерения не изменяются, доменные и интеграционные (Kafka) события не публикуются (dev-stats — листовая аналитика, событий не порождает), аудит не пишется (аудит — только экспорт, UC-STA-005).
  • Клиент получил типизированную сводку + временной ряд строго в scope своего DeveloperId.

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

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

Acceptance Criteria

#КритерийПроверка
AC1Запрос «проект за период» сканирует только префикс (DeveloperId, ProjectId) + затронутые месячные партицииТест плана запроса: уровни иерархии ниже проекта не сканируются
AC2DeveloperId берётся из актора; чужой DeveloperId403 до чтения витриныИзоляционный тест: подстановка чужого id не возвращает данные
AC3Подсчёт устойчив к дублям: повтор факта и пара open+closed одной сессии не задваивают ViewsТест: дубль факта и open+closed одной сессии считаются один раз
AC4Средняя длительность = DurationSum / Views (нулевая длительность open-строк не искажает среднюю)Тест: смесь open (0 c) и closed → корректная средняя
AC5Пустой период → 200 с нулевой сводкой и пустым рядом (не ошибка)Тест A1
AC6Ряд поддерживает granularity day/week/month (группировка по дате/началу недели/месяца)Тест: три гранулярности над одним диапазоном
AC7Ответ строго типизирован (ViewsSummary/ViewsTimeseriesPoint)Ревью контракта ответа
AC8Нет синхронных вызовов соседних сервисов; данные последних минут догоняются в окне freshness ≤ 5 мин (A4)Тест: запрос за текущий день не блокируется/не ошибается; повторный запрос отражает догнавшие факты

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

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

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