Files
healthlog/openspec/changes/razbor-metrik-v-obekty/proposal.md
T
av c6f27e3890 заведён change на разбор метрик в часовые объекты
- proposal и две capability: parsing (вывод слоя, форматы времени, канонизация)
  и storage (координатный ключ, слияние по полноте, часовые объекты)
- design фиксирует границы: разбор отдельным пакетом, синхронно после записи в
  архив, конкурентная запись в один час — риск с тестом
- вне scope сознательно: тренировки, reindex, словарь кодов, род агрегации
2026-08-01 14:50:10 +03:00

76 lines
6.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 шлёт две несовместимые схемы.
- Три формата времени на входе: локальное со смещением, RFC 3339 Z,
Unix-эпоха внутри `heartbeatSeries`. В хранилище — UTC RFC 3339 плюс офсет
исходной зоны.
- Разбор не влияет на код ответа приёма: непонятое содержимое по-прежнему
даёт 200, исход разбора виден в `delivery.parse_status` и в логе.
Не входит в изменение (сознательно, чтобы задача мерджилась целиком):
- **тренировки и секции с собственными `id`** (`workouts`, `stateOfMind`,
прочие `record`) — отдельная задача, у них другая модель хранения;
- **`healthlog reindex`** — отдельная задача. Следствие: разбираются только
доставки, пришедшие после выката; накопленные 89 доедут пересборкой.
Сходимость на них проверяется скриптом поверх архива, а не командой сервиса;
- **словарь категориальных значений** (переведённые строки → коды HealthKit) —
отдельная задача; пока строка хранится дословно и без кода рядом;
- **род агрегации и каталог разрезов** — отдельная задача, она следующая.
## Capabilities
### New Capabilities
- `parsing`: превращение тела доставки Health Auto Export в точки — вывод
слоя, разбор трёх форматов времени, канонизация содержимого, разделение
схем под одним именем метрики. Отдельно от хранения потому, что импорт
родного экспорта Apple будет другим разбором поверх того же хранилища.
- `storage`: идентичность точки, слияние и хранение часовыми объектами —
координатный ключ, правило разрешения столкновений, хеш как детектор
изменений, признак запечатанного часа.
### Modified Capabilities
Нет: `openspec/specs/` пуст, приём кодом существует, но спекой не описан и в
этом изменении не трогается.
## Impact
- Новые пакеты `internal/hae` (разбор) и расширение `internal/store` (объекты).
- Миграция `internal/store/migrations/00003_bucket.sql` — первая миграция после
приёма; вместе с ней заводится `docs/database.md` (ER-схема), которую требует
шаг гейта `er-schema`.
- `internal/ingest` получает шаг разбора после записи в архив; контракт приёма
не меняется.
- Появляется `testdata` с реальными пакетами HAE. Значения в них **вычищаются**:
структура, порядок ключей, форматы времени, неразрывные пробелы в именах
устройств и точность чисел сохраняются, измеренные величины заменяются —
данные о здоровье не попадают под контроль версий.