Andrey Epifancev ed5622374e Срез 0 [7.5]: сквозная проверка в реальном compose — aep_3/aep_6/aep_5.PDF прошли; R-27, R-37, R-38 (design #77–79)
Профиль 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>
2026-10-04 15:30:46 +04:00

12 KiB
Raw Blame History

Tasks: Срез 0 — скелет scan-gateway

Репозиторий — scan-gateway; команды — из api/. Зависимости: 3.1 ждёт контракт v1.0 (docs 1.1–1.4); интеграционные тесты 6.x — compose (docs 2.1, 5.3). Каждая задача ≤ половины дня. CI нет — проверки локальные.

1. Вырезать фронт и БД из шаблона (SMB остаётся)

  • 1.1 Удалить web/, setup/.npmrc, npm-шаги setup/setup.sh, api/alembic/, api/alembic.ini, api/src/db/, api/src/models/, api/src/repositories/; убрать Dockerfile из .gitignore; проверка: git status — только удаления и .gitignore
  • 1.2 src/app.py: убрать mount SPA/os.makedirs("web"), CORS, импорт raisa_fastapi_protected_api; оставить SMB-сессию (только при SCAN_HOTFOLDER_MODE=smb); src/core/config.py: убрать DBSettings; добавить ENV_FILE, SERVICE_NAME="scan-gateway", NATS_ENABLED=true, PUBLISH_RETRIES=3, DONE_CACHE_SIZE=1000, SCAN_HOTFOLDER_MODE="local", SCAN_HOTFOLDER_PATH, SCAN_INPUT_DIR="Input", SCAN_OUTPUT_DIR="Output", SCAN_POLL_SECONDS=5, SCAN_TIMEOUT_SECONDS=300, SCAN_MAX_INFLIGHT=4; requirements.txt без asyncpg, SQLAlchemy, alembic, argon2-cffi, raisa_fastapi_protected_api; проверка: чистый venv (python3.11), python run_server.py с .env.example, /healthcheck 200
  • 1.3 README.md — описание сервиса (назначение, протокол горячих папок, env, запуск против compose и эмулятора), CLAUDE.md — актуализировать «Соглашения шаблона» по факту; проверка: упомянутые файлы существуют

2. Инструменты

  • 2.1 api/pyproject.toml (ruff, mypy), requirements-dev.txt = -r requirements.txt + ruff, mypy, pytest, pytest-asyncio, pytest-env, httpx, jsonschema; Makefile: venv (python3.11, без raisa_*), lint, test, test-integration, check, check-full, up, emulator; проверка: make venv && make lint зелёные
  • 2.2 Dockerfile (python:3.11-slim; CMD python run_server.py, эмулятор — через command в compose), .env.example, .env.test; проверка: docker build успешен без Nexus

3. Контракт

  • 3.1 api/contracts/ — копии схем и examples/ из ../docs/contracts; src/contract/ — pydantic-модели (конверт со статусами processing | done | needs_check | error); tests/unit/test_contract.py — идентичность (skip без docs), примеры, round-trip; проверка: тесты зелёные

4. Горячие папки и эмулятор

  • 4.1 src/gateway/hotfolder.py: протокол HotFolder, LocalHotFolder (put/poll/healthy); tests/unit/test_local_hotfolder.py на tmp_path; проверка: put пишет <uuid>_<name>, poll возвращает None до появления ответа и байты после
  • 4.2 src/gateway/smb.py: SmbHotFolder поверх smbclient (UNC \\host\path\Input), те же методы; unit-тест с подменённым smbclient (вызовы open_file, listdir); проверка: тест зелёный, в режиме local модуль не импортируется
  • 4.3 src/scan_emulator/__init__.py + __main__.py: функция process_once(root, fixtures, delay) и цикл; поиск фикстуры по имени/префиксу, подстановка file_uid, ошибка INVALID_DOCUMENT, переименование в .done; tests/unit/test_emulator.py — сценарии spec «Точное имя», «Префикс», «Нет пары» на tmp_path с копиями ../fixtures/ocr/*.json (маркер fixtures, skip без фикстур); проверка: зелёный

5. Воркер и нормализатор-заглушка

  • 5.1 src/normalizer/__init__.py: протокол Normalizer, NormalizeOutcome, NullNormalizer (ошибка ИС → код из ответа; успех → NOT_IMPLEMENTED; не JSON → INVALID_FORMAT); unit-тесты на aep_5.json, aep_8_form4.json, синтетическом ответе с error; проверка: зелёный
  • 5.2 src/core/clients.py (get_bytes, put_json, put_bytes), src/core/nats.py (подключение с/без nkeys, автопереподключение, subscribe_requests(queue="scan-gateway", cb), publish с Nats-Msg-Id и повторами); проверка: unit с фейками, интеграционный тест подключения к compose-NATS без JetStream
  • 5.3 src/core/worker.py: on_request(msg): валидация → LRU job_id → ack processing → очередь; семафор SCAN_MAX_INFLIGHT; handle(job): S3 get → hotfolder.put → опрос до дедлайна → raw/{job_id}.json → Normalizer → результат/конверт → publish; таблица ошибок D-7; /healthcheck.nats, .hotfolder; проверка: unit-тесты handle с фейками и LocalHotFolder + process_once эмулятора — сценарии spec (ack раньше файла, повторный запрос → один файл, успех → NOT_IMPLEMENTED с raw_s3_key, INVALID_DOCUMENT, TIMEOUT с укороченными таймаутами, RECOGNITION_FAILED при отсутствии каталога, SCHEMA_MISMATCH без ack, FILE_READ_ERROR)
  • 5.4 Тест параллелизма: пять заданий при SCAN_MAX_INFLIGHT = 4 — пять ack сразу, четыре файла во входящем каталоге, пятый после освобождения слота (фейковые S3/NATS, LocalHotFolder); проверка: зелёный

6. Интеграция, проверки, завершение

  • 6.1 tests/integration/test_roundtrip.py (маркер integration): compose с scan-emulator — положить aep_5.pdf в MinIO uploads/…, опубликовать request-image.json, собрать recognition.result: processing, затем NOT_IMPLEMENTED с raw_s3_key; проверить объект raw/<job_id>.json в MinIO; второй запрос с unknown.pdf → INVALID_DOCUMENT; проверка: зелёный
  • 6.2 make check-full: make check + pytest -m integration + docker build; проверка: зелёный против compose docs на ветке feat/1-slice-0-skeleton
  • 6.3 Прогон DoD: make up → make check-full → ручная проверка из docs/dev/README.md; openspec validate slice-0-skeleton --strict; вывод make check-full — в описание PR

7. Доработка по реальным ответам ИС

Основание: решения человека 04.10.2026 (OQ-76/77/78/79), план docs/superpowers/plans/2026-10-04-multi-file-response.md, design №54–66. Контракт v1.0 не меняется; нормализация в statementDocument — Срез 3.

Формулировки уже закрытых задач 4.3, 5.1, 5.3, 6.1 (поиск фикстуры по префиксу <name>*, aep_5.pdf как единственный сценарий, единственный файл <job_id>.json, «не JSON → INVALID_FORMAT» без склейки) заменены задачами 7.x и обновлёнными спеками; действует группа 7.

  • 7.0 Привести change в соответствие с решениями человека: proposal.md, design.md (D-1, D-3–D-6, решения №54–62), specs/image-recognition-gateway/spec.md, specs/scan-emulator/spec.md, open-questions-proposal.md (OQ-76/77/78/79 решены), эта группа; проверка: openspec validate slice-0-skeleton --strict — valid; в спеках нет «читает ровно <job_id>.json», .part в Input, data_ocr как списка и успеха по error != null
  • 7.1 Горячие папки: SCAN_TMP_DIR (Tmp), SCAN_SETTLE_SECONDS (2); HotFolder.poll(job_id) возвращает None либо список AnswerFile(name, data) — все *.json каталога ответа по имени после settle; .part создаётся в <корень>/Tmp/ (LocalHotFolder, SmbHotFolder), в Input — только итоговый файл; проверка: unit-тесты на tmp_path (нет .part в Input; нет каталога → None; один json; два json по имени; дописываемый файл → None до стабилизации; посторонний файл игнорируется; SmbHotFolder с подменённым smbclient), make check зелёный
  • 7.2 Склейка, сырой файл, нормализатор, воркер: merge_answers(job_id, files); raw/{job_id}.json = склеенный ответ; NullNormalizer по правилам D-5 (успех = error.code == "", per-file ошибки в metrics.warnings, NOT_IMPLEMENTED при хотя бы одном успешном файле); проверка: unit-тесты merge_answers (порядок, невалидный json → parse_error), нормализатор на реальных фикстурах aep_6.json, aep_3.json (не ошибка), aep_3_ofr.json, aep_5.json (INVALID_DOCUMENT), aep_3.json + aep_3_ofr.json в склейке под именами <job_id>.json / <job_id>_ofr.json → NOT_IMPLEMENTED с одной строкой warnings <job_id>_ofr.json: INVALID_DOCUMENT: …, воркер с фейками; make check зелёный
  • 7.3 Эмулятор: набор файлов на задание — <name>.json и <name>_*.json в Output/<uuid>/ как <uuid>.json / <uuid>_<suffix>.json с file_uid = <uuid>, атомарная запись каталога; проверка: unit-тесты (маркер fixtures) — aep_3.pdf → два файла, aep_6.pdf → один, aep_5.PDF → один с ошибкой, aep_8.pdf → <uuid>_form4.json, aep_30* не попадает в набор aep_3, неизвестное имя → INVALID_DOCUMENT; сквозной «put → эмулятор → poll (с settle) → склейка» на aep_3; make check зелёный
  • 7.4 Интеграционные тесты и документация: реальные NATS и MinIO — aep_3.pdf → NOT_IMPLEMENTED с warnings про <job_id>_ofr.json и двумя файлами в raw/, aep_6.pdf → NOT_IMPLEMENTED без warnings, aep_5.PDF → INVALID_DOCUMENT; README.md, CLAUDE.md, .env.example, .env.test актуализированы (SCAN_TMP_DIR, SCAN_SETTLE_SECONDS, склеенный raw); проверка: make check-full (при поднятом make up) — exit 0, вывод в отчёт
  • 7.5 Проверка в реальном compose (без правок ../docs): сервисы scan-gateway и scan-emulator собираются и стартуют, сценарии aep_3.pdf, aep_6.pdf, aep_5.PDF проходят способом из ../docs/dev/README.md, содержимое raw/<job_id>.json зафиксировано; окружение остановлено (down -v; по R-27, design #77 — rm -sfv своих сервисов и том hotfolder, общие postgres/nats/minio не тронуты); необходимые правки compose (SCAN_TMP_DIR, SCAN_SETTLE_SECONDS) — точным фрагментом в open-questions-proposal.md; проверка: отчёт со снимками выводов