- секции `workouts` и `stateOfMind` покрыты разбором: тренировка лежит одной строкой вместе с маршрутом и внутренними рядами, запись — по ключу `род + id`; миграция 00007 заводит обе таблицы и возвращает в очередь `partial`-доставки с этими ключами - сущность заменяется целиком, но условно: приехавшая побеждает, если не теряет содержания сохранённой (множество ключей и длины верхнеуровневых массивов), а при равном содержании выигрывает версия из более поздней доставки ЖУРНАЛА — «побеждает приехавшая» было бы функцией порядка свёртки, и живая витрина расходилась бы с пересборкой молча - отпечаток витрины покрывает тренировки и записи и снимается одним снимком базы; отчёт `reindex` считает «было и стало» по каждой единице хранения
67 lines
6.6 KiB
Markdown
67 lines
6.6 KiB
Markdown
# Фикстуры разбора 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 её экземпляра.
|