106 lines
9.7 KiB
Markdown
106 lines
9.7 KiB
Markdown
# 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, миграции с нуля,
|
||
сборка образов), реестр образов. Секреты — через переменные окружения.
|