Files
anti-plagiarism/docs/INGESTION.md
jze9 d8630ca0f9 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>
2026-08-28 17:17:41 +05:00

121 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Наполнение корпуса — runbook
## Текущее состояние (на 2026-08-28)
- **177 147 документов**, русский большинство: `ru` 99 884, `en` 76 936,
остальные языки — единицы/десятки. Проблема «не с чем сравнивать русские
работы» из более ранней версии этого документа закрыта.
- По источникам: CyberLeninka 99 883, OpenAlex 49 276, PMC 14 910, arXiv 13 077,
`user_submission` (проверенные пользователями работы, не источник для сравнения
сами с собой — см. ARCHITECTURE.md §7) 1.
- Повторный прогон по уже залитым источникам даёт почти одни дубли (типично
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`),
англоязычные научные статьи открытого доступа.
- Массовое расширение по дисциплинам теперь двумя сидерами: `seed_ru_sources.py`
(русскоязычные, CyberLeninka/OpenAlex `lang=ru`) и **`scripts/seed_broad_corpus.py`**
(англоязычные — OpenAlex/arXiv/PMC по широкому списку дисциплин, добавлен позже
первой волны).
- Операционный риск массовой докачки (`consumer_timeout` RabbitMQ vs долгие таски)
закрыт бюджетом времени прогона и `worker_prefetch_multiplier=1` — подробности
и почему префетч тут главный, см. [DR-HA.md](DR-HA.md) §6.
## Что подготовлено
- **Парсер CyberLeninka починен** (`scripts/parsers/cyberleninka.py`): раньше слал GET
на `/api/search` → HTTP 405; теперь POST с JSON-телом (`mode=articles`), authors из
списка, чистка `<b>`/HTML-сущностей. Проверено вживую (5/5 тем) + юнит-тесты
(`scripts/parsers/tests/`), в гейте CI.
- **Прод-путь готов**: `index.run_parser(source_id)` уже умеет `cyberleninka` и
`openalex` c `lang=ru`.
- **Сидер источников**: `scripts/seed_ru_sources.py` — 30 студенческих дисциплин.
## Запуск русской заливки
```bash
# 1. (на app-хосте / в контейнере worker-indexer, где есть psycopg2 и прод-.env
# — .env на проде теперь генерируется из Infisical на каждом деплое, см.
# ARCHITECTURE.md §10, руками его не редактировать)
# Посмотреть план:
python scripts/seed_ru_sources.py
# Создать источники в parse_sources (лимит на дисциплину):
python scripts/seed_ru_sources.py --apply --limit 500
# 2. Проверить пару источников на темпе/качестве, затем запустить заливку:
# • Админ-панель → «Источники» → «Запустить» (или «Запустить всё»), ЛИБО
# • Celery: index.run_parser.delay(source_id) по каждому id
```
## Управление заливкой из админки
Всё, что ниже, доступно на странице «Источники» — CLI для этого больше не нужен:
- **Шкала загрузки** у каждого источника: стадия (выборка → индексация), сколько
получено из скольки, сколько добавлено/дублей/ошибок. Раскрытая строка —
журнал прогона по шагам с таймингами (таблица `parse_runs`).
- **«Запустить всё»** — прогон по всем включённым источникам; уже идущие
пропускаются. **«Остановить всё»** и остановка по одному — кооперативная
отмена: воркер останавливается сам на ближайшем тике, не обрывая запись в базу.
- **Пакетное добавление** — один тип источника + список тем (по строке),
опционально с немедленным запуском. Заменяет `seed_*.py` для разовых расширений.
- **Загрузка работ в базу** — PDF/DOCX/TXT прямо в корпус сравнения
(`index.ingest_upload`, `source=manual_upload`), минуя проверку и отстойник.
- **Страница «Отладка»** — очереди, воркеры, покрытие эмбеддингами, зависшие и
упавшие прогоны (см. ARCHITECTURE.md §12).
После большой заливки — свериться с покрытием 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`
RabbitMQ. Остаток добирается повторным запуском источника.
Заливка сама: fetch (rate-limit 1 req/s) → `add_document` (дедуп по `ext_id`,
fingerprints L1, MinHash L2) → батч-эмбеддинги `gpu.embed_documents` (L3). ~30 тем ×
500 ≈ 15K русских документов на первый заход.
## Проверка результата
```sql
SELECT lang, count(*) FROM documents GROUP BY lang ORDER BY 2 DESC; -- должен появиться ru
SELECT source, count(*) FROM documents WHERE source='cyberleninka'; -- > 0
```
## Масштаб (следующий уровень)
- Больше тем + выше `--limit`; добавить OpenAlex `lang=ru` (качество ниже — англ.
заголовки с меткой ru).
- Для миллионов — **bulk** (снапшот OpenAlex на S3), а не постраничный API.
- На масштабе обязателен `VECTOR_BACKEND=qdrant` (FAISS flat не тянет), а таблица
`fingerprints` (уже ~113M строк на 177K доков — партиционирование стоит планировать
заранее, не постфактум) потребует партиционирования. См.
[ARCHITECTURE.md](ARCHITECTURE.md) и [DR-HA.md](DR-HA.md).