# Идентичность эпизодных метрик **Приоритет:** блокеры Вынуто из задачи `razbor-metrik-v-obekty` ревью дизайна (профиль `design`, находка №1 триажа, severity critical). Пока не решено — эпизодные схемы не хранятся, см. «Что стоит без решения». ## Что решить Состав координатного ключа и правило слияния для метрик, у которых **метка времени не уникальна**. Принятая модель — `метрика + слой + метка` — для них неверна. ## Оракул: измерено на живых данных Из 173 координат сна три несут разное содержимое, одна — три разных эпизода: ``` sleep_analysis 2026-07-31 22:04:00 start=22:04 end=22:16 qty=0.2 value=Во сне start=22:04 end=02:21 qty=4.28 value=В кровати start=22:04 end=07:51 qty=9.78 value=В кровати ``` Хуже: **31 координата в 21 доставке из 89** (почти четверть) задвоена **внутри одной доставки**. Там `received_at` у обеих точек один и тот же, поэтому предписанный тай-брейк «побеждает больший `received_at`» неприменим в принципе — исход решил бы порядок элементов в JSON-массиве, а он нестабилен (находка 2). Это ломало бы и детерминированность свёртки: пересборка из архива давала бы другое состояние, чем живой приём. Правило полноты не спасает: точка сна несёт одновременно `start`/`end` и `startDate`/`endDate`, так что два набора разных полей равной мощности равнополны. ## Варианты и цена **(А) Включить `start`/`end` в координату для эпизодных схем.** Закрывает и междоставочные, и внутридоставочные дубли — все 31 относятся к `sleep_analysis`. Цена: ключ разной формы для разных классов метрик, и надо определить, что делает метрику «эпизодной» (наличие `start`/`end`? список?). **(Б) Эпизодные метрики — множество с идентичностью по хешу канонической формы (append-only).** Ничего не теряется по построению. Цена: две модели идентичности в `store` и рост объекта — эпизоды не схлопываются никогда, даже когда повтор действительно повтор. **(В) Принять потерю: нынешнее правило плюс обязательный `WARN`.** Дёшево. Цена: необратимая потеря эпизодов примерно в четверти доставок сна, обнаружимая только сверкой с экспортом Apple, то есть месяцами позже. Прямо противоречит инварианту «ничего не теряем молча». Независимо от выбора: тай-брейк `received_at` надо заменить или дополнить детерминированным (например, лексикографически по канонической форме) — без провенанса в схеме нынешний неисполним даже между доставками. ## Рекомендация **(А).** Она закрывает оба класса дублей, не плодит вторую модель хранения и не требует принимать потерю. «Эпизодность» выводится из данных, а не курируется: точка, несущая `start` и `end`, отличные от `date`, — эпизод. Это в духе того, как в проекте уже выводится слой — из формы данных, а не из объявления. Против (Б): рост объекта ради случая, которого можно избежать. Против (В): это ровно тот класс молчаливой потери, ради защиты от которого заведён инвариант. ## Что стоит без решения `sleep_analysis` (поэпизодная схема) в задаче `razbor-metrik-v-obekty` **не сохраняется**: точки разбираются и считаются, но в объекты не пишутся. Сохранять их по правилу, о котором известно, что оно теряет, — хуже, чем не сохранять: тела лежат в архиве, и после решения блокера их подберёт `reindex`. Суточная сводка (`sleep_analysis_summary`) блокером не затронута — у неё метка на полуночи уникальна.