# План Живой документ: шаги отмечаем по мере готовности, отложенное копится внизу. ## Ближайшая цель Минимальное ядро, которое принимает и сохраняет пакет. Проверяем его **локально**: сервис поднят на рабочей машине, телефон шлёт на её IP по локальной сети. Ни деплоя, ни домена, ни TLS для этого не нужно — они понадобятся, когда сервис поедет на rivendell (шаг 8). Это шаги 1–2. ## Шаги - [x] **1. Каркас.** `Taskfile.yml`, `.golangci.yml`, `CLAUDE.md`, TOML-конфиг с валидацией на старте, логгер, подкоманда `serve` с `/healthz`. - [x] **2. Приём без разбора.** `POST /api/v1/ingest`: лимит тела, gzip, запись тела в архив, строка в `delivery`. Разбора ещё нет. ← **подключаем телефон по локальной сети** - [ ] **3. Разбор и хранилище.** Миграции, часовые объекты метрик (`bucket`, ключ `метрика + слой + час`) + `workout`/`record` по своим `id`, вывод слоя из выравнивания меток, канонизация с округлением чисел и хеш содержимого, слияние точек в объект, два формата дат, `healthlog reindex`. - [ ] **4. Read API.** Каталог метрик со слоями и диапазонами, точки с пагинацией и выбором слоя (сборка из часовых объектов), тренировки, записи. - [ ] **5. Самоописание.** Каталог разрезов + выведенные из данных схемы содержимого со статистикой + статичная схема контракта API. - [ ] **6. `healthlog import`.** Заливка полной истории кусками по годам. - [ ] **7. Наблюдаемость.** `/stats`: последняя доставка, счётчики, тишина. - [ ] **8. Деплой.** `Dockerfile`, сборка образа локально, доставка на rivendell, конфиг Caddy, поддомен, токены. Порядок неслучаен. После шага 2 автоматизация в Health Auto Export включена и копит настоящие пакеты в сыром архиве. Документация формата HAE скудная, поэтому разбор на шаге 3 пишем по реальным данным, а не по догадкам — и заодно видим фактический объём и характер потока. ## Отложено - ~~Отсев идентичных тел доставок по `sha256`.~~ **Вычеркнуто:** находка 2 показала, что порядок ключей в JSON нестабилен, поэтому два пакета с одними и теми же данными почти никогда не совпадают побайтно — хеш тела не сработает. Дедупликация возможна только по канонизированному содержимому, а это и делает хеш часового объекта. Отдельная механика не нужна. - **Ретеншен сырого архива** (`storage.raw_retention`, 14 дней) — удаление старых тел. Пока архив не подчищается; включить после того, как разбор устоится, иначе страховка исчезнет раньше, чем перестанет быть нужна. - **Порог `sealed`** — с какого возраста час считается запечатанным. Ставим по факту: сначала пишем `WARN` на изменение старых объектов и смотрим, какая глубина досчёта встречается в жизни (наблюдалось до 22 минут, находка 10). - **Алерт «данных нет N часов».** Тихо сломавшаяся автоматизация — главный эксплуатационный риск коллектора. В v1 факт виден в `/stats`; активное уведомление добавим после. - **Аннотации к схемам** — человеческие описания метрик поверх выведенных схем, либо рукописный каталог. Выбор зависит от того, насколько стабильным окажется формат; меняться он может только с обновлением Health Auto Export, а это отслеживается. - **Схема тренировок** — глубину вывода определим по факту, когда увидим, как приходят маршруты. - **Пометка локализованных полей в схемах.** `context`, `value` и подобные приходят на языке телефона и изменятся при смене языка iOS — клиентам не стоит завязываться на конкретные строки ([local-research.md](local-research.md), находка 8). - **Вторая автоматизация без группировки** для `sleep_analysis` и `heart_rate_variability` — минутная группировка съедает фазы сна и межударные интервалы ценой ~130 точек в сутки (находка 6). - **Переход на более грубый нижний слой.** Посекундный слой стоит ~730 МБ в год против ~20 МБ у минутного. Когда решим, что мелкая подробность не нужна, достаточно выключить несуммированную автоматизацию — старые данные останутся в своих слоях, переписывать ничего не придётся. Обратный путь тоже есть: ручной экспорт из Apple Health + `healthlog import` восстанавливает нижний слой. - **NDJSON-поток** для больших выборок из read API. - **Слой агрегаций** поверх сырья (суточные/недельные срезы). - **Разворачивание маршрутов тренировок** в отдельную таблицу — если появится клиент, которому мало отдачи тренировки одним пакетом.