# Импорт банковских выписок за последний год **Создано:** 2026-06-23 **Цель:** Получить все транзакции за последний год (середина 2025 — июнь 2026) в Budget App, автоматизируя импорт банковских выписок как можно полнее. ## Проблема В `Budget.xlsx` данные заканчиваются в **середине 2025** (май-июнь 2025, в зависимости от счёта). Последние ~12 месяцев транзакций не внесены в Excel. Вручную вспомнить каждую трату за год — нереалистично. ## Существующий инструмент: budget-bank-statement-converter **Путь:** `~/Developer/budget-bank-statement-converter/` **Язык:** Swift (macOS command-line tool) **Формат вывода:** CSV с колонками `[дата, сумма, дебет, кредит, категория, комментарий, курс]` — совпадает с форматом Excel. ### Поддерживаемые банки | Банк | Формат входа | Конфиг | Статус | |------|-------------|--------|--------| | Demir | CSV (из PDF → Adobe Extract → CSV) | `demir-config.json` | ✅ Работает | | Сбер | CSV (выгрузка из СберБизнес) | `sber-config.json` | ✅ Работает | | Тинькофф | CSV (выгрузка из Тинькофф) | `tinkoff-config.json` | ✅ Работает | | ВТБ | CSV (из PDF → Adobe Extract → CSV) | `vtb-config.json` | ✅ Работает | | Альфа | — | — | ❌ `fatalError("Alfa not implemented")` | ### Как работает 1. **PDF → CSV**: использует Adobe PDF Extract API (`pdfservices-api-credentials.json`) — загружает PDF, получает ZIP с CSV-таблицами. 2. **CSV → формат App**: разбирает CSV, маппит категории через regex-конфиг, нормализует double-entry (дебет/кредит/конверсии). 3. Если нужно — автоматически конкатенирует `fileoutpart0001.csv`… файлы. 4. Использует OpenAI GPT-3.5-turbo для AI-категоризации (закомментировано, `aiMaxTokens = 80`). ### Что нужно для использования - Xcode (для сборки Swift-проекта) - `OPENAI_API_KEY` в env (не обязательно, выключено) - `pdfservices-api-credentials.json` для Adobe Extract - JSON config для каждого банка: `Сбер config`, `Tinkoff config`, `Demir config`, `VTB config` ## План импорта ### Шаг 1: Получить выписки из банков **Что нужно выгрузить за июнь 2025 — июнь 2026:** | Счёт | Банк | Как получить выписку | |------|------|---------------------| | Нал RUB | Наличные | Ручной ввод (см. ниже про наличные) | | Нал KGS | Наличные | Ручной ввод | | Нал USD | Наличные | Ручной ввод | | Нал KZT | Наличные | Ручной ввод | | Demir ИП | Demir | CSV (интернет-банк/моб. приложение) | | Demir ИП USD | Demir | CSV (интернет-банк/моб. приложение) | | Demir KGS | Demir | CSV (интернет-банк/моб. приложение) | | Demir USD | Demir | CSV (интернет-банк/моб. приложение) | | Тинькофф Black | Тинькофф | CSV (выгрузка из Тинькофф) | | Тинькофф Кредитка | Тинькофф | CSV (выгрузка из Тинькофф) | | Сбер | Сбер | CSV (СберБизнес / PDF) | | Сбер Кредитка | Сбер | CSV (СберБизнес / PDF) | | Альфа | Альфа | CSV — но конвертер Альфу не поддерживает | | Альфа Кредитка | Альфа | — | | ВТБ | ВТБ | PDF → Adobe Extract → CSV | | ВТБ Кредитка | ВТБ | PDF → Adobe Extract → CSV | ### Шаг 2: Конвертировать выписки в CSV формата App Запуск для каждого банка: ```bash ./budget-bank-statement-converter --bank --account ``` Выход: `_processed.csv` с колонками `дата, сумма, дебет, кредит, категория, комментарий, курс`. ### Шаг 3: Написать Python импортёр CSV → Budget App DB Существующий `xlsx_import.py` читает из Excel. Нужен новый: `csv_bank_import.py`, который: - Читает CSV в формате App (колонки из `csvHeaders` в `Common.swift`) - Привязывает `дебет`/`кредит` к существующим счетам в БД (по имени) - Маппит категории из CSV на существующие категории в БД (по имени подкатегории) - Игнорирует дубликаты (hash по `date + amount + source + dest + comment`) - Поддерживает несколько CSV-файлов за раз (много выписок) - Выводит отчёт: сколько добавлено, сколько пропущено (дубликаты), какие категории не найдены ### Шаг 4: Импортировать в Budget App ```bash cd ~/Developer/budget-app uv run python src/budget/importers/csv_bank_import.py ... ``` ### Шаг 5: Дописать недостающее в Swift-конвертере - **AlfaToCSV**: реализовать парсер для Альфа-банка (CSV выгрузка из моб. банка/СберБизнес) - **AI-категоризация**: раскомментировать и обновить (GPT-3.5 → DeepSeek/local LLM?) ## Наличные расходы — проблема и решение ### Проблема Наличные траты не трекались последний год. У нас есть конечный остаток налички на руках сейчас, но нет истории по категориям. ### Подходы #### A. Снять остаток наличных сейчас → счёт в БД (простой) - Посчитать физическую наличку сейчас → записать как `initial_balance` для `Нал RUB`, `Нал KGS`, `Нал USD`, `Нал KZT`. - Все траты наличными за год никогда не будут зафиксированы. - **Минус:** дыра в данных большого объёма (вероятно значительная часть расходов). #### B. Экстраполяция по историческим трендам (средний) - Взять помесячные тренды наличных трат по категориям за 2023–первую половину 2025. - Экстраполировать на июнь 2025 — июнь 2026 с учётом сезонности. - Создать транзакции-плейсхолдеры с пометкой `import_id = 'cash_estimate'`. - **Минус:** неточность, может не отражать реальные изменения. #### C. Ретроспектива через месяц-два + экстраполяция (предпочтительный) - **Сейчас:** начать трекать наличные расходы (вручную или через мобильный интерфейс Budget App). - **Через 1–2 месяца:** по собранным данным наличных трат вычислить реальные помесячные паттерны. - Экстраполировать на пропущенный год с этими паттернами. - **Плюс:** база для экстраполяции будет основана на реальных данных, а не на исторических. #### D. None of the above — принять дыру - Сделать только безналичный импорт. Наличные начинаем трекать с сегодня. - В аналитике отмечать периоды как "без наличных". - **Плюс:** не надо ничего выдумывать. ### Рекомендация: C+D combined 1. Трекать наличку вручную через UI Budget App начиная с сегодня. 2. Через 2 месяца посчитать реальные тренды и решить, стоит ли экстраполировать на прошлый год. 3. Если нет — просто принять дыру и жить с хорошей аналитикой начиная с 2026-06. ## Что уже реализовано в Budget App для импорта - ✅ `xlsx_import.py` — полный импорт из Budget.xlsx (34k строк) - ✅ Все счета, категории, курсы, транзакции — в БД - ✅ Идемпотентный UPSERT для счетов и курсов - ✅ Транзакции добавляются обычным insert (без import_hash после фикса) ## Реализованный скрипт: bank_scraper **Путь:** `~/Developer/budget-app/scripts/bank_scraper/` Структура: ``` scripts/bank_scraper/ ├── __init__.py ├── .gitignore # config.yaml + data/imports/ не коммитятся ├── config.example.yaml # шаблон для копирования в config.yaml ├── base_driver.py # base class BankDriver + load_config() ├── orchestrator.py # entry point (н.п.) └── drivers/ └── demir.py # Demir IB драйвер (н.п.) ``` **Статус:** ✅ base + Demir driver написаны, Playwright установлен. **НО — Demir требует QR-логин через мобильное приложение**, не логин/пароль на сайте. ## Match placeholder transactions ### Проблема Ручной ввод расходов с debit/credit счетов через CashInput создаёт placeholder-транзакции (`is_placeholder=true`). У них есть категория и комментарий, но нет реального подтверждения из банка. Когда выписка импортируется, те же траты появляются как новые транзакции из банка — без категории и комментария. Нужно сопоставить плейсхолдеры с реальными транзакциями. ### Механика матчинга При импорте CSV-выписки скрипт (будущий `csv_bank_import.py` или orchestrator) выполняет: 1. Для каждой строки из выписки найти плейсхолдеры в БД с теми же: - `source_account_id` (тот же счёт) - `is_placeholder = true` - Сумма совпадает с точностью ±0.01 - Дата в пределах ±3 дней от даты выписки 2. Если найден ровно 1 плейсхолдер: - `UPDATE transaction SET is_placeholder = false, import_id = '', category_id = , comment = WHERE id = ` - Реальная транзакция **не создаётся** — плейсхолдер становится постоянным с перенесёнными категорией и комментарием 3. Если найдено несколько плейсхолдеров: - Создать реальную транзакцию как есть - Пометить плейсхолдеры как "требуют ручного разрешения" (флаг в будущем через TG-бот) 4. Если не найден ни один: - Создать обычную транзакцию (is_placeholder=false, категория не указана) ### SQL для матчинга (ориентир) ```sql -- Найти плейсхолдер для строки выписки SELECT id, category_id, comment FROM transaction WHERE user_id = $user_id AND source_account_id = $source_account_id AND is_placeholder = true AND ABS(amount - $amount) < 0.01 AND ABS(EXTRACT(EPOCH FROM (date - $date))) < 259200 -- ±3 дня в секундах ORDER BY ABS(amount - $amount), ABS(EXTRACT(EPOCH FROM (date - $date))) LIMIT 1 ``` ## Реальность Demir IB Сайт `93.171.215.109` (и `apps.demirbank.kg/ib/`) — **Flutter web SPA** с QR-аутентификацией. Нет формы логина с паролем — нужно сканировать QR мобильным приложением Demir. **Варианты решения:** ### A. Продолжить с Playwright + session persistence - Один раз залогиниться руками (QR → моб. приложение) - Сохранить session cookies/storage в persistent context - Дальше переиспользовать сессию для выгрузок (пока не протухнет) - **Плюс:** минимум кода - **Минус:** сессия рано или поздно протухнет, нужен ручной ре-логин ### B. Appium / ADB — эмуляция мобильного приложения - Демонстратор Android/iOS эмулятора с мобильным приложением Demir - Appium для UI automation внутри приложения - **Плюс:** полный контроль - **Минус:** сложно, накладно ### C. Заменить Demir на первый банк с логином/паролем - Тинькофф имеет API для разработчиков (OAuth) - Сбер — есть API SberBusinessAPI (хотя для юрлиц) - Можно начать с Тинькофф: Tinkoff API → выписка без браузера - **Плюс:** самый простой tech-wise - **Минус:** Demir пока под вопросом ### D. Парсить CSV выписки, которые уже есть в mobile/email - Возможно Demir присылает выписки на email - Или можно скачать через мобильное приложение → экспорт → AirDrop/email себе - Это полу-ручной подход (но быстрее чем QR scraping) ## Решение **Рекомендация: A + D** 1. Самый ценный банк — **Тинькофф** (есть API) — начинаем с него 2. Demir — разово выгрузить через мобильное приложение (Export CSV/email) 3. Если сессия Demir долго живёт — Playwright persistent context отработает ### Новый порядок разработки 1. ✅ Demir driver (написан, но упирается в QR) 2. **Tinkoff API driver** — следующий приоритет (без браузера, REST API) 3. **Сбер / ВТБ / Альфа** — Playwright или Tinkoff-style API 4. **Parse Demir CSV** — Python-версия DemirToCSV для уже скачанных файлов ## Файл вывода Swift-конвертера ``` csvHeaders = ["дата", "сумма", "дебет", "кредит", "категория", "комментарий", "курс"] ``` - `дата` — `dd.MM.yyyy HH:mm` (формат EUR) - `сумма` — строка с суммой (±знак) - `дебет` — имя счёта-источника (пусто = доход извне) - `кредит` — имя счёта-получателя (пусто = расход вовне) - `категория` — имя подкатегории - `комментарий` — очищенный текст - `курс` — кросс-курс при внутреннем переводе между валютами ## Автоматизация выгрузки выписок из банков ### 1. Browser automation libraries (CV-driven) | Библиотека | Язык | Браузеры | CV | 2FA/SMS | |-----------|------|----------|----|---------| | **Playwright** (MS) | Python, JS, Java, .NET ⭐ | Chromium, Firefox, WebKit | Есть (locator screenshots) | `page.wait_for_selector` на поле ввода кода | | **Puppeteer** (Google) | JS (Python через pyppeteer) | Chromium | Есть | — | | **Selenium** | Python, Java, JS и др. | Все major | Через сторонние утилиты | — | **Рекомендация: Playwright Python** — де-факто стандарт в 2025, cross-browser, async, видит элементы даже в SPA, встроенные ожидания. Подходит и для РФ-банков (Сбер, Тинькофф, Альфа-клик — все на SPA). ### 2. Готовые решения на GitHub **AploBankParsers** ([github.com/Zaurrex1/AploBankParsers](https://github.com/Zaurrex1/AploBankParsers)): - Парсер выписок **СберБизнес** (production-ready) — читает xlsx/сsv из уже выгруженного файла - Заглушки для Альфа, ВТБ, Тинькофф - Это парсер **уже скачанных файлов**, не скрапер **bank_scrapers** ([github.com/eebette/bank_scrapers](https://github.com/eebette/bank_scrapers)): - Playwright-based для scraping bank websites - Generic, не специфичен под РФ-банки **Sber API** — официальный REST API Сбера: - `developers.sber.ru/docs/ru/sber-api/specifications/statement/transactions` - Получение выписки по счёту за 5 лет - **Требует** корпоративного доступа (SberBusinessAPI / ДБО), не подойдёт для личного СберБанк **Готового решения "под ключ" для РФ-банков** (Playwright → bank login → 2FA → CSV выписка) **нет** в открытом доступе. Каждый банк — свой уникальный UI и flow. Придётся писать самим. ### 3. Архитектура скрипта ``` ┌─────────────────────────────────┐ │ Telegram Bot (Hermes/кит) │ ← запрашивает SMS-код ├─────────────────────────────────┤ │ Orchestrator (Python) │ ← запускает по крону / кнопке │ ┌─────────────────────────┐ │ │ │ Playwright browser │ │ ← drives bank login page │ │ - headless=false │ │ (visible для отладки) │ │ - persistent context │ │ (сессия не слетает) │ └─────────────────────────┘ │ │ ┌─────────────────────────┐ │ │ │ Bank drivers: │ │ │ │ - tinkoff.py │ │ │ │ - sber.py │ │ │ │ - alfa.py │ │ │ │ - demir.py │ │ │ │ - vtb.py │ │ │ └─────────────────────────┘ │ │ ┌─────────────────────────┐ │ │ │ Output: CSV в формате │ │ │ │ budget-bank-statement- │ │ │ │ converter │ │ │ └─────────────────────────┘ │ └─────────────────────────────────┘ ``` ### 4. Flow для каждого банка ``` 1. Запустить headless Playwright (или visible=False для отладки) 2. Открыть страницу логина банка 3. Ввести credentials (из конфига, НЕ скрипта) 4. Если запрошен SMS-код: → отправить в Telegram: "Код из смс для {bank}:" → ждать ответа (polling/async) → ввести полученный код 5. Дождаться загрузки дашборда 6. Перейти на страницу выписок/истории 7. Указать период: 2025-06-01 — 2026-06-23 8. Скачать CSV/Excel 9. Сохранить в ~/Developer/budget-app/data/imports/{bank}/{date}.csv 10. Конвертировать через budget-bank-statement-converter (или Python-версию) 11. Импортировать в БД 12. Закрыть браузер ``` ### 5. Обработка SMS-кодов (Telegram) Скрипт не должен хранить сессию банка, каждый запуск — новая авторизация. **Варианты:** 1. **Telegram Bot (inline keyboard)**: скрипт ждёт сообщение, когда нужен код — присылает кнопку "Отправить код для {bank}", пользователь вводит → скрипт вставляет 2. **Hermes-агент**: крон-джоб спрашивает в Telegram нужный код, ждёт ответа через webhook 3. **Простой stdin**: скрипт пишет "Введите код для Тинькофф:" и ждёт ввод (если запуск из терминала) **Рекомендация: вариант 1** — TG bot минимальная зависимость, полный контроль. Для реализации: существующий Hermes/Zulip может служить relay. Или простой скрипт на Python + python-telegram-bot с `await incoming_message`. ### 6. Чувствительность данных — ограничения Скрипт будет: - Знать **логины/пароли** банков (хранятся в локальном конфиге, НЕ в коде) - Открывать **браузер на машине Алекса** (никаких VPN/прокси) - Передавать только SMS-коды через TG — пароли не передаются - Работать **локально**, без LLM/агентов в browser automation Код пишем так, чтобы ни одна строка credentials не была в скрипте: ```python # config.yaml (chmod 600) banks: tinkoff: login: "7999..." password: "..." phone: "7999..." sber: login: "..." password: "..." ``` ### 7. Альтернатива: API банков (без browser) | Банк | REST API для личных счетов | Комментарий | |------|---------------------------|-------------| | Тинькофф | Есть (Tinkoff API для разработчиков) | Требует регистрации приложения, OAuth | | Сбер | Sber API для юрлиц, нет для личных | Не подходит | | Альфа | Альфа-Бизнес API (юрлица) | Не подходит | | Demir | Нет публичного API | — | | ВТБ | Нет публичного API | — | Тинькофф — единственный из списка, у кого есть адекватный API для физлиц (Tinkoff API / Tinkoff Invest API). Можно получить выписку через API, без browser. Остальные — только SPA scraping. **Код:** 10 swift-файлов, ~2 400 строк. **Что хорошо:** - Хорошая архитектура: каждый банк = отдельный struct с чётким интерфейсом - Конфиги вынесены из кода (JSON) - Regex-маппинг категорий гибкий - Умеет объединять multi-part CSV и извлекать из PDF через Adobe API - Формат вывода совпадает со структурой Excel/Budget App **Чего не хватает:** - Парсер Альфа-банка (только заглушка) - AI-категоризация закомментирована (GPT-3.5, устарела) - Нет интеграции с Budget App (только → CSV, не → БД) - Нет обработки для Demir ИП USD / Demir USD / Demir KGS отдельно (DemirToCSV один конфиг на все) - PDF-парсер привязан к Adobe PDF Extract API (платный сервис, credentials нужны) - Нет обработки для Сбер Кредитка как отдельного счёта (SberToCSV один конфиг) - Нет автоматического определения новых форматов CSV от банков