Files
healthlog/internal/hae/testdata
av 8331328134 Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет
  вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и
  ряд из null больше не затирают маршрут. Победитель внутри доставки стал
  функцией множества версий — общим помощником с точками, — а провенанс
  поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину
  к прежнему содержимому.
- Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной
  записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма
  считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает
  на старте; текст ошибки разбора не несёт значений из тела.
- Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты:
  безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя
  был квадратичен по числу присланных версий одного ключа.
2026-08-02 16:38:18 +03:00
..

Фикстуры разбора Health Auto Export

Конвенция требует тестов на реальных пакетах: документация формата тонкая и местами расходится с тем, что приложение шлёт на самом деле. Инвариант при этом запрещает данным о здоровье попадать под контроль версий. Поэтому фикстуры собраны из живого архива скриптом tmp/research/fixtures.py, который сохраняет всё, на чём работает разбор, и уничтожает всё, что является измерением.

Сохранено дословно: порядок ключей, форма числового литерала, выравнивание меток по секундам и минутам, смещение зоны, длительности интервалов, невидимые символы в именах устройств, категориальные значения. Заменено: числа (цифры переставлены с сохранением формы), даты (сдвинуты на постоянную величину), имена устройств.

Для сущностей с собственным id вычищается дополнительно: сами идентификаторы (псевдо-UUID той же формы), метки RFC 3339 и route[].timestamp (сдвигаются, как и прочие даты), а также словарные значения stateOfMindkind, 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 её экземпляра.