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