docs: adds rules and agents specs

This commit is contained in:
Anton
2026-02-27 19:08:55 +03:00
commit 9551b93a09
12 changed files with 1151 additions and 0 deletions

147
docs/backlog/db.md Normal file
View File

@@ -0,0 +1,147 @@
# Модель БД (PostgreSQL)
## Общие принципы
- Используется PostgreSQL, развёрнутый локально (например, на Synology).
- Основные сущности:
- `accounts` — банковские счета пользователя;
- `transactions` — движения средств по счетам;
- `category_rules` — правила автоматической категоризации транзакций (подготовка к SPA-редактору правил).
- Все суммы хранятся в минорных единицах (копейки) как `BIGINT`.
- Время хранится в `TIMESTAMPTZ` (временная зона сохраняется).
## Таблица `accounts`
Предназначение: хранение информации о счетах, по которым загружаются выписки.
Структура:
- `id BIGSERIAL PRIMARY KEY` — внутренний идентификатор счёта в системе.
- `bank TEXT NOT NULL` — код/имя банка (например, `"VTB"`).
- `account_number TEXT NOT NULL` — номер счёта в банке (как в выписке).
- `currency TEXT NOT NULL` — код валюты счёта (например, `"RUB"`).
Ограничения и индексы:
- Уникальность комбинации `(bank, account_number)` — один и тот же счёт в банке не должен дублироваться.
Рекомендуемый DDL:
```sql
CREATE TABLE accounts (
id BIGSERIAL PRIMARY KEY,
bank TEXT NOT NULL,
account_number TEXT NOT NULL,
currency TEXT NOT NULL
);
CREATE UNIQUE INDEX ux_accounts_bank_number
ON accounts(bank, account_number);
```
## Таблица `transactions`
Предназначение: хранение всех операций по счетам с привязкой к `accounts`.
Структура:
- `id BIGSERIAL PRIMARY KEY` — внутренний идентификатор транзакции.
- `account_id BIGINT NOT NULL` — внешний ключ (FK) на `accounts(id)`;
каждая транзакция жёстко привязана к одному счёту.
- `operation_at TIMESTAMPTZ NOT NULL` — дата и время операции.
- `amount_signed BIGINT NOT NULL` — сумма операции в копейках; знак отражает тип движения (приход/расход).
- `commission BIGINT NOT NULL` — комиссия по операции в копейках.
- `description TEXT NOT NULL` — описание операции из выписки.
- `direction TEXT NOT NULL` — направление движения:
- `"income"` — приход;
- `"expense"` — расход;
- `"transfer"` — перевод между своими счетами / на другие свои счета;
- `"internal"` — служебные/внутренние движения (опционально, по мере необходимости).
- `fingerprint TEXT NOT NULL` — вычисляемый хэш для обеспечения идемпотентности импорта.
- `category_id BIGINT` — ссылка на таблицу категорий (будет определена позже).
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания записи в БД.
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время последнего обновления записи.
Ограничения и индексы:
- Внешний ключ `account_id` ссылается на `accounts(id)` и обеспечивает целостность (нельзя создать транзакцию для несуществующего счёта).
- Уникальный индекс `(account_id, fingerprint)` обеспечивает идемпотентность: одна и та же операция не может быть загружена дважды.
- Опционально — CHECK-ограничение на поле `direction`.
Рекомендуемый DDL:
```sql
CREATE TABLE transactions (
id BIGSERIAL PRIMARY KEY,
account_id BIGINT NOT NULL REFERENCES accounts(id),
operation_at TIMESTAMPTZ NOT NULL,
amount_signed BIGINT NOT NULL,
commission BIGINT NOT NULL,
description TEXT NOT NULL,
direction TEXT NOT NULL,
fingerprint TEXT NOT NULL,
category_id BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX ux_transactions_account_fingerprint
ON transactions(account_id, fingerprint);
ALTER TABLE transactions
ADD CONSTRAINT chk_transactions_direction
CHECK (direction IN ('income', 'expense', 'transfer', 'internal'));
```
## Таблица `category_rules`
Предназначение: хранение правил автоматической категоризации транзакций на основе текста описания.
Структура:
- `id BIGSERIAL PRIMARY KEY` — идентификатор правила.
- `pattern TEXT NOT NULL` — строка-шаблон, вводимая пользователем через SPA (в простом виде).
- `match_type TEXT NOT NULL` — тип сопоставления:
- `"contains"` — простое вхождение подстроки;
- `"starts_with"` — строка начинается с шаблона;
- `"regex"` — регулярное выражение (формируется и/или проверяется в коде на основе пользовательского ввода).
- `category_id BIGINT NOT NULL` — ссылка на категорию (таблица категорий будет описана отдельно).
- `priority INT NOT NULL DEFAULT 0` — приоритет правила; чем выше число, тем раньше правило применяется при конфликте.
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — активно ли правило.
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания правила.
Логика приоритета:
- При авто-категоризации для транзакции ищутся все правила, которые ей соответствуют.
- Если совпало несколько правил, выбирается правило с максимальным `priority`.
- Это позволяет задавать общие правила с низким приоритетом и более точные (например, по конкретным мерчантам) с высоким приоритетом.
Рекомендуемый DDL:
```sql
CREATE TABLE category_rules (
id BIGSERIAL PRIMARY KEY,
pattern TEXT NOT NULL,
match_type TEXT NOT NULL,
category_id BIGINT NOT NULL,
priority INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
## Взаимосвязь JSON → БД при импорте
1. По полям `bank` и `statement.accountNumber` ищется или создаётся запись в `accounts`.
2. Для каждой транзакции из `transactions`:
- преобразуются суммы в копейки (`amountSigned`, `commission``BIGINT`);
- вычисляется `fingerprint` на основе комбинации полей (например,
`accountNumber + operationAt + amountSigned + commission + normalizedDescription`);
- определяется `direction` по знаку суммы и/или шаблонам текста (приход/расход/перевод);
- выполняется попытка вставки в `transactions`;
- при срабатывании уникального ограничения `(account_id, fingerprint)` запись считается дубликатом и пропускается.
- При импорте новых транзакций is_category_confirmed всегда = FALSE.
- При автокатегоризации обычными правилами, если правило “жёсткое” (не маркетплейс) — TRUE, для маркетплейсов — всегда FALSE.
3. Категория (`category_id`) пока может оставаться `NULL` и заполняться на следующих этапах
(правила, ИИ-агент, ручное редактирование в SPA).