Finance Assistant: PDF bank statements to a Telegram budget
our own product: a bot and web dashboard, four banks' statements, Gemini
The task
Money flows through several banks, relatives' cards and cash. Each banking app shows only its own slice. Transfers between your own accounts look like income and expenses, and a hand-kept spreadsheet usually dies after a couple of weeks. On top of that, some spending belongs to work and your own projects and some to personal life, and in a bank app they are mixed together.
What was needed was one honest picture of the money: how much came in, where it went, which part is business and which is personal. And without typing in every operation by hand: a bank statement should simply be dropped into the chat, and a small expense told in an ordinary sentence.
The solution
Finance Assistant is our own Telegram bot with a web dashboard on top of the same data. Implemented in version 1.14.0:
- Statements from four banks. PDFs from VTB (credit card, credit card account, debit cards), Sberbank, T-Bank and Ozon Bank. The bank is detected automatically, and a universal CSV and XLSX parser is the fallback.
- A register of accounts and cards. Account number, contract, last four card digits, owner. The next statement lands on the right account by itself; for a VTB credit card the debt, limit, rate and payment date are shown.
- Expenses in words. "Coffee 250 yesterday", "+65000 salary cash", "recurring expenses 2500 subscription". One message can hold several operations, and one purchase can be split across categories.
- Voice and receipt photos. Gemini transcribes speech; a receipt becomes line items with categories that are matched to the card operation.
- Account in words. "By credit card", "from Sber", "card 5515": the record goes to the right account, and the bot asks rather than guessing when it is ambiguous.
- Night run. Unknown merchants go to the model in one request, categories are set by morning, and doubtful ones arrive as a list of buttons.
- Questions about your money. "What do I spend the most on?", "compare with last month": the answer is built from the bot's own data, and the bot gives no investment advice.
- Debts, recurring payments and budgets. A debt ledger per person, payments with a charge day, category budgets with warnings at 80 % and 100 %.
- Directions and reinvestments. Income, expenses and 12-month dynamics for each direction.
- A dashboard as a Telegram Mini App. Business, Purchases and Debts tabs, a spending calendar, comparison with the previous period, CSV and XLSX export.
- Access for two people. The owner and a close relative, each with their own accounts and summary.
How it works
Detecting the bank, account and card from a PDF
Each bank has its own parser (a program that reads the structure of a file), and a separate module reads the statement header to detect the bank, owner, account, cards, period and balance. If the bank format is unknown, the fallback CSV and XLSX parser takes over. The parsers are built for each bank's current PDF layout: when a bank changes it, the sums stop matching the statement totals and the parser has to be fixed. A VTB debit card statement has no account number, so cards of one account are joined by an identical balance on the statement date, and a "Same account" button helps when that is not enough.
Reconciling overlapping statements without duplicates
A bank often issues a statement with spare dates, so the same day lands in two files. The bot compares the incoming operations with the saved ones and does not record them again; mismatches in the overlap are listed in the reply. Uploading the same file twice changes nothing.
Transfers between your own accounts are not income
A transfer from card to card looks like an expense in one place and income in another. The bot recognises such transfers by the banks' wording, by the owner's own name and by "out, in" pairs of the same amount, and they never reach income and expenses. Any account can be excluded from the overall totals with one button.
Merging a manual entry with the statement operation
An expense you typed into the chat is not doubled when the statement arrives. The amount may differ slightly: the tolerance is 2 % or 100 currency units, plus a small date window. A place name from the message is searched in the bank's description with transliteration taken into account, so a Cyrillic name and its Latin spelling count as the same place. If several operations fit, the bot offers a choice with buttons, and a merge can be undone. The weak spot: two different expenses of the same amount on the same day may merge, and a button separates them.
Cash withdrawal: a transfer, not an expense
It used to be impossible to tell the bot about a withdrawal: from the statement it recognised the ATM operation, but from a message it did not, and the withdrawal either vanished or became an expense. Now a withdrawal is recorded as money moving from the card to the pocket, while expenses from the same message stay separate records. A withdrawal recorded in words merges with the ATM operation when the statement arrives, so the pocket is not counted twice. The same withdrawal is not recorded twice in one day; a genuine second withdrawal of the same amount is recorded with the word "another".
Night run and merchant rules
A category is chosen in this order: learned merchant rules, then keywords (the longest match wins), then the category the bank supplied. Whatever stays unknown goes to the model in one request overnight. The clear cases are categorised and remembered as a rule, and in the morning a list of buttons arrives: the unclear ones on top, the sorted ones below for correction.
One set of calculations for the bot and the dashboard
All analytics live in one layer shared by the bot and the dashboard, so the numbers in the Telegram menu and on the charts match. The dashboard is a web page with no build step, written in HTML and Chart.js, and it opens as a Telegram Mini App. Login uses the Telegram signature or a personal link from the bot: the link works for an hour, after which the browser is issued a cookie for 30 days.
Backups and liveness monitoring
A daily database dump is checked for integrity and rotated, and a restore can be tried on a separate database without touching the live one. Every 5 minutes an external check looks at the containers, the database, the "bot is alive" mark (the bot sets it once a minute) and the dashboard address. On failure a Telegram message arrives. An error in a handler is never left silent: the person sees a reply, and the owner gets what failed and on which message.
Results
- A one-message menu: the month at a glance (income, expenses, balance, today, recurring payments until month end, debts).
- Directions and "who eats the most": directions and personal spending in one row by descending expense, with a share and a shift against the previous month.
- The dashboard: daily spending, a spending calendar, "what changed", purchases by receipt line, export of operations to CSV and XLSX.
- Hints: rising expenses, exceeded budgets, outdated statements, an approaching credit card payment.
- A credit card payment reminder 3 days ahead, a weekly digest on Mondays, cash reconciliation.
- Automated tests (about 120 pytest functions) cover text parsing, statement matching, debts, bank detection, export and cash handling.
Technologies and why
- Python 3.13 and aiogram 3 — the Telegram bot: menus, messages, files, the reminder scheduler.
- FastAPI and uvicorn — the dashboard API; the frontend itself has no build step and uses HTML, CSS, JS and Chart.js.
- PostgreSQL 17 and SQLAlchemy 2.0 (async) — storage of operations, accounts, rules, debts and budgets.
- pdfplumber, pandas, openpyxl, chardet — reading PDF statements, the fallback CSV and XLSX, export.
- Google Gemini — complex text, voice, receipt photos, the night run and questions about money; keys and ordered model lists are set in
.env. - Docker Compose — three containers: the database, the bot and the web part; tables are created at startup.
Status
Our own product, in production. The current version, 1.14.0 of 22 September 2026, adds cash withdrawals and deposits from a message. Earlier the same day came spreading expenses across directions, questions about your own money, the spending calendar and naming the account in words.
Limitations: there are no migrations, so rolling back a version is done from a database copy; there is no multi-currency support; complex text and receipt photos go to Google. Transfers between your account and a relative's account in the "All" view count as income for one and an expense for the other.
Questions about this project
How do I combine statements from several banks into one picture without duplicates?
How do I separate business spending from personal spending?
How do I keep track of debts by person?
How do I account for cash that the bank does not see?
Where is the data stored, and what goes to Google?
Can a second person use it?
More in this area
Telegram ticketing system for an IT department
for a museum complex in Moscow: tickets, asset tracking, knowledge base and analytics
Tech Poly VPN: VPN subscription billing in Telegram
our own product: SBP payments, key issuing in Marzban, a backup VKontakte bot
Branding e-policies (OSAGO) with a Telegram bot
for accident commissioners: contacts on the form, data and QR code untouched
Need something similar?
Tell us about the task — we'll show how we solved it and estimate the scope.