Files
healthlog/docs/backlog/read-api-tochki.md
T
av 03edf1087d Каталог разрезов и измеренный род агрегации
- род метрики выводится сверкой минутного слоя с часовым: часовое значение
  сходится с суммой минутных — накопительная, со средним — мгновенная, иначе
  `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки,
  31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль
- `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и
  род вместе с основанием измерения; род нигде не хранится — он функция витрины,
  а витрина функция журнала, устаревать в нём нечему
- миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
  не разжимая содержимое объектов
2026-08-02 19:23:59 +03:00

5.7 KiB

Read API: точки, выбор слоя, свёртка по сетке

Приоритет: высокий

Сейчас данные достаются только sqlite3 на хосте. Все три сценария — агент-медик, трекер тренировок, фитнес-игра — упираются в отсутствие чтения.

Формы запроса ровно две, и это один запрос с необязательным параметром: ?from&to — все значения за период (вес, лекарства, симптомы), ?from&to&bucket — с разбивкой (шаги, энергия).

Решение по размеру ответа (вариант «б»): разбивка не задана и ответ не влезает — сервер сам берёт сетку погрубее и называет её в ответе; разбивка задана явно и не влезает — ошибка со списком доступных сеток, а не тихая подмена. Различие существенно: иначе агент, попросивший минутную сетку, получит суточные суммы.

Отдача тренировок и записей входит сюда же. Разбор и хранение сущностей с собственным id сделаны (change 2026-08-02-trenirovki-i-zapisi), а эндпоинтов нет: тренировка с маршрутом и записи stateOfMind лежат в витрине и наружу не отдаются. Вводить их раньше конверта ответа значило бы задать контракт мимоходом, поэтому GET /workouts, GET /workouts/{id} и GET /records/{kind} закрываются этой задачей — вместе с формой конверта и правилом размера ответа. Второй сценарий паспорта (трекер) до тех пор не закрыт.

Готово, когда «шаги за неделю по дням» и «вес за год» отвечаются одним запросом каждый, тренировка отдаётся одним пакетом вместе с маршрутом, а в ответе всегда видно layer, bucket и aggregation.

Порог неполного ведра решается здесь, и вместе с ним — его полярность. Каталог и род агрегации сделаны (change 2026-08-02-katalog-i-rod-agregacii), и измерению порог заполненности не понадобился: у него две конкурирующие гипотезы, и неполный час не сходится ни с одной сам собой. Свёртке в ответе он нужен, а готовые решения задают его противоположно: Graphite xFilesFactor — доля обязательно известных точек (умолчание 0.5 при роллапе и 0 при рендере, один параметр с двумя умолчаниями), RRDtool xff — доля допустимо неизвестных. Обе величины выглядят как «0.5», означая разное; полярность придётся назвать вслух в architecture.md, иначе через полгода два места кода поймут поле по-разному.

Предел размера ответа тоже здесь. У каталога его нет намеренно: правило размера — общее для маршрутов чтения, и задавать его мимоходом на первой ручке значило бы решить контракт до того, как известна форма тяжёлого ответа. Каталог станет первым его потребителем.

Форма провода наследуется от каталога, и это надо решить один раз. Сегодня типы internal/catalog сами несут json-теги, а транспорт владеет только обёрткой: переименование поля в домене меняет публичный контракт без касания httpapi. Держит это один байтовый тест непустого ответа. Либо объявить в architecture.md, что типы чтения и есть форма провода для всех транспортов (HTTP и MCP отдают её байт в байт), либо завести DTO в транспорте — но выбрать до того, как образец скопирует эта задача.

Клиент обязан смотреть на границы окна измерения. Род метрики измерен по 48 самым свежим ОБЩИМ часам, а не по последним 48 часам календаря: если минутная автоматизация HAE выключена, множество общих часов не пополняется и окно замирает. Род при этом продолжает объявляться, и единственный след — last_hour в ответе. Правило выбора свёртки в Read API обязано это учитывать (или явно объявить, что не учитывает).

Связано: docs/architecture.md → «Read API», «Измерение рода агрегации», план → шаг «Read API».