Files
healthlog/docs/database.md
T
av 01d0de59df разбор метрик HAE: фикстуры, канонизация, парсер
- tmp/research/fixtures.py собирает фикстуры из архива, вычищая измерения и
  сохраняя порядок ключей, форму литералов, выравнивание меток и невидимые
  символы; шесть фикстур в internal/hae/testdata
- internal/canon — общий дом канонической формы, полноты и хеша: числа читаются
  литералом через json.Number, округление до 12 значащих цифр
- internal/hae — разбор секции metrics, вывод слоя по метрике, разделение схем
  сна, координаты интервалом; recover внутри Parse, фаззинг
- миграция bucket и docs/database.md
2026-08-01 17:31:41 +03:00

6.8 KiB

Схема базы

SQLite (modernc.org/sqlite, чистый Go), миграции — goose, файлы в internal/store/migrations. Время в текстовых колонках всегда UTC RFC 3339 с секундной точностью: фиксированная ширина делает лексикографическую сортировку TEXT совпадающей с хронологией.

База производна. Источник истины — сырой архив тел (data/raw); состояние пересобирается свёрткой import(экспорт Apple) + replay(доставки по received_at). Поэтому терять базу неприятно, но не смертельно, а вот терять архив — смертельно.

┌────────────────────────────┐          ┌──────────────────────────────┐
│ delivery                   │          │ bucket                       │
│ ─────────────────────────  │          │ ────────────────────────────  │
│ id              TEXT PK    │          │ metric            TEXT ┐      │
│ received_at     TEXT       │          │ layer             TEXT ├ PK   │
│ automation_name TEXT       │  ┄┄┄┄┄▶  │ hour_utc          TEXT ┘      │
│ automation_id   TEXT       │ первая   │ units             TEXT        │
│ aggregation     TEXT       │ доставка │ payload           BLOB        │
│ period          TEXT       │  часа    │ content_hash      TEXT        │
│ session_id      TEXT       │          │ points            INTEGER     │
│ bytes           INTEGER    │          │ first_ts          TEXT        │
│ sha256          TEXT       │          │ last_ts           TEXT        │
│ raw_path        TEXT       │          │ first_delivery_id TEXT        │
│ parse_status    TEXT       │          │ sealed            INTEGER     │
│ points          INTEGER    │          │ created_at        TEXT        │
│ headers         TEXT       │          │ updated_at        TEXT        │
└────────────────────────────┘          └──────────────────────────────┘

Связь bucket.first_delivery_id → delivery.id внешним ключом не объявлена намеренно: доставки подрезаются ретеншеном до следующего проверенного экспорта, а объекты живут дольше. Ссылка — провенанс для разбора слияний, а не целостность.

delivery — учёт принятых пакетов

Одна строка на принятое тело. Само тело лежит в архиве по raw_path (относительный путь внутри data/raw).

Колонка Смысл
id ULID, он же имя файла в архиве
received_at время приёма, UTC
automation_name, automation_id какая автоматизация HAE прислала; automation_id нужен выводу слоя — по нему наследуется слой доставки без плотных метрик
aggregation заголовок automation-aggregation. Режима не означает: значение Default наблюдалось у посекундного, минутного и часового режимов одновременно
period заголовок периода (Since Last Sync и прочие)
bytes, sha256 размер и хеш тела; хеш пока только для учёта
parse_status pending / parsed / failed. Код ответа приёма от него не зависит: сохранили — значит приняли
points сколько точек дал разбор
headers все заголовки запроса JSON-объектом, кроме несущих секреты

Индексы: delivery_received_at (порядок журнала), delivery_sha256 (учёт повторов).

bucket — часовой объект точек

Единица хранения — час одной метрики в одном слое, а не отдельная точка.

Колонка Смысл
metric имя метрики как прислал HAE. Исключение — sleep_analysis_summary: под именем sleep_analysis приезжают две несовместимые схемы, и они разводятся на разные имена
layer sample / raw / minute / hour / day. Выводится из выравнивания меток
hour_utc начало часа, UTC. Час берётся по началу точки
units единицы метрики. Внутри точки их нет, они живут на уровне метрики
payload точки часа: JSON-массив исходных байтов, gzip. Точки упорядочены по началу
content_hash хеш канонической формы — детектор изменений, не ключ. Совпал — записи нет
points сколько точек внутри
first_ts, last_ts границы содержимого; каталогу разрезов, чтобы не разжимать блоб ради диапазона
first_delivery_id доставка, создавшая объект
sealed час, в который досчёт не ожидается. Правило перевода пока не определено

Таблица WITHOUT ROWID: обращение всегда по полному первичному ключу, и лишний уровень косвенности через rowid ни разу не нужен.

Идентичность точки внутри объекта — координаты метрика + слой + начало + конец, у точки-измерения конец равен началу. source в ключ не входит: он нестабилен и переписывается задним числом. При столкновении выигрывает более полная точка, а не последняя пришедшая.