9.7 KiB
03. Архитектура (целевая, по решениям DECISION-1…5 и ARCH-* в DBML v5.13)
Компоненты (= репозитории, см. 00-workspace-layout.md)
┌──────────────┐ JWT (email) ┌──────────────────────────────┐
браузер ──────▶│ React SPA │ ───────────────▶│ arm-core (FastAPI) │
└──────────────┘ │ REST /api/v1, policy-слой, │
│ единственный писатель в БД │
└───────┬──────────┬───────────┘
│ │
PostgreSQL │ │ S3 (uploads/ results/ exports/ archive/)
│ │
NATS JetStream ◀─────────────────┘ 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(черновик; финализировать в срезе 2). Правила распознавания заголовков/группировок —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)
| Поток | Subject | Кто пишет | Полезная нагрузка |
|---|---|---|---|
| запрос | recognition.tabular.request / recognition.image.request |
arm-core (outbox из recognition_job) |
job_id, file_id, s3 (bucket/key исходника), file_type, schema_version, reply_deadline |
| результат | recognition.result |
tabular-parser / scan-gateway | job_id, status (done/error), error_code, service_name/version, result_s3 (bucket/key JSON), result_schema_version, metrics, group_schema (только tabular) |
Правила:
recognition_jobсоздаётся в статусеqueuedв одной транзакции соstored_file; фоновый публикатор переводит вpublished(request_msg_id=Nats-Msg-Id). Повтор публикации — поqueuedстарше N секунд.- Результат принимается через durable consumer;
result_msg_id UNIQUE= дедуп. Послеmax_deliver→dlq; replay = сброс вqueuedсattempt + 1. - Результат одного файла содержит N документов; ядро на каждый создаёт
documentи маппит данные. - Сервисы не знают про БД и про бизнес-смысл: они возвращают структуру файла; нормализация счетов, резолв контрагентов, проверки — в ядре.
Схема ключей S3
{bucket_role}/{file_id}/{job_id}/{name}; роли: uploads (исходники, срок — весь срок хранения), results
(нормализованные JSON; ретеншн — ARCH-010, открыт), exports (7 дней), archive (data lake). Все объекты регистрируются
в s3_object (sha256, size, expires_at). Сырой ответ OCR raw/{job_id}.json — по конвенции, без записи в БД.
Аутентификация и авторизация
- 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 (предложение; подстроить под шаблон команды)
Ниже — структура репозитория arm-core; tabular-parser и scan-gateway — отдельные репозитории по тому же шаблону
без frontend/.
backend/app/
api/ # роутеры FastAPI по контекстам: directories, files, documents, consolidation, kfo, ex, audit, users
domain/ # чистая предметная логика без I/O: osv parsing rules, period normalize, matching, elimination,
# checks, thresholds, status machine, policy
services/ # use-cases: оркестрация domain + repositories + outbox
repositories/ # SQLAlchemy, запросы-вычисления (сводная ОСВ, шахматка) — SQL + pandas там, где уместно
models/ # ORM 1:1 с DBML v5.13; имена таблиц/колонок — как в DBML
integrations/ # nats (outbox publisher, result consumer), s3, raisa
migrations/ # alembic
frontend/
openspec/
tests/ # фикстуры — из ../fixtures (ARM_KA_FIXTURES)
Принципы: domain/ тестируется без БД на фикстурах из ../fixtures; вычисляемые объекты консолидации возвращаются
как pydantic-модели и не имеют ORM-таблиц; все мутации идут через services/ с записью audit_event.
Статусы и машины состояний
Три независимые машины: file_status (техника файла), document_status (проверка аналитиком), consolidation_status
(маршрут КФО) + recognition_status (жизненный цикл задания). Переходы — 05-domain-rules.md §9. Реализовать как
явные таблицы переходов в domain/, а не if-ы по роутерам.
Что ожидаем от платформы (для запроса ресурсов)
PostgreSQL 15+ (одна БД, схема public), NATS JetStream (один stream RECOGNITION, 3 subject), S3-совместимое
хранилище (4 бакета по ролям или один бакет с префиксами + lifecycle), Gitea + CI (lint, pytest, миграции с нуля,
сборка образов), реестр образов. Секреты — через переменные окружения.