Files
Anton 172246db0b fix: align docs and code with actual DB schema and format spec
- Rewrite db.md as canonical schema: add categories, sessions tables; add alias to accounts, is_category_confirmed/comment to transactions, FK references to categories(id); mark budgets as post-MVP

- Fix account masking to use fixed 6 asterisks (code + docs)

- Remove budgets from MVP requirements in agent_backend.md

- Add explicit 'not in MVP' note to analytics.md budgets section

- Fix test_Statement.json: convert amounts to kopecks (integers), remove fingerprint fields (computed by backend)

Made-with: Cursor
2026-03-02 11:34:00 +03:00

234 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модель БД (PostgreSQL)
## Общие принципы
- Используется PostgreSQL, развёрнутый локально (например, на Synology).
- Основные сущности:
- `accounts` — банковские счета пользователя;
- `categories` — категории расходов, доходов и переводов;
- `transactions` — движения средств по счетам;
- `category_rules` — правила автоматической категоризации транзакций;
- `sessions` — серверные сессии авторизации.
- Все суммы хранятся в минорных единицах (копейки) как `BIGINT`.
- Время хранится в `TIMESTAMPTZ` (временная зона сохраняется).
## Таблица `accounts`
Предназначение: хранение информации о счетах, по которым загружаются выписки.
Структура:
- `id BIGSERIAL PRIMARY KEY` — внутренний идентификатор счёта в системе.
- `bank TEXT NOT NULL` — код/имя банка (например, `"VTB"`).
- `account_number TEXT NOT NULL` — номер счёта в банке (как в выписке).
- `currency TEXT NOT NULL` — код валюты счёта (например, `"RUB"`).
- `alias TEXT` — человекочитаемый алиас счёта (например, `"Текущий"`, `"Накопительный"`).
При создании счёта через импорт `alias = NULL`; пользователь задаёт его позже через SPA.
Ограничения и индексы:
- Уникальность комбинации `(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,
alias TEXT
);
CREATE UNIQUE INDEX ux_accounts_bank_number
ON accounts(bank, account_number);
```
## Таблица `categories`
Предназначение: хранение категорий для классификации транзакций.
Структура:
- `id BIGSERIAL PRIMARY KEY` — идентификатор категории.
- `name TEXT NOT NULL` — отображаемое имя категории (например, `"Продукты"`, `"ЖКХ"`).
- `type TEXT NOT NULL` — тип категории:
- `"expense"` — расходная категория;
- `"income"` — доходная категория;
- `"transfer"` — переводы между собственными счетами.
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — используется ли категория.
Ограничения:
- CHECK-ограничение: `type IN ('expense', 'income', 'transfer')`.
Рекомендуемый DDL:
```sql
CREATE TABLE categories (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
ALTER TABLE categories
ADD CONSTRAINT chk_categories_type
CHECK (type IN ('expense', 'income', 'transfer'));
```
Начальный набор из 23 категорий заполняется seed-миграцией (см. `category.md`).
## Таблица `transactions`
Предназначение: хранение всех операций по счетам с привязкой к `accounts` и `categories`.
Структура:
- `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` — направление движения:
- `"income"` — приход;
- `"expense"` — расход;
- `"transfer"` — перевод между своими счетами / на другие свои счета.
- `fingerprint TEXT NOT NULL` — вычисляемый хэш для обеспечения идемпотентности импорта.
- `category_id BIGINT REFERENCES categories(id)` — ссылка на категорию; `NULL` для некатегоризированных транзакций.
- `is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE`
признак того, что категория подтверждена пользователем (явно или неявно через правило без `requires_confirmation`).
- `comment TEXT` — пользовательский комментарий к транзакции.
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания записи в БД.
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время последнего обновления записи.
Ограничения и индексы:
- Внешний ключ `account_id` ссылается на `accounts(id)`.
- Внешний ключ `category_id` ссылается на `categories(id)`.
- Уникальный индекс `(account_id, fingerprint)` обеспечивает идемпотентность: одна и та же операция не может быть загружена дважды.
- CHECK-ограничение: `direction IN ('income', 'expense', 'transfer')`.
Рекомендуемый 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 REFERENCES categories(id),
is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
comment TEXT,
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'));
```
## Таблица `category_rules`
Предназначение: хранение правил автоматической категоризации транзакций на основе текста описания.
Структура:
- `id BIGSERIAL PRIMARY KEY` — идентификатор правила.
- `pattern TEXT NOT NULL` — строка-шаблон, вводимая пользователем через SPA (в простом виде).
- `match_type TEXT NOT NULL` — тип сопоставления:
- `"contains"` — простое вхождение подстроки;
- `"starts_with"` — строка начинается с шаблона;
- `"regex"` — регулярное выражение (формируется и/или проверяется в коде на основе пользовательского ввода).
- MVP: при создании правила принимается только `"contains"`. Расширение до `"starts_with"` | `"regex"` запланировано.
- `category_id BIGINT NOT NULL REFERENCES categories(id)` — ссылка на категорию.
- `priority INT NOT NULL DEFAULT 0` — приоритет правила; чем выше число, тем раньше правило применяется при конфликте.
- `requires_confirmation BOOLEAN NOT NULL DEFAULT FALSE`
если `TRUE`, транзакции, категоризированные этим правилом, получают `is_category_confirmed = FALSE`
и требуют ручного подтверждения пользователем. Используется для правил с неоднозначным
соответствием (например, маркетплейсы: OZON, WILDBERRIES).
- `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 REFERENCES categories(id),
priority INT NOT NULL DEFAULT 0,
requires_confirmation BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
## Таблица `sessions`
Предназначение: хранение серверных сессий авторизации с поддержкой таймаута по бездействию.
Структура:
- `id TEXT PRIMARY KEY` — идентификатор сессии (UUID).
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания сессии.
- `last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время последней активности; обновляется при каждом запросе с действительной сессией.
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — флаг активности; устанавливается в `FALSE` при логауте или истечении таймаута.
Рекомендуемый DDL:
```sql
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
is_active BOOLEAN NOT NULL DEFAULT TRUE
);
```
## Таблица `budgets` (после MVP)
> **Не входит в MVP.** Таблица запланирована для поддержки бюджетов/лимитов по категориям
> на будущих этапах. Подробности см. в `analytics.md`, секция 5.3.
## Взаимосвязь JSON → БД при импорте
1. По полям `bank` и `statement.accountNumber` ищется или создаётся запись в `accounts`.
Если счёт создан впервые, `alias = NULL`.
2. Для каждой транзакции из `transactions`:
- суммы в JSON 1.0 уже в копейках — записываются как есть;
- вычисляется `fingerprint` на основе комбинации полей (например,
`accountNumber + operationAt + amountSigned + commission + normalizedDescription`);
- определяется `direction`:
- `amountSigned > 0``"income"`;
- `amountSigned < 0``"expense"`;
- `"transfer"` — определяется по фиксированным ключевым фразам банка в `description`
(например, для ВТБ: "Перевод между своими счетами", "Внутри ВТБ" и т.п.).
Если ключевые фразы не сработали — остаётся `"income"` / `"expense"` по знаку суммы;
- выполняется попытка вставки в `transactions`;
- при срабатывании уникального ограничения `(account_id, fingerprint)` запись считается дубликатом и пропускается;
- при импорте `is_category_confirmed` всегда = `FALSE`, `category_id = NULL`.
3. Импорт атомарный: при ошибке валидации любой транзакции весь файл откатывается (возвращается `422`).
4. Категория (`category_id`) заполняется на следующих этапах:
- автокатегоризация по `category_rules`:
- если у сработавшего правила `requires_confirmation = FALSE``is_category_confirmed = TRUE`;
- если `requires_confirmation = TRUE``is_category_confirmed = FALSE` (требуется ручное подтверждение);
- ручное редактирование в SPA.