Files
akyldash/docs/product/search-relevance-findings-2026-11-03.md
codex 849c19e2ab
CI / tests-and-images (pull_request) Successful in 2m12s
Runner smoke test / runner-smoke (pull_request) Successful in 2s
docs: record final search benchmark findings
2026-11-03 02:38:36 +03:00

30 KiB
Raw Permalink Blame History

Диагностика релевантности поиска — 3 ноября 2026 года

Решения и границы

Эта заметка фиксирует исходные измерения от 2 ноября, узкое исправление терминологии, условный multi-index scoring и контракт для закрытия пробела корпуса. Выдачи и AI-аннотации не являются юридической оценкой. Целевой A/B прогон и полный benchmark для итогового SHA описаны ниже; они не заменяют экспертную оценку.

Изменён backend 0.13.3 → 0.13.4, frontend внутренней лаборатории 0.3.3 → 0.3.4, версия алгоритма search-6 → search-8. search-7 относится к предшествующему составу ranking semantics; новые подписанные снимки этого PR помечаются search-8. Сохранённые отзывы не переписываются. Финальный runtime/batch прогон кода выполнен на 1c7ddf2; последующий коммит этого отчёта и changelog меняет только документацию.

Исходный benchmark

Зафиксированный набор содержит 7 500 русских формулировок: 15 тематических групп, по 500 запросов в каждой. Это 750 тем с десятью вариантами формулировки, а не 7 500 независимых правовых ситуаций. Исходный индекс akyldash-fragments-v2 содержал около 42,6 млн фрагментов и занимал 11,2 ГБ.

Показатель Акылдаш, исходный прогон Результат
Собранные выдачи 7 500 / 7 500
Пустые выдачи 6
Медиана / P95 ответа 21,67 / 77,36 мс
Совпадение основ темы в видимых названии/фрагменте, top 1 40,57%
То же, top 3 / top 10 47,05% / 51,40%

Сравнение с КонсультантПлюс в исходном отчёте — только диагностическое: юрисдикции и корпуса различаются. Совпадение основ темы не измеряет Recall, MRR, полноту ответа или юридическую правильность; российская выдача не является эталоном права Кыргызстана. Для вывода о качестве нужны экспертные релевантные коды, контрольный набор и отдельные метрики.

Подтверждённые классы ошибок

14 коротких документов прочитаны целиком; Codex (ИИ) подготовил по ним 79 предварительных оценок пар «запрос—документ» в 71 сессии: 59 помечены как нерелевантные и 20 как косвенно полезные. Это тематическая AI-разметка для триажа, а не заключение юриста или независимая экспертная проверка. Она не подтверждает полноту правового ответа. Неоценённые результаты не считались ошибками.

Тема Наблюдавшийся неверный приоритет Вывод
Расторжение договора аренды 135066 Решение по одной сделке с землёй не даёт общих оснований расторжения
Международный коммерческий арбитраж 26411 Продажа земельных участков не регулирует арбитраж и отказ в признании решения
Испытательный срок работника 94603 Земельный участок под завод/испытательный полигон совпадает словами, но не предметом
Место жительства ребёнка 50760 Финансирование приюта не устанавливает порядок спора родителей
Перевод студента в другой вуз 14165 Разовое финансирование студентов не устанавливает процедуру перевода
Таможенные пошлины 41854 Индивидуальная отсрочка одному предприятию не задаёт общие правила
Общий порядок предоставления земли 54735, 54571 Решения по отдельным участкам не являются общей процедурой
Защита критической инфраструктуры 59082 Предоставление земли под завод не устанавливает защитные требования
Купля-продажа / расторжение договора 60053 Расторжение одной сделки с госакциями не является общей нормой
Подача искового заявления 18516, 7115 Отдельный спор и узкая льгота по пошлине не дают общего порядка подачи иска
Принятие нормативных актов 77190 Первое чтение одного законопроекта не устанавливает общий процесс
Подключение к водоснабжению 135061 Целевая помощь отдельным домохозяйствам не задаёт технические условия

Проверенные варианты глобального повышения веса кодексов отвергнуты: вес 2 не исправил аренду и подачу иска, а вес 4 поднял Налоговый кодекс выше профильных источников по воинской обязанности и подаче иска. Общие веса или жёсткие приоритеты кодексов не добавлялись.

Узкая терминологическая поправка

В исследованной редакции ГК КР II (код 5, редакция 460) глава 27 и статья 542 связывают договор аренды с имущественным наймом; статьи 558–559 используют термин «имущественный наем». В полном тексте этой редакции буквальная фраза «расторжение договора аренды» не встречалась. Исходный запрос возвращал код 135066 на первом месте, а формулировка «расторжение договора имущественного найма» — код 5.

Backend теперь сохраняет исходную строку и добавляет только для точного сочетания «договор [арендная форма]» вариант «договор [та же форма] имущественного найма». В частности, договора аренды превращается в договора имущественного найма. Расширение не срабатывает для аренды квартиры или земельного участка, поднайма, найма работника или лизинга. Точный запрос остаётся в строгой лексической ветке, поэтому его профильный муниципальный акт не исключается из поиска.

В read-only пробе предыдущей версии aa6fabe для общего запроса код 5 поднялся с позиции за пределами top 10 на #5, но нерелевантный индивидуальный акт 135066 оставался первым. На SHA 726b1ee isolated code-only прогон на исходном индексе поставил код 5 первым для общего запроса; из 7 500 запросов изменились 19 выдач, 7 481 совпала с baseline, число пустых осталось 6. Этот результат проверяет search-7 с match_phrase boost 12 для синтетической юридической фразы, исходный phrase boost 3; он не описывает multi-index DFS. После подключения трёх актов в отдельном supplement на e653f3b изменились 184 выдачи против code-only варианта и 38 top-1; все шесть пустых выдач сохранились. Разные наборы индексов нельзя считать одним benchmark. Итоговый conditional-DFS прогон на 1c7ddf2 приведён ниже. Контроль запросов об аренде жилья/земли, поднайме, трудовых отношениях, международном праве и KY формулировках выполнялся отдельно; rank-метрики не являются юридической оценкой.

Условный scoring для явного multi-index scope

При поиске по явно настроенному comma-separated scope (base,supplement) API передаёт search_type=dfs_query_then_fetch как URL query parameter. Запросы по одному настроенному индексу или alias сохраняют прежний query_then_fetch; одиночные wildcard и alias не классифицируются по результату их expansion. DFS включается только при запятой в настроенной строке scope. Тело DSL, фильтры, query weights, сортировка, индексы и документы не меняются. Параметр применяется только к /search; фильтры и остальные API вызовы используют прежний режим.

OpenSearch описывает query_then_fetch как scoring по локальным term/document frequencies шардов, а DFS — по глобальным frequencies через шарды; DFS обычно точнее, но может быть медленнее. search_type поддерживается именно как параметр Search API URL (OpenSearch Search API). Поэтому ограниченный режим использует глобальные statistics для явной multi-index конфигурации и не меняет behavior одиночного поиска.

На изолированном OpenSearch 3.7.0 оба режима сравнили на одинаковых DSL bytes и одном неизменном наборе данных: 300 заранее выбранных запросов, максимум 300. Порядок менялся в 93 случаях, top-1 — в 29; из 9 именованных риск-кейсов изменились 3, все 5 выбранных positive controls сохранили top-1. Контроль с индивидуальным актом 135066 сохранил этот акт первым. В пяти запросах о педагогических/научных работниках и одном запросе о болезнях животных top-1 не изменился. Эти шесть случаев требуют предметной оценки и не объявляются автоматически нерелевантными: статья 150(8) закона об охране здоровья связывает аттестацию педагогических/научных работников с медицинской деятельностью, а случай с болезнями животных остаётся без экспертной оценки. Новые оценки пар не выставлялись; специальные веса или document IDs не добавлялись. Сравнение ограничено выборкой и не является разметкой юридической правильности. Медианы 22,955 мс для default и 16,115 мс для DFS не используются как оценка скорости: default всегда исполнялся первым, возможен cache/order bias.

Frozen experiment metadata is stored outside the repository in reports/search-benchmark-2026-11-02/dfs-probe-e653-summary.json. The paired response-capture artifact dfs-probe-e653.jsonl has SHA-256 4a9373ba5236c5c5a1d85139b6dbe67bcf01df02216f8b2f292e5c451e6432c8. Выборка содержала base index (42 624 501 fragments) и read-only supplement (8 934 fragments); UUID/count guards прошли, основной индекс и сохранённые отзывы во время эксперимента не менялись. В PR не включены сырые ответы, персональные данные или выдачи документов.

Кодовый тест проверяет URL, encoded multi-index path, RU/KY, пустую выдачу, прежнее тело запроса и неизменность single alias/index и filters. Read-only integration gate для root-owned runtime:

AKYLDASH_OPENSEARCH_URL=http://127.0.0.1:8081 \
AKYLDASH_OPENSEARCH_INDEX='akyldash-fragments-v2,akyldash-benchmark-supplement-20261103-r1' \
PYTHONPATH=backend python3 -m unittest \
  backend.test_search_api_opensearch.SearchMultiIndexDfsOpenSearchTest.test_global_statistics_mode_accepts_read_only_multi_index_search -v

Root выполнил этот read-only gate на 8081 для 1c7ddf2: фактический multi-index URI использовал DFS и подписанный token сохранял полный scope. Gate выполняет только поиск и не создаёт/удаляет индексы. Полный benchmark на том же code SHA и frozen baseline приведён ниже; 300-query experiment и более ранние прогоны остаются отдельными сравнительными срезами.

Откат к предшествующему приложению возвращает query_then_fetch для multi-index поиска. Индекс, документы, saved reviews и старые подписанные токены при этом не требуют изменений или миграции; новые отзывы, сохранённые до rollback, остаются помечены search-8. Для supplement действует отдельный rollback контракт ниже: отключение его подключения скрывает только добавленные редакции, не удаляя подготовленные файлы и базовый индекс.

Итоговый pinned прогон search-8

Полный isolated прогон для executable SHA 1c7ddf2fcbd7c55920470e7ac52339fdab0366db собрал 7 500 уникальных запросов с версией search-8. SHA-256 выдачи: fc02e22e5b3d612813ffc0bdaf9892bb16ee4c29af3e1b77f79e58a0a45c8ec1. Итог сопоставлен с frozen baseline и двумя явно указанными промежуточными режимами:

Сравнение Изменён порядок Изменён top-1
Исходный baseline search-6 537 103
Code-only 726b1ee, search-7 521 93
Code + три акта, e653f3b, без DFS 425 63

Это число изменений порядка, а не улучшений. Все шесть пустых выдач сохранились во всех четырёх прогонах. Среди ранее проверенных Codex (ИИ) пар с нулевой оценкой top-1 стало 26 вместо 28 в baseline; остальные 26 не объявлены исправленными, и новые выдачи автоматически не оценивались.

Добавленный акт Наблюдался в top-1 / top-3 / top-10
Трудовой кодекс КР 230021083 24 / 58 / 105
Земельный кодекс КР 230029465 4 / 11 / 26
Закон об охране здоровья 230000918 34 / 51 / 92

Все шесть точных названий новых актов на RU/KY дали ожидаемый текущий акт первым. Все 284 запроса, общие с целевым DFS экспериментом, совпали с ним по полному порядку кодов и редакций. Проверки dataset guard подтвердили неизменность исходного индекса, manifest, mappings и UUID/count, а также 115 подключённых файлов. 7 503 записи локальной лаборатории совпали с backup; обе SQLite integrity checks прошли. Записи оценок, комментарии и пользователи не менялись.

Шесть межтематических контекстов нельзя автоматически считать нерелевантными: медицинский закон остался первым в пяти запросах о педагогических/научных работниках и в одном запросе о болезнях животных. Для педагогических случаев проверенная статья 150(8) связывает аттестацию этих работников с медицинской деятельностью; новые оценки пар не выставлялись. Вопрос о животных также остался без экспертной оценки. ИИ-аннотации не доказывают полноту правового ответа или юридическую правильность ранжирования.

Режим и scope дополнительно проверены на runtime 8081: для multi-index /search уходит DFS, для одиночного индекса и filters — прежний default; тело DSL одинаково. Alias проверен transport mock без создания alias. Runtime mode/API smoke включал HMAC/TTL, signed scope, сохранение пустого результата и отрицательные rank/code сценарии. Browser UI fixture прошёл на 726b1ee; UI побайтно не менялся в следующих SHA, но отдельный browser прогон на 1c7ddf2 не выполнялся. Медиана backend 23.685 мс описательна и не является SLO или доказательством ускорения.

Эксперимент подключал только три акта в отдельном supplement и не доказывает полноту всего корпуса, актуальность всех редакций, наличие вложений или production-готовность. Отключение overlay/supplement скроет эти новые редакции из локального поиска/API, но сохранит подготовленные файлы, review-записи и исходный индекс. Production не менялся. Подробный технический отчёт и raw benchmark остаются во внешнем рабочем наборе и в PR не включены.

Пустые выдачи в лаборатории

Исходный контракт блокировал сохранение шести подписанных пустых снимков: API отвергал results: [], а интерфейс читал поля из пустого placeholder. Теперь signed snapshot может сохранить пустую оценку с обязательным общим комментарием. В записи top_result_code остаётся null, список результатов пустой. Непустой снимок по-прежнему обязан получить оценку каждого ранга ровно один раз, совпадающую с подписанным кодом документа. HMAC, TTL и текущая аутентификация не меняются.

Подписанный снимок теперь сохраняет scope запроса при явно настроенном multi-index выражении (A,B): provenance не сужается до индекса, который случайно вернул единственный результат, и сохраняется при пустой выдаче. Для одного настроенного индекса или alias остаётся прежнее поведение: единственный concrete _index записывается как provenance; если OpenSearch не сообщил его, сохраняется настроенный scope. Новые отзывы получают эту метку при сохранении. SQLite-схема, исторические записи и формат подписанных токенов не меняются. Откат к старому коду не требует миграции и не переписывает существующие отзывы; пока работает откатанная версия, новые multi-index отзывы снова могут получить суженную метку, поэтому исправление provenance следует вернуть вместе с основным выпуском.

Существующая отдельная база локальной лаборатории содержала 7 494 сохранённых сессии, 79 AI-оценок и 55 516 диагностических комментариев; 74 715 оценок результатов остались пустыми намеренно. Integrity check прошёл, порядок кодов и редакций baseline не менялся. Эти сохранённые оценки не переписывались. В последующем изолированном trial шесть реальных подписанных пустых отзывов с общими комментариями были сохранены как записи 7498–7503; итоговые 7 503 строки лаборатории совпали с backup, integrity check прошёл. Пустые query-level отзывы не являются оценками отдельных документов.

Пробел актуального корпуса и безопасный контракт закрытия

Отдельно от ранжирования, в исходном base index не было трёх подтверждённых действующих актов (все три canonical ID имели _count = 0):

Акт Canonical ID Последняя проверенная редакция
Трудовой кодекс КР от 23.01.2025 № 23 230021083 57548 от 28.07.2026
Земельный кодекс КР от 18.07.2025 № 149 230029465 34746 от 18.07.2025
Об охране здоровья граждан КР от 12.01.2024 № 14 230000918 47553 от 23.01.2026

Это разрыв между нормализованными документами и индексом, а не полное отсутствие актов в локальном архиве: в data/minjust-normalized уже есть canonical-каталоги всех трёх документов, manifest сообщает 209 958 успешно обработанных документов. Однако трудовой акт там отстаёт от официальной редакции 57548 от 28.07.2026 (последняя подготовленная — № 60 от 23.06.2026); земельный акт нормализован до редакции № 10 от 18.07.2025; для акта о здоровье последняя подготовленная — № 60 от 23.01.2026. В base index эти три canonical ID не попали. Прежние отменённые трудовой и земельный кодексы нельзя переименовать или поднять как замену актуальным нормам.

Для локальной изолированной проверки подготовлены точные официальные RU/KY редакции: 3 документа и 8 934 фрагмента. На runtime 8081 они были временно подключены отдельным supplement-index и доступны API через read-only overlay catalogues. Это не изменение base index или production и не доказательство полноты корпуса. Вложения и полная история новых canonical ID отдельно не подтверждены.

Закрытие пробела требует отдельного контролируемого обновления данных:

  1. Заново сверить официальные canonical ID, статус, edition ID и дату; сохранить исходный ответ, хеши и provenance в отдельном staging-каталоге.
  2. Проверить полноту обеих языковых версий, ссылки на редакции, историю и вложения; не импортировать метаданные с ненужными персональными полями.
  3. Нормализовать документы отдельно и проверить exact-count/hash. Для ограниченного дополнения собрать отдельный версионный индекс только из этих актов; не пересобирать и не перезаписывать индекс из 42,6 млн фрагментов.
  4. Изолированный read-only trial уже проверил отдельный supplement-index, подключённые каталоги data_root, RU/KY выдачу, current-edition routes, collapse и контрольные запросы. Перед новым подключением нужно повторно сверить official source и dataset guards; trial не записывал новые документы в base index и не менял alias.
  5. Публикация требует отдельного владельческого разрешения и проверенного rollback. При отключении локального supplement-index поиск и просмотр новых редакций из него прекращаются; подготовленные файлы и уже сохранённые отзывы сохраняются, базовый индекс не удаляется. PR фиксирует код и результаты локального испытания; production, base index и исходные данные не менялись.

Что остаётся проверить

  • Финальный code/data эксперимент и full 7 500 run относятся к executable SHA 1c7ddf2; текущий follow-up commit меняет только этот report и changelog. CI и полное независимое ревью должны повторно пройти на новом PR head.
  • Browser fixture пустой выдачи прошёл на 726b1ee; UI побайтно неизменён в следующих commit. Для 1c7ddf2 выполнены реальные API/runtime smokes, но новый полный browser run не заявляется.
  • Три supplement-акта не подтверждают полноту всего корпуса, полную историю, вложения, лицензирование или production readiness. Для новой публикации нужен свежий source/edition check и отдельное разрешение владельца.
  • Измерения top-k, AI-разметка и технические smoke checks не заменяют независимую экспертную оценку юридической релевантности.

Исходные измерения и хеши хранятся вне репозитория в рабочем наборе search-benchmark-2026-11-02. В этот репозиторий не включаются сырые ответы MinJust, диагностические логи, внешние пользовательские данные или выгрузка лабораторных комментариев.