- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
объявляет слой, измеренный род, его применимость к отданному ряду и границу
окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
записи: дословность содержимого точки иначе не удерживается, а оборванное
тело уходило под видом успешного `200`
74 lines
6.0 KiB
Markdown
74 lines
6.0 KiB
Markdown
## 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.
|