local-research: разобран формат экспорта Apple за 5.5 лет
- девять экспортов, версии 11→13→14; ни один тип записи не исчез, только добавлялись — разбор можно писать аддитивно - Correlation (давление) появился только в 2026 и ломает парсер по одним Record; имя файла локализовано, DTD расходится с данными - коды HealthKit переименовываются: те же записи сна приезжают как Asleep из экспорта 2021 и как AsleepUnspecified из свежего
This commit is contained in:
@@ -9,14 +9,36 @@
|
||||
Это же основание для устаревания нижнего слоя и единственный способ поднять
|
||||
историю глубже недели: дыра старше недели проходами синхронизации не чинится.
|
||||
|
||||
Формат разобран на девяти экспортах за 5.5 лет (находки 42–45), гадать не
|
||||
придётся:
|
||||
|
||||
- **версии 11 → 13 → 14**, на 14 стоит больше года; за всё время **ни один тип
|
||||
записи не исчез**, только добавлялись. Значит незнакомый тип — новый тип, а
|
||||
не сломанный парсер: падать на нём нельзя;
|
||||
- **`Correlation`** — обёртка из двух записей, ею приезжает давление. Появилась
|
||||
только в 2026 году. Парсер по одним `<Record>` разберёт давление как две
|
||||
несвязанные метрики и потеряет их парность;
|
||||
- **`WorkoutStatistics`** внутри тренировки — с 2024 года;
|
||||
- **имя файла локализовано**: `экспорт.xml`, не `export.xml` — так во всех
|
||||
девяти архивах;
|
||||
- **DTD расходится с данными** (в v11 у `<Me>` на атрибут больше объявленного)
|
||||
— валидировать документ его же DTD нельзя;
|
||||
- объём: 1.6 ГБ XML и 3.6 млн записей в свежем экспорте — только потоковый
|
||||
разбор, документ целиком в память не влезет.
|
||||
|
||||
Шаги:
|
||||
- разбор `экспорт.xml` (HealthKit Export Version 14, `<Record>` с
|
||||
`startDate`/`endDate`/`value`/`sourceName`/`device`) в слой `sample`;
|
||||
- маршруты GPX и ЭКГ отдельными CSV — они не в XML;
|
||||
- заливка кусками по годам: файл измеряется сотнями мегабайт.
|
||||
- потоковый разбор `экспорт.xml` в слой `sample`, включая `Correlation`;
|
||||
- `HeartRateVariabilityMetadataList` с `InstantaneousBeatsPerMinute` — это
|
||||
тот же `heartbeatSeries`, что в HAE (находка 39), 1.25 млн ударов;
|
||||
- маршруты GPX и ЭКГ отдельными файлами — их в XML нет;
|
||||
- заливка кусками по годам, идемпотентно: повторный импорт того же архива не
|
||||
должен ничего менять;
|
||||
- `export_cda.xml` игнорируем — это клинический формат тех же данных.
|
||||
|
||||
Готово, когда история за несколько лет лежит в слое `sample`, а суммы по нему
|
||||
сходятся с часовым слоем HAE на пересечении периодов.
|
||||
Готово, когда история за несколько лет лежит в слое `sample`, повторный импорт
|
||||
не меняет ничего, а суммы по слою сходятся с часовым слоем HAE на пересечении
|
||||
периодов.
|
||||
|
||||
Есть готовый файл для проверки: `/home/av/MediaEverything/HealthData/apple_health/`.
|
||||
Архивы: `/home/av/MediaEverything/HealthData/apple_health/` — девять штук,
|
||||
2021-12 … 2026-08. Старые версии формата годятся как регрессионный набор.
|
||||
|
||||
|
||||
@@ -11,9 +11,19 @@
|
||||
появятся сами. Задача — не пропустить момент: убедиться, что новые секции
|
||||
разбираются, а не молча падают в `parse_status`.
|
||||
|
||||
Отдельный вопрос, на который ответят эти же данные: есть ли эти секции в родном
|
||||
экспорте Apple. Если нет — экспорт им не источник истины, и устаревание
|
||||
нижнего слоя к ним неприменимо, держим всегда.
|
||||
Часть вопроса закрыта разбором экспортов (находка 42): в Health эти данные
|
||||
**есть** и в экспорте они присутствуют — `BodyMass` (1127 записей),
|
||||
`BloodPressureSystolic`/`Diastolic` (по 18), `BodyTemperature` (11), `Headache`
|
||||
(36), `SexualActivity` (46), `Dietary*` (по 88). Значит вопрос не «есть ли
|
||||
данные», а «доедут ли они через HAE и в какой форме».
|
||||
|
||||
Остаётся непроверенным `stateOfMind`: в экспорте его нет ни одним типом. Если
|
||||
подтвердится, что Apple его не выгружает, то экспорт ему не источник истины —
|
||||
устаревание нижнего слоя к нему неприменимо, держим всегда.
|
||||
|
||||
Давление приезжает обёрткой `Correlation` из двух записей (находка 44) — в
|
||||
экспорте точно, а вот как его отдаёт HAE, неизвестно. Это первое, на что
|
||||
смотреть, когда данные появятся.
|
||||
|
||||
Готово, когда каждая новая секция либо разобрана, либо явно описана в
|
||||
`docs/local-research.md` как не пришедшая, и ни одна не числится в ошибках
|
||||
|
||||
@@ -15,6 +15,24 @@ HAE отдаёт перечислимые значения строками ло
|
||||
код. Словарь ключуется парой `(локаль, строка)`, локаль берётся из
|
||||
`Accept-Language`. Незнакомая строка → пустой код, а не догадка.
|
||||
|
||||
**Словарь фаз сна уже выведен** сопоставлением потока с экспортом за тот же
|
||||
период (находка 43) — составлять руками не нужно:
|
||||
|
||||
```
|
||||
Основная → AsleepCore Бодрствование → Awake БДГ → AsleepREM
|
||||
Глубокий → AsleepDeep В кровати → InBed Во сне → AsleepUnspecified
|
||||
```
|
||||
|
||||
Тем же способом добираются `heart_rate.context` и типы тренировок.
|
||||
|
||||
Осложнение, всплывшее на истории экспортов: **коды тоже не вечны.** Одни и те
|
||||
же записи сна приезжают как `…Asleep` в экспорте 2021 года и как
|
||||
`…AsleepUnspecified` в экспорте 2026-го: Apple переименовала значение и
|
||||
переписывает историю при выгрузке (находка 43). Значит словарь должен
|
||||
переживать переименование самих кодов, иначе после обновления iOS история
|
||||
расколется вторично — уже на «стабильной» стороне. Простейшее решение: хранить код как есть, а
|
||||
эквивалентность старых и новых имён держать отдельной таблицей синонимов.
|
||||
|
||||
Готово, когда фазы сна из потока и из экспорта Apple сравниваются напрямую, а
|
||||
`/stats` показывает строки, для которых кода ещё нет.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user