docs: документация переведена на канон av-dev-pm
- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги переименованы с транслита на английские, 85 ссылок поправлены - conventions.md разобран в docs/conventions/, local-research.md — в docs/research/, review-journal.md — в docs/review.md с разделом настройки конвейера; заведены security.md, adr/ и .pm.json - шаг docs.py check добавлен в task gate; поведение в architecture.md помечено девятью маркерами долга, database.md получил настройки с числовым значением
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# Read API: точки, выбор слоя, свёртка по сетке
|
||||
|
||||
**Секция:** ядро · **Хук:** Данные видны только через sqlite на хосте — ни один из трёх потребителей ничего прочитать не может · **Теги:** goal: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`, иначе через полгода два места кода поймут поле по-разному.
|
||||
|
||||
**Предел размера ответа тоже здесь, и он унаследовал измеренную цену.** У
|
||||
каталога предела нет намеренно: правило размера — общее для маршрутов чтения, и
|
||||
задавать его мимоходом на первой ручке значило бы решить контракт до того, как
|
||||
известна форма тяжёлого ответа. Каталог станет первым его потребителем.
|
||||
|
||||
Цена измерена на каталоге (задача «цена читающего маршрута», закрыта чекпойнтом
|
||||
WAL и условным запросом): 693 мс и +153 МиБ живой кучи на враждебном запросе
|
||||
(20 метрик × 8 часов × 5000 точек), при том что приём в том же процессе уже даёт
|
||||
пик 768 МиБ на теле 40 МиБ. Условный запрос снял повтор, но первый запрос стоит
|
||||
столько же, а множители «метрики × окно × точки × одновременные запросы»
|
||||
по-прежнему без потолка. Сюда же уезжают отложенные варианты той задачи:
|
||||
собственный дедлайн маршрута и потоковое измерение по метрике (второе — только
|
||||
если счётчик заговорит).
|
||||
|
||||
**Машинерия условного запроса готова, и её надо взять, а не написать заново.**
|
||||
`store.VersionedRead` держит правило «версией, снятой после чтения, не
|
||||
подписывать»; `httpapi` — разбор `If-None-Match` и `304`. Метка обязана нести
|
||||
**область действия**: у точек ответ есть функция параметров запроса, и
|
||||
`etag(scope, version)` требует их канонизированную форму — иначе `304` ответит
|
||||
на другой набор данных. Детали — `docs/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».
|
||||
Reference in New Issue
Block a user