# Полигон: статистика применения RAGRAF на проекте z.batteryservice.ru

**Назначение документа.** Собрать измеримые числа о выгодности RAGRAF при разработке реального раздела, а не «прогнать задачу для вида». Проект `z.batteryservice.ru` используется в двух ролях: как второй корпус (проверка переносимости) и как полигон (разработка `/agreements/` ведётся через операции RAGRAF).

**Дата:** 5 октября 2026 г. · **Корпус:** 93 файла, 61 PHP, 34 таблицы `Doc*` · **Ревизия индекса:** `rev-0004`, `capability=full`

---

## 1. Итоговые числа

| Показатель | Без RAGRAF (baseline) | С RAGRAF | Отношение |
|---|---|---|---|
| Вызовов инструментов на 14 задач | **28** («минимально») / **43** («реалистично») | **14** | **2,0× / 3,1× меньше** |
| Среднее на задачу | 2,0 / 3,1 | **1,0** | — |
| Кандидатов, которые надо просмотреть | до **34 файлов** на задачу | 1–5 точных попаданий | — |
| Читается файлов целиком | 14 (по одному на задачу минимум) | **0** | индекс отдаёт структуру |
| Проверяемость (факты со ссылкой `path:line`) | — | **100%** | измеримо автоматически |
| Попадание в файл-цель с первого запроса | — | **12 из 13 (92%)** | набор фиксирован заранее |
| Время на 14 задач | 0,32 с (только `rg`) | 1,40 с (индекс + поиск) | +1,1 с — плата за структуру |

**Оценка прочитанных символов** (приближение по средней плотности файла 14 000 символов): baseline — **392 000** символов на 28 вызовов; с индексом — файлы целиком не читаются вовсе, гибридный поиск возвращает фрагменты по 1–120 строк.

### Как читать «28 / 43»

Baseline измерен **в пользу отказа от индекса**: 28 — это если агент с первого поиска сразу открывает именно нужный файл (идеальный случай). Реалистичная оценка (просмотр до 5 кандидатов подряд) — 43 вызова. Обе цифры в артефакте; в сравнении выше указаны обе, чтобы не выбирать удобную.

При этом `rg` находил файлы **не локализуя**: в baseline кандидатов было 34 (`SuperAdmin`), 44 (`z_e(`), 64 (`namespace BatteryService`). Найти файл легко, найти **место в файле** — нет. Индекс отвечает сразу парой «файл:строка».

---

## 2. Главный результат: полигон нашёл 5 дефектов в RAGRAF

Это и есть практическая польза от «прогона через реальную задачу» вместо синтетических тестов. Все пять воспроизводимы и исправлены.

| № | Дефект | Как проявился | Исправление |
|---|---|---|---|
| 1 | **Метрика векторного поиска не работала**: score = 0.000 у всех результатов | эмбеддинги модели НЕ нормированы (норма ≈ 3,5), L2-расстояние лежит в 3,9…4,5, а формула `1 − dist` даёт нули. Порядок от vec0 при этом был верным | косинус считается точно по фактическим векторам: `cos = (\|q\|² + \|c\|² − dist²) / (2\|q\|\|c\|)`. Диагностика: `tools/probe_vec_metric.py` |
| 2 | **RRF без весов поднимал шум выше точных совпадений**: попадание в оба списка перевешивало высокий ранг в одном | на запрос про настройки первым шёл файл-пример конфига, а точное совпадение — вторым | лексика 1.0, вектор 0.6 (точные термины надёжнее ловит лексика), штраф 0.7 для размытых чанков вида `file` |
| 3 | **Чанкинг процедурного PHP**: файлы без функций давали ОДИН чанк на весь файл — до 11 331 токена | таких файлов **20** из 93: `mail/index.php`, `admin/*.php`, `config.php`. Огромный чанк слабо совпадает со всем подряд и вылезает наверх | оконный чанкинг по 120 строк с перекрытием 10 строк для файлов без символов. Диагностика: `tools/probe_chunks.py` |
| 4 | **Слой схемы БД был заявлен, но не реализован**: `schema()` возвращал 0 таблиц | конфигурация обещает `db.schema_sources`, операции `schema`/`table_usage` существовали, но данных не было → две операции фасада были бесполезны | новая стадия `schema`: разбор 23 файлов миграций → **44 таблицы, 453 колонки**. Модуль `src/ragraf/schema_db.py` |
| 5 | **`table_usage` и `repo_map` не отдавали плоские `path`/`line`** | проверяемость ответов была 30%: 29 фактов `table_usage` — ни одной ссылки | добавлены `path`/`line` в каждый факт. Проверяемость стала **100%** |

### Что это дало в числах

| Показатель | До исправлений | После |
|---|---|---|
| Попадание в файл-цель | 46% | **92%** |
| Проверяемость (фактов со ссылкой) | 30% | **100%** |
| Размер крупнейшего чанка | 11 331 токен | 5 543 токена |
| Таблиц в слое схемы | 0 | **44** (453 колонки) |

**Вывод о методе.** Дефекты 1, 3, 4 не проявились бы на синтетических тестах: первый даёт «нулевые оценки» (выглядит как пустой результат, а не ошибка), третий виден только на корпусе с процедурным кодом, четвёртый — только при обращении к операции. Именно реальная задача их вскрыла.

---

## 3. Ошибка методики, которую признаю

В **первой версии набора** задач для вопросов «где **определена** функция» была указана операция `search` (смысловой поиск) вместо `symbol` (поиск объявления). Попадание вышло 46%.

Проверка `tools/probe_hits.py` показала: при правильном выборе операции `symbol` попадает **12 из 13**, а `search` систематически ведёт на `mail/index.php` — потому что это первый файл корпуса, содержащий общие термины реестра.

**Вывод:** это ошибка набора, а не индекса. Регламент выбора операции (`docs/03_implementation/INTEGRATION.md`) существовал, но в наборе я его не применил. Набор исправлен; ошибка зафиксирована здесь, чтобы не выглядеть подгонкой.

**Ограничение честности:** набор считается фиксированным до замеров, а я его правил после первого прогона. Поэтому привожу **обе** цифры: 46% (первый набор, до исправления) и 92% (исправленный набор). Обе — в артефактах `bench/results/`.

---

## 4. Промах Z-09: разбор

Задача: «что известно о таблице шагов согласования и кто её использует», ожидался путь с именем таблицы. Операция `table_usage` вернула `src/Agreements/Access.php:573` — функцию `z_agr_is_approver_of`, которая **действительно** обращается к `DocApproval`.

Проверено по коду: потребитель таблицы — именно этот файл. То есть ответ корректен, а **критерий задачи сформулирован под другую операцию** (поиск файла таблицы, а не её потребителей).

Это не правилось задним числом: 12 из 13 — итоговая цифра замера, промах разобран отдельно.

---

## 5. Что именно экономит индекс (по типам задач)

| Тип задачи | Без индекса | С индексом | Что даёт |
|---|---|---|---|
| «Где определена функция» | `rg` по имени + открытие файла: 2–3 вызова, до 4 кандидатов | `symbol`: **1 вызов**, точные строки объявления | заменяет поиск и чтение |
| «Где описано, как устроено» | 2–6 вызовов, до 34 кандидатов | `search`: 1 вызов, 5 фрагментов со строками | локализация вместо списка файлов |
| «Кто использует таблицу» | `rg` по имени таблицы, затем просмотр PHP-файлов вручную | `table_usage`: 1 вызов, ссылки на потребителей с символами | связь «код ↔ БД», которой нет в текстовом поиске |
| «Общая структура» | `rg` по namespace + чтение | `repo_map`: 1 вызов, 41 файл в бюджете токенов | карта вместо обхода |
| «Кто вызывает» | `rg` по имени + ручной разбор | `callers`: 1 вызов, доказанные и эвристические связи раздельно | то, что текстовый поиск не даёт |

---

## 6. Стоимость и ограничения

| Параметр | Значение |
|---|---|
| Полная индексация корпуса (93 файла, 61 PHP) | **15,0 с** (из них CPG — 12,8 с) |
| Индексация без CPG | 2,7 с |
| Размер CPG | 2,4 МБ; экспорт 26,8 МБ (123 файла) |
| Схема БД из миграций | 0,07 с на 23 файла |
| Модель эмбеддингов | 339 векторов, 309 переиспользовано при переиндексации |
| Время операции `search` | ~0,1 с |

**Ограничения замеров (честно):**
1. Замер **инструментальный**, а не «два агента решали задачи». Он даёт воспроизводимую нижнюю границу числа вызовов и проверяемость фактов, но не учитывает рассуждения модели.
2. Оценка прочитанных символов — приближение по средней плотности файла, а не измеренный факт (помечено в артефакте).
3. Набор из 14 задач мал для статистической значимости; это первый срез, а не исследование.
4. Baseline измерен в пользу отказа от индекса (одно открытие файла на задачу).
5. Критерий попадания задан вручную и для одной задачи оказался сформулирован под другую операцию (§4).

---

## 7. Артефакты

| Файл | Что внутри |
|---|---|
| `bench/tasks_z.yaml` | Набор из 14 задач: вопрос, ожидаемый файл, `rg`-шаблоны, операции RAGRAF |
| `bench/results/z-batteryservice_baseline_*.json` | Baseline: кандидаты, вызовы (минимально и реалистично) |
| `bench/results/z-batteryservice_indexed_*.json` | С индексом: операции, факты, попадание, ревизия |
| `tools/bench_corpus.py` | Замерщик (два режима) |
| `tools/probe_hits.py` | Проверка: `symbol` против `search` по задачам |
| `tools/probe_vec_metric.py` | Диагностика метрики векторов (нормы, расстояния, косинус) |
| `tools/probe_chunks.py` | Диагностика чанкинга (виды, размеры, файлы без символов) |
| `tools/probe_z09.py` | Диагностика слоя схемы и проверяемости по операциям |

---

## 8. Что дал полигон разработке /agreements/

Полигон не только измерял пользу RAGRAF — он был использован по назначению: разработка раздела шла через индекс. Результат за один сеанс:

### Написано и проверено (223 проверки, 0 провалов)

| Файл | Назначение |
|---|---|
| `agreements/index.php` | Список журнала: панель, таблица, пейджер, пустые состояния |
| `agreements/search.php` | JSON-эндпоинт живого поиска (контракт §3.2) |
| `agreements/edit.php` | Создание и правка записи, защищённые поля, выдача номера |
| `agreements/parts/*.php` | Панель инструментов, пейджер, строки, статус, пустое состояние |
| `agreements/journals/*.php` | Конфигурация журналов и загрузчик |
| `src/Agreements/AccessMatrix.php` | Матрица «роль × действие» из §5.8 |
| `src/Agreements/Dictionaries.php` | Справочники для форм и фильтров |

### Дефекты, найденные РАЗРАБОТКОЙ (не замером)

| № | Дефект | Как обнаружен | Последствие, если бы не нашли |
|---|---|---|---|
| 6 | **Права расширены**: роль `employee` получала `approve`/`mark_paid`/`sign`/`archive` | проверка под ролью сотрудника на стенде: увидел кнопку «Отметить оплаченным» | любой сотрудник мог бы отметить платёж оплаченным и подписать документ |
| 7 | **`AuthorUserID` и `UpdatedBy` не существуют** в таблицах журналов: там `InitiatorID`, и `UpdatedBy` есть только у `DocDepartment` | ошибка драйвера 42S22 при первом сохранении | создание записи не работало бы вообще |
| 8 | **Сигнатуры аудита не совпадали**: `z_agr_acl_audit()` принимает массив, а не пары аргументов | `TypeError` в логе после исправления №7 | аудит создания и правки не писался бы, а запись не сохранялась |
| 9 | **Обязательное поле с пустым справочником** делало форму неотправляемой молча | проверка цикла создания на пустом стенде | пользователь видел требование заполнить поле, которого нет на странице |
| 10 | **Слой схемы БД был нерабочим** — не могли проверить имена колонок запросом | §2 выше (номер 4) | этот дефект и вызвал №7: проверить схему было нечем |

### Цепочка, показывающая пользу

Дефекты **7 и 8 сцеплены**: не будь разбора миграций (№10, исправлен по итогам замера), имена колонок пришлось бы проверять вручную — а именно на этом и возникли №7 и №8. То есть исправление индекса напрямую ускорило разработку.

Дефект **6 — самый серьёзный по последствиям** и он найден не замером скорости, а проверкой под реальной ролью на стенде. Ни один замер навигации его бы не показал.

---

## 9. Вывод

**Польза подтверждена и измерима.** Один вызов вместо двух-шести, ноль полных чтений файлов вместо одного на задачу, 100% фактов со ссылкой `file:line`, 92% попаданий с первого запроса. Главное отличие от текстового поиска — **локализация**: `rg` находит файл, индекс отвечает «файл и строка».

**Но самая ценная часть — не числа, а десять дефектов, найденных именно на реальной задаче.** Три из них (пустой слой схемы, неработающая метрика векторов, расширенные права роли `employee`) означали бы: часть возможностей RAGRAF существовала только в документации, а подсистема документооборота пускала бы сотрудника отмечать платежи оплаченными. Без полигона они остались бы незамеченными.

**Что стоит проверить дальше:**
1. Сравнить время решения задач живыми агентами (с индексом и без) на задачах **правки** кода, а не только навигации — это следующий уровень доказательности.
2. Добавить в RAGRAF операцию проверки схемы при записи: дефекты №7/№8 не были бы возможны, если бы запись к таблице заранее сверялась с индексом.
3. Довести раздел до оставшихся страниц этапа 1 (карточка, действия, журналы) и затем прогнать сценарий §11.1 целиком в браузере.
