заведён change на разбор метрик в часовые объекты

- proposal и две capability: parsing (вывод слоя, форматы времени, канонизация)
  и storage (координатный ключ, слияние по полноте, часовые объекты)
- design фиксирует границы: разбор отдельным пакетом, синхронно после записи в
  архив, конкурентная запись в один час — риск с тестом
- вне scope сознательно: тренировки, reindex, словарь кодов, род агрегации
This commit is contained in:
av
2026-08-01 14:50:10 +03:00
parent 01b13084ba
commit c6f27e3890
7 changed files with 515 additions and 0 deletions
@@ -0,0 +1,43 @@
## 1. Фикстуры и схема
- [ ] 1.1 Скрипт `tmp/research/fixtures.py`: собирает фикстуры из `data/raw`, вычищая измеренные значения и сохраняя порядок ключей, точность чисел, неразрывные пробелы и форматы времени
- [ ] 1.2 Набор `internal/hae/testdata`: минутная доставка, посекундная, часовая, смешанная (перенастройка автоматизации), обе схемы `sleep_analysis`, точка с `heartbeatSeries`
- [ ] 1.3 Миграция `internal/store/migrations/00003_bucket.sql`: таблица `bucket` (`metric`, `layer`, `hour_utc`, `payload` BLOB, `content_hash`, `points`, `sealed`, `created_at`, `updated_at`), уникальность по `(metric, layer, hour_utc)`
- [ ] 1.4 `docs/database.md` — ER-схема с `delivery` и `bucket` (шаг гейта `er-schema` требует её при изменении миграций)
## 2. Разбор — пакет `internal/hae`
- [ ] 2.1 Разбор трёх форматов времени в UTC с сохранением офсета исходной зоны; неразобранная метка — не паника, а пропуск точки со счётчиком
- [ ] 2.2 Вывод слоя: выравнивание меток, плотная метрика (≥10 точек) сама, редкая наследует преобладающий слой, при отсутствии плотных — заголовок доставки
- [ ] 2.3 Расхождение выведенного слоя с заголовком доставки — запись `WARN` без значений точек
- [ ] 2.4 Разделение `sleep_analysis` на поэпизодную и `sleep_analysis_summary` со слоем `day`
- [ ] 2.5 Канонизация: рекурсивная сортировка ключей, округление чисел до 12 значащих цифр
- [ ] 2.6 `hae.Parse` возвращает точки со слоем, метрикой, меткой и содержимым как пришло; незнакомые поля и метрики сохраняются
- [ ] 2.7 Тесты разбора на фикстурах из 1.2, включая смешанную доставку и обе схемы сна
## 3. Хранение — часовые объекты
- [ ] 3.1 Модель точки и объекта в `internal/store`; сериализация содержимого в gzip-BLOB, точки внутри упорядочены по времени
- [ ] 3.2 Слияние: координатный ключ `метрика + слой + метка`, победа более полной точки, при равной полноте — по `received_at`
- [ ] 3.3 Хеш канонической формы объекта как детектор изменений: совпал — записи нет
- [ ] 3.4 Запись объекта в транзакции; повтор при `SQLITE_BUSY`
- [ ] 3.5 Признак `sealed`: изменение запечатанного часа пишет `WARN` и всё равно сохраняет
- [ ] 3.6 Тест конкурентной записи в один `hour_utc`: точки обеих сторон на месте
- [ ] 3.7 Тест идемпотентности: повторное слияние того же набора не меняет ни содержимое, ни хеш
## 4. Сшивка с приёмом
- [ ] 4.1 `internal/ingest` вызывает разбор после записи тела в архив и строки `delivery`
- [ ] 4.2 Отказ и паника разбора не меняют код ответа: `200`, `parse_status=failed`, запись лога уровня `ERROR` без значений
- [ ] 4.3 Единственный логирующий чекпоинт на границе: счётчики метрик, точек и объектов, идентификатор доставки; ни значений, ни имён устройств
- [ ] 4.4 Тест приёма: битый JSON — 400, непонятое содержимое — 200 с `parse_status=failed`
## 5. Сходимость на реальных данных
- [ ] 5.1 Скрипт `tmp/research/verify_buckets.py`: прогоняет архив через разбор и сверяет суммы по часовому слою с прежней проверкой из разведки
- [ ] 5.2 Прогон на всех накопленных доставках: расхождений по накопительным метрикам нет
- [ ] 5.3 `task gate` зелёный; `task restart` поднимает сервис, новая доставка с телефона разбирается
## 6. Приёмочные критерии ревью дизайна
<!-- Заполняется рубрикой из healthlog-review-rubric на шаге ревью дизайна -->