docs: прямая запись в Input, SCAN_TMP_DIR только у эмулятора, таймаут 1200 с (#26)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7dc0317b99
commit
3413328f02
12
CLAUDE.md
12
CLAUDE.md
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
## Что это
|
## Что это
|
||||||
Адаптер внешней ИС Сканирование (Content AI) для PDF/PNG/JPG: положить файл во «Входящие» как `<job_id>.<ext>`
|
Адаптер внешней ИС Сканирование (Content AI) для PDF/PNG/JPG: положить файл во «Входящие» как `<job_id>.<ext>`
|
||||||
(расширение `original_name` в нижнем регистре; `.part` пишется в `SCAN_TMP_DIR`, по умолчанию `Tmp`, на той же ФС/шаре и
|
(расширение `original_name` в нижнем регистре; пишется **сразу** открытием `wb`, существующий файл перезаписывается — как в
|
||||||
публикуется **без замены**), забрать **все** `Исходящие/<job_id>/*.json` (ИС кладёт по одному json на каждый
|
старом АРМ), забрать **все** `Исходящие/<job_id>/*.json` (ИС кладёт по одному json на каждый
|
||||||
распознанный документ), склеить и сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), очистить
|
распознанный документ), склеить и сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), очистить
|
||||||
горячую папку (только при `SCAN_CLEANUP=true`), нормализовать в `../docs/contracts/recognition-result.schema.json`
|
горячую папку (только при `SCAN_CLEANUP=true`), нормализовать в `../docs/contracts/recognition-result.schema.json`
|
||||||
(`statementDocument` v1.2: по одному документу на таблицу `table_structure[i]`), опубликовать `files.tabular_parsed`
|
(`statementDocument` v1.2: по одному документу на таблицу `table_structure[i]`), опубликовать `files.tabular_parsed`
|
||||||
(`done` / `needs_check` / `error`). Таймаут → ошибка `TIMEOUT` по `min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS=300)`,
|
(`done` / `needs_check` / `error`). Таймаут → ошибка `TIMEOUT` по `min(reply_deadline, старт + SCAN_TIMEOUT_SECONDS=1200)`,
|
||||||
старт — запись файла во «Входящие». **Нет доступа к БД.**
|
старт — запись файла во «Входящие». **Нет доступа к БД.**
|
||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
@ -28,9 +28,9 @@
|
|||||||
и golden.
|
и golden.
|
||||||
- Эталоны нормализатора лежат в **fixtures** (`../fixtures/ocr/expected_normalize/`): `make golden-update`, diff смотреть
|
- Эталоны нормализатора лежат в **fixtures** (`../fixtures/ocr/expected_normalize/`): `make golden-update`, diff смотреть
|
||||||
в fixtures и объяснять; эталоны меняются отдельным PR fixtures.
|
в fixtures и объяснять; эталоны меняются отдельным PR fixtures.
|
||||||
- Горячие папки (раскладка FileWorker): имя `<job_id>.<ext>`, `.part` только в `Tmp`, запись без замены (`os.link` /
|
- Горячие папки (раскладка FileWorker): имя `<job_id>.<ext>`, запись сразу в `Input` открытием `wb` с перезаписью, без
|
||||||
`smbclient.rename`; существующее назначение = «доставлено»). `.part`, ставший целью публикации, снимается самой записью
|
временного файла и `Tmp` — как в старом АРМ (change `hotfolder-direct-write`, ASSUMPTION(OQ-79)); `Tmp` — только у
|
||||||
независимо от флага (№26); любое другое удаление (ответ, входящий файл, `.done`, осиротевшие `.part`) — только при
|
эмулятора. Любое удаление (ответ, входящий файл, `.done`, недописанный при ошибке записи входящий файл) — только при
|
||||||
`SCAN_CLEANUP=true` и только после сохранения `raw/` (не при `TIMEOUT`, `FILE_READ_ERROR`, ошибке записи во входящий каталог или `raw/`). Ошибка
|
`SCAN_CLEANUP=true` и только после сохранения `raw/` (не при `TIMEOUT`, `FILE_READ_ERROR`, ошибке записи во входящий каталог или `raw/`). Ошибка
|
||||||
удаления — одна строка лога, конверт не меняется. Нет расширения у `original_name` → `RECOGNITION_FAILED`.
|
удаления — одна строка лога, конверт не меняется. Нет расширения у `original_name` → `RECOGNITION_FAILED`.
|
||||||
- Набор ответа готов по settle: есть `*.json`, а имена и размеры не менялись `SCAN_SETTLE_SECONDS` (по умолчанию 2;
|
- Набор ответа готов по settle: есть `*.json`, а имена и размеры не менялись `SCAN_SETTLE_SECONDS` (по умолчанию 2;
|
||||||
|
|||||||
56
README.md
56
README.md
@ -28,7 +28,7 @@ files.img_uploaded (NATS, queue group scan-gateway)
|
|||||||
2. ack: files.tabular_parsed, status=processing (до чтения S3 и до ожидания слота)
|
2. ack: files.tabular_parsed, status=processing (до чтения S3 и до ожидания слота)
|
||||||
3. слот (SCAN_MAX_INFLIGHT одновременных заданий)
|
3. слот (SCAN_MAX_INFLIGHT одновременных заданий)
|
||||||
4. файл из S3 (s3.bucket/s3.key)
|
4. файл из S3 (s3.bucket/s3.key)
|
||||||
5. горячая папка: <Input>/<job_id>.<ext> (через <Tmp>/.<job_id>.<ext>.part, без замены)
|
5. горячая папка: <Input>/<job_id>.<ext> (сразу, wb, существующий перезаписывается)
|
||||||
6. опрос <Output>/<job_id>/ каждые SCAN_POLL_SECONDS до дедлайна; набор *.json готов, когда
|
6. опрос <Output>/<job_id>/ каждые SCAN_POLL_SECONDS до дедлайна; набор *.json готов, когда
|
||||||
не менялся SCAN_SETTLE_SECONDS
|
не менялся SCAN_SETTLE_SECONDS
|
||||||
7. склейка всех *.json каталога ──► S3 raw/{job_id}.json (до нормализации)
|
7. склейка всех *.json каталога ──► S3 raw/{job_id}.json (до нормализации)
|
||||||
@ -60,19 +60,15 @@ ack (с `attempt` повторного запроса); файл в ИС вто
|
|||||||
`original_name` запроса (разделители пути `/` и `\` учитываются), в нижнем регистре, 1–16 букв и цифр ASCII
|
`original_name` запроса (разделители пути `/` и `\` учитываются), в нижнем регистре, 1–16 букв и цифр ASCII
|
||||||
(`aep_5.PDF` → `<job_id>.pdf`): ИС различает форматы по расширению, а имя файла от пользователя в горячую папку не
|
(`aep_5.PDF` → `<job_id>.pdf`): ИС различает форматы по расширению, а имя файла от пользователя в горячую папку не
|
||||||
попадает. Нет допустимого расширения — `RECOGNITION_FAILED` без записи в горячую папку.
|
попадает. Нет допустимого расширения — `RECOGNITION_FAILED` без записи в горячую папку.
|
||||||
- Запись атомарна и идёт **без замены**: сначала временный файл `.<job_id>.<ext>.part` в служебном каталоге
|
- Запись — **сразу в `Input`**, как в старом АРМ (решение владельца 09.10.2026, change `hotfolder-direct-write`):
|
||||||
`<SCAN_TMP_DIR>` (по умолчанию `Tmp`), затем он публикуется в `Input` под настоящим именем. `Tmp` лежит рядом с `Input`
|
файл открывается на запись с усечением (`wb`) и пишется целиком; временного файла, служебного каталога и
|
||||||
и `Output`, на той же ФС/шаре: публикация атомарна, а ИС не может забрать недописанный файл (временных файлов в
|
переименования нет. В режиме `smb` — `smbclient.open_file(<Input>\<имя>, "wb")`: файл открыт монопольно, пока идёт
|
||||||
`Input` нет — решение владельца 06.10.2026, у FileWorker `.tmp` лежит рядом с назначением). В режиме `local` публикация —
|
запись, и ИС его не прочитает (ASSUMPTION(OQ-79) — так работал старый АРМ). Если `Input/<job_id>.<ext>` уже существует
|
||||||
жёсткая ссылка `.part` → `Input/<имя>` и удаление `.part`; на файловой системе без жёстких ссылок
|
(повтор задания ядром после потери ack в другом процессе), он **перезаписывается** содержимым объекта S3, задание
|
||||||
(`EPERM`/`EXDEV`/`ENOTSUP`/`EOPNOTSUPP` от самой ссылки) — `os.replace`, если назначения ещё нет (решение №17).
|
продолжается опросом `Output/<job_id>/`. Ошибка посреди записи → `RECOGNITION_FAILED`; недописанный файл снимается
|
||||||
`.part` создаётся заново: остаток прежней записи удаляется (ошибка удаления подавляется), файл открывается с `O_EXCL`;
|
только при `SCAN_CLEANUP=true` (иначе остаётся в `Input`). Каталог `Input` создаётся при необходимости (без
|
||||||
поверх оставшегося `.part` пишем только если это не жёсткая ссылка (`st_nlink == 1`) — иначе исключение (решение №32),
|
родителей; отказ в правах при существующем каталоге — не ошибка), корень горячей папки сам не создаётся: нет корня —
|
||||||
чтобы не испортить уже опубликованный файл. В режиме `smb` — `smbclient.rename` (поднимает `FileExistsError`, если
|
`RECOGNITION_FAILED`.
|
||||||
назначение есть). Если `Input/<job_id>.<ext>` уже существует (повторная публикация задания после потери ack в другом
|
|
||||||
процессе), файл считается доставленным: ничего не пишется, назначение не перезаписывается, `.part` не трогается (при
|
|
||||||
`SCAN_CLEANUP=true` существующий `.part` снимается), задание продолжается опросом `Output/<job_id>/` (решение №35). Каталоги `Input` и `Tmp`
|
|
||||||
создаются при необходимости (без родителей), корень горячей папки сам не создаётся: нет корня — `RECOGNITION_FAILED`.
|
|
||||||
- Ответ ИС — каталог `<SCAN_OUTPUT_DIR>/<job_id>/`: ИС кладёт туда **все** распознанные документы задания одновременно,
|
- Ответ ИС — каталог `<SCAN_OUTPUT_DIR>/<job_id>/`: ИС кладёт туда **все** распознанные документы задания одновременно,
|
||||||
по одному `*.json` на файл (`Output/<job_id>/*.json`). Шлюз читает **все** `*.json` каталога и склеивает их в один
|
по одному `*.json` на файл (`Output/<job_id>/*.json`). Шлюз читает **все** `*.json` каталога и склеивает их в один
|
||||||
ответ; прочие файлы каталога игнорируются. `job_id` проверяется на безопасность пути. В одном json может быть
|
ответ; прочие файлы каталога игнорируются. `job_id` проверяется на безопасность пути. В одном json может быть
|
||||||
@ -96,24 +92,19 @@ ack (с `attempt` повторного запроса); файл в ИС вто
|
|||||||
- **Когда.** Воркер вызывает `HotFolder.cleanup` сразу после того, как набор ответа прочитан, склеен и сохранён в
|
- **Когда.** Воркер вызывает `HotFolder.cleanup` сразу после того, как набор ответа прочитан, склеен и сохранён в
|
||||||
`raw/{job_id}.json` — при любом исходе разбора и нормализации (в том числе когда все файлы набора — ошибки ИС).
|
`raw/{job_id}.json` — при любом исходе разбора и нормализации (в том числе когда все файлы набора — ошибки ИС).
|
||||||
- **Что удаляется** (всё, что есть): **всё содержимое** каталога `Output/<job_id>/` (не только `*.json`) и сам каталог,
|
- **Что удаляется** (всё, что есть): **всё содержимое** каталога `Output/<job_id>/` (не только `*.json`) и сам каталог,
|
||||||
`Input/<job_id>.<ext>`, `Input/<job_id>.<ext>.done` (след эмулятора), `Tmp/.<job_id>.<ext>.part`. Отсутствующий файл
|
`Input/<job_id>.<ext>`, `Input/<job_id>.<ext>.done` (след эмулятора). `Tmp` шлюз не трогает. Отсутствующий файл
|
||||||
или каталог не ошибка. В режиме `local` каталог ответа удаляется рекурсивно; в режиме `smb` удаляются файлы каталога, а
|
или каталог не ошибка. В режиме `local` каталог ответа удаляется рекурсивно; в режиме `smb` удаляются файлы каталога, а
|
||||||
подкаталог внутри `Output/<job_id>/` не даст снять каталог (`rmdir` → `STATUS_DIRECTORY_NOT_EMPTY`): ошибка очистки
|
подкаталог внутри `Output/<job_id>/` не даст снять каталог (`rmdir` → `STATUS_DIRECTORY_NOT_EMPTY`): ошибка очистки
|
||||||
поднимается и логируется, остальные пункты (входящий файл, `.done`, `.part`) в этом задании не удаляются.
|
поднимается и логируется, остальные пункты (входящий файл, `.done`) в этом задании не удаляются.
|
||||||
- **Когда не вызывается:** `TIMEOUT` (ИС может ещё писать ответ), `FILE_READ_ERROR`, `RECOGNITION_FAILED` записи во
|
- **Когда не вызывается:** `TIMEOUT` (ИС может ещё писать ответ), `FILE_READ_ERROR`, `RECOGNITION_FAILED` записи во
|
||||||
входящий каталог, ошибка записи `raw/` в S3. Входящий файл и каталог ответа тогда остаются.
|
входящий каталог, ошибка записи `raw/` в S3. Входящий файл и каталог ответа тогда остаются.
|
||||||
- **Ошибки удаления** (например, `PermissionError` на шаре): одна строка в логе (`warning`) на задание; конверт,
|
- **Ошибки удаления** (например, `PermissionError` на шаре): одна строка в логе (`warning`) на задание; конверт,
|
||||||
`metrics.warnings` и `/healthcheck` не меняются — исход задания определён разбором ответа.
|
`metrics.warnings` и `/healthcheck` не меняются — исход задания определён разбором ответа.
|
||||||
- **`.part` и флаг (решение №26).** `.part`, ставший целью успешной публикации, снимается **самой записью**, независимо
|
- **Флаг управляет всем удалением:** при `SCAN_CLEANUP=false` сервис не удаляет ничего — ни каталог ответа, ни входящий
|
||||||
от `SCAN_CLEANUP` (в `local` — удаление после жёсткой ссылки, ошибка удаления подавляется; в `smb` `.part`
|
файл и `.done`, ни недописанный при ошибке записи входящий файл — и не пишет об этом в лог (решение №27). Остатки
|
||||||
переименовывается во входящий и отдельного удаления не требует): иначе при выключенной очистке на каждое задание
|
`Tmp/.*.part` от версий шлюза до change `hotfolder-direct-write` сервис не трогает — убрать вручную.
|
||||||
оставалась бы лишняя ссылка, а ошибка удаления на шаре с неподтверждёнными правами превращала бы доставленный файл в
|
- Права на SMB: для записи учётной записи шары нужно создание и перезапись файлов в `Input`, для чтения ответа —
|
||||||
`RECOGNITION_FAILED`. **Флаг управляет остатками:** осиротевшие `.part` (оборванная запись, `.part` при уже
|
листинг и чтение `Output`; для очистки — удаление в `Input` и `Output` (включая каталог `<job_id>`). Пока права на удаление не
|
||||||
существующем назначении), каталог ответа, входящий файл и `.done`. При `SCAN_CLEANUP=false` сервис не удаляет ничего из
|
|
||||||
этого и не пишет в лог об осиротевших `.part` (решение №27): они накапливаются в `Tmp` и убираются вручную или
|
|
||||||
включением флага.
|
|
||||||
- Права на SMB: для очистки учётной записи шары нужны права на удаление в `Input`, `Output` (включая каталог
|
|
||||||
`<job_id>`) и `Tmp`; для записи — создание файлов в `Tmp` и `Input` и переименование. Пока права на удаление не
|
|
||||||
подтверждены, оставляйте `SCAN_CLEANUP=false`.
|
подтверждены, оставляйте `SCAN_CLEANUP=false`.
|
||||||
|
|
||||||
### Эмулятор ИС в режиме `local`
|
### Эмулятор ИС в режиме `local`
|
||||||
@ -315,12 +306,11 @@ pydantic-валидацией; битый файл (код не из 4–5 ци
|
|||||||
| `SCAN_HOTFOLDER_PATH` | пусто | корень горячей папки в режиме `local` (обязателен, каталог должен существовать) |
|
| `SCAN_HOTFOLDER_PATH` | пусто | корень горячей папки в режиме `local` (обязателен, каталог должен существовать) |
|
||||||
| `SCAN_INPUT_DIR` | `Input` | «Входящие» относительно корня |
|
| `SCAN_INPUT_DIR` | `Input` | «Входящие» относительно корня |
|
||||||
| `SCAN_OUTPUT_DIR` | `Output` | «Исходящие» относительно корня |
|
| `SCAN_OUTPUT_DIR` | `Output` | «Исходящие» относительно корня |
|
||||||
| `SCAN_TMP_DIR` | `Tmp` | служебный каталог относительно корня (на той же ФС/шаре, что `Input`/`Output`): временный `.<job_id>.<ext>.part` записи входящего файла |
|
|
||||||
| `SCAN_POLL_SECONDS` | `5` | период опроса «Исходящих», секунды |
|
| `SCAN_POLL_SECONDS` | `5` | период опроса «Исходящих», секунды |
|
||||||
| `SCAN_SETTLE_SECONDS` | `2` | набор `*.json` в `Output/<job_id>/` готов, когда имена и размеры не менялись столько секунд, считая от первого опроса, увидевшего окончательный набор (>= 0; `0` — сразу) |
|
| `SCAN_SETTLE_SECONDS` | `2` | набор `*.json` в `Output/<job_id>/` готов, когда имена и размеры не менялись столько секунд, считая от первого опроса, увидевшего окончательный набор (>= 0; `0` — сразу) |
|
||||||
| `SCAN_TIMEOUT_SECONDS` | `300` | потолок ожидания ответа ИС, секунды (дополнительно к `reply_deadline`) |
|
| `SCAN_TIMEOUT_SECONDS` | `1200` | потолок ожидания ответа ИС, секунды (дополнительно к `reply_deadline`; 20 мин — в паре со сроком ответа OCR ядра) |
|
||||||
| `SCAN_MAX_INFLIGHT` | `4` | одновременно обрабатываемых заданий |
|
| `SCAN_MAX_INFLIGHT` | `4` | одновременно обрабатываемых заданий |
|
||||||
| `SCAN_CLEANUP` | `false` | очистка горячей папки после чтения набора ответа: удаляет всё содержимое `Output/<job_id>/` и каталог, `Input/<job_id>.<ext>` (и `.done`), `Tmp/.<job_id>.<ext>.part`; `false` — ничего не удаляется (кроме `.part`, снятого самой записью); см. «Очистка горячей папки» |
|
| `SCAN_CLEANUP` | `false` | очистка горячей папки после чтения набора ответа: удаляет всё содержимое `Output/<job_id>/` и каталог, `Input/<job_id>.<ext>` (и `.done`); `false` — ничего не удаляется; см. «Очистка горячей папки» |
|
||||||
| `SCAN_RULES_DIR` | `rules` | каталог справочников нормализатора (`statement_lines.yaml`, `ocr_lookalikes.yaml`, `statement_labels.yaml`), путь от текущего каталога (`api/`); битый справочник — сервис не стартует |
|
| `SCAN_RULES_DIR` | `rules` | каталог справочников нормализатора (`statement_lines.yaml`, `ocr_lookalikes.yaml`, `statement_labels.yaml`), путь от текущего каталога (`api/`); битый справочник — сервис не стартует |
|
||||||
| `SERVICE_NAME` | `scan-gateway` | `service.name` в конверте |
|
| `SERVICE_NAME` | `scan-gateway` | `service.name` в конверте |
|
||||||
| `VERSION` | `1.0.0` | версия сервиса (`/healthcheck`, `service.version`) |
|
| `VERSION` | `1.0.0` | версия сервиса (`/healthcheck`, `service.version`) |
|
||||||
@ -335,7 +325,8 @@ MinIO и NATS); те же значения подхватываются из `EN
|
|||||||
| Переменная | По умолчанию | Назначение |
|
| Переменная | По умолчанию | Назначение |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `SCAN_HOTFOLDER_PATH` | — (обязательна) | корень горячей папки; должен существовать, иначе код выхода 2 |
|
| `SCAN_HOTFOLDER_PATH` | — (обязательна) | корень горячей папки; должен существовать, иначе код выхода 2 |
|
||||||
| `SCAN_INPUT_DIR`, `SCAN_OUTPUT_DIR`, `SCAN_TMP_DIR` | `Input`, `Output`, `Tmp` | подкаталоги корня (как у шлюза); в `Tmp` эмулятор собирает набор ответа перед rename в `Output/<uuid>/` |
|
| `SCAN_INPUT_DIR`, `SCAN_OUTPUT_DIR` | `Input`, `Output` | подкаталоги корня (как у шлюза) |
|
||||||
|
| `SCAN_TMP_DIR` | `Tmp` | служебный каталог эмулятора: в нём собирается набор ответа перед rename в `Output/<uuid>/` (шлюз эту переменную не читает) |
|
||||||
| `SCAN_FIXTURES_DIR` | `../../fixtures/ocr` | каталог эталонных пар: исходник (PDF и др.) и ответы ИС (`aep_N.json`, `aep_N_<суффикс>.json`); пара ищется по sha256 содержимого входящего файла; путь от `api/` |
|
| `SCAN_FIXTURES_DIR` | `../../fixtures/ocr` | каталог эталонных пар: исходник (PDF и др.) и ответы ИС (`aep_N.json`, `aep_N_<суффикс>.json`); пара ищется по sha256 содержимого входящего файла; путь от `api/` |
|
||||||
| `SCAN_EMULATOR_DEFAULT` | пусто | основа имени набора (например, `aep_6`), который отдаётся при промахе по хешу; пусто или набора нет — ответ-ошибка `INVALID_DOCUMENT` |
|
| `SCAN_EMULATOR_DEFAULT` | пусто | основа имени набора (например, `aep_6`), который отдаётся при промахе по хешу; пусто или набора нет — ответ-ошибка `INVALID_DOCUMENT` |
|
||||||
| `SCAN_EMULATOR_DELAY` | `2` | задержка перед выдачей ответа, секунды (>= 0) |
|
| `SCAN_EMULATOR_DELAY` | `2` | задержка перед выдачей ответа, секунды (>= 0) |
|
||||||
@ -508,8 +499,9 @@ make golden-update # пересобрать эталоны нормализ
|
|||||||
- Недописанный набор ответа ИС: защита — settle (`SCAN_SETTLE_SECONDS`), а не разбор содержимого. Если ИС дописывает
|
- Недописанный набор ответа ИС: защита — settle (`SCAN_SETTLE_SECONDS`), а не разбор содержимого. Если ИС дописывает
|
||||||
файл дольше settle, будет принят неполный набор; файл, не разобравшийся как JSON, после склейки не перечитывается:
|
файл дольше settle, будет принят неполный набор; файл, не разобравшийся как JSON, после склейки не перечитывается:
|
||||||
`response = null`, `parse_error` в `raw/`, ошибка этого файла (`INVALID_FORMAT`) — в `metrics.warnings`.
|
`response = null`, `parse_error` в `raw/`, ошибка этого файла (`INVALID_FORMAT`) — в `metrics.warnings`.
|
||||||
- Запись без замены на файловой системе без жёстких ссылок (`EPERM`/`EXDEV`/`ENOTSUP`): между проверкой назначения и
|
- Запись сразу в `Input` (change `hotfolder-direct-write`): защита от чтения недописанного файла — только монопольное
|
||||||
`os.replace` есть крошечное окно гонки (повтор задания с тем же `job_id` редок, решение №17).
|
открытие `smbclient` на время записи (ASSUMPTION(OQ-79), как в старом АРМ). Если ИС всё же возьмёт файл во время
|
||||||
|
записи, распознавание даст ошибку; откат — вернуть запись через временный файл.
|
||||||
- Эмулятор: повреждённая фикстура (не JSON, не UTF-8 или корень не объект) не останавливает его — на её месте в наборе
|
- Эмулятор: повреждённая фикстура (не JSON, не UTF-8 или корень не объект) не останавливает его — на её месте в наборе
|
||||||
он кладёт ответ `INVALID_DOCUMENT` с сообщением о повреждённой фикстуре и пишет ошибку в лог. Индекс хешей
|
он кладёт ответ `INVALID_DOCUMENT` с сообщением о повреждённой фикстуре и пишет ошибку в лог. Индекс хешей
|
||||||
пересчитывается при изменении списка файлов каталога фикстур: изменённый, но не переименованный PDF не
|
пересчитывается при изменении списка файлов каталога фикстур: изменённый, но не переименованный PDF не
|
||||||
|
|||||||
@ -43,19 +43,17 @@ SCAN_HOTFOLDER_PATH=./.hotfolder
|
|||||||
# Подкаталоги корня: «Входящие» (сюда кладётся UUID_имя) и «Исходящие» (оттуда забираются все *.json из <UUID>/)
|
# Подкаталоги корня: «Входящие» (сюда кладётся UUID_имя) и «Исходящие» (оттуда забираются все *.json из <UUID>/)
|
||||||
SCAN_INPUT_DIR=Input
|
SCAN_INPUT_DIR=Input
|
||||||
SCAN_OUTPUT_DIR=Output
|
SCAN_OUTPUT_DIR=Output
|
||||||
# Служебный каталог корня: временный .part пишется сюда и переименовывается во «Входящие» (та же ФС/шара — rename
|
|
||||||
# атомарен, во «Входящих» не бывает недописанных файлов); создаётся при первой записи
|
|
||||||
SCAN_TMP_DIR=Tmp
|
|
||||||
# Период опроса «Исходящих», секунды
|
# Период опроса «Исходящих», секунды
|
||||||
SCAN_POLL_SECONDS=5
|
SCAN_POLL_SECONDS=5
|
||||||
# Набор *.json в <UUID>/ считается готовым, когда имена и размеры файлов не менялись столько секунд (>= 0)
|
# Набор *.json в <UUID>/ считается готовым, когда имена и размеры файлов не менялись столько секунд (>= 0)
|
||||||
SCAN_SETTLE_SECONDS=2
|
SCAN_SETTLE_SECONDS=2
|
||||||
# Потолок ожидания ответа ИС, секунды (дополнительно ограничен reply_deadline запроса); повторной отправки нет
|
# Потолок ожидания ответа ИС, секунды (дополнительно ограничен reply_deadline запроса; 20 мин — в паре со сроком
|
||||||
SCAN_TIMEOUT_SECONDS=300
|
# ответа OCR ядра); повторной отправки нет
|
||||||
|
SCAN_TIMEOUT_SECONDS=1200
|
||||||
# Сколько заданий обрабатывается одновременно
|
# Сколько заданий обрабатывается одновременно
|
||||||
SCAN_MAX_INFLIGHT=4
|
SCAN_MAX_INFLIGHT=4
|
||||||
# Очистка горячей папки после прочтения ответа (D-2): true — удалить всё содержимое Output/<job_id>/, входящий файл
|
# Очистка горячей папки после прочтения ответа (D-2): true — удалить всё содержимое Output/<job_id>/, входящий файл
|
||||||
# (и его .done), осиротевший .part в Tmp; false (по умолчанию) — ничего не удалять
|
# (и его .done); false (по умолчанию) — ничего не удалять
|
||||||
SCAN_CLEANUP=false
|
SCAN_CLEANUP=false
|
||||||
# Каталог справочников нормализатора OCR (statement_lines.yaml, ocr_lookalikes.yaml, statement_labels.yaml);
|
# Каталог справочников нормализатора OCR (statement_lines.yaml, ocr_lookalikes.yaml, statement_labels.yaml);
|
||||||
# относительный путь — от каталога запуска (api/); битый справочник — сервис не стартует
|
# относительный путь — от каталога запуска (api/); битый справочник — сервис не стартует
|
||||||
@ -70,7 +68,9 @@ SCAN_RULES_DIR=rules
|
|||||||
# --- Эмулятор ИС Сканирование (python -m src.scan_emulator, make emulator; только dev) ---
|
# --- Эмулятор ИС Сканирование (python -m src.scan_emulator, make emulator; только dev) ---
|
||||||
# Эти параметры читает сам эмулятор, сервис шлюза их не использует. Корень горячей папки и подкаталоги
|
# Эти параметры читает сам эмулятор, сервис шлюза их не использует. Корень горячей папки и подкаталоги
|
||||||
# эмулятор берёт из SCAN_HOTFOLDER_PATH / SCAN_INPUT_DIR / SCAN_OUTPUT_DIR выше; набор ответа на задание он собирает
|
# эмулятор берёт из SCAN_HOTFOLDER_PATH / SCAN_INPUT_DIR / SCAN_OUTPUT_DIR выше; набор ответа на задание он собирает
|
||||||
# во временном каталоге SCAN_TMP_DIR (выше) и переименовывает в <Output>/<uuid>/ целиком.
|
# во временном каталоге SCAN_TMP_DIR и переименовывает в <Output>/<uuid>/ целиком (шлюз SCAN_TMP_DIR не читает: файл
|
||||||
|
# во «Входящие» он пишет сразу, как в старом АРМ).
|
||||||
|
SCAN_TMP_DIR=Tmp
|
||||||
# Каталог с фикстурами ответов ИС (aep_N.json); путь от api/ (репозиторий fixtures лежит рядом с workspace)
|
# Каталог с фикстурами ответов ИС (aep_N.json); путь от api/ (репозиторий fixtures лежит рядом с workspace)
|
||||||
SCAN_FIXTURES_DIR=../../fixtures/ocr
|
SCAN_FIXTURES_DIR=../../fixtures/ocr
|
||||||
# Задержка перед выдачей ответа, секунды (имитация работы ИС)
|
# Задержка перед выдачей ответа, секунды (имитация работы ИС)
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
## 1. Тесты (scan-gateway, `api/tests/`)
|
## 1. Тесты (scan-gateway, `api/tests/`)
|
||||||
|
|
||||||
- [ ] 1.1 `unit/test_smb_hotfolder.py`: `put` пишет `open_file(<Input>\<имя>, "wb")` без `Tmp`/`rename`; существующий
|
- [x] 1.1 `unit/test_smb_hotfolder.py`: `put` пишет `open_file(<Input>\<имя>, "wb")` без `Tmp`/`rename`; существующий
|
||||||
файл перезаписывается; ошибка записи — снятие файла только при `cleanup=True`; `cleanup` не трогает `.part`
|
файл перезаписывается; ошибка записи — снятие файла только при `cleanup=True`; `cleanup` не трогает `.part`
|
||||||
- [ ] 1.2 `unit/test_local_hotfolder.py`: то же для `LocalHotFolder`; `Tmp` не создаётся `put`
|
- [x] 1.2 `unit/test_local_hotfolder.py`: то же для `LocalHotFolder`; `Tmp` не создаётся `put`
|
||||||
- [ ] 1.3 `unit/test_config.py`, `unit/test_hotfolder_factory.py`: нет `SCAN_TMP_DIR` у шлюза, `SCAN_TIMEOUT_SECONDS`
|
- [x] 1.3 `unit/test_config.py`, `unit/test_hotfolder_factory.py`: нет `SCAN_TMP_DIR` у шлюза, `SCAN_TIMEOUT_SECONDS`
|
||||||
по умолчанию 1200; интеграционные `test_roundtrip.py` — без ожиданий про `Tmp/.part`
|
по умолчанию 1200; интеграционные `test_roundtrip.py` — без ожиданий про `Tmp/.part`
|
||||||
|
|
||||||
## 2. Реализация (scan-gateway, `api/src/`)
|
## 2. Реализация (scan-gateway, `api/src/`)
|
||||||
|
|
||||||
- [ ] 2.1 `gateway/smb.py`: `put` — прямая запись (D-1, D-2, D-5), убрать `tmp_dir`, `.part` из `cleanup`
|
- [x] 2.1 `gateway/smb.py`: `put` — прямая запись (D-1, D-2, D-5), убрать `tmp_dir`, `.part` из `cleanup`
|
||||||
- [ ] 2.2 `gateway/hotfolder.py`: `LocalHotFolder.put` — прямая запись; `tmp_dir` только для эмулятора (D-3)
|
- [x] 2.2 `gateway/hotfolder.py`: `LocalHotFolder.put` — прямая запись; `tmp_dir` только для эмулятора (D-3)
|
||||||
- [ ] 2.3 `core/config.py`, `gateway/__init__.py`: убрать `SCAN_TMP_DIR` у шлюза; `SCAN_TIMEOUT_SECONDS = 1200`
|
- [x] 2.3 `core/config.py`, `gateway/__init__.py`: убрать `SCAN_TMP_DIR` у шлюза; `SCAN_TIMEOUT_SECONDS = 1200`
|
||||||
|
|
||||||
## 3. Документация
|
## 3. Документация
|
||||||
|
|
||||||
- [ ] 3.1 `README.md`, `CLAUDE.md`, `api/.env.example` — прямая запись, `SCAN_TMP_DIR` только у эмулятора, таймаут 1200
|
- [x] 3.1 `README.md`, `CLAUDE.md`, `api/.env.example` — прямая запись, `SCAN_TMP_DIR` только у эмулятора, таймаут 1200
|
||||||
- [ ] 3.2 `make check-full`
|
- [x] 3.2 `make check-full`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user