- открытых вопросов не осталось: три решения владельца доведены до берущегося вида, по entity-without-parsed-label принято хранить с NULL-меткой после Read API - unseen-sections-check сжата до остатка — активная проверка появления секции; разбор невиденных секций из неё вынут, вслепую он не пишется - спринт под целью parsing-and-storage: categorical-value-dictionary и unseen-sections-check, обеим написаны критерии приёмки с оракулами
5.2 KiB
Словарь категориальных значений → коды HealthKit
- Секция: ядро
- Зачем: Фазы сна и типы тренировок приходят строками русской локали — с экспортом Apple их не сверить
- Теги: goal:parsing-and-storage
HAE отдаёт перечислимые значения строками локали телефона: «БДГ», «Сидячий
образ жизни», «В помещении Ходьба». Родной экспорт Apple при этом говорит
кодами (HKCategoryValueSleepAnalysisAsleepREM) — источники несопоставимы
(находка 37).
Три следствия, и третье решающее: клиент угадывает словарь; смена языка телефона молча расколет историю; сверить покрытие экспортом нечем — а на этой сверке стоит устаревание нижнего слоя.
Решение (вариант «б»): строка хранится дословно, рядом кладётся выведенный
код. Словарь ключуется парой (локаль, строка), локаль берётся из
Accept-Language. Незнакомая строка → пустой код, а не догадка.
Словарь фаз сна уже выведен сопоставлением потока с экспортом за тот же период (находка 43) — составлять руками не нужно:
Основная → AsleepCore Бодрствование → Awake БДГ → AsleepREM
Глубокий → AsleepDeep В кровати → InBed Во сне → AsleepUnspecified
Тем же способом добираются heart_rate.context и типы тренировок.
Осложнение, всплывшее на истории экспортов: коды тоже не вечны. Одни и те
же записи сна приезжают как …Asleep в экспорте 2021 года и как
…AsleepUnspecified в экспорте 2026-го: Apple переименовала значение и
переписывает историю при выгрузке (находка 43). Значит словарь должен
переживать переименование самих кодов, иначе после обновления iOS история
расколется вторично — уже на «стабильной» стороне. Простейшее решение: хранить код как есть, а
эквивалентность старых и новых имён держать отдельной таблицей синонимов.
stateOfMind в словаре не нуждается — он и так шлёт коды HealthKit.
Критерии приёмки
- фаза сна из потока («БДГ») и из экспорта Apple
(
HKCategoryValueSleepAnalysisAsleepREM) за один период сопоставляются напрямую — оракул: запрос на живой базе за период с известным перекрытием, ноль несопоставимых строк - строка сохранена дословно, код лежит рядом отдельным полем — оракул: тест
разбора на реальном пакете HAE из
internal/hae/testdata - незнакомая строка даёт пустой код, разбор не падает, а событие попадает в счётчик — оракул: тест на выдуманной фазе сна плюс проверка счётчика
- переименование кода самой Apple (
…Asleep→…AsleepUnspecified, находка 43) не раскалывает историю — оракул: тест на паре синонимов, обе формы сходятся в один код - повторный прогон живого архива даёт то же состояние — оракул:
task verify:archive
Критерий «/stats показывает строки без кода» снят при переоценке 2026-08-03:
/stats ещё нет (stats-endpoint), и вешать приёмку на
несуществующий оракул значит либо блокировать задачу, либо принять её
непроверенной. Наблюдаемость закрывается счётчиком; показ в /stats — строка
задачи наблюдаемости, а не этой.
Рамки
Схема трогается: у категориального значения появляется поле кода, плюс таблица
синонимов. Дословную строку не заменяем и не нормализуем — инвариант «точки
хранятся дословно». Пересборка обязана оставаться детерминированной, оракул
тот же task verify:archive.