proposal, design (D-1…D-9, решения без обсуждения №1–12), дельты спек image-recognition-gateway
(папки, очистка под флагом, нормализация, справочники с редакциями, статусы) и scan-emulator (пара по sha256,
набор по умолчанию). tasks.md заполняется планом.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- README: верхняя граница задержки settle, шаг 5 «Полного круга»; критерии ошибочного файла (корень не объект)
- CLAUDE.md: по одному json на каждый распознанный документ; критерии ошибочного файла
- design.md: D-5 — оговорка про нестроковый error.code отдельным предложением; №79 — источник указания, SQL уборки в порядке FK
- open-questions-proposal.md п.20: замены ../docs/dev/README.md (185–187, 224–226, 252–253) и комментарий docker-compose.yml:184
- план: Task 6 — пометка про замену down -v (R-27)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Профиль services (arm-core, scan-gateway из этого worktree, scan-emulator): aep_3.pdf → error NOT_IMPLEMENTED
с metrics.warnings про <job_id>_ofr.json, aep_6.pdf → error NOT_IMPLEMENTED без metrics, aep_5.PDF →
error INVALID_DOCUMENT; raw/<job_id>.json — склейка набора, тела совпадают с фикстурами; Tmp пуст, .part нет.
Compose правки не требует (умолчания Tmp/settle 2 с). Устаревшие места ../docs/dev/README.md — точными
заменами в open-questions-proposal.md, пункт 20. Уборка — rm -sfv своих сервисов и том hotfolder вместо
down -v (R-27). Отметки: tasks 7.5, план Task 6.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Раскладка Output/<job_id>/*.json (склеиваются все файлы), SCAN_TMP_DIR, SCAN_SETTLE_SECONDS, структура raw/{job_id}.json,
успех = error.code == "", ошибочные файлы в metrics.warnings, правило набора эмулятора. D-5: не строковый error.code
тоже ошибка (UNKNOWN_ERROR, R-32). Решения R-36 (design #76). Тест TestAnswerSet на реальных фикстурах помечен fixtures.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Для `<uuid>_<name>.<ext>` эмулятор собирает все `<name>.json` и `<name>_*.json` из SCAN_FIXTURES_DIR (не префикс
`<name>*`) и кладёт их в `Output/<uuid>/` как `<uuid>.json` / `<uuid>_<suffix>.json` с `file_uid = <uuid>`. Набор
пишется в `<root>/<SCAN_TMP_DIR>/.<uuid>.answer` и переименовывается в каталог ответа целиком (R-26). Ответ «нет пары» —
`data_ocr` объектом с пустыми полями (R-28). Решения исполнителя — design №75 (R-35).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- spec «Ответ из одного файла»: набор передаётся не позднее SCAN_SETTLE_SECONDS +
SCAN_POLL_SECONDS после первого опроса, увидевшего окончательный набор (R-29).
- design D-5: сигнатура NormalizeOutcome с warnings; воркер дописывает их в metrics.warnings.
- Decisions made without discussion: №67 R-29, №68 решения исполнителя Task 2, №69–72
R-30..R-33 (предел warnings, склейка в src/gateway/merge.py, крайние случаи NullNormalizer,
слияние warnings с metrics), №73 T2.1/T2.2/T2.4, №74 R-34 (адаптация test_roundtrip.py).
- Отметки: Task 3 в плане, 7.2 в tasks.md.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Заменённые решения помечены в design.md (№32, 35, 43, 48), добавлены №63-66 (R-22, R-23, R-25, R-28),
маркеры R-n (design #m); уточнены правила результата (UNKNOWN_ERROR, не-объект, коды в warnings),
сценарии таймаута по settle, имена файлов <job_id>*.json, ответ эмулятора «нет пары» с data_ocr-объектом.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Склейка всех 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>
open-questions-proposal.md: SCAN_GATEWAY_PORT=8002 для `../docs/dev` (T16.1); проверки после применения
compose-фрагмента — `up -d --wait scan-gateway scan-emulator` с кодом 0 и строки лога эмулятора в формате `run()`
(T16.2, I-2); пометка, что фрагмент рабочий только после C-1 (проверено `docker run`, код 2); I-3 — replay
с `attempt + 1` для задания из LRU; `make up` в tabular-parser без `minio-init` (только сообщить); в «Что осталось
человеку» — не архивировать до проверки фрагмента, решение по I-3, приоритет T12.5.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- open-questions-proposal.md: compose-фрагмент scan-emulator/scan-gateway, таблица предложений по контракту и OQ,
чеклист для человека (файлы в ../docs не менялись)
- .env.example: SCAN_EMULATOR_POLL_SECONDS; уточнён комментарий PUBLISH_RETRIES (повторы после первой попытки)
- отмечена задача 16 плана и пункт 6.3 tasks.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Добавлена цель docker-build (IMAGE ?= scan-gateway:dev, контекст — корень репозитория).
check-full вызывает шаги последовательно через $(MAKE), поэтому порядок и остановка на
первой ошибке гарантированы и при make -j. README/CLAUDE.md и help приведены в соответствие,
отмечены задача 15 плана и пункт 6.2 tasks.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
README.md описывает сервис как он есть: границы (без БД и фронта), поток обработки,
протокол горячих папок, переменные окружения шлюза и эмулятора, коды ошибок,
контракт NATS, запуск (dev, полный круг против compose и эмулятора, проверки),
/healthcheck, Docker-образ и ограничения Среза 0. CLAUDE.md: «Соглашения шаблона»
переписаны по фактическому дереву api/, устаревший SCAN_EMULATOR=1 заменён на
`make emulator` / `python -m src.scan_emulator`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lifespan приложения и эмулятор ИС в процессе теста (R-7) против compose NATS/MinIO, горячая папка в tmp-каталоге.
Три сценария (R-10): aep_5.pdf (реальная фикстура) — INVALID_DOCUMENT от ИС, повтор во время ожидания и после
результата даёт ещё один ack без второго файла; ok_doc.pdf (синтетический успех) — NOT_IMPLEMENTED; unknown.pdf —
INVALID_DOCUMENT эмулятора. Проверяются схема конверта, service.name, Nats-Msg-Id, raw/<job_id>.json байт в байт,
входящий файл против объекта S3, /healthcheck, остановка без незавершённых задач и ошибок в логе.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Сценарий spec «Пять заданий подряд» переписан детерминированно: паузы опроса горячей
папки управляются тестом (SteppedSleep), ожидания — по событиям с тайм-аутом. Проверяется:
пять ack'ов до освобождения слота, ровно четыре файла и четыре чтения S3 при занятых слотах,
пятый файл — только после итога освобождённого задания, у всех пяти — итоговый конверт.
Добавлены строгая очерёдность при SCAN_MAX_INFLIGHT = 1 и остановка воркера при очереди
на слот (T12.9): задачи отменены, утечек задач и слотов нет, новых файлов нет.
Прежний test_five_jobs_with_four_slots удалён из test_worker.py (без дублирования).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
src/core/health.py: HealthProvider — nats ok/down/disabled по is_connected; hotfolder —
healthy() в потоке под wait_for (2 с) с кэшем 5 с, зависшая проба не запускается
повторно (T8.4). Сбой регистрации SMB-сессии виден естественно: SmbHotFolder.healthy()
обращается к шаре с теми же учётными данными (T2.3).
app.py: в lifespan S3Storage, NatsGateway, Worker(NullNormalizer); подключение к NATS
и подписка — фоновой задачей (connect ждёт сервер), её сбой — logger.exception и
nats=down без падения HTTP; остановка отменяет подключение, останавливает воркер,
закрывает NATS и S3. Объекты lifespan — в app.state.
test_app.py: тесты lifespan переведены на NATS_ENABLED=false и tmp-корень (иначе фоновое
подключение шло бы в compose NATS), проверка полей healthcheck расширена nats/hotfolder.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
NatsGateway: подключение с nkeys/JWT или без учётных данных, NATS_ENABLED=false — без подключения,
автопереподключение nats-py, subscribe_requests в queue group scan-gateway, publish с Nats-Msg-Id и
повторами (PUBLISH_RETRIES попыток всего) -> PublishFailed, publish_result для конвертов ack/result.
src.core.clients и src.core.nats — в strict-списке mypy. Интеграционные тесты против compose-NATS/MinIO:
queue group, заголовок Nats-Msg-Id, roundtrip S3, отсутствующий ключ.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ошибка ИС пробрасывается с кодом из ответа, неизвестный код/вид ошибки -> UNKNOWN_ERROR с исходным
представлением, успех -> NOT_IMPLEMENTED, не объект -> INVALID_FORMAT. Пакет добавлен в strict-список mypy.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
process_once(root, fixtures, delay): новые <uuid>_<имя> -> ответ из фикстуры (точное имя, затем префикс,
file_uid подставляется) либо INVALID_DOCUMENT, атомарная запись ответа, затем переименование в .done.
Цикл python -m src.scan_emulator с корректной остановкой по SIGINT/SIGTERM.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SmbHotFolder: UNC-корень \\host\SMB_PATH (или полный UNC в SMB_PATH), put — `.<name>.part` + smbclient.replace,
poll — open_file(rb), ENOENT (в т.ч. нет каталога) -> None, healthy — listdir корня без исключений.
create_hotfolder(settings): local -> LocalHotFolder, smb -> SmbHotFolder с ленивым импортом модуля (R-6);
отсутствие импорта src.gateway.smb в режиме local проверяется в отдельном интерпретаторе.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Обмен с ядром на core NATS: подписка queue group, ack status=processing сразу по получении, process-local
LRU job_id против повторных запросов, публикация результата с повторами; удалены durable consumer / max_deliver /
ack_wait. Dockerfile python:3.11-slim; make check / make check-full вместо CI; raisa_* фильтруются при
dev-установке. Обновлены proposal, design (решения без обсуждения), specs, tasks, CLAUDE.md, openspec/config.yaml.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Срез 0 (issue #1): раздел «Соглашения шаблона» (что оставлено от шаблона команды, что
вырезается, что добавляется); change openspec/changes/slice-0-skeleton — proposal, design
с таблицей «Decisions made without discussion», спеки и tasks; в openspec/config.yaml
строки с двоеточием взяты в кавычки (YAML разбирал их как словари).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>