- README, CLAUDE.md, docs: назначение и границы, архитектура, конвенции, план - docs/local-research.md — 36 находок по формату Health Auto Export, снятых на живых данных; документация приложения местами расходится с тем, что оно шлёт - Taskfile, .golangci.yml, самодокументируемый config.example.toml
5.8 KiB
CLAUDE.md
Памятка для работы над healthlog. Перед задачей прочитай также README.md, docs/architecture.md, docs/conventions.md и docs/plan.md.
Что это
Коллектор данных Apple Health. Принимает выгрузки из Health Auto Export, хранит их и отдаёт другим моим проектам через HTTP API. Это хранилище, а не аналитика: принять, дедуплицировать, сохранить, отдать. Не считать агрегаты, не переименовывать поля Apple, не интерпретировать значения.
Стек
Go, один статический бинарь (CGO_ENABLED=0). SQLite (modernc.org/sqlite,
чистый Go), chi, sqlx, goose (миграции), pelletier/go-toml/v2,
log/slog, ULID через internal/ident.
Module path — git.vakhrushev.me/av/healthlog.
Инварианты
- Точки хранятся дословно. Часовой объект держит точки ровно в том виде, в каком их прислал HAE. На этом инварианте держится всё остальное: сырой архив живёт лишь 14 дней, дальше истина — сами объекты. Начнём что-то отбрасывать внутри точки — срок хранения архива станет сроком жизни данных.
- Сохранили — значит приняли. Код ответа отражает доставку, а не разбор: битый JSON — 400, непонятое содержимое — 200.
- Ничего не теряем молча. Идентичность — хеш канонизированного
(рекурсивно отсортированного) содержимого: повтор не меняет ничего,
различие сохраняется. Изменение запечатанного часа —
WARN, но данные всё равно пишутся. - Дыры закрываются сами. Три прохода синхронизации разной глубины (5 минут / час / сутки), настройки данных у всех одинаковы.
- Форма Apple не транслируется. Значения отдаём как пришли, нормализовано
только время (
ts_utc+ офсет исходной зоны). - Своей агрегации нет — есть слои. Метрика хранится в той подробности, в
какой пришла (
raw/minute/hour); слой выводится из выравнивания меток, а не из заголовка HAE — тот врёт. Клиенту показываем каталог разрезов, выбор за ним. - Секреты не в логах — токены приёма и чтения. Данные о здоровье
чувствительны: тела запросов только на
DEBUGи с обрезкой.
Команды
Запуск через Task (task --list — полный список):
task run— локальный запуск (--config ./config.toml)task build— статический бинарь linux/amd64task test/task lint— тесты и golangci-linttask tidy—go mod tidytask setup— установка golangci-lint
Конвенции
Механизируемое проверяет task lint (.golangci.yml): форма логов
(sloglint), fmt.Print* / os.Getenv / time.Now мимо единых точек
(forbidigo), сравнение ошибок (errorlint), сторонние пакеты ошибок
(depguard). Пересказывать эти правила не нужно — линтер скажет точнее.
Прозой остаётся то, что правилом не выражается:
docs/conventions.md — уровень лога по адресату,
единственный логирующий чекпоинт на доменной границе, трансляция ошибки на
внешней границе, самодокументируемый config.example.toml, время в БД в UTC
RFC 3339, ULID через ident.
Отдельно: тесты на разбор формата HAE держим на реальных пакетах в
testdata. Документация формата тонкая и местами расходится с тем, что
приложение реально шлёт, — источником истины служат живые данные.
Что показал реальный поток — docs/local-research.md.
Читать до работы над разбором: там же лежат находки, которых нет в
документации HAE (поле source существует; порядок ключей в JSON нестабилен,
поэтому хеш содержимого считается по канонической форме с рекурсивной
сортировкой; мелкая группировка даёт интерполяцию, а не измерения). Файл
пополняется по мере накопления доставок.
Язык
- Документация, комментарии, сообщения коммитов — русский.
- Код и идентификаторы — английский.