Files
healthlog/openspec/changes/razbor-metrik-v-obekty/specs/parsing/spec.md
T
av c6f27e3890 заведён change на разбор метрик в часовые объекты
- proposal и две capability: parsing (вывод слоя, форматы времени, канонизация)
  и storage (координатный ключ, слияние по полноте, часовые объекты)
- design фиксирует границы: разбор отдельным пакетом, синхронно после записи в
  архив, конкурентная запись в один час — риск с тестом
- вне scope сознательно: тренировки, reindex, словарь кодов, род агрегации
2026-08-01 14:50:10 +03:00

8.3 KiB
Raw Blame History

ADDED Requirements

Requirement: Разбор секции метрик

Система SHALL разбирать секцию data.metrics тела доставки Health Auto Export в точки. Точка несёт имя метрики, единицы, слой, метку времени и содержимое в том виде, в каком его прислал HAE.

Незнакомое поле внутри точки MUST сохраняться, а не отбрасываться: сырой архив недолговечен, и отброшенное поле теряется безвозвратно.

Scenario: Метрика с точками разбирается в точки

  • WHEN тело содержит data.metrics[] с непустым data[]
  • THEN каждая точка с непустым date становится точкой хранилища
  • AND все поля точки, кроме служебных, сохраняются дословно

Scenario: Незнакомая метрика не ломает разбор

  • WHEN приходит метрика с именем, которого разбор не знает
  • THEN её точки разбираются наравне с остальными
  • AND разбор не завершается ошибкой

Scenario: Точка без метки времени пропускается

  • WHEN точка не содержит date либо date не разбирается ни одним из поддерживаемых форматов
  • THEN точка не попадает в хранилище
  • AND факт учитывается в итоге разбора доставки

Requirement: Вывод слоя гранулярности

Система SHALL выводить слой точки из выравнивания меток времени, а не из заголовка доставки. Заголовок automation-aggregation непригоден: значение Default соответствует трём разным режимам выгрузки.

Слои: raw (метка на произвольной секунде), minute (секунды нулевые), hour (секунды и минуты нулевые).

Классификация MUST быть по метрике внутри доставки, а не по доставке целиком: при перенастройке автоматизации приезжают смешанные доставки, и отнесение такой доставки к одному слою складывает минутные точки с посекундными.

Scenario: Плотная метрика классифицируется сама

  • WHEN в доставке у метрики не меньше десяти точек с метками
  • THEN слой определяется выравниванием её собственных меток

Scenario: Редкая метрика наследует преобладающий слой

  • WHEN в доставке у метрики меньше десяти точек
  • THEN она получает самый мелкий слой среди плотных метрик этой доставки
  • AND её собственное выравнивание во внимание не принимается

Scenario: В доставке нет плотных метрик

  • WHEN ни у одной метрики доставки нет десяти точек
  • THEN слой берётся из заголовка automation-aggregation (Minutesminute, Hourshour, иначе raw)

Scenario: Выведенный слой расходится с заголовком

  • WHEN выведенный слой не совпадает с тем, что объявляет заголовок доставки
  • THEN система пишет запись уровня WARN
  • AND сохраняет точки по выведенному слою, а не по заголовку

Requirement: Разбор форматов времени

Система SHALL понимать три формата времени, встречающиеся в теле доставки, и приводить их к UTC, сохраняя офсет исходной зоны.

  • 2026-07-31 21:03:51 +0300 — метрики и тренировки;
  • 2026-07-31T18:03:51Z — RFC 3339 в UTC;
  • 1785446196.4132624 — Unix-эпоха дробным числом внутри heartbeatSeries.

Scenario: Локальное время со смещением

  • WHEN метка имеет вид 2026-07-31 21:03:51 +0300
  • THEN точка получает время в UTC и офсет +10800 секунд

Scenario: Время внутри серии ударов

  • WHEN точка метрики heart_rate_variability содержит heartbeatSeries
  • THEN элементы серии сохраняются дословно вместе с их эпохой
  • AND серия не разворачивается в отдельные точки

Requirement: Разделение схем под одним именем метрики

Система SHALL разводить на разные имена метрики те схемы, которые Health Auto Export шлёт под одним именем, чтобы одно имя означало одну схему.

Под именем sleep_analysis приезжают две несовместимые схемы: поэпизодная (start/end/value/qty) и суточная сводка (totalSleep/core/rem/deep/awake с меткой на местной полуночи). Общих полей, кроме date и source, у них нет.

Scenario: Поэпизодная запись сна

  • WHEN точка sleep_analysis содержит поле value
  • THEN она сохраняется под именем sleep_analysis

Scenario: Суточная сводка сна

  • WHEN точка sleep_analysis содержит поле totalSleep
  • THEN она сохраняется под именем sleep_analysis_summary
  • AND её слой фиксирован как day, а не выводится из выравнивания

Requirement: Канонизация содержимого

Система SHALL приводить содержимое точки к канонической форме перед сравнением и хешированием: рекурсивная сортировка ключей и округление чисел до двенадцати значащих цифр.

Без округления сравнение бесполезно: 63% повторно приехавших точек различались последним разрядом double при одинаковом измерении.

Scenario: Повтор с иным порядком ключей опознаётся как тот же

  • WHEN та же точка приезжает с другим порядком ключей в JSON
  • THEN её каноническая форма совпадает с сохранённой

Scenario: Дребезг последнего разряда не считается изменением

  • WHEN значение отличается только за пределами двенадцатой значащей цифры
  • THEN каноническая форма совпадает с сохранённой

Requirement: Разбор не влияет на код ответа приёма

Система MUST сохранять правило «сохранили — значит приняли»: исход разбора не меняет код ответа на доставку.

Scenario: Содержимое не разобралось

  • WHEN тело сохранено в архив, но разбор его содержимого не удался
  • THEN ответ на приём остаётся 200
  • AND исход виден в delivery.parse_status и в записи лога