Andrey Epifancev 5dbb4dab20 Срез 0 [7.0]: change обновлён под решения человека 04.10.2026
Склейка всех json каталога ответа (settle), SCAN_TMP_DIR вне Input, успех = error.code == "",
per-file ошибки в metrics.warnings, набор файлов в эмуляторе; решения №54-62 в design.md;
OQ-76/77/78/79 отмечены решёнными; группа 7 в tasks.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 13:38:48 +04:00

62 lines
5.1 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.

# Spec Delta: scan-emulator
## Purpose
Эмулятор ИС Сканирование для разработки и тестов: воспроизводит протокол горячих папок и формат ответов Content AI,
отдавая реальные ответы из `../fixtures/ocr` по имени файла, чтобы конвейер `scan-gateway` проверялся без доступа к
банковской системе.
## ADDED Requirements
### Requirement: Эмулятор отвечает по протоколу горячих папок
Процесс `python -m src.scan_emulator` SHALL опрашивать входящий каталог каждую секунду; для нового файла
`<uuid>_<name>.<ext>` через `SCAN_EMULATOR_DELAY` секунд (по умолчанию 2) SHALL создать каталог `<output>/<uuid>/` с
набором ответов (по одному файлу на документ); каталог SHALL появиться в `<output>` атомарно целиком: набор записывается
во временный каталог эмулятора и переименовывается. После обработки входящий файл SHALL быть переименован в `<uuid>_<name>.<ext>.done`, чтобы не обрабатываться повторно.
#### Scenario: Один файл
- **WHEN** во входящем каталоге появляется `3f1c…_aep_5.pdf`
- **THEN** через ≤ 5 с существует `Output/3f1c…/3f1c….json`, а входящий файл переименован в `…pdf.done`
#### Scenario: Набор появляется целиком
- **WHEN** эмулятор записывает набор из нескольких файлов
- **THEN** каталог `Output/3f1c…/` либо не существует, либо уже содержит весь набор; частичного набора в `Output` нет
### Requirement: Ответы берутся из фикстур по имени
Эмулятор SHALL собрать в `SCAN_FIXTURES_DIR` **все** файлы `<name>.json` и `<name>_*.json`, где `<name>` — имя входящего
файла без префикса `<uuid>_` и расширения (регистр расширения входящего файла не важен). Файл `<name>.json` SHALL быть
записан как `<uuid>.json`, файл `<name>_<suffix>.json` — как `<uuid>_<suffix>.json`; в каждом `file_uid = <uuid>`.
Фикстуры вида `<name><другие символы>.json` без разделителя `_` (например, `aep_30.json` для `aep_3`) MUST NOT попадать в набор.
#### Scenario: Точное имя
- **WHEN** входящий файл — `…_aep_5.pdf`, в фикстурах есть `aep_5.json`
- **THEN** в `Output/<uuid>/` один файл `<uuid>.json`, равный `aep_5.json` с заменённым `file_uid`
#### Scenario: Суффикс
- **WHEN** входящий файл — `…_aep_8.pdf`, в фикстурах есть `aep_8_form4.json`
- **THEN** в `Output/<uuid>/` файл `<uuid>_form4.json`, равный `aep_8_form4.json` с заменённым `file_uid`
#### Scenario: Несколько файлов на одно задание
- **WHEN** входящий файл — `…_aep_3.pdf`, в фикстурах есть `aep_3.json` и `aep_3_ofr.json`
- **THEN** в `Output/<uuid>/` два файла: `<uuid>.json` (из `aep_3.json`) и `<uuid>_ofr.json` (из `aep_3_ofr.json`), в обоих `file_uid = <uuid>`
#### Scenario: Чужой префикс
- **WHEN** входящий файл — `…_aep_3.pdf`, в фикстурах есть также `aep_30.json`
- **THEN** `aep_30.json` в набор не попадает
### Requirement: Неизвестный файл — ошибка в формате Content AI
Если ни одного файла набора не найдено, эмулятор SHALL записать один ответ (`<uuid>.json`) `{"file_uid": "<uuid>", "error": {"code": "INVALID_DOCUMENT",
"message": "<имя>: нет эталонного ответа в фикстурах"}, "data_ocr": []}`.
#### Scenario: Нет пары
- **WHEN** входящий файл — `…_unknown.pdf`
- **THEN** ответ содержит `error.code = INVALID_DOCUMENT` и пустой `data_ocr`; `scan-gateway` публикует конверт с тем же кодом
### Requirement: Эмулятор запускается из того же образа
В `docs/dev/docker-compose.yml` сервис `scan-emulator` SHALL использовать образ `scan-gateway` с командой
`python -m src.scan_emulator`, общий том `hotfolder` и `../../fixtures/ocr`, смонтированный только для чтения.
#### Scenario: Compose
- **WHEN** выполнен `docker compose --profile services up -d`
- **THEN** контейнер `scan-emulator` работает, его лог сообщает каталоги и число найденных пар фикстур