- tmp/research/fixtures.py собирает фикстуры из архива, вычищая измерения и сохраняя порядок ключей, форму литералов, выравнивание меток и невидимые символы; шесть фикстур в internal/hae/testdata - internal/canon — общий дом канонической формы, полноты и хеша: числа читаются литералом через json.Number, округление до 12 значащих цифр - internal/hae — разбор секции metrics, вывод слоя по метрике, разделение схем сна, координаты интервалом; recover внутри Parse, фаззинг - миграция bucket и docs/database.md
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 в ключ не входит: он нестабилен и переписывается задним числом. При
столкновении выигрывает более полная точка, а не последняя пришедшая.