arm-ka-docs/context/03-architecture.md
2026-10-02 23:02:44 +04:00

9.7 KiB
Raw Blame History

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 в S3 results/, публикует recognition.result. Контракт результата — docs/contracts/recognition-result.schema.json (черновик; финализировать в срезе 2). Правила распознавания заголовков/группировок — rules/*.yaml.
  • scan-gateway — адаптер ИС Сканирование: переименовать файл UUID_имя.pdf, положить в «Входящие», опросить «Исходящие/UUID/UUID.json», сохранить сырой ответ в S3 raw/{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 — по конвенции, без записи в БД.

Аутентификация и авторизация

  1. SPA получает JWT от РАИСА; каждый запрос несёт Authorization: Bearer.
  2. arm-core валидирует подпись/срок, извлекает email, находит app_user (is_active); нет пользователя — 403.
  3. Права: effective_permissions = ∪ role_permission(roles(user)). Проверка в зависимости FastAPI require(permission_code).
  4. Policy-слой поверх прав (отдельный модуль, чистые функции): видимость по филиалу (regional_office), по ЗО (business_segment), допустимость действия по статусу версии (consolidation_status) и по активной блокировке (consolidation_lock). Право говорит «может», policy — «эту ли и сейчас ли».
  5. Локальная разработка: заглушка-эмитент 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, миграции с нуля, сборка образов), реестр образов. Секреты — через переменные окружения.