244 lines
13 KiB
Markdown
244 lines
13 KiB
Markdown
# Backend Акылдаш
|
||
|
||
Версия: `0.9.1`
|
||
|
||
Первая backend-область проекта — загрузка правовых документов из официального
|
||
Open Data API ЦБД Минюста Кыргызской Республики. Код расположен в
|
||
`ingestion/minjust_cbd.py`: его можно запускать как CLI сейчас и импортировать
|
||
из будущего планировщика backend без запуска отдельного процесса.
|
||
|
||
## Пробная загрузка
|
||
|
||
Из корня репозитория:
|
||
|
||
```bash
|
||
python3 backend/ingestion/minjust_cbd.py --limit 10
|
||
```
|
||
|
||
В интерактивном терминале отображаются процент, количество документов,
|
||
ошибки, скорость и примерное оставшееся время.
|
||
|
||
Полная загрузка выполняется без `--limit`. По умолчанию архив сохраняется в
|
||
`data/minjust-cbd`, который исключён из Git. Повторный запуск пропускает уже
|
||
загруженные документы; `--refresh` принудительно проверяет их заново.
|
||
Если временный идентификатор списка API истечёт или запрос страницы исчерпает
|
||
повторы во время многодневной загрузки, скрипт пересоздаст список на текущей
|
||
странице и продолжит автоматически.
|
||
|
||
В версии `0.1.0` обновление существующих документов выполняется полной проверкой
|
||
через `--refresh`. Инкрементальную проверку по `lastmod` из sitemap следует
|
||
добавить вместе с backend-планировщиком, когда будет определена частота запуска.
|
||
|
||
## Защита API
|
||
|
||
Запросы выполняются последовательно, по умолчанию не чаще одного в секунду.
|
||
Timeout одного ответа — 60 секунд, число попыток — 5, задержка между повторами
|
||
растёт экспоненциально. При HTTP 429 учитывается заголовок `Retry-After`.
|
||
Лимит действует на один процесс, поэтому одновременно следует запускать только
|
||
один экземпляр загрузчика.
|
||
|
||
Более осторожный режим:
|
||
|
||
```bash
|
||
python3 backend/ingestion/minjust_cbd.py --requests-per-second 0.5
|
||
```
|
||
|
||
## Вызов из будущего backend
|
||
|
||
```python
|
||
from ingestion.minjust_cbd import sync_archive
|
||
|
||
result = sync_archive(output_path)
|
||
```
|
||
|
||
Планировщик, очередь задач и PostgreSQL пока не добавлены: модуль не зависит от
|
||
выбора будущего backend-фреймворка.
|
||
|
||
## Нормализация архива
|
||
|
||
Нормализатор читает исходный архив без изменений и создаёт отдельный набор
|
||
данных для будущих поиска, API и RAG:
|
||
|
||
```bash
|
||
python3 backend/normalization/minjust_cbd.py --limit 10
|
||
python3 backend/normalization/minjust_cbd.py
|
||
```
|
||
|
||
По умолчанию источник читается из `data/minjust-cbd`, а результат записывается
|
||
в `data/minjust-normalized`. Пути можно изменить параметрами `--input` и
|
||
`--output`; `--refresh` принудительно обрабатывает неизменившиеся документы,
|
||
`--log-level` задаёт уровень журнала.
|
||
Каталоги `--input` и `--output` не должны совпадать, содержать друг друга или
|
||
пересекаться через разрешённые абсолютные пути.
|
||
|
||
```text
|
||
data/minjust-normalized/
|
||
manifest.sqlite3
|
||
documents/<code>/document.json
|
||
documents/<code>/editions/<edition>/edition.json
|
||
documents/<code>/editions/<edition>/<lang>/content.html
|
||
documents/<code>/editions/<edition>/<lang>/content.txt
|
||
documents/<code>/editions/<edition>/<lang>/fragments.json
|
||
```
|
||
|
||
`content.html` содержит только разрешённую безопасную разметку, `content.txt` —
|
||
текст для поиска, а `fragments.json` — адресуемые блоки с детерминированными ID
|
||
и SHA-256. Манифест пропускает документы с неизменившимися исходниками и
|
||
повторяет документы, обработка которых завершилась ошибкой.
|
||
|
||
Первая версия не выполняет OCR, перевод, юридические выводы о редакциях,
|
||
сопоставление фрагментов, загрузку в PostgreSQL/OpenSearch и построение RAG.
|
||
Внешние и встроенные `data:`-изображения из HTML удаляются; сведения и пути к
|
||
локальным изображениям исходного архива сохраняются в `edition.json`.
|
||
|
||
## Проверка
|
||
|
||
```bash
|
||
PYTHONPATH=backend python3 -m unittest backend/test_minjust_cbd.py -v
|
||
PYTHONPATH=backend python3 -m unittest backend/test_minjust_normalization.py -v
|
||
PYTHONPATH=backend python3 -m unittest backend/test_minjust_opensearch.py -v
|
||
```
|
||
|
||
## Подготовка индекса OpenSearch
|
||
|
||
Mapping поискового индекса находится в
|
||
`search/minjust-fragments-index.json`. Для кыргызского текста он использует
|
||
`icu_analyzer`, поэтому в OpenSearch должен быть установлен плагин
|
||
`analysis-icu`.
|
||
|
||
Потоковый экспорт в формат Bulk API без внешних Python-зависимостей:
|
||
|
||
```bash
|
||
python3 backend/search/minjust_opensearch.py
|
||
```
|
||
|
||
По умолчанию создаётся `data/opensearch/minjust-fragments.ndjson`. Экспорт
|
||
атомарный и детерминированный; для проверки можно передать `--limit 1`.
|
||
Для прямой загрузки без большого промежуточного файла используется `--url`:
|
||
|
||
```bash
|
||
python3 backend/search/minjust_opensearch.py \
|
||
--url http://127.0.0.1:9200 \
|
||
--index akyldash-fragments-dev-v1 \
|
||
--limit 1
|
||
```
|
||
|
||
Запросы Bulk API ограничены 25 МБ и не разрывают пару action/source. Для
|
||
полного прохода убрать `--limit` и выбрать новое имя версионного индекса.
|
||
После проверки production-индекса следует переключать alias, чтобы удалённые
|
||
фрагменты не оставались в поиске.
|
||
|
||
Чтобы переключить alias атомарно только после успешной полной загрузки,
|
||
передайте `--alias`:
|
||
|
||
```bash
|
||
python3 backend/search/minjust_opensearch.py \
|
||
--url http://127.0.0.1:9200 \
|
||
--index akyldash-fragments-v2 \
|
||
--alias akyldash-fragments-current
|
||
```
|
||
|
||
После каждого принятого Bulk-пакета загрузчик атомарно сохраняет checkpoint и
|
||
печатает код безопасного возобновления. При временных HTTP 429/5xx, timeout и
|
||
обрыве соединения запрос повторяется автоматически. Прерванную загрузку можно
|
||
продолжить без ручного выбора документа:
|
||
|
||
```bash
|
||
python3 backend/search/minjust_opensearch.py \
|
||
--url http://127.0.0.1:9200 \
|
||
--index akyldash-fragments-v1 \
|
||
--resume
|
||
```
|
||
|
||
По умолчанию checkpoint хранится в
|
||
`data/opensearch/<index>.checkpoint.json`; путь можно изменить через
|
||
`--checkpoint`. Checkpoint привязан к URL, cluster UUID, index UUID, `--limit`,
|
||
`--alias` и SHA-256 нормализованного manifest. При `--resume` передавайте то же
|
||
значение `--alias`; старый checkpoint без alias можно продолжить только без
|
||
него. Resume отклоняется при любом несовпадении:
|
||
для обновлённого корпуса или пересозданного индекса нужно создать новый
|
||
версионный индекс, проверить его и переключить alias. Это не оставляет
|
||
удалённые trailing-фрагменты старых документов.
|
||
|
||
## HTTP API v1
|
||
|
||
Запустите публичный read-only API поверх текущего alias и нормализованного
|
||
корпуса:
|
||
|
||
```bash
|
||
PYTHONPATH=backend python3 -m search.api
|
||
```
|
||
|
||
Он публикует OpenAPI в `GET /openapi.json` и поддерживает `GET /search`,
|
||
`/search/filters`, `/documents/{code}`, `/documents/{code}/editions` и
|
||
`/documents/{code}/editions/{edition}`. Значения фильтров возвращаются со
|
||
стабильным кодом справочника v1 и подписями RU/KY; применяйте `code` как
|
||
параметр поиска. API не подменяет отсутствующий язык документа.
|
||
|
||
## Локальный OpenSearch
|
||
|
||
Стенд использует один узел OpenSearch без Dashboards, устанавливает
|
||
`analysis-icu`, выделяет JVM 8 ГБ и доступен только на `127.0.0.1:9200`.
|
||
Индекс хранится в `data/opensearch-node` на диске проекта.
|
||
|
||
```bash
|
||
sudo sysctl -w vm.max_map_count=262144
|
||
docker compose -f deploy/local-opensearch/compose.yaml up -d --build
|
||
curl http://127.0.0.1:9200/_cluster/health
|
||
```
|
||
|
||
Security plugin отключён только для локальной разработки; этот compose нельзя
|
||
публиковать в сеть или использовать в production. Mapping локального стенда
|
||
также задаёт одну shard и ноль replicas; для production число shard следует
|
||
рассчитать по размеру корпуса и настроить не менее одной replica.
|
||
|
||
## Оценка качества поиска
|
||
|
||
`search/relevance-set-v1.template.json` содержит заготовку для 25 русских и
|
||
25 кыргызских запросов. Для каждого запроса человек должен указать реальную
|
||
формулировку и коды всех релевантных документов; пустая или неполная разметка
|
||
должна быть отклонена при ручной проверке, а технически некорректная — самим
|
||
оценщиком. Критерии выбора запросов, релевантности и двойной проверки описаны в
|
||
`search/RELEVANCE_ANNOTATION.md`. Рабочую копию следует хранить в игнорируемом
|
||
каталоге `data/`, пока набор не проверен и не разрешён к публикации.
|
||
|
||
Baseline использует поля названия и текста соответствующего языка, оставляет в
|
||
выдаче один результат на документ и вычисляет макро-средние Recall@10 и MRR@10:
|
||
|
||
```bash
|
||
mkdir -p data/search
|
||
cp backend/search/relevance-set-v1.template.json data/search/relevance-set-v1.json
|
||
PYTHONPATH=backend python3 -m search.evaluate_relevance \
|
||
data/search/relevance-set-v1.json \
|
||
> data/search/baseline-v1.json
|
||
```
|
||
|
||
Менять веса или анализаторы следует только после фиксации этого baseline и
|
||
разбора ошибок выдачи.
|
||
|
||
## Внутренняя лаборатория релевантности
|
||
|
||
Запустите Search API на localhost и откройте `http://127.0.0.1:8080/review`:
|
||
|
||
```bash
|
||
PYTHONPATH=backend python3 -m search.api \\
|
||
--reviews-db data/search-reviews.sqlite3
|
||
```
|
||
|
||
Лаборатория показывает фактический порядок выдачи OpenSearch, позволяет открыть
|
||
текст редакции, поставить результату оценку от 0 до 3 и сохранить снимок с
|
||
комментариями. Оценки сохраняются в SQLite, экспорт доступен через
|
||
`GET /search-reviews/export`. Интерфейс предназначен только для локальной сети
|
||
или защищённого reverse proxy; не публикуйте его напрямую в интернет.
|
||
|
||
Для проверки текущей выдачи без будущего HTTP API используйте CLI:
|
||
|
||
```bash
|
||
PYTHONPATH=backend python3 -m search.query "как открыть ОсОО" --language ru
|
||
PYTHONPATH=backend python3 -m search.query "ЖЧК ачуу тартиби" --language ky
|
||
```
|
||
|
||
---
|
||
|
||
Акылдаш · Backend v0.9.1 · Frontend — не создан
|