Files
healthlog/openspec/changes/archive/2026-08-04-tochki-metriki-za-period/proposal.md
T
av 29ca8d415c httpapi: точки метрики за период отдаются одним запросом
- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
  объявляет слой, измеренный род, его применимость к отданному ряду и границу
  окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
  точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
  под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
  хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
  записи: дословность содержимого точки иначе не удерживается, а оборванное
  тело уходило под видом успешного `200`
2026-08-04 18:46:45 +03:00

74 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Why
Точки лежат в витрине и наружу не отдаются: «вес за год» достаётся только
`sqlite3` на хосте. Каталог уже отвечает, **что** есть, — но ни один из трёх
потребителей не может получить сами значения.
Ответ обязан быть самоописательным. Метрика лежит сразу в нескольких слоях
подробности, а род её свёртки не объявлен источником, а **измерен** окном в 48
самых свежих **общих** часов — окном, которое замирает при выключенной минутной
автоматизации HAE и продолжает объявлять род. Клиент, не видящий ни слоя, ни
границы этого окна, принимает решение по числу, происхождения которого не знает.
## What Changes
- Новый читающий маршрут `GET /api/v1/metrics/{name}?from&to&layer` — все точки
метрики за период, одним запросом, без доступа к файлу базы.
- Конверт ответа объявляет **фактические** параметры выдачи: `metric`, `from`,
`to`, `layer`, `bucket`, `aggregation` (род, его применимость к отданному ряду
и граница окна измерения), `points`. Поля присутствуют всегда — клиент не
выводит их наличием или отсутствием.
- Слой выбирается правилом, а не молча: параметр `layer` задаёт разрез явно, без
него берётся слой с наибольшим охватом **точек** внутри запрошенного периода,
при равенстве — самый мелкий. Смены слоя внутри одного ответа не бывает.
- Род свёртки в конверте — тот же измеренный `catalog.Style`, что у каталога, и
измеряется он тем же правилом и тем же окном. Род неизвестен — так и сказано
словом `unknown`, а не молчанием. Рядом едет применимость: `cumulative` на
нижнем слое HAE объявляется неприменимым, иначе конверт приглашает потребителя
сложить интерполяцию и завысить втрое.
- Свёртка в этом изменении **не выполняется**: `bucket` всегда `null`, а
присутствие параметра `bucket` в запросе отвергается `400`, а не игнорируется
молча.
- Ответ снимается **одной транзакцией чтения**, а метка ответа включает
канонизированную форму запроса **и горизонт измерения** — иначе соседняя
задача условного запроса подтвердит `304` на сменившемся роде.
- Форма провода — по образцу каталога (ADR
`ADR-2026-08-04-forma-provoda-prinadlezhit-transportu`): типы `*Wire` в
`internal/httpapi`, перевод присваиванием, строка в таблице образцов
`wire_internal_test.go`.
Схема базы не трогается: обе выборки отвечают по существующим ключу и
покрывающему индексу `bucket_catalog`.
## Capabilities
### New Capabilities
- `points`: точки метрики за период — форма запроса и его разбор, правило выбора
слоя, состав конверта ответа, объявление измеренного рода вместе с границей
окна измерения, дословность значений точки.
### Modified Capabilities
- `read-api`: добавляются два общих правила читающих маршрутов — сериализация
ответа не экранирует содержимое (иначе `&` внутри дословно сохранённой точки
уезжает как `&`) и ответ чтения помечается непригодным для разделяемого
кеша. Оба механизма общие (`writeJSON`, `setReadHeaders`), и решать их заново
на каждом маршруте — тот же второй способ.
`catalog` (правило измерения рода) применяется новым маршрутом без изменения его
требований.
## Impact
- `internal/httpapi` — маршрут, разбор параметров, форма провода точек,
выключение HTML-экранирования в общем `writeJSON`.
- `internal/points` (новый) — use-case «точки метрики за период»: выбор слоя,
сборка ряда, измерение рода и метка ответа по одной метрике.
- `internal/store` — один вход чтения ряда (`ReadSeries`) в одной транзакции:
охваты слоёв в периоде, точки выбранного слоя, объекты окна измерения.
- `docs/architecture.md` — разделы «Read API» (форма ответа, правило выбора
слоя) и таблица компонентов.
- Потребители: HTTP-клиенты; следом этот же конверт копируют свёртка по сетке,
условный запрос по точкам, тренировки, записи и MCP.