diff --git a/dev/README.md b/dev/README.md index 168ecf7..ad4f10e 100644 --- a/dev/README.md +++ b/dev/README.md @@ -145,6 +145,112 @@ curl -s localhost:8000/api/v1/me -H "Authorization: Bearer " # email, **Остановка и сброс профиля** — только с `--profile services`: без него compose не видит контейнеры профиля (см. «Сброс окружения»). +## Проверка окружения (сквозной сценарий) + +Ручная проверка обмена `recognition_job` через NATS на поднятом профиле `services`: ядро публикует задание, сервис +отвечает ack и результатом, ядро меняет статус. UI и API загрузки файлов в Срезе 0 нет, поэтому файл кладётся в MinIO +через `mc`, а строки `stored_file` / `s3_object` / `recognition_job` вставляются через `psql`. Проверено 2026-10-04 +(все три сценария ниже — фактический вывод, время UTC). + +Помощники (bash/zsh, из `docs/dev/`; `uuidgen` есть в macOS и util-linux): + +```bash +dpsql() { docker compose exec -T postgres sh -c 'psql -X -q -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" "$@"' sh "$@"; } +dmc() { docker compose run --rm -v "$PWD/../../fixtures:/fixtures:ro" --entrypoint sh minio-init \ + -c 'mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null && mc "$@"' sh "$@"; } +new_job() { # new_job <файл в fixtures> — кладёт файл в uploads/ и ставит задание queued + F=$(uuidgen | tr A-Z a-z); JOB=$(uuidgen | tr A-Z a-z); O=$(uuidgen | tr A-Z a-z); N=$(basename "$3") + dmc cp -q "/fixtures/$3" "local/arm-ka/uploads/$F/$JOB/$N" >/dev/null || return 1 + dpsql < — статус задания $JOB раз в секунду, до финального статуса + for _ in $(seq "$1"); do + s=$(dpsql -At -c "select status || ' ' || coalesce(error_code, '') || ' ' || coalesce(metrics->>'republish_count', '') from dkb.recognition_job where id = '$JOB'") + [ "$s" != "$p" ] && echo "$(date -u +%T) $s" && p=$s + case $s in error*|done*|needs_check*|timeout*|dlq*) p=; return;; esac + sleep 1 + done; p= +} +``` + +Ключ исходника — `uploads/{file_id}/{job_id}/{имя}` (`../context/03-architecture.md`, «Схема ключей S3»). Сообщения на +шине можно смотреть параллельно: `docker run --rm --network arm-ka_default natsio/nats-box:0.17.0 nats sub -s +nats://nats:4222 'recognition.>'` (печатает `Nats-Msg-Id` и тело каждого запроса, ack и результата). + +**1. `tabular` → `NOT_IMPLEMENTED`.** Заглушка парсера Среза 0 отвечает ошибкой на любой файл: + +```bash +new_job tabular XLSX "osv/schastl/2025-Q3/Оборотносальдовая ведомость по счету 60 за 3 квартал 2025 г..xlsx" && poll 30 +# JOB=e585f47b-1b38-44a6-84c5-9432c7e6d194 +# 02:12:15 queued +# 02:12:20 error NOT_IMPLEMENTED +dpsql -c "select status, error_code, error_message, published_at::time(3), started_at::time(3), finished_at::time(3) from dkb.recognition_job where id = '$JOB'" +# error | NOT_IMPLEMENTED | разбор XLS/XLSX/XLSB реализуется в Срезе 2 | 02:12:18.98 | 02:12:19.021 | 02:12:19.024 +``` + +`published → processing → error` проходит за десятки миллисекунд (в прогонах 14–51 мс), опросом его не поймать; последовательность видна по +`published_at < started_at < finished_at` (`started_at` пишет ack), в логе `arm-core` («Запрос распознавания +опубликован» → «Сервис подтвердил получение запроса (ack): задание в processing» → «Результат распознавания принят … +error_code=NOT_IMPLEMENTED») и на шине: `recognition.tabular.request` (`Nats-Msg-Id: :1`) → +`recognition.result` `ack::1` (`status: processing`) → `result::1` (`status: error`). Публикатор +опрашивает outbox раз в 5 с, поэтому от вставки до публикации проходит до 5 с. + +**2. `image` → эмулятор ИС → `INVALID_DOCUMENT`, сырой ответ в `raw/`:** + +```bash +new_job image PDF ocr/aep_5.PDF && poll 30 +# JOB=46b0c90b-5a1b-4ac0-9e8a-0cdad80d857e +# 02:12:27 queued +# 02:12:29 processing +# 02:12:34 error INVALID_DOCUMENT +dmc ls local/arm-ka/raw/ +# [2026-10-04 02:12:34 UTC] 588B STANDARD 46b0c90b-5a1b-4ac0-9e8a-0cdad80d857e.json +dmc cat "local/arm-ka/raw/$JOB.json" # {"file_uid": "", "error": {"code": "INVALID_DOCUMENT", "message": "Не удалось определить тип документа."}, "data_ocr": {…}} +``` + +Код `INVALID_DOCUMENT` здесь — **настоящий ответ ИС из фикстуры**: обе пары в `fixtures/ocr` (`aep_5.json`, +`aep_8_form4.json`) — ответы-ошибки ИС Сканирование («Не удалось определить тип документа.», пустой `table_structure`). +Эмулятор нашёл пару (в логе `scan-emulator`: `_aep_5.PDF -> …/Output//.json`; при +отсутствии пары сообщение было бы «…: нет эталонного ответа в фикстурах»). Поэтому успешный путь `image` +(`done`/`needs_check`, файл `results/…`) сквозной проверкой на текущих фикстурах не проходит — нужен успешный ответ ИС +в `fixtures/ocr`. Имя файла — `aep_5.PDF` (расширение в верхнем регистре; на macOS `aep_5.pdf` тоже находится, на Linux — нет). +`processing` виден ~5 с: задержка эмулятора 2 с плюс опрос горячей папки шлюзом. + +**3. Сервис недоступен → три повторные публикации → `dlq` / `NO_ACK`:** + +```bash +docker compose stop tabular-parser # имя сервиса указано явно — профиль не нужен +new_job tabular XLSX "osv/schastl/2025-Q3/Оборотносальдовая ведомость по счету 60 за 3 квартал 2025 г..xlsx" && poll 180 +# 02:12:43 queued +# 02:12:44 published +# 02:13:14 published 1 ← republish_count: повтор через ACK_TIMEOUT_SECONDS = 30 с, тот же Nats-Msg-Id :1 +# 02:13:45 published 2 +# 02:14:14 published 3 +# 02:14:44 dlq NO_ACK 3 ← ещё через 30 с после третьего повтора (OUTBOX_MAX_REPUBLISH = 3) +dpsql -c "select status, error_code, error_message, metrics, request_msg_id from dkb.recognition_job where id = '$JOB'" +# dlq | NO_ACK | Сервис распознавания не подтвердил получение запроса (ack) после 3 повторных публикаций | {"republish_count": 3} | :1 +docker compose --profile services up -d --wait tabular-parser # вернуть сервис; rc=0, Healthy +``` + +От первой публикации до `dlq` — ~120 с (4 × 30 с). `published_at` при каждом повторе перезаписывается (время последней +публикации), `attempt` остаётся 1, `started_at` и `service_name` пусты. Задание в `dlq` после возврата сервиса само не +перезапускается (replay — вне Среза 0). + +Что сквозная проверка **не** покрывает (Срез 0): успешные результаты (`done`/`needs_check`, регистрация файла +`results/…` в `s3_object`) — заглушка парсера и фикстуры ИС дают только ошибки; `stored_file.status` остаётся +`UPLOADED`, `current_recognition_job_id` не заполняется (маппинг результата — `NoopMapper`, Срез 2); у ошибок +`scan-gateway` `recognition_job.metrics` пустой (`tabular-parser` присылает `duration_ms`). Тестовые строки и объекты +остаются в БД и MinIO; полная очистка — `docker compose --profile services down -v`. + ## Переменные `.env` Файл читают `docker compose` (из этого каталога) и, вторым блоком, сервисы при запуске из venv. Значения по умолчанию —