Дельты влиты в openspec/specs (parsing, storage), задача убрана из беклога, план отражает сделанную часть шага 3. Не закрыт один пункт: живая доставка с телефона не разобрана — поток молчит с 17:13, пауза началась до перезапуска сервиса.
9.9 KiB
План
Живой документ: шаги отмечаем по мере готовности, отложенное копится внизу.
Это порядок и его обоснование, а не список работ. Единицы работы живут в беклоге — одна задача, один файл, свой приоритет. План отвечает «почему в таком порядке», беклог — «что брать следующим».
Ближайшая цель
Метрики разбираются и ложатся в часовые объекты: тела перестали быть
недифференцированной кучей. Дальше — доразобрать остаток шага 3
(тренировки и записи со своими id, reindex, словарь категориальных
значений) и разобрать блокеры, накопившиеся из ревью.
Разведка закончена: правило вывода слоя, модель идентичности и формы точки проверены на живом потоке, выводы — в local-research.md, 47 находок.
Шаги
- 1. Каркас.
Taskfile.yml,.golangci.yml,CLAUDE.md, TOML-конфиг с валидацией на старте, логгер, подкомандаserveс/healthz. - 2. Приём без разбора.
POST /api/v1/ingest: лимит тела, gzip, запись тела в архив, строка вdelivery. Разбора ещё нет. ← подключаем телефон по локальной сети - [~] 3. Разбор и хранилище. Метрики — сделано (
bucket,internal/hae,internal/canon,internal/fold); остались тренировки и записи со своимиid,reindexи словарь категориальных значений. Миграции, часовые объекты метрик (bucket, ключметрика + слой + час) +workout/recordпо своимid. Вывод слоя из выравнивания меток; разводsleep_analysisна два имени (находка 38). Канонизация с округлением чисел и хеш содержимого как детектор изменений. Слияние точек: при столкновении выигрывает более полная точка, а не последняя (находка 41). Три формата времени: локальное со смещением, RFC 3339 Z, Unix-эпоха внутриheartbeatSeries(находка 39). Словарь(локаль, строка) → код HealthKitдля переведённых значений (находка 37).healthlog reindex. - 4. Каталог и род агрегации. Род (
cumulative/instant/unknown) измеряется сверкой слоёв между собой, а не размечается руками (находка 40). Каталог метрик со слоями, диапазонами и родом. - 5. Read API. Точки метрики из часовых объектов, выбор слоя, необязательная свёртка по сетке. Огрубление, когда сетка не задана; ошибка со списком доступных сеток, когда задана явно. Тренировки, записи.
- 6. Самоописание. Выведенные из данных схемы содержимого со статистикой + статичная схема контракта API.
- 7. MCP. Эндпоинт того же процесса, транспорт Streamable HTTP, токен чтения общий с Read API. Три инструмента: каталог, значения за период, значения с разбивкой.
- 8.
healthlog import. Родной экспорт Apple Health: разборэкспорт.xmlв слойsample, маршруты GPX, ЭКГ из CSV. Заливка полной истории кусками по годам. - 9. Устаревание нижнего слоя. Пометка данных HAE старше проверенного экспорта. Проверка покрытия — непрерывность по дням и сходимость сумм с часовым слоем. Пометка ≠ удаление: удаление включаем только после того, как восстановление из экспорта отработает на живых данных хотя бы раз.
- 10. Наблюдаемость.
/stats: последняя доставка, счётчики, тишина по потоку, список строк без кода в словаре. - 11. Деплой.
Dockerfile, сборка образа локально, доставка на rivendell, конфиг Caddy, поддомены приёма и чтения, токены.
Порядок неслучаен. Шаг 4 стоит перед Read API, потому что без измеренного рода свёртка на шаге 5 неотличима от угадывания. Шаг 8 стоит перед 9: пока импорт экспорта не написан, помечать что-либо устаревшим не на основании чего.
Отложено
Отсев идентичных тел доставок поВычеркнуто: находка 2 показала, что порядок ключей в JSON нестабилен, поэтому два пакета с одними и теми же данными почти никогда не совпадают побайтно — хеш тела не сработает. Дедупликация возможна только по канонизированному содержимому, а это и делает хеш часового объекта. Отдельная механика не нужна.sha256.Вторая автоматизация без группировки.Сделано на телефоне: метрики здоровья идут в трёх разрезах — несуммированном для несуммируемых метрик, минутном и часовом для всех.Пометка локализованных полей в схемах.Переросло в шаг 3: одной пометки мало, нужен словарь кодов, иначе не сойтись с родным экспортом (находка 37).- Ретеншен сырого архива — удаление доставок старше последнего проверенного экспорта (не фиксированный срок: журнал не должен рваться). Пока архив не подчищается; включить после того, как разбор устоится.
- Порог
sealed— с какого возраста час считается запечатанным. Ставим по факту: сначала пишемWARNна изменение старых объектов и смотрим, какая глубина досчёта встречается в жизни (наблюдалось до 22 минут, находка 10). - Месячный проход по ручным секциям. Симптомы и лекарства заводятся задним числом на недели; количественным метрикам недельного прохода хватает. Заводить, когда эти секции появятся в потоке живьём.
- Алерт «данных нет N часов». Тихо сломавшаяся автоматизация — главный
эксплуатационный риск коллектора. В v1 факт виден в
/stats; активное уведомление добавим после. - Аннотации к схемам — человеческие описания метрик поверх выведенных схем, либо рукописный каталог. Выбор зависит от того, насколько стабильным окажется формат; меняться он может только с обновлением Health Auto Export, а это отслеживается.
- Схема тренировок — глубину вывода определим по факту, когда увидим, как приходят маршруты.
- Отказ от
heartbeatSeries. 93% объёма HRV (находка 39) ради данных, которых нет ни в одном планируемом запросе. Решать, когда станет ясна цена хранения нижнего слоя за год. - Выгрузка в parquet — отдельной командой, на случай тяжёлой аналитики снаружи. DuckDB читает и parquet, и файл SQLite напрямую, поэтому спешить некуда: дверь открыта без миграции.
- NDJSON-поток для больших выборок из read API.
- Разворачивание маршрутов тренировок в отдельную таблицу — если появится клиент, которому мало отдачи тренировки одним пакетом.