Handle cashback commission imports, include commissions in analytics with separate investment metrics, and expose commission/version details in the UI. Made-with: Cursor
13 KiB
Инструкция агента: конвертация банковской выписки ВТБ (PDF → JSON)
Цель
Преобразовать PDF-выписку банка ВТБ в JSON-файл строго по схеме 1.0.
На выходе — валидный JSON-файл без потерь операций, с правильными суммами в копейках и корректной проверкой баланса.
Схема JSON (schema 1.0)
{
"schemaVersion": "1.0",
"bank": "VTB",
"statement": {
"accountNumber": "string",
"currency": "RUB",
"openingBalance": integer,
"closingBalance": integer,
"exportedAt": "ISO 8601 string"
},
"transactions": [
{
"operationAt": "ISO 8601 string",
"amountSigned": integer,
"commission": integer,
"description": "string"
}
]
}
Шаг 1. Извлечение данных из PDF
1.1 Шапка выписки
Извлечь из заголовочного блока:
| Поле PDF | Поле JSON | Примечание |
|---|---|---|
| Номер счёта | statement.accountNumber |
строка, без пробелов |
| Баланс на начало периода | statement.openingBalance |
перевести в копейки (× 100) |
| Баланс на конец периода | statement.closingBalance |
перевести в копейки (× 100) |
| Поступления | только для валидации | не записывать в JSON |
| Расходные операции | только для валидации | не записывать в JSON |
| Период выписки | используется для валидации | формат ДД.ММ.ГГГГ – ДД.ММ.ГГГГ |
1.2 Поле exportedAt
Взять дату и время первой строки таблицы операций (самая поздняя по дате операции).
Формат: ISO 8601 с московским часовым поясом: YYYY-MM-DDTHH:MM:SS+03:00
Шаг 2. Извлечение операций
2.1 Столбцы таблицы в PDF
Каждая строка операции содержит:
| Столбец PDF | Описание |
|---|---|
| Дата и время операции | дата + время совершения операции |
| Дата обработки банком | дата, когда банк обработал операцию |
| Сумма операции в валюте операции | знаковая сумма (- = расход, без знака = приход) |
| Приход (в валюте счёта) | сумма, если поступление |
| Расход (в валюте счёта) | сумма, если списание |
| Комиссия | комиссия банка |
| Описание операции | текстовое описание |
2.2 Правила маппинга
operationAt — из столбца «Дата и время операции», формат ISO 8601 + московский TZ:
29.03.2026 20:38:01 → 2026-03-29T20:38:01+03:00
amountSigned — сумма в копейках (целое число):
- Если операция — приход: значение положительное
- Если операция — расход: значение отрицательное
- Допустим специальный кейс:
amountSigned = 0, только еслиcommission > 0и вdescriptionесть подстрока «Зачисление» (без учёта регистра) - Определять знак по столбцу «Приход»/«Расход», а не по знаку в PDF (там
-стоит у расходов, но надёжнее смотреть, в какой колонке стоит сумма) - Перевод в копейки: умножить на 100 и округлить до целого (
round(amount * 100)) - Разделитель дробной части в PDF — точка или запятая — оба варианта обрабатывать
commission — комиссия в копейках (целое число):
- Если в PDF стоит
0.00→ записать0 - Если указана ненулевая комиссия → перевести в копейки аналогично
amountSigned - Комиссия всегда неотрицательная
description — полный текст описания операции, строка:
- Убрать лишние пробелы и переносы строк (привести к одной строке)
- Не обрезать и не сокращать
- Сохранить кириллицу и спецсимволы как есть
2.3 Порядок операций
Операции записывать в том же порядке, что в PDF (от новых к старым, сверху вниз).
Шаг 3. Валидация перед записью
3.1 Проверка баланса
Вычислить:
income_sum = сумма всех amountSigned > 0 (в копейках)
expense_sum = сумма всех amountSigned < 0 (в копейках, отрицательная)
Проверить, что:
openingBalance + income_sum + expense_sum == closingBalance
Если не совпадает — это нормально и объясняется поведением ВТБ:
ВТБ формирует итоги в шапке по дате обработки банком, а не по дате операции.
Операции с датой обработки вне периода выписки включаются в список транзакций, но не учитываются в строках «Поступления» и «Расходные операции» в шапке.
Правильная проверка:
- Определить границы периода из шапки PDF (
дата_начала,дата_конца) - Из
ПоступленияиРасходные операциивычислить ожидаемый net:
expected_net = openingBalance_kopecks + income_pdf_kopecks - expense_pdf_kopecks - Убедиться, что
expected_net == closingBalance(это всегда верно если данные из PDF прочитаны правильно) - Разница между суммой всех транзакций и
(closingBalance - openingBalance)— это сумма операций, обработанных банком вне периода. Это не ошибка.
3.2 Проверка полноты
- Подсчитать количество извлечённых транзакций
- Пройтись по всем страницам PDF и убедиться, что ни одна строка таблицы не пропущена
- Особое внимание: операции в конце страницы и в начале следующей (частые точки потери данных)
3.3 Проверка типов
Убедиться, что:
openingBalance,closingBalance— целые числа (int)amountSigned,commission— целые числа (int)operationAt,exportedAt— строки в формате ISO 8601 с+03:00description— строка (неnull, не пустая)
Шаг 4. Частые ошибки — предотвращение
| Ошибка | Причина | Как избежать |
|---|---|---|
| Сумма в рублях вместо копеек | Забыли умножить на 100 | Всегда применять round(x * 100) |
| Дробные числа вместо int | Потеря точности float | Использовать round(), результат кастовать в int |
Неверный знак amountSigned |
Ориентировались на знак в PDF | Определять знак по столбцу «Приход»/«Расход» |
| Потеря операций на стыке страниц | Парсинг постраничный | Читать PDF целиком, не разбивать по страницам |
| Обрезанное описание | Перенос строки в ячейке PDF | Объединять строки одной ячейки в одну строку |
| Комиссия со знаком минус | Перепутали знак | Комиссия всегда >= 0 |
amountSigned = 0 без условия |
Импорт отклонит файл | Разрешать amountSigned = 0 только при commission > 0 и наличии «Зачисление» в description |
Неверный exportedAt |
Взяли дату начала периода | Брать дату и время первой (самой новой) операции в таблице |
| Расхождение баланса | Не учли операции вне периода | Это не ошибка — см. Шаг 3.1 |
| Лишние пробелы в описании | PDF добавляет пробелы при переносе | Применять .strip() и нормализацию пробелов |
Шаг 5. Запись файла
- Имя файла: то же, что у исходного PDF, с заменой расширения
.pdf→.json
Пример:file-18.pdf→file-18.json - Кодировка: UTF-8
- Формат: JSON с отступами (indent = 2)
ensure_ascii = false— кириллица записывается как есть, не экранируется
Пример корректной транзакции
{
"operationAt": "2026-03-29T20:38:01+03:00",
"amountSigned": -500000,
"commission": 0,
"description": "Оплата товаров и услуг. IP KOVTUN L.B.. по карте *9058"
}
Расшифровка: списание 5 000.00 ₽ = -500 000 копеек, комиссии нет.
Пример корректного поступления
{
"operationAt": "2026-03-24T15:09:41+03:00",
"amountSigned": 9537522,
"commission": 0,
"description": "Поступление заработной платы. 0726 НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ УНИВЕРСИТЕТ \"ВЫСША Поступление заработной платы/иных выплат Salary по реестру Z_0000005862_20260323_001_01 от 23.03.2026. Без НДС.."
}
Расшифровка: поступление 95 375.22 ₽ = +9 537 522 копейки.
Пример корректного кэшбэка (сумма в commission)
{
"operationAt": "2026-04-07T19:56:59+03:00",
"amountSigned": 0,
"commission": 592000,
"description": "Зачисление кешбэка по программе лояльности."
}
Расшифровка: для такого кейса amountSigned = 0 допустим, потому что есть commission > 0 и маркер «Зачисление» в описании.
Итоговый чеклист перед отдачей файла
schemaVersion="1.0"bank="VTB"accountNumberсовпадает с PDFopeningBalanceиclosingBalanceв копейках, совпадают с PDFexportedAt= дата и время первой операции в таблице, формат ISO 8601 +03:00- Количество транзакций совпадает с количеством строк в PDF
- Все
amountSignedиcommission— целые числа - Если
amountSigned = 0, тоcommission > 0и вdescriptionесть «Зачисление» (без учёта регистра) - Поступления и баланс проверены (Шаг 3.1)
- Файл сохранён в UTF-8, кириллица не экранирована