- README, CLAUDE.md, docs: назначение и границы, архитектура, конвенции, план - docs/local-research.md — 36 находок по формату Health Auto Export, снятых на живых данных; документация приложения местами расходится с тем, что оно шлёт - Taskfile, .golangci.yml, самодокументируемый config.example.toml
80 lines
7.0 KiB
Markdown
80 lines
7.0 KiB
Markdown
# План
|
||
|
||
Живой документ: шаги отмечаем по мере готовности, отложенное копится внизу.
|
||
|
||
## Ближайшая цель
|
||
|
||
Минимальное ядро, которое принимает и сохраняет пакет. Проверяем его
|
||
**локально**: сервис поднят на рабочей машине, телефон шлёт на её 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.
|
||
- **Слой агрегаций** поверх сырья (суточные/недельные срезы).
|
||
- **Разворачивание маршрутов тренировок** в отдельную таблицу — если появится
|
||
клиент, которому мало отдачи тренировки одним пакетом.
|