- три решения опровергнуты экспериментами: повтор при SQLITE_BUSY не сходится без _txlock=immediate (242 из 800 против 800 из 800), разбор в map[string]any держит 197 МиБ против 54, канонизация без json.Number теряет литерал - канонизации назначен дом: общий internal/canon вместо hae, иначе импорт экспорта Apple потребует второй реализации и хеш-детектор станет бесполезен - вывод слоя вернул шаг наследования, WARN сравнивается только с надёжным заголовком; в bucket возвращены units и границы содержимого
85 lines
7.2 KiB
Markdown
85 lines
7.2 KiB
Markdown
## Why
|
|
|
|
Приём работает третьи сутки, но дальше архива данные не идут: 89 доставок лежат
|
|
телами `.json.gz`, а в SQLite только строки `delivery`. Ни одна из трёх целей
|
|
проекта — агент-медик, трекер тренировок, фитнес-игра — не может прочитать
|
|
ничего, потому что читать нечего. Каталог, Read API, MCP и OpenAPI упираются в
|
|
эту задачу.
|
|
|
|
Правила разбора не надо изобретать: они выведены измерением на живом потоке и
|
|
записаны в `docs/local-research.md` (находки 30, 33, 35, 36, 38, 39, 41).
|
|
Задача — перенести их в код, а не спроектировать заново.
|
|
|
|
## What Changes
|
|
|
|
- Секция `metrics` тела доставки разбирается в **точки**: метрика, слой, метка
|
|
времени, содержимое как пришло.
|
|
- **Слой выводится из выравнивания меток**, а не из заголовка HAE: плотная
|
|
метрика (≥10 точек) классифицируется сама, редкая наследует преобладающий
|
|
слой доставки. Заголовок `automation-aggregation` непригоден — значение
|
|
`Default` соответствует трём разным режимам.
|
|
- Точки складываются в **часовые объекты** (`bucket`, ключ `метрика + слой +
|
|
час`), содержимое — gzip-BLOB. Запись — read-modify-write со слиянием.
|
|
- **Идентичность точки — координаты** (`метрика + слой + метка`). `source` в
|
|
ключ не входит: он нестабилен и меняется задним числом. При столкновении
|
|
выигрывает **более полная** точка, а не последняя пришедшая.
|
|
- **Канонизация с округлением** чисел до ~12 значащих цифр; хеш канонической
|
|
формы — детектор изменений, а не ключ.
|
|
- `sleep_analysis` разводится на два имени: поэпизодное и суточную сводку —
|
|
под одним именем HAE шлёт две несовместимые схемы.
|
|
- Метка точки разбирается из локального времени со смещением и хранится в UTC
|
|
плюс офсет исходной зоны. Эпоха внутри `heartbeatSeries` **не разбирается** —
|
|
серия проходит исходными байтами; RFC 3339 встречается только в
|
|
`stateOfMind` и нормируется вместе с ним, отдельной задачей.
|
|
- Разбор не влияет на код ответа приёма: непонятое содержимое по-прежнему
|
|
даёт 200, исход разбора виден в `delivery.parse_status` и в логе.
|
|
|
|
Не входит в изменение (сознательно, чтобы задача мерджилась целиком):
|
|
|
|
- **тренировки и секции с собственными `id`** (`workouts`, `stateOfMind`,
|
|
прочие `record`) — отдельная задача, у них другая модель хранения;
|
|
- **`healthlog reindex`** — отдельная задача. Следствие: разбираются только
|
|
доставки, пришедшие после выката; накопленные 89 доедут пересборкой.
|
|
Сходимость на них проверяется скриптом поверх архива, а не командой сервиса;
|
|
- **словарь категориальных значений** (переведённые строки → коды HealthKit) —
|
|
отдельная задача; пока строка хранится дословно и без кода рядом;
|
|
- **род агрегации и каталог разрезов** — отдельная задача, она следующая;
|
|
- **хранение поэпизодного `sleep_analysis`** — вынуто ревью дизайна блокером
|
|
`identichnost-epizodnyh-metrik`: координатный ключ его схлопывает (измерено:
|
|
31 координата в 21 доставке из 89 задвоена внутри одной доставки, где
|
|
тай-брейк по времени приёма неприменим в принципе). Точки разбираются и
|
|
считаются, но не сохраняются; тела в архиве, подберёт пересборка.
|
|
Суточной сводки это не касается.
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
|
|
- `parsing`: превращение тела доставки Health Auto Export в точки — вывод
|
|
слоя, разбор трёх форматов времени, канонизация содержимого, разделение
|
|
схем под одним именем метрики. Отдельно от хранения потому, что импорт
|
|
родного экспорта Apple будет другим разбором поверх того же хранилища.
|
|
- `storage`: идентичность точки, слияние и хранение часовыми объектами —
|
|
координатный ключ, правило разрешения столкновений, хеш как детектор
|
|
изменений, признак запечатанного часа.
|
|
|
|
### Modified Capabilities
|
|
|
|
Нет: `openspec/specs/` пуст, приём кодом существует, но спекой не описан и в
|
|
этом изменении не трогается.
|
|
|
|
## Impact
|
|
|
|
- Новые пакеты `internal/hae` (разбор), `internal/canon` (каноническая форма,
|
|
полнота, хеш — общий дом для разбора и хранения) и расширение
|
|
`internal/store` (объекты).
|
|
- Миграция `internal/store/migrations/00003_bucket.sql` — первая миграция после
|
|
приёма; вместе с ней заводится `docs/database.md` (ER-схема), которую требует
|
|
шаг гейта `er-schema`.
|
|
- `internal/ingest` получает шаг разбора после записи в архив; контракт приёма
|
|
не меняется.
|
|
- Появляется `testdata` с реальными пакетами HAE. Значения в них **вычищаются**:
|
|
структура, порядок ключей, форматы времени, неразрывные пробелы в именах
|
|
устройств и точность чисел сохраняются, измеренные величины заменяются —
|
|
данные о здоровье не попадают под контроль версий.
|