feat: разбор и хранение тренировок и состояния разума
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`; миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки с этими ключами - сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА — «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина расходилась бы с пересборкой молча - отпечаток витрины покрывает тренировки и записи и снимается одним снимком базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
## Why
|
||||
|
||||
Половина живого потока разбором не покрыта. Замер по 118 доставкам архива:
|
||||
65 несут `metrics`, 27 — `workouts`, 26 — `stateOfMind`. Тренировки и состояние
|
||||
разума сохраняются в архив и числятся `partial`, но в витрину не попадают:
|
||||
трекеру (второй потребитель паспорта) взять тренировку неоткуда, агенту-медику
|
||||
состояние разума — тоже.
|
||||
|
||||
Для `stateOfMind` это дороже, чем для метрик: в родном экспорте Apple секции нет
|
||||
ни одним типом (находка 46), то есть доставки HAE — её **единственный** источник,
|
||||
и ретеншен архива без разобранной секции нельзя включать вовсе.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Разбор покрывает две новые секции тела: `workouts` и `stateOfMind`. Остальные
|
||||
секции с собственными `id` (`ecg`, `symptoms`, `cycleTracking`, `medications`,
|
||||
`heartRateNotifications`) остаются непокрытыми намеренно — живьём поток их не
|
||||
приносил ни разу, и модель под них закладывается, а разбор — нет.
|
||||
- Две новые таблицы: `workout` (заголовок колонками, всё остальное, включая
|
||||
маршрут и внутренние ряды, — `payload` дословно) и `record` (секции с
|
||||
собственным `id`, ключ `kind + id`).
|
||||
- Сущность с собственным `id` **заменяется целиком**, а не сливается по полям.
|
||||
Правило замены названо явно: приехавшая версия побеждает, если не теряет
|
||||
содержания сохранённой; иначе сохранённая остаётся, факт считается и идёт в
|
||||
`WARN`. При равных наборах полей выигрывает версия из более поздней доставки
|
||||
**журнала**, а не свёрнутая последней, — иначе живая витрина расходилась бы с
|
||||
пересборкой молча.
|
||||
- Сущность несёт провенанс — доставку своей версии и её метку приёма.
|
||||
- Разбор метки времени принимает второй формат — RFC 3339 в UTC, которым HAE шлёт
|
||||
`stateOfMind` (находка 16).
|
||||
- Отпечаток витрины (оракул сходимости `reindex`) покрывает тренировки и записи,
|
||||
а не одни часовые объекты, и снимается одним снимком базы. Отчёт `reindex`
|
||||
считает «до и после» по каждой единице хранения и называет «покрыта новая
|
||||
секция» ожидаемым классом расхождения.
|
||||
- «Отказ разбора — всё или ничего» распространяется на сущности явно, включая
|
||||
ветку невыводимого слоя и повтор ключа секции.
|
||||
- Миграция переводит в `pending` доставки, у которых в списке непокрытых секций
|
||||
стоят ставшие покрытыми имена, — правило «покрыли секцию — пересверните» уже
|
||||
записано в спеке хранения.
|
||||
- **Не входит:** отдача тренировок и записей наружу. Read API в проекте пока нет
|
||||
вовсе; его форма (конверт ответа, выбор слоя, предел размера) проектируется
|
||||
задачей `read-api-tochki`, и вводить два эндпоинта раньше конверта значило бы
|
||||
задать контракт мимоходом.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Новых нет: тренировка и запись — это то же хранилище и тот же разбор, только
|
||||
другая единица хранения. Отдельная capability создала бы второй словарь для того
|
||||
же домена.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `parsing`: покрытых секций становится три вместо одной; появляется разбор
|
||||
сущностей с собственным `id` и второй формат метки времени (RFC 3339),
|
||||
оставленный предыдущей дельтой явно ненормированным; «всё или ничего»
|
||||
распространяется на сущности.
|
||||
- `storage`: появляется вторая единица хранения — сущность с собственным `id`, со
|
||||
своим правилом замены версии; отпечаток витрины перестаёт быть отпечатком одних
|
||||
часовых объектов; запрет на данные о здоровье в логах распространяется на
|
||||
содержимое сущностей.
|
||||
- `reindex`: счётчики отчёта покрывают все единицы хранения, а «покрыта новая
|
||||
секция» становится названным классом ожидаемого расхождения.
|
||||
|
||||
## Impact
|
||||
|
||||
- `internal/hae` — разбор двух секций, второй формат метки, новые счётчики
|
||||
пропусков.
|
||||
- `internal/store` — таблицы `workout` и `record`, слияние доставки одной
|
||||
транзакцией вместе с точками, отпечаток витрины.
|
||||
- `internal/fold` — счётчики и единственный логирующий чекпоинт свёртки.
|
||||
- `internal/replay` — ничего, кроме того, что отпечаток стал шире: пересборка
|
||||
зовёт ту же свёртку.
|
||||
- Миграция `00007` — две таблицы и перевод `partial`-доставок в `pending`.
|
||||
- `docs/database.md`, `docs/architecture.md`, `docs/local-research.md` — схема,
|
||||
правило замены версии и находка о поведении тренировки при переприсылке.
|
||||
- Фикстуры `internal/hae/testdata` и скрипт их сборки `tmp/research/fixtures.py`:
|
||||
тренировка с маршрутом и состояние разума — на реальных пакетах с вычищенными
|
||||
измерениями.
|
||||
Reference in New Issue
Block a user