Dev Specs

Epic E08 — Reviews [desktop]

Назначение эпика: Сбор отзывов гостей по завершённым бронированиям (per-reservation ratings + comment) и операционная аналитика — какие юниты и какие сотрудники дают какое качество гостевого опыта. Участвует в use-cases: UC-5 (review request → review → reviews dashboard, см. 00-overview §5). Сущности (owns/touches): Review (owns), Reservation (touches — источник review request), Unit (touches), Employee (touches — heat-grid строится по сотрудникам). Экраны: S05 (Reviews list per-reservation), S52 (Reviews heat-grid: Employees × Units). Открытые findings: F-rating-scale-airbnb-vs-booking (CRITICAL для модели Review — см. findings.md; нормализация шкал описана ниже).

Статус реализации (2026-06-21):

  • WP-1 DONE + PROD LIVE (PR #22 41ca688, DEV+PROD на /staffapp/reviews): S05 Reviews list (/reviews, standalone) — FiltersBar (search + Platform + Unit dropdown + inert Filters 🟡) + DataTable 14 колонок (горизонт-скролл, normalized rating) + Drawer (row-click → comment + разбивка под-рейтингов normalized+raw). Owns Review/ReviewListItem/ReviewSubRatings/ReviewFilters + ReviewPlatform/RatingScale. Решает F-rating-scale-airbnb-vs-booking (прототип): raw+scale хранятся, normalized к 5 отображается (Booking /2), null под-рейтинг → «—». utils normalizeRating/ formatRating/filterReviews/toReviewListItem. План: docs/superpowers/plans/2026-06-21-e08-reviews-wp1.md. Read-only (мутаций нет).
  • WP-2 DONE (2026-06-22, ветка feat/e08-reviews-wp2): S52 heat-grid (/reviews/heat-grid, Employees × Units). Новый 2D-grid примитив core/ui/heat-grid (sticky оси, горизонт/верт-скролл); heat-cell = цвет по avg normalized (high/mid/low/none токены, AA-контраст) + число = count. Pure-utils heatLevel/buildHeatGrid (sparse cells, оси sort alpha, avg по normalized 5-scale) / filterHeatRows (rows-only, units сохраняются) + action getHeatGrid. Связь Review→Employee — явное опциональное поле Review.attributedEmployees?: string[] (домен-правило A/B/C/D заказчик выберет позже; фронт независим). Drill-down: клик по ячейке → атомарный applyDrill(employee, unit) в reviews-filters store → /reviews отфильтрован + drill-context banner «{employee} · {unit}» с Clear. Toggle List↔Heat-grid. Fast-filter = employee-search + Platform; period (today)/advanced — inert 🟡. PDD blind: pre-review 3 CRIT+2 IMP (resolved-in-plan: sparse cells, units-preserve, i18n-strings, store-init+atomic drill, employee-allowlist) → validation 661/661 первый прогон (+77) → post-review 1 IMP (heat-cell AA contrast) +1 NIT (banner unit) fix-now → verify-feature SHIP 9/9 (real-browser, EN/ID, 0 console errors). План: docs/superpowers/plans/2026-06-22-e08-reviews-wp2.md. E08 desktop завершён.

S05 — Reviews [desktop]

  • Назначение: Табличный реестр всех отзывов гостей, привязанных к бронированиям, с детализацией по под-рейтингам качества обслуживания.
  • Источник (Miro): Reviews · id 3458764672705780426 (1440×900, input_text×1 — поле fast filters).
  • Layout: header → строка fast filters + кнопка filters (расширенные/сохранённые) → широкая data-table со скроллом по горизонтали (много колонок под-рейтингов). Detail отзыва — 🟡 через right-side drawer (см. ниже).
  • Данные на экране (колонки таблицы, дословные лейблы):
    • ID — идентификатор отзыва/брони (в данных: 34324324, 234234324, 224324324).
    • Platform — источник: Booking.com / Airbnb.
    • Guest — имя гостя (placeholder John Wick).
    • Review Date — дата отзыва (22/05/2026).
    • Unit — юнит (Alaya).
    • Check In / Check Out — даты проживания (22/05/2026).
    • Rating — общий рейтинг отзыва.
    • Под-рейтинги (6 шт.): Value, Cleanliness, Location, Communication, Accuracy, Check In.
    • В данных значения под-рейтингов идут единым блоком (3 3 3 3 3 3 3 + хвост 1; 4 4 4 4 4 4 4 + 2; 5 5 5 5 5 5 5 + 3) — общий + 6 под-рейтингов; хвостовое значение (1/2/3) — 🟡 интерпретируем как агрегированный балл/нормализованную оценку (см. эпик-уровень).
  • Контролы и действия:
    • fast filters (input/filter) → быстрый поиск/фильтр по строкам.
    • filters (button) → панель расширенных фильтров. Фильтруемые поля из контента: Reservation, Comments, Communication, Accuracy, Check In (+ предполагаемо Platform, Unit, Rating🟡).
    • Клик по строке → drawer с полным текстом comment + разбивкой под-рейтингов 🟡 (в скелете detail-экрана отзыва нет).
  • Состояния/вкладки: default (все отзывы) · filtered · empty (нет отзывов) · loading · error. Вкладок нет.
  • Связи (data-flow): данные приходят из Review (создаётся по review request после check-out брони, 00-overview §5). Читает: Review, Reservation, Unit, Guest. Действия (фильтр/просмотр) — read-only, мутаций не порождает.
  • Мутации: нет (read-only реестр). Создание Review — вне этого экрана (review request flow).
  • Права: Operations / Coordinators — read всех своих department-scoped отзывов. Owner Services — read (качество по объектам). Field staff — нет доступа (desktop-only эпик).
  • 🟡 [не было додумано]:
    • Detail-drawer отзыва — в скелете отдельного detail-экрана нет, но колонка comment обрезана → нужен способ прочитать полный текст. Минимальное добавление.
    • Хвостовой балл (1/2/3) после 6 под-рейтингов трактуем как нормализованный/агрегированный показатель — однозначной подписи в данных нет.
  • Edge cases:
    • Empty: «No reviews yet» (брони без отзывов / гость не оставил).
    • Длинный comment → truncate в таблице + полный текст в drawer.
    • Отзыв без под-рейтингов (Booking возвращает не все категории) → пустые ячейки, не 0.
    • Смешанные шкалы Booking/Airbnb в одной таблице → отображение нормализуется (см. эпик-уровень) во избежание «5 Booking ≠ 5 Airbnb».

S52 — Reviews (heat-grid) [desktop]

  • Назначение: Аналитическая матрица «Employees × Units» — визуальная тепловая карта качества: какой сотрудник на каком юните даёт какие отзывы. Операционный взгляд поверх плоского реестра S05.
  • Источник (Miro): Copy of Reviews · id 3458764672713018874 (1440×900, mockups×29 = ячейки сетки, input_text×1 = fast filters).
  • Layout: header → fast filters со встроенным пресетом (today) + кнопка filters → двумерная grid-таблица: строки = Employees, колонки = Units (в данных колонки Alaya ×6). Ячейки (29 mockups) — цветовые/числовые маркеры рейтинга (heat-cell).
  • Данные на экране:
    • Ось строк: Employees (список сотрудников; конкретные имена в скелете не заполнены).
    • Ось колонок: Units (Alaya повторяется — placeholder под список юнитов).
    • Ячейка: агрегированный рейтинг сотрудника по юниту (среднее/счётчик отзывов) 🟡 — точная метрика ячейки в скелете не подписана.
  • Контролы и действия:
    • fast filters (filter) с пресетом (today) → временно́е окно агрегации (today и др. периоды).
    • filters (button) → расширенные фильтры (период, platform, department 🟡).
    • Клик по ячейке → 🟡 drill-down к отфильтрованному S05 (отзывы этого сотрудника по этому юниту). В скелете переход не задан.
  • Состояния/вкладки: default · фильтр по периоду (today / иной) · empty (нет отзывов в окне) · loading. Вкладок нет.
  • Связи (data-flow): агрегирует те же Review, что и S05, по осям Employee и Unit. Read-only. Источник привязки «сотрудник ↔ отзыв» — 🟡 через Task/Assignment, обслуживавший бронь (точная связь Review→Employee в модели не задана — см. эпик-уровень).
  • Мутации: нет (аналитический read-only вид).
  • Права: Operations / Coordinators (по своим department-scoped сотрудникам и юнитам). Owner Services — read.
  • 🟡 [не было додумано]:
    • Семантика heat-cell (среднее vs количество) — в скелете не подписана; для прототипа: цвет = среднее нормализованное (electric-green высокое / amber среднее / red низкое), число = кол-во отзывов.
    • Связь Review→Employee — не было явной модели; предполагаем через бронь/задачу обслуживания. Подтвердить у заказчика.
  • Edge cases:
    • Пустая ячейка (нет отзывов сотрудник×юнит) → нейтральный серый, без числа.
    • Много сотрудников/юнитов → grid скроллится, оси фиксируются (sticky).
    • Период today без данных → empty-state по всей сетке.

Cross-screen relations (внутри эпика)

S05 (плоский реестр отзывов)  ──агрегация по Employee×Unit──►  S52 (heat-grid)
S52 ячейка ─клик (🟡 drill-down)─►  S05 (отфильтрованный по employee+unit)
Оба читают одну сущность Review; ни один не мутирует её.

Решения заказчика (2026-06-22)

Ответы со встречи Akira × Sergey (реестр 00-client-questions.md). Перекрывают соответствующие 🟡-допущения ниже.

  • Q-04 · review-employee-attribution — РЕШЕНО (resolves S52 §2 связь Review→Employee): атрибуция отзыва — по цепочке отзыв → вилла → кластер → команда кластера: отзыв получает вся команда, прикреплённая к кластеру этой виллы. (Не per-role, не ручная — на уровне cluster-team.) Цель — мотивация (показывать хорошие отзывы сотрудникам по их объекту; запрос Mark & Alex). Поле Review.attributedEmployees? (WP-2) бэкенд заполняет командой кластера — фронт heat-grid не меняется.
  • F-rating-scale (Booking 1–10 vs Airbnb 1–5) — ПОДТВЕРЖДЕНО: нормализация шкал на бэке к единой (как уже сделано в WP-1: raw+scale хранятся, normalized к 5 отображается). Заказчик подтвердил необходимость модификатора/transform.

Дозумано на уровне эпика 🟡

  1. Нормализация шкал рейтинга (F-rating-scale-airbnb-vs-booking). Booking.com — 10-балльная шкала, Airbnb — 5-балльная; в данных S05 рейтинги смешаны. Решение для модели Review:
    • Хранить сырой рейтинг + его scale (platform_scale: 5 | 10) — без потери исходных данных.
    • Для отображения и любой агрегации (S05 общий Rating, S52 heat-cell) приводить к единой 5-балльной шкале (Booking /2), показывая нормализованное значение; сырое — в tooltip/drawer.
    • Под-рейтинги (Value/Cleanliness/Location/Communication/Accuracy/Check In) — категории Airbnb; Booking даёт частично иной набор → при отсутствии категории ячейка пустая (не 0), агрегаты считают только присутствующие.
    • Без нормализации S05 и S52 показывали бы несопоставимые числа («4 у Booking» хуже «4 у Airbnb»). Это минимально необходимое отклонение от скелета, прямо требуемое findings.
  2. Связь Review→Employee для S52 — в скелете не было явной модели. Предполагаемая привязка: отзыв относится к брони → бронь обслуживали задачи → исполнитель(и) задач = сотрудники в heat-grid. Подтвердить у заказчика.
  3. Detail-drawer отзыва (полный comment + разбивка) — в скелете отдельного экрана нет; добавлен как минимальный способ прочитать обрезанный в таблице текст.