← All ivy-agents
IVYXSTUDIO · IVY AGENT
B

Bank Statement Reader

ivy.agent.bank-statement · v1.1.0

ivyx

Finds the newest bank statement that came by mail, saves the PDF, has a model read the balances and every transaction out of it in any language, checks the rows, and writes a report.

Steps

In the order the agent's file lists them, each with where its inputs come from and the values the file fixes. A branch or a loop is a step too.

  1. 01
    Find the statement mail
    ivy.node.mail-fetch

    Reads the newest messages of a mailbox folder that match a query over IMAP and returns each one's sender, subject, date and plain text, newest first.

    takes
    username from the agent's input
    query from the agent's input
    attachments_dir from the agent's input
    imap_host from the agent's input
    imap_port from the agent's input
    use_ssl from the agent's input
    set
    imap_host imap.gmail.com
    imap_port 993
    use_ssl true
    query subject:statement
    limit 1
    save_attachments true
    secret
    mail_password the stored secret mail-password
    returns
    pdf
    Ivy Node
  2. 02
    Read each PDF

    Runs PDF Text once for each item of attachment_paths of step 1.

    For each
  3. 03
    Read the statement

    The context holds the text of a bank statement, in any language and layout. Return statement, an object with exactly these keys: statement_number (the statement's number), date (the statement date, or the last day of its period, as YYYY-MM-DD), account (the account number), holder (the account holder), previous_balance (the opening or previous balance), total_debits (the money out, as the statement totals it), total_credits (the money in, as the statement totals it), new_balance (the closing or new balance), fees (the bank's charges, 0 when none are shown) and orders (how many transactions). Return transactions: one row per transaction, in the statement's order, with n (1, 2, 3 ...), counterparty (who was paid or who paid; the description when no name is given), account (theirs, empty when not shown), date (YYYY-MM-DD), debit (money out, as a number, 0 when none), credit (money in, as a number, 0 when none), code (the statement's code for it, empty when none) and purpose (what it was for). Write every amount as a plain number: no thousands separators, no currency. When the statement gives no total of money out or in, add the transactions. Use only what the text says and do not leave any transaction out.

    takes
    context from step 2, results
    returns
    statement
    Model turn
  4. 04
    Check each transaction
    ivy.node.rows-validate

    Checks every row against a JSON Schema and splits the rows into valid ones and invalid ones with their problems.

    takes
    rows from step 3, output.transactions
    set
    schema {"type":"object","required":["n","counterparty","date","debit","credit"],"properties":{"n":{"type":"integer","minimum...
    returns
    transactions, rejected, problems
    Ivy Node
  5. 05
    Lay out the transactions
    ivy.node.markdown-table

    Formats rows as a Markdown table, with the columns in the order given or as they first appear.

    takes
    rows from step 4, valid_rows
    set
    columns ["n","date","counterparty","debit","credit","purpose"]
    returns
    count
    Ivy Node
  6. 06
    Assemble the report
    ivy.node.template-render

    Fills {{ name }} placeholders in a text template from a mapping, and reports which names were missing.

    takes
    values.statement from step 3, output.statement
    values.count from step 5, row_count
    values.orders from step 5, markdown
    set
    template # Statement {{ statement.statement_number }} of {{ statement.date }} Account {{ statement.account }} ({{ statement.ho...
    on_missing empty
    Ivy Node
  7. 07
    Write the report
    ivy.node.file-write

    Writes text to a file, creating its folder, and returns the absolute path and the number of bytes written.

    takes
    text from step 6
    set
    path reports/bank-statement.md
    overwrite true
    returns
    path
    Ivy Node

What it touches

Collected from what each of its nodes declares, plus the model call when a step is a model turn. A declaration is the author's statement, and it is what policy rules select on.

Writes filesCalls a modelmessage-readNetwork

What it needs

  • A stored secret named mail-password. The agent's file carries the name, never the value.

Inputs

FieldTypeDescription
usernamerequiredstringThe mailbox address, which is the login name; its password is the stored secret mail-password.
querystringWhich mail holds the statement: subject:statement by default; name the bank's own word, such as subject:izvod or subject:ekstre, or add from:<bank>.
attachments_dirstringWhere the PDF is saved, relative to the workspace.
imap_hoststringThe IMAP server; imap.gmail.com when left out.
imap_portintegerThe IMAP port; 993 when left out.
use_sslbooleanConnect to IMAP over SSL; on when left out.

Outputs

FieldTypeDescription
statementobjectstatement_number, date, account, holder, previous_balance, total_debits, total_credits, new_balance, fees, orders.
transactionsarrayOne row per transaction: n, counterparty, account, date, debit, credit, code, purpose.
countintegerHow many transactions were read.
rejectedintegerHow many of the model's rows lacked a field.
problemsarrayThe rejected rows, each with its problems as one sentence.
pdfarrayThe statement files saved from the mail.
pathstringThe report that was written.

Tests

2 of 2 test cases passed on Oct 7, 2026, in the publisher's own environment, before this version was published. The registry keeps that record; it does not run the cases again.

Requires: python:3.9, a mailbox over IMAP (Gmail unless imap_host says otherwise) with its password stored as the secret mail-password, for the cases, GreenMail on 127.0.0.1:3143/3025 (mkdata-mail.py), a model core, the two [ivyx-bank] statement mails of mkdata-mail.py (data/bank/, made up)

  • izvod-42

    A made-up Montenegrin statement (izvod 42 of 15.09.2026): five orders, 1000.00 before, 368.50 out, 1200.00 in, 1831.50 after.

    given
    imap_host 127.0.0.1
    imap_port 3143
    use_ssl false
    username agent@ivyx.test
    query subject:izvod subject:[ivyx-bank]
    expects
    count equals 5
    rejected equals 0
    statement.statement_number matches ^0*42$
    statement.previous_balance equals 1000
    statement.total_debits equals 368.5
    statement.total_credits equals 1200
    statement.new_balance equals 1831.5
    pdf matches statement-42\.pdf
    path matches reports/bank-statement\.md$
  • english-7

    A made-up English statement in another layout, with a balance column and thousands separators: five transactions, 2,500.00 to 4,499.81.

    given
    imap_host 127.0.0.1
    imap_port 3143
    use_ssl false
    username agent@ivyx.test
    query subject:"account statement" subject:[ivyx-bank]
    expects
    count equals 5
    rejected equals 0
    statement.statement_number matches ^0*7$
    statement.previous_balance equals 2500
    statement.total_debits equals 1400.19
    statement.total_credits equals 3400
    statement.new_balance equals 4499.81
    pdf matches statement-7-en\.pdf