Andrey Epifancev 7cec4596d9 chore(openspec): архив change slice-0-skeleton
Срез 0 реализован и влит (PR #3), change перенесён в openspec/changes/archive, спеки — в openspec/specs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 19:44:47 +04:00

63 lines
5.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.

# scan-emulator Specification
## Purpose
Эмулятор ИС Сканирование для разработки и тестов: воспроизводит протокол горячих папок и формат ответов Content AI,
отдавая реальные ответы из `../fixtures/ocr` по имени файла, чтобы конвейер `scan-gateway` проверялся без доступа к
банковской системе.
## 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` в формате реальных ответов-ошибок
ИС (`aep_5.json`, `aep_8_form4.json`): `file_uid = <uuid>`, `error = {"code": "INVALID_DOCUMENT", "message": "<имя>: нет
эталонного ответа в фикстурах"}`, `data_ocr` — **объект** с пустыми полями (`label`, `organization`, `inn`, `units`,
`date` — `{"form_name": "", "confidence": 0}`) и `table_structure: []`.
#### Scenario: Нет пары
- **WHEN** входящий файл — `…_unknown.pdf`
- **THEN** ответ содержит `error.code = INVALID_DOCUMENT`, `data_ocr` — объект с пустыми полями и `table_structure: []`; `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` работает, его лог сообщает каталоги и число найденных пар фикстур