Files
av f8200f7f80 feat: разбор и хранение тренировок и состояния разума
- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной
  строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`;
  миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки
  с этими ключами
- сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет
  содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а
  при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА —
  «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина
  расходилась бы с пересборкой молча
- отпечаток витрины покрывает тренировки и записи и снимается одним снимком
  базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
2026-08-02 13:05:16 +03:00

67 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Фикстуры разбора Health Auto Export
Конвенция требует тестов на **реальных** пакетах: документация формата тонкая и
местами расходится с тем, что приложение шлёт на самом деле. Инвариант при этом
запрещает данным о здоровье попадать под контроль версий. Поэтому фикстуры
собраны из живого архива скриптом `tmp/research/fixtures.py`, который сохраняет
всё, на чём работает разбор, и уничтожает всё, что является измерением.
Сохранено дословно: порядок ключей, форма числового литерала, выравнивание
меток по секундам и минутам, смещение зоны, длительности интервалов, невидимые
символы в именах устройств, категориальные значения. Заменено: числа (цифры
переставлены с сохранением формы), даты (сдвинуты на постоянную величину),
имена устройств.
Для сущностей с собственным `id` вычищается дополнительно: сами
идентификаторы (псевдо-UUID той же формы), метки RFC 3339 и `route[].timestamp`
(сдвигаются, как и прочие даты), а также словарные значения `stateOfMind`
`kind`, `valenceClassification`, `labels`, `associations`. Последнее не
перестраховка: это измерение душевного состояния, самое чувствительное, что
есть в потоке. Значения подменяются другими кодами из того же словаря HealthKit,
поэтому форма (snake_case, строка против массива строк) сохраняется, а смысл —
нет. Побочный эффект подмены: в `labels` могут появиться повторы, которых HAE не
шлёт; разбору это безразлично.
Координаты маршрута вычищаются как обычные числа — они не отличаются от прочих
измерений и после подмены указывают в никуда.
| Файл | Что проверяет |
|---|---|
| `minute.json` | минутная доставка; плотные метрики минутные, одна (`apple_stand_hour`) часовая — классификация **по метрике**, а не по доставке; редкие наследуют минутный слой; суточная сводка сна |
| `hour.json` | часовая доставка целиком |
| `raw.json` | посекундная доставка |
| `mixed.json` | одна доставка с минутными, посекундными и часовыми метриками — перенастройка автоматизации |
| `heartbeat_series.json` | точка с `heartbeatSeries`: третий формат времени, серия проходит исходными байтами |
| `sparse_sleep.json` | доставка **без плотных метрик** (заголовок `Default` не спасает) и поэпизодный сон с задвоенной меткой — интервальная идентичность |
| `workout_route.json` | уличная тренировка с маршрутом и внутренними рядами; ряды урезаны `--limit`, форма точки маршрута сохранена |
| `workout_indoor.json` | тренировка без маршрута: набор полей зависит от типа (`temperature`, `humidity`, `intensity` вместо `route`, `avgSpeed`, `flightsClimbed`) |
| `state_of_mind.json` | `stateOfMind`: RFC 3339 в UTC, коды вместо переводов, поля `source` нет вовсе |
| `uncovered_sections.json` | одна доставка со всеми родами секций сразу — покрытыми и непокрытыми. Рукотворная: живой поток шлёт по одной секции за раз |
| `handmade_edge.json` | случаи, которых живой поток не даёт (см. ниже) |
| `handmade_entities.json` | краевые случаи сущностей: пустой, отсутствующий и слишком длинный `id`; неразбираемая метка и её отсутствие; неразбираемый `end`; нечисловая и отсутствующая длительность; две версии одного `id` в одном теле; элемент, не являющийся объектом |
`handmade_edge.json` собран руками, скриптом не воспроизводится:
- `mixed_alignment` — плотная метрика, где 10 меток часовые и одна минутная:
слой обязан выйти минутным, потому что слой метрики — **самое мелкое**
встретившееся выравнивание;
- `unknown_shape` — незнакомое поле точки (в том числе вложенное), `1.0`, целое
больше 2⁵³ и дробь длиннее двенадцати значащих цифр: всё это обязано пережить
хранение дословно;
- `broken_timestamps` — RFC 3339 вместо формата HAE, пустая строка, отсутствие
`date` и число вместо строки: каждая точка пропускается со счётчиком, разбор
не падает;
- `interval_points` — три записи с одной меткой и разными интервалами: ключ по
метке схлопнул бы их в одну.
Обновить набор:
```
python3 tmp/research/fixtures.py --list
python3 tmp/research/fixtures.py <файл из data/raw> --metrics a,b --limit 14 \
--out internal/hae/testdata/<имя>.json
```
`--limit` режет и внутренние ряды тренировки (маршрут, пульс, энергия): фикстуре
нужна форма точки ряда, а не 593 её экземпляра.