docs: описать проект в CLAUDE.md и ARCHITECTURE.md

Домен, стек, слои, команды, тесты, базовая ветка test.
.claude/code-paths: api/src/, api/alembic/, web/src/.

Refs #198

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-25 20:24:22 +04:00
parent cf85326558
commit 5190e5ef13
3 changed files with 68 additions and 19 deletions

3
.claude/code-paths Normal file
View File

@ -0,0 +1,3 @@
api/src/
api/alembic/
web/src/

View File

@ -1,15 +1,42 @@
# Архитектура
{{Карта модулей на одну страницу: что где лежит, границы, потоки данных. Читается агентом перед architectural brainstorming. Обновляется вместе со спеками.}}
Монорепозиторий: `api/` (FastAPI) отдаёт REST, WebSocket и собранный фронт; `web/` — SPA на React. Основная бизнес-логика — PL/pgSQL-функции схемы `v3` в Postgres. Python-слой проверяет роли, валидирует запрос, вызывает функцию и маппит её ошибки.
Поток редактирования ячейки: `RealtimeTable` → WebSocket `/api/v1/ws/form/{id}/sheet/{sheet}` (`cell_edit_start` — блокировка, `cell_updated`) → `SheetService` → `v3.upd_form_cell(s)`. Функция проверяет роль и окно этапа и пишет аудит `v3.log_event`, после чего изменение рассылается остальным подключённым.
## Модули
| Модуль | Ответственность | Зависит от |
|---|---|---|
| | | |
| `api/src/api/v1/` | Роутеры: auth, users, admin, audit, forms, projects, form_phases (`/stages`), phase_template, export, websocket, org_unit, vsp (`/dict/info`), expense_item, summary (`/svod`) | services, deps |
| `api/src/api/v1/deps.py` | Текущий пользователь из JWT, гарды ролей | repository.user_repository, core.security |
| `api/src/api/v1/websocket.py` | ConnectionManager, блокировки ячеек в памяти, события форм и FORM_4/проектов, синхронизация сателлитов AHR | services |
| `api/src/services/` | Сервисы по сущностям, контроль доступа (`access_control_service.py`) | repository |
| `api/src/services/export_service/` | Фоновые выгрузки в Excel: ProcessPool, таблица `export_job`, файлы в `tmp/exports`, маппинг колонок `export_mappings.py` | repository, openpyxl/XlsxWriter |
| `api/src/repository/` | SQLAlchemy-запросы и вызовы SQL-функций `v3.*`, `SET LOCAL app.user_id` | db |
| `api/src/db/` | engine, `Base`, сессия, ORM-модели (36) | core.config |
| `api/src/domain/` | pydantic-схемы API, enums | — |
| `api/src/core/` | Настройки, JWT/argon2, исключения и маппинг ошибок Postgres в HTTP | — |
| `api/alembic/` | Миграции (около 49 ревизий); `versions/sql/*.sql` — DDL, функции, сиды, около 43 тыс. строк | Postgres |
| `web/src/app/` | Роуты, AuthProvider (токены в localStorage) | pages |
| `web/src/pages/` | Задачи (формы), карточка формы или проекта, проекты и их своды, свод 1 и 2, админка, справочники | components, api |
| `web/src/components/RealtimeTable/` | Редактируемая таблица листа: WebSocket, блокировки, конфиги колонок `constants/FORM_*` | api |
| `web/src/api/` | axios-клиент `/api/v1`, модули по сущностям, WebSocket выгрузок | — |
## Границы
- Что не должно импортировать что.
- Роутеры не ходят в БД напрямую, только через сервисы и репозитории. Сейчас нарушают `admin.py`, `main.py` (engine) и `websocket.py` (бизнес-логика в слое API).
- Проверка прав на ячейки и окна этапов — в SQL. Python её не дублирует, только маппит ошибки.
- Изменения схемы и функций — только новой миграцией alembic; старые миграции не правятся.
- Фронт обращается только к `/api/v1` через `web/src/api/`.
## Открытые вопросы
-
- Prod-авторизация через Keycloak (`raisa_fastapi_protected_api`) закомментирована в `main.py`, `deps.py`, `auth.py`; сейчас всегда работает локальный JWT. Как будет устроен prod?
- `websocket.py:333`, `:357`: условие `form_key != form_key` всегда ложно, поэтому блокировки сравниваются по `line_id` между всеми формами; `release_locks_for_row` ничего не возвращает.
- Раскладка колонок дублируется: `web/.../constants/FORM_1|2|4/*.js` (около 4 тыс. строк каждый) и `api/.../export_mappings.py`. Источник правды не определён.
- Названия форм в БД и UI расходятся (FORM_2, FORM_4).
- Мёртвый код фронта: `pages/TablePage.jsx`, `FormsPage.jsx`, `TablesTest.jsx`, `TableTempPage/`, `api/tables.js`, `api/client_form.js`, `api/ws.js`, `constants/FORM_1/*_NO_LOCAL.js`.
- В `package.json` не объявлены `@mui/x-date-pickers`, `@mui/icons-material`; tailwind/daisyui не используются.
- `Settings.CORS_ORIGINS` не применяется (в `main.py` `allow_origins=["*"]`), `APP_ENV` не читается, `JWT_SECRET` по умолчанию небезопасный.
- `/` монтируется дважды в `api/src/main.py:289` и `:291`.
- Интеграционные тесты зависят от данных в общей БД и не изолированы; `downgrade()` в миграциях пустые.
- Python-линтеров и проверок CI в репозитории нет.

View File

@ -1,32 +1,51 @@
# harness-describe
# DFiP_Budget_planing
## Что это
{{ОДНИМ АБЗАЦЕМ: что делает система, для кого, главный сценарий.}}
Система бюджетного планирования ДФиП РСХБ (платформа raisa). Исполнители ССП и региональных филиалов (РФ) совместно, в реальном времени заполняют бюджетные формы (сметы) по листам, статьям расходов и кварталам. Что и когда может править каждая роль, задают этапы (окна редактирования колонок). ДФиП и ДУРРС корректируют (секвестр), администратор ведёт пользователей, оргструктуру, шаблоны этапов и журнал аудита. Поверх форм строятся своды 1 и 2, реестр проектов РФ (открытие, закрытие и переезд ВСП и т.п.) с отчётами по годам, справочник ВСП и фоновая выгрузка в Excel.
Формы (`v3.form_type`): FORM_1 «Смета ГО» (AHR/CAP/OPER/SMETA, AHR и CAP делятся по direction Support/Development), FORM_2 «ПД» (плюс сателлиты AHR_LIMIT/RENT/UTILITY/SECURITY, OTCH9F), FORM_3 «Отчёт РФ развитие» (хранится в project → rf_project_year → rf_project_report), FORM_4 «Смета РФ поддержка» (плюс STRUCTURE — программы и проекты). Названия на фронте (`web/src/constants/constants.jsx`) расходятся с БД, источник истины — БД.
Роли (`api/src/db/models/role.py`): 1 ADMIN; 2 EXECUTOR_DFIP (несколько ССП/РФ); 3 EXECUTOR_RF (одна оргединица); 4 EXECUTOR_DURRS.
## Стек и ограничения
- {{Язык, сборка, тесты — например: Python 3.12, uv, pytest.}}
- {{Жёсткие ограничения среды исполнения — без чего код не должен собираться.}}
- Backend `api/`: Python, FastAPI 0.111, SQLAlchemy 2 async + asyncpg, Alembic, pydantic-settings, JWT (python-jose, HS256) + argon2.
- **БД — только PostgreSQL**, всё в схеме `v3`. Бизнес-логика в основном в PL/pgSQL-функциях `v3.add_*`/`upd_*`/`del_*`/`v_*`, их зовут через `text("SELECT ... FROM v3.fn(...)")` из `api/src/repository/`. Функции живут в `api/alembic/versions/sql/*.sql` и переопределяются `CREATE OR REPLACE` в последующих миграциях: актуальна последняя версия. SQLite в `DATABASE_URL` по умолчанию — ловушка, на нём приложение не работает.
- Frontend `web/`: React 18 на Rsbuild, **JavaScript (JSX)**, хотя есть tsconfig. UI — MUI и material-react-table. Основная таблица — собственный `components/RealtimeTable/`. Состояние в React Context, роутинг react-router-dom 6, axios. Biome для линта. Tailwind/daisyui остались от шаблона и не используются.
- Платформа raisa: обязателен `api/run_server.py`; хост и порт из `SERVER__HOST`/`SERVER__PORT`. CI — внешний шаблон `cicd/ci/fastapi-app.yml` (`.gitlab-ci.yml`), ресурсы в `raisa_config.yml`. Собранный фронт отдаёт сам API из `api/web` (StaticFiles).
- Зависимости ставятся из корпоративного Nexus (`setup/pip.conf`, `setup/.npmrc`). `raisa-fastapi-protected-api` есть только там.
- cwd процессов API, тестов и alembic — `api/`: пути `alembic.ini`, `.env`, `web`, `back_static`, `tmp/exports` относительные.
- Состояние WebSocket-блокировок ячеек и менеджер выгрузок живут в памяти процесса: один процесс, без нескольких воркеров.
- Настройки (`api/src/core/config.py`): `OPENBAO__SETTINGS__*` или короткое имя (`DATABASE_URL`, `DEBUG`, `DATABASE_SCHEMA`). `DEBUG` по умолчанию true. При false на старте выполняются `alembic upgrade head` и цикл чистки аудита.
## Архитектура
- `src/core/` (или {{ваш путь}}) — чистая логика без зависимостей от фреймворка/платформы. Покрывается тестами полностью.
- `src/{{adapter}}/` — тонкая обёртка над платформой. Тестируется руками или интеграционно.
- {{Ключевые интерфейсы/порты между ними, если есть.}}
Подробная карта — в ARCHITECTURE.md.
- `api/src/api/v1/` — роутеры (`/api/v1/...`) и `deps.py` (текущий пользователь, гарды `require_admin`/`require_executor*`). `websocket.py` — realtime-редактирование и блокировки ячеек.
- `api/src/services/` — сервисы `*Service(db)`; `export_service/` — Excel-выгрузки в ProcessPool.
- `api/src/repository/` — `*Repository(db)`: SQLAlchemy и вызовы SQL-функций. Текущий пользователь передаётся в SQL через `SET LOCAL app.user_id` (для аудита `v3.log_event`).
- `api/src/db/models/` — ORM (36 моделей), `api/src/domain/schemas.py` — pydantic-схемы, `api/src/core/` — конфиг, безопасность, ошибки.
- Права на ячейки проверяет SQL (`role_not_allowed:`, `window_closed:`). `core/exception_handlers.py` по тексту ошибки Postgres превращает их в 403/409/400/404/422.
- `web/src/app/Routes.jsx` — страницы, `web/src/api/*.js` — клиент API, `web/src/components/RealtimeTable/` — редактируемая таблица, конфиги колонок по формам в `constants/FORM_*`.
## Команды
- `{{npm test / pytest}}` — весь набор тестов. Зелёным считается только весь набор.
- `{{npm run build / ...}}` — сборка.
- `{{npm run dev / ...}}` — локальный запуск.
- Установка: `cd setup && ./setup.sh` (venv в `api/.venv`, `npm i` в `web/`). Для тестов дополнительно `pip install -r api/requirements-dev.txt`.
- API: `cd api && python run_server.py` (нужен Postgres в `DATABASE_URL`). Миграции: `cd api && alembic upgrade head`.
- Web: `cd web && npm run start` (dev), `npm run build` (результат в `web/dist`). Линт: `cd web && npx biome check src`.
- Тесты backend: `cd api && pytest` — весь набор. Зелёным считается только весь набор. `pytest tests/unit` — без БД. `pytest tests/integration` — нужен живой Postgres; `--init_db` накатывает миграции и сид `tests/sqls/fixture.sql`.
- Тесты frontend: `cd web && node --test src/components/RealtimeTable/utils/*.test.js`.
## Тесты
- Golden-тесты: `tests/fixtures/<name>.*` + эталон `<name>.expected.*`. Внешние сервисы в тестах мокаются.
- {{Где лежит тестовое окружение (vault, БД, песочница).}}
- `api/tests/unit/` — моки сессии и репозиториев (`AsyncMock`, `monkeypatch`), БД не нужна.
- `api/tests/integration/` — `TestClient(app)` с полным lifespan, сессия с rollback. Фикстуры `admin_tokens`/`isp_tokens` (`api/tests/conftest.py`, `tests/integration/conftest.py`). Тестовое окружение — Postgres из `DATABASE_URL`, пароли `OPENBAO__TEST_ADMIN_PASSWORD`/`OPENBAO__TEST_EXECUTOR_PASSWORD`. Часть тестов делает `skip` при отсутствии данных.
- Новая логика в SQL-функциях покрывается интеграционными тестами через API. Логику без БД выносить в сервисы и покрывать unit-тестами.
## Первая подсистема
{{Что делается первым и почему. Тестовая инфраструктура — Task 1 первого плана.}}
## Ветки и PR
- **Базовая ветка — `test`**, не `main`: в неё идут все PR, `main` — устаревший шаблон. Везде, где процесс обвязки говорит «main» (база worktree, PR, `openspec archive`, `git pull --ff-only` в основном checkout), читать `test`.
- Хук guard-main защищает только main/master, `test` он не охраняет. Прямые коммиты в `test` запрещены так же, как в main; исключение — только коммит `openspec archive`.
- PR создаётся с `--base test`.
## OpenSpec и superpowers
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.
- Когда brainstorming классифицировал задачу как architectural, структуру change создать через `openspec new change <id>` (или `/opsx:propose <id>`), а содержимое proposal.md, specs/ и design.md написать по итогам brainstorming. tasks.md из OpenSpec не заполнять — задачи живут в плане superpowers.
- `/opsx:apply` не использовать: исполнение идёт через writing-plans и SDD/executing-plans.
- `openspec archive <id>` — после merge PR, как часть чеклиста закрытия.
- Хук require-plan защищает любой каталог `src/` на любой глубине. Если код лежит иначе — перечислить каталоги (префиксы от корня, по одному в строке) в `.claude/code-paths`.
- Хук require-plan защищает каталоги из `.claude/code-paths`: `api/src/`, `api/alembic/` (SQL-логика), `web/src/`.