- род метрики выводится сверкой минутного слоя с часовым: часовое значение сходится с суммой минутных — накопительная, со средним — мгновенная, иначе `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки, 31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль - `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и род вместе с основанием измерения; род нигде не хранится — он функция витрины, а витрина функция журнала, устаревать в нём нечему - миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам, не разжимая содержимое объектов
62 lines
5.7 KiB
Markdown
62 lines
5.7 KiB
Markdown
# 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».
|
||
|