# Задание A6 (Antigravity): данные для нового дашборда

## Дата поступления
2026-08-21

## База
Проверочный HEAD: **`2b2ccd8`**, `origin/main` = `2b2ccd8`. `git fetch`, зафиксировать `BASE_SHA`.

## Ветка
`antigravity/dashboard-data`

---

## Что принято по A5

Проверено исполнением:

- телеметрия пишется и агрегируется: 3 вызова → `total_prompt_tokens=360`, `completion=135`, суммы сходятся; `source: own_measurement`;
- **честность подтверждена тремя проверками**: без вызовов `has_data=False` и `p50=None` вместо нулей; провайдер не вернул `usage` → `total_tokens=None`, а не оценка; подставленные в запрос `hunter2` и секретный текст в файл телеметрии **не попали**;
- ограничение размера настоящее: очередь 10 000 записей, файл 5 МБ, 3 ротации;
- контракт разделён правильно — Gap 12 закрыт как самоизмеряемый, для неизмеримого заведён **новый Gap 13**, молчаливой пропажи ограничений нет;
- долги: `HKCU` за флагом `HERMES_HUB_NO_REGISTRY`, `fastapi`/`uvicorn` перенесены в `optional-dependencies`.

Одна неточность в отчёте: YAML-комментарии помечены «Закрыт», фактически сохраняются только заголовочные. Теряются пять внутренних, включая пояснения к таблице цен, которые вы же и добавили. Не блокирует, но статус — «частично».

---

## Контекст

Владелец утвердил макет главного экрана в трёх цветовых схемах. Codex получает задание на редизайн (B5). Часть блоков макета уже обеспечена данными после A5, часть — нет. Ваша задача: закрыть недостающее, чтобы Codex рисовал правду, а не заглушки.

Разбор макета по источникам:

| Блок макета | Источник | Состояние |
|---|---|---|
| Время отклика 412 мс | `TelemetryService` latency | **есть** |
| Агенты онлайн 18/20 | `SystemReadiness` | **есть** |
| Последние события | `EventLogService` | **есть** |
| Провайдеры 3/3 + мс на каждого | `get_aggregates(provider=…)` | есть срез, **не выставлен в снапшот** |
| Доли маршрутизации 45/35/20 % | доля вызовов по провайдеру | **выводимо, не считается** |
| Счётчики запросов 128/74/56 | вызовы по ролям | **выводимо, не считается** |
| Квота сегодня 78 % | `QuotaSnapshot`, baseline `None` | Н/Д до реального 429 — так и оставить |
| Системные показатели CPU/Память/Диск/Сеть | `psutil` — в зависимостях, **не используется** | см. P1-3 |
| Активные задачи 24 | подсистемы задач нет | см. P1-4 |
| Очереди задач по приоритетам | подсистемы очередей нет | **не делать** |
| Окно обслуживания 09:00–21:00 | понятия нет | **не делать** |

---

## P0-1. Вывести телеметрию в снапшот

`get_aggregates()` уже умеет резать по `provider`, `profile_id`, `model`, `role` — но наружу, в `HubSnapshot`, ничего из этого не попадает. UI получает снапшот и не может показать ни латентность, ни токены.

Добавить в снапшот готовые агрегаты:

- общие: латентность P50/P95, токены, доля ошибок, число вызовов;
- **на провайдера**: латентность и число вызовов — для правой панели «Статус в реальном времени»;
- **на роль**: число вызовов — для счётчиков на схеме маршрутизации.

Считать в том же цикле обновления, что и остальное состояние, чтобы UI не инициировал вычисления сам. Окно агрегации — параметр, по умолчанию сутки.

## P0-2. Доля вызовов по провайдеру

Проценты на схеме (45 / 35 / 20) — это распределение вызовов между провайдерами за окно. Данные для этого уже в телеметрии, самой доли нет.

Добавить в агрегаты `call_share` по провайдеру. При отсутствии вызовов в окне — `None`, а не «0 %» и не равномерное деление. Правило прежнее: нет данных — нет числа.

## P1-3. Показатели хоста через `psutil`

`psutil` объявлен обязательной зависимостью и **не используется ни в одном модуле**. Между тем блок «Системные показатели» на макете — это CPU, память, диск и сеть **самой машины**, и они измеримы честно.

В контракте Gap 13 сейчас объявляет их «неизмеримыми, так как не относятся к логике роутера». Формулировка неточна: они не относятся к провайдерам, но вполне измеримы. Владелец включил их в утверждённый макет — значит, они нужны.

Требуется: сбор показателей хоста через `psutil` с источником `host_measurement`, отдельным от `own_measurement` (вызовы роутера) и от данных провайдера. Обновить Gap 13: оставить в нём только действительно недоступное — RPS провайдера, SLA, uptime внешних сервисов.

Частота опроса — щадящая, в общем цикле обновления, без отдельного потока на каждый показатель.

## P1-4. Активные вызовы

«Активные задачи» на макете — в терминах Hermes Hub это вызовы, выполняющиеся прямо сейчас. `LeaseManager` уже знает число занятых лизов по каждому профилю; наружу это не выставлено (`active_leases` в `ProfileHealthRecord` всегда 0 — старое замечание, до сих пор не закрыто).

Вывести в снапшот число активных вызовов — суммарно и по профилям. Очереди по приоритетам **не изобретать**: подсистемы очередей нет, и придумывать её ради макета не нужно.

## P1-5. Дописать контракт

Все новые поля — в `docs/UI_STATE_CONTRACT.md`: имя, тип, источник (`own_measurement` / `host_measurement`), окно агрегации, поведение при отсутствии данных. Раздел 8 уже есть, дополнить его.

## P2-6. Остаток долга

YAML-комментарии: сохраняются только заголовочные, внутренние теряются. Либо довести до полного round-trip, либо переписать статус в контракте и отчёте на «частично» с указанием, что именно теряется.

---

## Ограничения

- Граница прежняя: зона Codex (`router/ui/**`, `hermes_hub_app.py`, `tests/test_ui_*.py`) — не трогать.
- Никаких оценок вместо измерений. Отсутствие данных — `None`, не ноль, не среднее, не равномерное распределение.
- Не изобретать подсистемы ради макета: очередей задач и окна обслуживания в продукте нет.
- Тег `v0.1.1` не создавать.

## Критерии приёмки

1. Ни один файл зоны Codex не изменён.
2. В `HubSnapshot` доступны агрегаты: общие, на провайдера, на роль; проверено тестом.
3. `call_share` считается верно на известном наборе вызовов; при отсутствии вызовов — `None`; проверено тестом.
4. Показатели хоста собираются через `psutil` с источником `host_measurement`; при недоступности `psutil` — отсутствие данных, а не нули.
5. Число активных вызовов отражает реальные лизы; проверено тестом с занятым лизом.
6. Gap 13 переформулирован: в нём осталось только недоступное.
7. Контракт дополнен по всем новым полям.
8. Прогон **в обоих окружениях**; обе команды и оба результата в отчёте.
9. `ruff check .` чисто; release gate PASSED на финальном коммите.
10. Отчёт: `START_HEAD`, `FINAL_HEAD`, `origin/main`, `git status`, точный `X passed / Y skipped / Z failed`.

## Порядок сдачи
Передать точный `FINAL_COMMIT_SHA`.
