Files
healthlog/CLAUDE.md
T
av 5e2385ba6e добавлены документация проекта и каркас разработки
- README, CLAUDE.md, docs: назначение и границы, архитектура, конвенции, план
- docs/local-research.md — 36 находок по формату Health Auto Export, снятых на
  живых данных; документация приложения местами расходится с тем, что оно шлёт
- Taskfile, .golangci.yml, самодокументируемый config.example.toml
2026-08-01 12:37:03 +03:00

83 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
Памятка для работы над healthlog. Перед задачей прочитай также
[README.md](README.md), [docs/architecture.md](docs/architecture.md),
[docs/conventions.md](docs/conventions.md) и [docs/plan.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](https://taskfile.dev) (`task --list` — полный список):
- `task run` — локальный запуск (`--config ./config.toml`)
- `task build` — статический бинарь linux/amd64
- `task test` / `task lint` — тесты и golangci-lint
- `task tidy``go mod tidy`
- `task setup` — установка golangci-lint
## Конвенции
Механизируемое проверяет `task lint` (`.golangci.yml`): форма логов
(`sloglint`), `fmt.Print*` / `os.Getenv` / `time.Now` мимо единых точек
(`forbidigo`), сравнение ошибок (`errorlint`), сторонние пакеты ошибок
(`depguard`). Пересказывать эти правила не нужно — линтер скажет точнее.
Прозой остаётся то, что правилом не выражается:
[docs/conventions.md](docs/conventions.md) — уровень лога по адресату,
единственный логирующий чекпоинт на доменной границе, трансляция ошибки на
внешней границе, самодокументируемый `config.example.toml`, время в БД в UTC
RFC 3339, ULID через `ident`.
Отдельно: **тесты на разбор формата HAE держим на реальных пакетах** в
`testdata`. Документация формата тонкая и местами расходится с тем, что
приложение реально шлёт, — источником истины служат живые данные.
Что показал реальный поток — [docs/local-research.md](docs/local-research.md).
Читать **до** работы над разбором: там же лежат находки, которых нет в
документации HAE (поле `source` существует; порядок ключей в JSON нестабилен,
поэтому хеш содержимого считается по канонической форме с рекурсивной
сортировкой; мелкая группировка даёт интерполяцию, а не измерения). Файл
пополняется по мере накопления доставок.
## Язык
- Документация, комментарии, сообщения коммитов — **русский**.
- Код и идентификаторы — английский.