fix(admin): покрытие L3 мерить по индексу, а не по отметкам в базе

Проверка на живых данных вскрыла, что панель отладки врала в мою же пользу:
показывала 154 046 «документов с эмбеддингом» (87%), тогда как в FAISS реально
93 053 вектора (52.5%). Колонка documents.faiss_id для этого непригодна —
отметка остаётся после пересоздания индекса (смена модели: 768 → 1024) и после
сбоев worker-gpu. Выборочная проверка: у 8 из 20 «отмеченных» вектора нет.

- gpu.index_stats — новая задача, отдаёт реальное содержимое активного
  векторного бэкенда; API спрашивает её для панели отладки;
- панель показывает «векторов в индексе» и отдельно предупреждает о ложных
  отметках (их 60 993), потому что такие документы молча выпадают из L3:
  в индексе их нет, а на пересчёт они не попадут — reembed ищет faiss_id IS NULL;
- scripts/ops/faiss_reconcile.py — сверяет отметки с индексом и обнуляет ложные,
  после чего reembed_missing.py отправляет их на пересчёт. Проверен вживую
  (dry-run на проде: 93 053 в индексе, 60 993 ложных отметок).

Документация: зафиксирована реальная глубина корпуса — полный текст только у
27.5% документов, у 79% меньше 500 отпечатков (уровень аннотации). Система
ловит списывание из того, что есть целиком; это граница, а не поломка.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-08-28 17:17:41 +05:00
parent ea62f10829
commit d8630ca0f9
7 changed files with 226 additions and 15 deletions

View File

@@ -95,7 +95,7 @@
| Очередь | Задачи | Воркер |
|---------|--------|--------|
| `queue.index` | `index.extract_and_check`, `index.add_document`, `index.run_parser`, `index.enrich_full_text`, `index.ingest_upload` | worker-indexer |
| `queue.gpu` | `gpu.check_plagiarism`, `gpu.embed_documents`, `gpu.search_semantic` | worker-gpu |
| `queue.gpu` | `gpu.check_plagiarism`, `gpu.embed_documents`, `gpu.search_semantic`, `gpu.index_stats` | worker-gpu |
| `queue.gost` | `gost.format_bibliography` | worker-gost |
| `queue.notify` | `notify.send_task_done`, `notify.send_verification` | worker-notifier |
@@ -232,6 +232,12 @@ Identity (Universal Auth) и генерирует `.env` заново (`infisica
`VECTOR_BACKEND`).
- **Бюджет времени прогона** (`PARSER_TIME_BUDGET_S`, по умолчанию 1500с) —
защита от краш-лупа по `consumer_timeout` RabbitMQ, см. [DR-HA.md](DR-HA.md) §6.
- **Покрытие L3 меряется по индексу, а не по БД.** Колонка `documents.faiss_id`
для этого непригодна: отметка остаётся после пересоздания индекса (смена
модели/размерности) и после сбоев worker-gpu. Панель отладки спрашивает
реальное число векторов задачей `gpu.index_stats` и отдельно предупреждает о
ложных отметках; чинит их
[`scripts/ops/faiss_reconcile.py`](../scripts/ops/faiss_reconcile.py).
- **Добор эмбеддингов** — [`scripts/ops/reembed_missing.py`](../scripts/ops/reembed_missing.py):
документ попадает в корпус сразу, а вектор для L3 считает отдельная задача
`gpu.embed_documents`; если worker-gpu или Ollama были недоступны, эти задачи

View File

@@ -11,6 +11,22 @@
- Повторный прогон по уже залитым источникам даёт почти одни дубли (типично
1490 из 1500 на источник): прирост дают только новые публикации. Реальный
рост корпуса — поднятый `limit` или новые темы, а не повторный запуск.
### Глубина корпуса — чем реально располагает детекция (замер 2026-08-28)
| Что | Значение | Следствие |
|-----|----------|-----------|
| Документов | 177 147 | — |
| С полным текстом в MinIO | 48 848 (27.5%) | arXiv 93.7%, КиберЛенинка 27.8%, PMC 17.0%, OpenAlex 12.7% |
| Отпечатков < 500 на документ | 128 246 (79%) | по этим документам L1 сравнивает фактически аннотацию, а не тело статьи |
| Векторов в индексе (L3) | 93 053 (52.5%) | остальные документы семантический поиск не видит |
| Ложных отметок `faiss_id` | 60 993 | вектора нет, но документ считается векторизованным — см. `faiss_reconcile.py` |
Это не поломка, а честная граница возможностей: **система ловит списывание из
того, что у неё есть целиком.** Скопированное из тела статьи, от которой в базе
только аннотация, L1 не найдёт. Рычаги углубления: `FETCH_FULL_TEXT=true`
(докачка OA-PDF, уже включена), приоритет источников с полным текстом (PMC,
arXiv), и добор эмбеддингов для L3.
- OpenAlex при массовом запуске упирается в лимит вежливого пула (10 req/s
на mailto, общий для всех воркеров) — пауза между страницами поднята до 1с.
- Добавлен 4-й парсер — **PMC** (PubMed Central, `scripts/parsers/pmc.py`),
@@ -66,10 +82,17 @@ python scripts/seed_ru_sources.py --apply --limit 500
- **Страница «Отладка»** — очереди, воркеры, покрытие эмбеддингами, зависшие и
упавшие прогоны (см. ARCHITECTURE.md §12).
После большой заливки стоит свериться с покрытием L3 (панель отладки, строка
«без вектора»): эмбеддинги считаются отдельной задачей на worker-gpu, и если он
или Ollama были недоступны, документы останутся без вектора. Догнать —
`scripts/ops/reembed_missing.py` (dry-run по умолчанию, `--apply` отправляет).
После большой заливки — свериться с покрытием L3 (панель отладки, «Векторов в
индексе»). Порядок именно такой, из двух шагов:
1. `scripts/ops/faiss_reconcile.py` (в контейнере worker-gpu) — сверяет отметки
`faiss_id` с реальным содержимым индекса и обнуляет ложные. Без этого шага
документы, потерявшие вектор при пересоздании индекса, не попадут на
пересчёт: они всё ещё «отмечены».
2. `scripts/ops/reembed_missing.py` (в контейнере worker-indexer) — отправляет
`gpu.embed_documents` для всех `faiss_id IS NULL`.
Оба по умолчанию dry-run, отправляют/меняют только с `--apply`.
Прогон со статусом `partial` — это не ошибка: сработал бюджет времени
(`PARSER_TIME_BUDGET_S`, 1500с), заливка остановилась раньше `consumer_timeout`