Каталог разрезов и измеренный род агрегации

- род метрики выводится сверкой минутного слоя с часовым: часовое значение
  сходится с суммой минутных — накопительная, со средним — мгновенная, иначе
  `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки,
  31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль
- `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и
  род вместе с основанием измерения; род нигде не хранится — он функция витрины,
  а витрина функция журнала, устаревать в нём нечему
- миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
  не разжимая содержимое объектов
This commit is contained in:
av
2026-08-02 19:23:59 +03:00
parent 98e0772ec5
commit 03edf1087d
39 changed files with 4744 additions and 58 deletions
+61
View File
@@ -1725,6 +1725,67 @@ apple_stand_time 14
ноль, частично разобранных ноль, в витрине 2049 часовых объектов, 2 тренировки и
2 записи; повторное проигрывание дало тот же отпечаток.
## 53. Род агрегации измерен: 16 метрик из 31, противоречий ноль
Правило из находки 40 доведено до кода и прогнано на всём архиве (123 доставки,
31 метрика, витрина 2342 объекта). Сверка идёт по парам «минутный объект —
часовой объект за тот же час»; час участвует, только если у часового объекта
ровно одна точка со значением на границе часа, у минутного не меньше двух точек,
а сумма минутных отличима от их среднего.
| исход | метрик |
|---|---|
| `cumulative` | 7 |
| `instant` | 9 |
| `unknown` | 15 |
```
cumulative active_energy, basal_energy_burned, step_count,
walking_running_distance, apple_stand_time, apple_exercise_time,
time_in_daylight
instant heart_rate, respiratory_rate, blood_oxygen_saturation,
environmental_audio_exposure, walking_speed, walking_step_length,
walking_double_support_percentage, walking_asymmetry_percentage,
stair_speed_up
```
**Противоречащих часов ноль на всём корпусе** — ни у одной метрики свидетельства
не разошлись. Это и есть главный результат: правило не «чаще всего работает», а
не дало ни одного контрпримера.
### Что выяснилось по дороге
**Нулевой час обязан отбрасываться, иначе правило конфликтует само с собой.**
Первый прогон дал у `walking_asymmetry_percentage` 4 часа «накопительная» против
3 «мгновенная». Разбор: в часе, где все значения нули, сумма равна среднему, и
проверка «сходится с суммой» выполняется тождественно. Условие «сумма отличима
от среднего» убирает весь конфликт.
**Часовой слой HAE считается арифметически, а не по Apple.** HealthKit относит
`environmental_audio_exposure` к логарифмическому усреднению по энергии, а пульс
— к среднему, взвешенному по длительности. На наших данных часовое значение
аудиоэкспозиции сходится с обычным арифметическим средним минутных в 59 часах из
62, а у пульса — точно в 29 часах из 63 и с точностью 0.1% в 49. Значит четыре
стиля агрегации HealthKit в потоке ничем не различимы, и родов ровно два.
**Допуск сравнения на вердикты не влияет, а на счётчики влияет вдвое.** Прогон
сеткой: при относительном допуске от `1e-9` до `1e-3` роды всех метрик
одинаковы; число согласных часов у `heart_rate` при этом меняется с 29 на 49, у
`step_count` — с 25 на 35. Взят строгий `1e-9`: канонизация округляет числа до
12 значащих цифр, то есть всё крупнее `1e-12` представлением не объясняется.
**Окно в 48 часов обходится дешевле, чем кажется, но редкие метрики уводит в
`unknown`.** Полный обход всех 696 пар часов занимал 123 мс, окно даёт 68 мс и
перестаёт расти вместе с журналом. Плата: у `physical_effort` за всю историю
было 5 согласных часов, а в последних 48 — только 2, и метрика уходит в
`unknown`. Это честный исход: свидетельств в свежем окне действительно мало.
**Неполные часы видны в основании и ничего не ломают.** У `step_count` из 48
часов окна пригодны 41, а вердикт дали 24 — остальные не сошлись ни с суммой, ни
со средним, потому что минутный слой за них неполон. Отдельного порога
заполненности (`xFilesFactor`) измерению не нужно: две конкурирующие гипотезы
отсеивают неполный час сами.
## Инструмент
Разбор ведётся скриптом `tmp/research/hl.py` (Python 3, только стандартная