05-domain-rules: §1.3, §1.5, §3 п. 4, §9.2–9.3, новый §9.5 (загрузка, маппинг, автозамена, подтверждение, удаление, аудит). 03-architecture: цикл маппинга, порядок блокировок, схема ключей S3. 07-open-questions: закрыты OQ-67/69/70/59/109, новые OQ-110…116. 09-delivery-plan: Срез 2c по факту. dev/qa-guide: раздел 6.6 «Загрузка ОСВ и просмотр строк через API». tasks.md: 2.1 выполнен. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
36 KiB
03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.15)
Компоненты (= репозитории, см. 00-workspace-layout.md)
┌──────────────┐ JWT (email) ┌──────────────────────────────┐
браузер ──────▶│ React SPA │ ───────────────▶│ arm-core (FastAPI) │
└──────────────┘ │ REST /api/v1, policy-слой, │
│ единственный писатель в БД │
└───────┬──────────┬───────────┘
│ │
PostgreSQL │ │ S3 (uploads/ results/ exports/ archive/)
│ │
NATS (core) ◀─────────────────┘ outbox │
recognition.tabular.request │
recognition.image.request │
recognition.result ───────────────────▶ arm-core (inbox, дедуп по msg_id)
│
┌───────────────┴───────────────┐
┌────────▼────────┐ ┌─────────▼────────┐ ┌──────────────────┐
│ tabular-parser │ │ scan-gateway │ ◀─────▶│ ИС Сканирование │ (внешняя, горячие папки)
│ XLS/XLSX/XLSB │ │ PDF/PNG/JPG │ └──────────────────┘
│ stateless, без БД│ │ stateless, без БД│
└─────────────────┘ └──────────────────┘
- arm-core — монолитное ядро: справочники, файлы/документы, маппинг результатов распознавания, консолидация (вычисления), согласование, ЕХ, аудит, права. Один Alembic, одна схема.
- tabular-parser — читает файл из S3
uploads/, находит на листах ОСВ (§1) и формы ББ/ОФР (§12) по содержимому, пишет нормализованный JSON в S3results/, публикуетrecognition.result. Контракт результата —docs/contracts/recognition-result.schema.json(контракт v1.0). Правила распознавания заголовков/группировок —rules/*.yaml. - scan-gateway — адаптер ИС Сканирование: переименовать файл
UUID_имя.pdf, положить в «Входящие», опросить «Исходящие/UUID/UUID.json», сохранить сырой ответ в S3raw/{job_id}.json(в БД не регистрируется), нормализовать в тот же контракт результата, опубликоватьrecognition.result. Таймаут 5 мин →timeout. - Frontend — React + TS по шаблону команды; экраны по макету
dkb_24.09.26(справочник ГК, консолидации, первичные документы, проверка распознавания, сводная ОСВ, шахматка, корректировки, гудвилл, КФО, ЕХ, журнал, роли).
Контракт обмена через NATS (DECISION-4)
Схемы — docs/contracts/ (v1.0), JSON Schema 2020-12: recognition-request.schema.json
и recognition-result-message.schema.json — тела сообщений,
recognition-result.schema.json — файл результата в S3; примеры —
docs/contracts/examples/. Примеры проверяются схемами: python scripts/validate_contracts.py
(из корня репозитория docs). Примеры — иллюстрации формы сообщений, а не готовые к исполнению задания:
схема проверяет у reply_deadline только формат date-time и не сравнивает срок с текущим временем, поэтому
request-image.json проходит схему при reply_deadline в прошлом. Срок проверяет сервис: scan-gateway отвечает
error TIMEOUT, если reply_deadline истёк до отправки файла в ИС Сканирование; tabular-parser срок не проверяет.
Файлы examples/ копируются в сервисы побайтно, поэтому пример не правится ради «свежей» даты.
| Поток | Subject | Кто пишет | Полезная нагрузка | Nats-Msg-Id |
Схема |
|---|---|---|---|---|---|
| запрос | recognition.tabular.request / recognition.image.request |
arm-core (outbox из recognition_job) |
schema_version, job_id, file_id, pipeline, file_type, original_name, s3 {bucket, key} исходника, reply_deadline, attempt |
<job_id>:<attempt> |
request |
| ack | recognition.result |
tabular-parser / scan-gateway | конверт со schema_version, status = processing, job_id, file_id, service {name, version} |
ack:<job_id>:<attempt> |
message |
| результат | recognition.result |
tabular-parser / scan-gateway | schema_version, job_id, file_id, status (done / needs_check / error), service {name, version}, metrics (опц.), raw_s3_key (опц., только scan-gateway); при done / needs_check обязательны result_s3 {bucket, key} и result_schema_version, при error — error {code, message} |
result:<job_id>:<attempt> |
message; файл в S3 — result |
group_schema в конверт не входит — ядро берёт его из documents[].group_schema файла результата при маппинге.
На платформе NATS без JetStream (ответ 03.10.2026): нет durable consumer'ов, серверного дедупа и redelivery.
Поэтому гарантии доставки — на уровне приложения: сервисы подписаны queue group'ами (tabular-parser, scan-gateway),
ядро — queue group arm-core на recognition.result; сервис сразу по получении корректного запроса публикует ack processing;
ядро, не получив ack за ACK_TIMEOUT_SECONDS (30), публикует запрос повторно с тем же Nats-Msg-Id, не более
OUTBOX_MAX_REPUBLISH (3) раз, затем dlq. Сервис держит process-local множество job_id в работе и на повторный
запрос отвечает ack'ом без второй обработки. Публикация ack и результата сервисом при временной ошибке повторяется
с тем же Nats-Msg-Id: первая попытка плюс PUBLISH_RETRIES повторов (по умолчанию 3, всего до 4 попыток); повторы
исчерпаны — error-лог, задание закроет ядро (по reply_deadline). Появится JetStream — меняются только publisher/subscriber.
Правила:
recognition_jobсоздаётся в статусеqueuedв одной транзакции соstored_file; фоновый публикатор переводит вpublished(request_msg_id=Nats-Msg-Id), ack сервиса — вprocessing(started_at). Повтор публикации — поqueuedстарше N секунд и поpublishedбез ack.- Результат принимается подпиской queue group;
result_msg_id UNIQUE= дедуп. Нет ack после повторов →dlq; потерянный результат →reply_deadline→timeout; replay = сброс вqueuedсattempt + 1. - Результат одного файла содержит N документов; ядро на каждый создаёт
documentи маппит данные. - Три формы
Nats-Msg-Id: запрос<job_id>:<attempt>, ackack:<job_id>:<attempt>, результатresult:<job_id>:<attempt>. Ack (processing) сервис публикует до чтения файла из S3; повторная публикация запроса ядром идёт с тем жеNats-Msg-Idи тем жеattempt. Дубликат результата ядро определяет поrecognition_job.result_msg_id. schema_version—MAJOR.MINOR; ядро и сервисы принимают тот же MAJOR и любой MINOR. Сервис отвергает запрос с неизвестным MAJOR (как и запрос не по схеме) результатомerrorс кодомSCHEMA_MISMATCHбез предшествующего ackprocessing: финальный конверт сам закрывает задание в ядре (published → error), повторной доставки не будет. Конверт публикуется, только если из тела извлекаются корректныеjob_idиfile_id; дляjob_id, уже находящегося в работе или обработанного, он не публикуется (егоNats-Msg-Idсовпал бы с настоящим результатом). Нечитаемый запрос (не JSON, нетjob_id) остаётся без ответа: сервис пишет лог и отбрасывает его, ядро не получает ack, повторяет публикациюOUTBOX_MAX_REPUBLISHраз и закрывает задание вdlqсerror_code = NO_ACK. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения.- Сервисы не знают про БД и про бизнес-смысл: они возвращают структуру файла; нормализация счетов, резолв контрагентов, проверки — в ядре.
Цикл маппинга результата и порядок блокировок (Срез 2c)
Обработка результата разделена на два шага, чтобы подписка NATS не ждала S3 и тяжёлые вставки (иначе ack следующих
сообщений не успевает за ACK_TIMEOUT_SECONDS, OQ-69):
- Inbox (
handle_message, в callback подписки) — быстрый: дедуп поresult_msg_id, регистрация файла результата вs3_object, статус задания (done/needs_check/error), статус файла по заданию (PROCESSING/ERROR). Файл результата не читает и документов не создаёт. - Цикл маппинга (
services/result_mapping.py:run_mapping_loop, фоновая задача lifespan наравне с публикатором outbox и вахтёром сроков — всего три цикла; периодMAPPING_POLL_SECONDS= 5) — берёт кандидатаdone/needs_check, чей файлPROCESSINGи для которого это текущее задание (FOR UPDATE SKIP LOCKED), проверяет ключ результата (бакет хранилища и префиксresults/{file_id}/{job_id}/, OQ-70), читает объект из S3 вне транзакции, валидируетRecognitionResultv1.x и в одной транзакции создаёт документы и строки, переводит файл вPROCESSEDи выполняет автозамену. Повторяемые сбои — доMAPPING_MAX_ATTEMPTS(счётчик вrecognition_job.metrics.mapping_attempts), затем файлERROR(metrics.mapping_error). Алгоритм —05-domain-rules.md§9.5; чистые функции —domain/osv_mapping.py.
Статус файла (stored_file.status) меняет только код, проверивший current_recognition_job_id == job.id
(services/file_status.py); результат устаревшего задания файл не трогает; PROCESSED/SUPERSEDED обменом не
перезаписываются.
Порядок блокировок: задание → файл → документы. Его соблюдают все участники: inbox, вахтёр, публикатор (dlq), цикл
маппинга (после выбора кандидата блокирует файл), confirm (файл, затем документ, оба перечитываются под FOR UPDATE) и
DELETE /files/{id} (все задания файла по id, затем файл и документы). Автозамена одного ЮЛ дополнительно берёт
pg_advisory_xact_lock по ЮЛ (иначе два параллельно смапленных файла с одним документом не увидят друг друга), загрузка —
advisory-lock по (ЮЛ, sha256) (двойной клик не проскакивает дедуп).
Схема ключей S3
Один бакет (OPENBAO__MINIO__MINIO_BUCKET шаблона); роль объекта — первый сегмент ключа:
{bucket_role}/{file_id}/{job_id}/{name}. Роли: uploads (исходники, срок — весь срок хранения), results
(нормализованные JSON; ретеншн — ARCH-010, открыт), exports (7 дней), archive (data lake). Все объекты регистрируются
в s3_object (bucket_role, bucket, key, sha256, size, expires_at). Сырой ответ OCR raw/{job_id}.json — по
конвенции, без записи в БД. Lifecycle-правила — по префиксам.
Срез 2c: ядро кладёт исходник по ключу uploads/{file_id}/{job_id}/{безопасное имя} (job_id задаётся заранее — ключ
известен до записи в БД; имя очищается от путей и управляющих символов); results/{file_id}/{job_id}/… пишет сервис
распознавания, а ядро перед чтением проверяет, что ключ лежит именно в папке задания (OQ-70). При удалении файла объектам
uploads и results проставляется s3_object.expires_at = now(), физически они не стираются (ретеншн — OQ-111, ждёт
lifecycle-правил платформы); объект, записанный в S3 до сбоя БД, остаётся сиротой (то же правило).
Аутентификация и авторизация
- SPA получает JWT от РАИСА; каждый запрос несёт
Authorization: Bearer. - arm-core валидирует подпись/срок, извлекает email, находит
app_user(is_active); нет пользователя — 403. - Права:
effective_permissions = ∪ role_permission(roles(user)). Проверка в зависимости FastAPIrequire(permission_code). - Policy-слой поверх прав (отдельный модуль, чистые функции): видимость по филиалу (
regional_office), по ЗО (business_segment), допустимость действия по статусу версии (consolidation_status) и по активной блокировке (consolidation_lock). Право говорит «может», policy — «эту ли и сейчас ли». - Локальная разработка: заглушка-эмитент JWT с теми же claims (feature-flag
AUTH_DEV_ISSUER).
Слои arm-core (по раскладке шаблона команды)
Структура — раскладка шаблона api/src/ (см. «Соглашения шаблона» ниже); tabular-parser и scan-gateway — те же
каталоги без web/, db/, models/, alembic/. Соответствие прежним названиям этого документа: backend/app/ →
api/src/, integrations/ → core/, migrations/ → api/alembic/, frontend/ → web/.
api/src/
api/endpoints/ # роутеры FastAPI по контекстам: me, directories, files, documents, consolidation, kfo, ex, audit, users
api/dependencies/ # фабрики Depends: сессия, сервисы, current_user, require(permission)
domain/ # чистая предметная логика без I/O: правила ОСВ, периоды, матчинг, элиминация, проверки, пороги,
# машины статусов, policy — pydantic-модели на входе и выходе
services/ # use-cases: оркестрация domain + repositories + outbox + audit_event
repositories/ # SQLAlchemy-запросы, в т. ч. запросы-вычисления (сводная ОСВ, шахматка)
models/ # ORM 1:1 с DBML v5.15; имена таблиц/колонок/enum — как в DBML
schemas/ # pydantic DTO API /api/v1
core/ # config, clients (S3; NATS outbox-publisher и result-consumer), auth (JWT РАИСА / dev-эмитент),
# logger, exception_handlers
db/ # engine, сессия
api/alembic/ # миграции
api/scripts/ # check_dbml_vs_orm.py, seed
api/tests/ # фикстуры — из ../fixtures (ARM_KA_FIXTURES)
web/ # SPA
openspec/
Принципы: domain/ тестируется без БД на фикстурах из ../fixtures; вычисляемые объекты консолидации возвращаются
как pydantic-модели и не имеют ORM-таблиц; все мутации идут через services/ с записью audit_event.
Статусы и машины состояний
Три независимые машины: file_status (техника файла), document_status (проверка аналитиком), consolidation_status
(маршрут КФО) + recognition_status (жизненный цикл задания). Переходы — 05-domain-rules.md §9. Реализовать как
явные таблицы переходов в domain/, а не if-ы по роутерам.
Соглашения шаблона команды
Три сервисных репозитория развёрнуты из нулевого коммита репозитория команды RSHB/DKB_ARMKA (ba172f8, «Шаблон
FastAPI» платформы РАИСА/aurora): дерево импортировано как есть (arm-core@ba92222), доменный код удалён, каркас
оставлен (8e1f423). Документы подстраиваются под шаблон, а не наоборот. Ниже — что шаблон задаёт и что мы к нему
добавляем. Один и тот же текст действует для всех трёх репозиториев; отличия сервисов — в разделе «Что вырезано».
Раскладка репозитория
<repo>/
README.md описание шаблона (в Срезе 0 заменяется описанием сервиса)
raisa_config.yml профиль ресурсов платформы (cpu/ram); читает платформа при деплое
setup/ бутстрап разработчика: setup.sh (venv + pip + npm i); pip.conf и .npmrc → внутренний Nexus банка
api/ backend. РАБОЧИЙ КАТАЛОГ для всех команд: от него ищутся .env, pytest.ini и web/ для раздачи SPA
run_server.py точка входа платформы: uvicorn, host/port из SERVER__HOST / SERVER__PORT, lifespan=on
requirements.txt прод-зависимости, pinned (fastapi 0.135, SQLAlchemy 2.0.36, alembic 1.14, asyncpg, pydantic 2.13,
pydantic-settings, nats-py 2.14, miniopy-async, smbprotocol, loguru, sentry-sdk, raisa_fastapi_protected_api)
requirements-dev.txt dev-зависимости (pytest-asyncio, pytest-env, openpyxl, …) — пины рассинхронизированы с requirements.txt
pytest.ini asyncio_mode=auto, loop scope session, env APP_ENV=test / ENV_FILE=.env.test, testpaths=tests
alembic.ini, alembic/ env.py берёт URL из настроек приложения, схема dkb, compare_type=True, include_schemas
src/
__init__.py экспортирует app и get_settings
app.py FastAPI: lifespan (SMB-сессия, MinIO, NATS), middleware (gzip, proxy headers, CORS, РАИСА при DEBUG=false),
GET /healthcheck, include_router, раздача SPA из api/web/ (SPAStaticFiles, fallback index.html)
api/router.py агрегирующий APIRouter; api/endpoints/* — роутеры; api/dependencies/* — фабрики Depends
core/config.py pydantic-settings (см. «Конфигурация»); модуль создаёт settings при импорте
core/clients.py клиенты инфраструктуры (S3Storage поверх miniopy-async)
core/nats_callback.py обработчики сообщений NATS
core/logger.py loguru: setup_logger, перехват std logging и uvicorn, request_id, шумовые пути
core/exception_handlers.py ошибки → JSON {"detail": …}
common/exceptions.py доменные исключения (NotFoundError, CouldntCreateError)
db/db.py async engine (asyncpg), search_path=dkb, get_async_session
models/base.py DeclarativeBase с MetaData(schema="dkb"), UUIDPrimaryMixin (uuid4 на стороне приложения)
schemas/ pydantic DTO и enum'ы API
repositories/ запросы: класс на таблицу, принимает AsyncSession
services/ use-cases
tests/conftest.py
web/ frontend (остаётся только в arm-core): React 18 + TS, rsbuild, antd, Redux Toolkit, biome
Наши дополнения к api/src/, не меняющие существующих каталогов: domain/ — чистая предметная логика без I/O
(правила §1–§10, машины статусов, policy); core/auth.py — JWT РАИСА и dev-эмитент; api/scripts/ — служебные скрипты
(check_dbml_vs_orm.py, seed). Пути в документах вида rules/*.yaml, scripts/…, tests/… отсчитываются от api/.
Конфигурация
pydantic-settings, файлapi/.env, вложенность через__, лишние переменные игнорируются. Все инфраструктурные секреты — под префиксомOPENBAO(в проде заполняет OpenBao):OPENBAO__DATABASE__DATABASE_URL,OPENBAO__MINIO__{MINIO_URL, MINIO_ADMIN_LOGIN, MINIO_ADMIN_PASSWORD, MINIO_BUCKET},OPENBAO__NATS__{NATS_URL, NATS_SEED, NATS_JWT},OPENBAO__SMB__{SMB_HOST, SMB_PATH, SMB_LOGIN, SMB_PASSWORD}. Прочее:DEBUG,ROOT_PATH,VERSION,SERVER__HOST,SERVER__PORT.settingsсоздаётся при импорте (core/config.py,db/db.py,app.py) — импорт почти любого модуля требует полного окружения; тесты получают его из.env.test.DEBUG=true— режим разработки шаблона: NATS не подключается, middleware РАИСА не импортируется. Наши флаги разработки (AUTH_DEV_ISSUER,SCAN_EMULATOR, режим горячих папок) добавляются так же — отдельными полямиSettings, по умолчанию выключены; в проде ни один не включается.
База данных и миграции
- SQLAlchemy 2.0 async + asyncpg. Одна схема PostgreSQL
dkb— зашита в шаблоне (MetaData,search_path,version_table_schemaиinclude_nameAlembic). Следуем шаблону, неpublic. - Alembic берёт URL из настроек, не из
alembic.ini; ревизии —api/alembic/versions/, имя файла стандартное<rev>_<slug>.py. Автогенерация — только черновик: итоговую ревизию сверяет с DBMLapi/scripts/check_dbml_vs_orm.py. - PK везде
uuidчерезUUIDPrimaryMixin; имена таблиц, колонок и enum'ов — ровно как в DBML v5.15.
Инфраструктурные клиенты
- S3 —
miniopy-async(Minio,cert_check=False), один бакетMINIO_BUCKET; ролиs3_object.bucket_roleстановятся первым сегментом ключа (uploads/…,results/…,raw/…,exports/…,archive/…). - NATS —
nats-py; аутентификация nkeys-seed + JWT (NATS_SEED,NATS_JWT) черезuser_jwt_cb/signature_cb; соединение и подписки создаются в lifespan и живут вapp.state.nats/app.state.nats_subs. Core NATS без JetStream: подписки с queue group, заголовкиNats-Msg-Idставим сами, ack и повторы — на уровне приложения. - SMB (горячие папки ИС Сканирование) —
smbprotocol/smbclient, сессия регистрируется в lifespan; нужен толькоscan-gateway. - Внешняя аутентификация — пакет
raisa_fastapi_protected_apiиз внутреннего Nexus (AuthorizationMiddleware,ProtectedSettings, …), импортируется только приDEBUG=false; фронтовая пара —raisa-fastapi-protected-web. Вне сети банка оба пакета недоступны (см. вопросы Среза 0).
DI, слои, ошибки, логи
- Контейнера DI нет: ручные фабрики
Dependsвapi/dependencies/— фабрика получаетAsyncSessionизget_async_session, собирает репозитории и сервис; инфраструктурные клиенты берёт изrequest.app.state(storage,nats). - Поток запроса:
api/endpoints→services→repositories→models; DTO —schemas/. Доменные исключения изcommon/exceptions.pyмаппятся в HTTP вcore/exception_handlers.py(RequestValidationError → 422, IntegrityError → 409, SQLAlchemyError → 500, HTTPException как есть, прочее → 500); тело всегда{"detail": …}, тексты по-русски. - Логи — loguru (
core/logger.setup_logger): перехват std logging и uvicorn,request_idв extra, успешные обращения к/healthcheck,/docs, статике не логируются.
Тесты
- pytest + pytest-asyncio (
asyncio_mode=auto, loop scopesession) + pytest-env; запуск изapi/; тесты вapi/tests/. - Тестовую БД шаблон не задаёт. Решение: тесты, которым нужна БД, идут на PostgreSQL из docker-compose (enum'ы,
partial unique, jsonb,
NULLS NOT DISTINCTв SQLite не воспроизводятся);domain/тестируется без БД.
Линтеры и типизация
- Backend: шаблон декларирует black / isort / pylint (бейджи README), конфигов не содержит. Принимаем
ruff(format — совместим с black; lint с правилами isortIи подмножеством pylintPL) иmypy; конфиг —api/pyproject.toml(решение Среза 0; откат на black/isort/pylint — замена одного файла конфига и шага CI). - Frontend:
biome1.9 (npx biome check src): ширина строки 140, одинарные кавычки,bracketSameLine,noExplicitAnywarn,useAwait/noEmptyBlockStatementserror; типы —npx tsc --noEmit(strict). ESLint/Prettier нет.
Сборка и деплой
- Платформа РАИСА запускает
api/run_server.pyсSERVER__HOST/SERVER__PORTиз окружения; ресурсы —raisa_config.yml. Python на платформе — 3.11 (ответ 03.10.2026); версия Node неизвестна (локальноnode:22). Dockerfile,docker-compose.yml,.dockerignoreшаблон держит в.gitignore(«Custom ignore»): образ собирает платформа своим CI/CD, в который мы не лезем. Для локальной разработки: compose — на уровне workspace (docs/dev/docker-compose.yml),Dockerfile(python:3.11-slim) — в корне каждого сервиса, строкаDockerfileубирается из.gitignore(единственная правка шаблона; решение Среза 0).- Фронт:
npm run build(rsbuild) →web/dist/→ копируется вapi/web/, откуда FastAPI раздаёт SPA; роутеры подключаются до mount'а/. - Зеркала пакетов: pip и npm настроены на
nexus.dev.raisa.go.rshbank.ru(setup/pip.conf,setup/.npmrc). Вне банка Nexus недоступен до прода: dev-установка ставитrequirements.txtбез строкraisa_*(make venv), вweb/package.jsonпакетыraisa-*переносятся вoptionalDependencies; импортыraisa_fastapi_protected_apiусловные (DEBUG=false), как в шаблоне. - CI в Gitea нет и не планируется; конфигураций CI в репозиториях нет. Проверки качества — одинаковые цели
Makefileво всех репозиториях:make check(ruff, mypy, pytest без Docker, идентичность контракта) иmake check-full(+ интеграционные тесты против compose, для arm-core — цикл миграций иcheck_dbml_vs_orm.py);make check-fullобязателен перед PR. Описание —docs/dev/checks.md.
Frontend (только arm-core)
- React 18.3 + TypeScript 5.7 (strict), сборка rsbuild 1.6 (
npm run start | build | preview); основной UI-кит — antd 6 (+@ant-design/icons); установлены также tailwind 3 + daisyui 4 (темаrshb),react-daisyui,raisa-ui— точечно; состояние — Redux Toolkit 2 (store/store.ts, слайсы вstore/slices/, типизированныеuseAppDispatch/useAppSelector); маршрутизация — react-router-dom 7 (BrowserRouter basename=Config.APP_ADDRESS, маршруты вApp.tsx);pdfjs-dist— PDF. - Структура
web/src/:layouts/— страницы,widgets/— составные блоки,components/— примитивы,store/,conf/; стили — CSS-модули (styles.module.css) и токены antd (ConfigProviderвindex.tsx). - Переменные окружения только с префиксом
REACT_APP_(rsbuildloadEnv), читаются черезconf/config.ts: все запросы к бэку —${Config.API_URL}/api/v1/…(в сборке относительный путь);REACT_APP_ROOT_PATH=ROOT_PATHбэка (по умолчанию/aurora/apps/fastapi-dkb-armka) задаётbase/assetPrefixrsbuild иbasenameроутера.
Что вырезано в tabular-parser и scan-gateway
Сервисы распознавания — тот же шаблон без фронта и без БД (DECISION-4). На момент Среза 0 оба репозитория — полные копии
каркаса arm-core; change slice-0-skeleton каждого сервиса удаляет: web/ целиком; setup/.npmrc и npm-шаги
setup/setup.sh; из app.py — mount SPA (SPAStaticFiles, os.makedirs("web")), CORS, импорт
raisa_fastapi_protected_api (внешнего API нет — только /healthcheck); db/, models/, repositories/, alembic/,
alembic.ini и OPENBAO.DATABASE из настроек; из requirements.txt — asyncpg, SQLAlchemy, alembic, argon2-cffi,
raisa_fastapi_protected_api; шаблонный README.md заменяется описанием сервиса. tabular-parser дополнительно без SMB
(smbprotocol, OPENBAO.SMB, регистрация сессии в lifespan). Остаются: run_server.py, raisa_config.yml, setup/setup.sh
pip.conf,pytest.ini, настройкиOPENBAO__MINIO__*/OPENBAO__NATS__*/DEBUG,core/config.py,core/clients.py,core/nats_callback.py,core/logger.py,core/exception_handlers.py,common/exceptions.py. Точный список — вCLAUDE.mdсервиса и вtasks.mdего change'а.
Что ожидаем от платформы (для запроса ресурсов)
По ответам платформы (03.10.2026): PostgreSQL 16 (одна БД, схема dkb — создавать схемы можно), NATS без
JetStream (3 subject recognition.*; аутентификация nkeys/JWT, как в шаблоне), S3-совместимое хранилище (один бакет;
префиксы-роли + lifecycle по префиксам), Python 3.11 в базовом образе, Nexus (pip/npm, пакеты raisa_*) — только внутри
банка, CI/CD — пайплайн платформы (мы отдаём команды make check / make check-full). Секреты — через переменные
окружения (OPENBAO__*). Открыто: версия Node, параметры JWT РАИСА.