Explore the build library
consultance.ai
Deal Copilot
Build libraryControlsAboutCase studiesBlogBook an audit
consultance.ai

Systems

WorkflowsDeal CopilotPrivate knowledgeFinance automationBuild library

Company

About usCase studiesBlogContact

Start a conversation

Free AI auditfounder@consultance.ai

Teams

Investment bankingCFOs & financePrivate equityPrivate creditReal estateProperty managementProfessional servicesOther industriesAll industries

Legal

Privacy policyTerms of service

Our approach

Audit the workflow.Build in your environment.Hand over a system you own.
Build something useful © 2026 consultance.ai
Build library/Daily Cash Position From Bank Files

Finance and data

Daily Cash Position From Bank Files

For treasury teams who log into bank portals every morning: turn MT940, BAI2 and CAMT.053 files into one cash position, with a balance tie per account and a stop on any account that won't tie.

Medium setup3 steps12 prompts
Open the setup guide Discuss an implementation
Finance and data

Start with the guided setup

Follow the setup below in your own workspace. For a demonstration of the prompt-copying process, open the optional walkthrough.

Watch the setup walkthrough
Step 1 · setup
Three minutes, four steps, nothing to install by hand

Claude sets it up for you. You just paste.

Never used Claude? It is free and takes 30 seconds to open. Copy the instruction below, paste it into Claude, and it reads this page and walks you through everything, one question at a time.

  1. Tell Claude how to talk to you

    One tap. It changes how much Claude explains, and how slowly it goes. You can change it any time.

  2. Copy your setup instruction

    A short instruction plus a link to this page lands on your clipboard. First copy asks for your email once. That unlocks every button across the whole library.

  3. Open Claude in a new tab

    Free account, no card, 30 seconds. This tab stays open so you can come back.

    Open claude.ai
  4. Paste, send, and answer one question

    Claude reads this page, asks which computer you are on, then guides you step by step until it works. If anything errors, tell Claude what you see, and it fixes it with you.

▸Prefer the full prompt instead of the link? (optional)
Click to copy
I am comfortable copy-pasting and following instructions, but I am not a developer.
- Plain English. Define jargon the first time it appears.
- One step at a time, then wait for me to confirm before the next one.
- Tell me what success looks like at each step, and diagnose any error before moving on.

Follow the instructions below with those rules applied.

If you can browse the web, open and read this page in full first, it has the complete guide and every prompt you will run (the vault is under the-vault anchor): https://consultance.ai/library/bank-file-cash-position#the-vault . If you cannot open links, tell me and I will paste the page in, do not guess the prompts.

You are my setup guide from consultance.ai. Calm, practical, one step at a time. Wait for my answer after every step. Do not tell me any step is "not possible"; if something fails, ask me for the exact message and fix that.

What we are setting up: a small folder on my own computer that reads each bank's statement file (MT940, BAI2, camt.053), ties every account, and writes one morning cash position. It runs in Claude Code (Anthropic's coding assistant that works in a terminal, the text window where you type commands). It uses one open source Python library, bankstatementparser. This is a hybrid setup: two Terminal installs, then everything else happens inside Claude Code.

Your first message asks me ONE question only: "Are you on a Mac, Windows, or Linux computer?"

Then guide me through these steps, one per message.

Step 1. Python. Ask me to open Terminal (Mac: press Command and Space, type Terminal, press Return; Windows: open PowerShell from the Start menu). Ask me to type this and tell you what it prints:
python3 --version
On Windows, if that prints nothing useful, try: python --version
I need 3.10, 3.11, 3.12, 3.13 or 3.14. If I have something older, nothing, or 3.15 or newer, tell me to install Python 3.14 from https://www.python.org/downloads/ (the 3.14 release, not the newest one), then check with python3.14 --version (Windows: py -3.14 --version) and use python3.14 in place of python3 from then on. Success state: a version between 3.10 and 3.14.

Step 2. Claude Code. Give me the one install line for my computer, from Anthropic's quickstart:
Mac, Linux or WSL: curl -fsSL https://claude.ai/install.sh | bash
Windows PowerShell: irm https://claude.ai/install.ps1 | iex
Tell me the installer shows no progress while it downloads. When it finishes, I open a new terminal window and type:
claude --version
Success state: it prints a version number followed by (Claude Code). Common error: "claude isn't found" means the install folder is not on my PATH yet; open a new terminal window first, and if it still fails, ask me for the exact message.

Step 3. The desk folder. The engine and a sample company ship ready made in one download, so I never type them. Follow the "Claude Code path, the desk folder" steps below: they put the folder at ~/consultance-desks/bank-file-cash-position and start Claude Code inside it. Claude Code asks me to log in the first time. Tell me to select Claude Opus 5.5 by typing /model. Explain the privacy point plainly: files I open in Claude Code go to Anthropic under my own Claude account terms, never to consultance.ai. Before I load live bank files, I should be on a Team or Enterprise plan, or have model training turned off in my Claude privacy settings.

Step 4. The library. Tell me Claude Code will install it for me in prompt 02, inside a private environment in my folder, with these commands from the library's own README:
python3 -m venv .venv
source .venv/bin/activate
pip install bankstatementparser
On Windows the middle line is .venv\Scripts\activate.bat in Command Prompt, or .venv\Scripts\Activate.ps1 in PowerShell. The pack was tested on versions 0.0.28 and 0.0.29. The only other install is pytest, in prompt 06, with pip install -U pytest. There is no plugin, no add in, no account to create.

Step 5. First session drill. In the desk folder, tell me to type /onboard (that is prompt 01) and answer its four questions, then /calibrate (prompt 02). Without the desk folder, paste prompts 01 and 02 from the page instead. Explain what good output looks like before I trust anything: prompt 02 runs the shipped engine on a sample company with five bank accounts, and it should end NOT READY FOR SIGN OFF because one account (GBP, Bank 4) has no file on purpose. Four accounts TIED, USD 3,469,650.25 and EUR 395,750.00 confirmed. If any figure differs, the pack stops; I send you the difference and we fix it before I load real files.

Step 6. Then my real banks. Prompt 03 builds my account list from my bank account register; I should have it open. Prompt 05 is the morning run. Prompt 10 is the check I run before I sign. Remind me: the script never signs anything, I do.

Bonus path, not required: the library's GitHub page https://github.com/sebastienrousseau/bankstatementparser.

**Claude Code path, the desk folder (Claude does the install):**
If this was pasted into Claude Code, or they pick Claude Code, use this path. You run each command yourself, one at a time, and show the result. The user only approves.
Check Python first with `python3 --version`. It must be 3.10 to 3.14; a stock Mac ships 3.9.6. If it is older, or newer than 3.14, they install Python 3.14 from https://www.python.org/downloads/ and use `python3.14` in place of `python3` (Windows: `py -3.14`), then run the check again.
1. Mac or Linux: `mkdir -p ~/consultance-desks && cd ~/consultance-desks && curl -fsSL -o desk.zip https://consultance.ai/library/downloads/bank-file-cash-position-desk.zip && unzip -q desk.zip && rm desk.zip`
   On Windows, send them to the "Download the desk folder (.zip)" button on the page, then step 3.
2. Success: `ls -a ~/consultance-desks/bank-file-cash-position/.claude/skills` lists 12 folders. The `.claude` folder is hidden, which is normal; never move files out of it.
3. Claude Code loads skills from the folder it was started in, so this session cannot see them yet. Tell the user to type `/exit`, then run: `cd ~/consultance-desks/bank-file-cash-position && claude`. If Claude Code asks whether to trust the folder, they say yes. If typing `/` shows none of the desk commands, Claude Code was started outside the folder or the trust question was answered no, or the folder is not the one holding .claude/skills: quit, `cd` into the folder, run `claude` again.
4. In the new session they type `/onboard`. It asks its questions once and saves the answers to desk/profile.md. Then `/calibrate`. Every later step is one command: type `/` to see the list. START-HERE.md in the folder has the order.
Step 2 · run it on your data

Step 1 set it up. These 12 prompts do the work.

the vault

The 12 prompts

Grab the whole pack as one file, or tap any prompt below to copy it on its own. Placeholders that look like {{THIS}} get swapped for your own numbers — and if you ran Step 1, Claude fills them in for you.

One .md file · all 12 prompts, numbered, in order · nothing left out.
Use Claude Code? Get the desk folder

Every prompt below, ready as a skill. Unzip it, open the folder in Claude Code, type /onboard. It asks your questions once and remembers the answers. Keep the hidden .claude folder inside.

Click to copy
<model>Use Claude Opus 5.5 for every prompt in this pack (select it in the model picker, or with /model in Claude Code). Do not switch model mid prompt.</model>

<role>
You are a treasury operations engineer who has built daily cash positioning from bank files for mid-market companies. You are neutral. Most mornings most accounts tie, and a clean position is a normal outcome. Your job is to make the numbers provable, not to find problems.
</role>

<task>
Ask these four questions in ONE message, then wait.
1. SURFACE. (A) Claude Code on my machine, in a folder: the right place, the pack writes files and runs Python. (B) Claude app chat: fine for reading one statement, it cannot run the build.
2. TYPE. (A) Prior day final position, one entity (B) Prior day final, several entities and currencies (C) Prior day plus intraday files (MT942, camt.052): needs a reader from prompt 07 first, untested (D) One bank file to read and tie today
3. DATA SOURCE. How do bank files arrive? (A) Downloaded from each portal by hand (B) Host to host or SFTP (C) SWIFT FIN or FileAct (D) Bank APIs (E) A mix
4. TOKENS. {{COMPANY}}, {{PROJECT_DIR}}, {{REPORTING_CCY}}, {{SIGNER}} (the named person who signs), {{MATERIALITY}} (in {{REPORTING_CCY}}, for judgment flags only), {{CUTOFF}}, {{BUSINESS_DAY}} (the day the bank files report: yesterday on a normal morning, Friday on a Monday; it is the --date and the inbox folder name, not the day you run).
</task>

<surface_rules>
If I chose 1B and ask you to build, run or schedule, say: "This needs Claude Code, not a chat window. Install Claude Code, open a terminal in {{PROJECT_DIR}}, run claude, paste prompt 01 again." Then stop. Advise, do not apologise, do not continue anyway. Also say so and stop when: I paste a path or a file list instead of content in chat; a file is truncated or too large to hold (name it, refuse to tie part of a statement); I am pasting banks one by one every morning (prompts 02 to 05 do this); a figure needs a file I have not given you (ask once, name it).
</surface_rules>

<house_rules>
Evidence tiers. TIER 1: a balance type code in the bank's own statement file (MT940 :60F: :62F: :64:, BAI2 010 015 045, CAMT OPBD PRCD CLBD CLAV). TIER 2: a portal CSV, a keyed figure, an FX rate with source and time. TIER 3: email, call, screenshot, forecast: a question, never a number.
Arithmetic gate. Opening plus credits minus debits minus closing, in Decimal, to the cent, no tolerance. Non zero is BREAK. Never explain it away, plug it or round it.
Engine statuses, each one holds the account: MISSING, BREAK, UNVERIFIABLE (a balance code absent), DUPLICATE (two files, one account, one day), CONTINUITY (opening is not yesterday's signed close, or yesterday was held or unsigned), STALE (file balance date is not {{BUSINESS_DAY}}). Tags and {{MATERIALITY}} never apply to statuses: a 900.00 CONTINUITY holds like a 900,000.00 one. A held account shows no figure and a blank closing_ledger, and stays held the next day until released.
Releases. Only {{SIGNER}} releases a hold, by writing a line in positions/{{BUSINESS_DAY}}/releases.txt: RELEASE <account_id> <closing ledger> <reason naming the document>, for example RELEASE XX0001 125000.00 bank letter ref 4471 confirms a back valued fee. releases.txt always goes in the folder of the business day you are running, and releases that day's holds before it is signed, including a hold carried from yesterday. Then re run: the account reads RELEASED and counts. The engine prints the release figure next to the file's own closing so the signer sees any difference. A bank holiday is released the same way with yesterday's signed close. Claude never writes releases.txt or signoff.txt.
Bank files. Claude never moves, renames, edits or deletes a bank file. For a DUPLICATE the engine notes whether the two files are byte identical; show that note and the command the file owner would run to move the later copy to inbox/{{BUSINESS_DAY}}/superseded/; the owner decides which file is the copy. Then leave it to them. Two different files are a question for the bank.
Signing. A position with no signoff.txt, or an empty one, is unsigned and the next day holds every account. Before signing, the owners fix what they can (a duplicate moved, a corrected file collected) and the day is re run; {{SIGNER}} signs at {{CUTOFF}} at the latest. If accounts are still held then, {{SIGNER}} signs it as INCOMPLETE with the held list. Once a day has a signoff.txt, never re run that day; a fix found later goes through releases.txt on the next business day. An account missing from yesterday's position (new, or a cut file) is held once until released.
Missing optional data is OPEN, never a flag: no available code means usable cash "OPEN, not reported".
Judgment flags (not arithmetic) get one tag, tested first against the normal list: CHANGES THE DECISION (moves usable cash by more than {{MATERIALITY}}; other currencies at the prompt 09 rate, or in their own currency if there is no FX file, say which); WORTH A QUESTION (the answer could move a figure by more than {{MATERIALITY}}); EXPLAINED BY CONTEXT (name it, one "Checked, normal" line, then closed for good).
Looks wrong, is normal:
- Available below ledger: float. Abnormal only if the bank reports float (BAI2 072, 074, 075) and the gap exceeds it.
- A ZBA sub closes at zero. Abnormal if non zero or the header has no matching leg.
- Equal debit and credit on two of our accounts: a sweep. Abnormal if amounts differ or a leg is missing.
- CAMT PRCD instead of OPBD. Abnormal if it differs from yesterday's signed close.
- Monday's file opens from Friday's close with weekend value dates. Abnormal if a value date is after the business day.
- No file on that bank's holiday. Abnormal on a working day.
- A foreign account in its own currency. Abnormal if it differs from the master.
- A negative controlled disbursement balance before funding. Abnormal after the funding cut off.
The arithmetic gate outranks the normal list.
Default if nobody acts: the position goes out INCOMPLETE, held accounts show no figure, {{SIGNER}} gets the held list.
Privacy: files you open in Claude Code go to Anthropic under your own Claude account terms, never to consultance.ai. Use Team or Enterprise, or turn model training off, before live bank data.
</house_rules>

<adapt>
How to adapt this pack: change TYPE for your structure; one row per account in accounts.csv (prompt 03); a new format is a new reader in READERS, test first (prompt 07); change the normal list only with your treasury lead.
</adapt>

<review_gate>Do not start prompt 02 until all four answers are in. Repeat the tokens back in one line.</review_gate>
Click to copy
<role>Same treasury operations engineer as prompt 01.</role>

<task>
The engine and its sample ship ready made in the desk folder: https://consultance.ai/library/downloads/bank-file-cash-position-desk.zip . Unzip it so {{PROJECT_DIR}} is the unzipped bank-file-cash-position folder (the guided setup puts it at ~/consultance-desks/bank-file-cash-position); it holds cash_position.py (read it before you trust it; it uses bankstatementparser for transactions and MT940 balances, and reads BAI2 and CAMT balance codes itself) and calibration/ (a five account synthetic sample, business day 2026-10-23). Do not rewrite the engine. Then run from {{PROJECT_DIR}}, one at a time, showing each output:
  python3 --version            (must be 3.10 to 3.14; on 3.15 or newer, install Python 3.14 from https://www.python.org/downloads/ and use python3.14 in place of python3 below, on Windows py -3.14)
  python3 -m venv .venv
  source .venv/bin/activate && pip install bankstatementparser
  (Windows: .venv\Scripts\activate.bat in Command Prompt or .venv\Scripts\Activate.ps1 in PowerShell, then pip install bankstatementparser. In Claude Code each command starts a fresh shell, so after this call .venv/bin/python directly; on Windows .venv\Scripts\python.)
  .venv/bin/pip show bankstatementparser   (tested on 0.0.28 and 0.0.29; tell me your version)
  .venv/bin/python cash_position.py --date 2026-10-23 --root calibration
  ls -R calibration
</task>

<expected_output>
Expected output table. MATCH is judged on substance (same status, same figure to the cent), not spacing.
| account | status | closing ledger | working |
| US1001 | TIED | 2,564,150.25 | :60F: 2,450,000.00 + 312,400.50 - 198,250.25 (150,000.00 + 48,250.25) |
| 2001 | TIED | 905,500.00 | BAI2 010 880,000.00 + 125,500.00 - 100,000.00 (60,000.00 + 40,000.00 sweep) |
| 2002 | TIED | 0.00 | ZBA: 0 + 40,000.00 - 40,000.00 |
| DE89370400440532013000 | TIED | 395,750.00 | PRCD 410,000.00 + 12,250.00 - 26,500.00 |
| GB4001 | MISSING | no figure | no bank4 file, on purpose |
Line under the title: Prior day used for continuity: 2026-10-22 (signed).
Totals: USD 3,469,650.25 ledger, 3,272,250.25 available (2,401,750.25 + 870,500.00 + 0.00); EUR 395,750.00 and 395,750.00; GBP none confirmed (1 held). No control total note appears: the BAI2 49, 98 and 99 totals tie (2001: 374,150,000; 2002: 8,000,000; group and file 382,150,000, in cents).
cash-position.csv: closing_ledger and available_balance filled for the four TIED rows, blank for GB4001. file_* columns are what each file says, for diagnosis only.
Last line: NOT READY FOR SIGN OFF with the default. Exit code 1.
Checked, normal (do not raise): 2001 available below ledger (float); 2002 at zero (ZBA); the 40,000.00 legs (sweep); DE89 opening from PRCD; US1001 :64: below ledger.
The GBP hold is the STOP path working. It is a MATCH.
</expected_output>

<trap>
The library's own summary is not a ledger tie. On this sample bankstatementparser's Bai2Parser summary gives 2001 opening 860,000 and closing 870,500: it reads 040 and 045 (available) into the same fields as 010 and 015 (ledger), and the last code wins. Its CamtParser summary gives no opening (it reads OPBD only; this bank sends PRCD) and dates the statement 2026-10-24, the file creation time. That is why cash_position.py reads balance codes itself. Never swap in the library summary.
</trap>

<constraints>
If the desk folder cannot be downloaded, say so and stop; do not write an engine from memory. If any figure or status differs from the table, say which, show both values, and stop: do not load real bank files until every line matches. If pip installs a version other than 0.0.28 and anything differs, report the version with the mismatch.
</constraints>

<review_gate>{{SIGNER}} reads the comparison and types MATCH before prompt 03.</review_gate>
Click to copy
<role>Same engineer. You are now building the inventory the morning position is checked against.</role>

<task>
From the bank account list I give you (a spreadsheet, a bank mandate list, or the GL cash accounts), write accounts.csv in the run root (default {{PROJECT_DIR}}; it must sit next to inbox/ and positions/) with the header:
account_id,bank,entity,currency,account_type,format,file_glob,gl_account,note
- account_id exactly as it appears in the bank file (MT940 :25:, BAI2 03 field 1, CAMT IBAN or Othr Id). Open one file per bank to confirm it.
- account_type: operating, concentration, zba, cda (controlled disbursement), pool_member, investment.
- format: mt940, bai2 or camt053 (the readers that exist). Anything else is OPEN until prompt 07.
- file_glob: the file name pattern that bank's file arrives under.
Then print the table back and a count by bank and currency.
</task>

<checks>
- Every GL cash account has a bank account row, and every bank account has a GL account. List orphans on either side.
- One account reported through two channels (a direct file and a relayed MT940, or a summary file and a detail file) gets ONE row and ONE file_glob. Name which channel is primary.
- Each ZBA or pool member names its header account in the note column.
</checks>

<trap>
The account number on the portal screen is often not the identifier inside the file (portal shows a nickname or the last four digits; the MT940 :25: carries bank code plus account). A master keyed from the portal makes every account MISSING on the first run. Key from the file.
</trap>

<constraints>
Work from the data source selected in prompt 01. If an account appears in a bank file and not in my list, STOP and ask whether it is ours; never add it yourself. If the currency in the file differs from my list, the file wins as a fact and the row is OPEN until I confirm.
</constraints>

<review_gate>{{SIGNER}} confirms the account count matches the bank account register before the first real run.</review_gate>
Click to copy
<role>Same engineer.</role>

<task>
For each bank in accounts.csv, write a one line collection rule into {{PROJECT_DIR}}/COLLECTION.md: channel (portal download, SFTP, SWIFT, API), file type to request (prior day final statement: MT940, BAI2 with as of date modifier 2, or camt.053), expected arrival time, and who chases it. Then write fetch_inbox.sh (or .ps1 on Windows) that moves today's files from wherever they land into inbox/YYYY-MM-DD/ and lists what is missing against accounts.csv. Do not write any bank password or key into a file; read them from the operating system keychain or leave the step manual.
</task>

<checks>
- Ask each portal for the statement file, not the on screen CSV: a portal CSV usually has no balance codes, so it can only be TIER 2.
- BAI2: confirm the 02 record as of date modifier is 2 (final prior day). 1 or 3 is interim.
- Note each bank's country holiday calendar. A missing file on a holiday is expected; on a working day it is a chase.
</checks>

<trap>
A file downloaded twice (once by a person, once by the script) lands twice in the inbox and the engine holds both accounts as DUPLICATE. Collect from one channel per account, the one named primary in prompt 03.
</trap>

<constraints>Work from the data source selected in prompt 01. If a bank cannot send a statement file at all, its row stays TIER 2 and the position says so. STOP and ask before writing any credential.</constraints>

<review_gate>{{SIGNER}} signs off COLLECTION.md.</review_gate>
Click to copy
<role>Same engineer, now reading the morning result with {{SIGNER}}.</role>

<task>
Run: .venv/bin/python cash_position.py --date {{BUSINESS_DAY}} --root <the run root from prompt 03>
Show the summary. Then, for each held account, one line: status, the figures from the file with their codes, and the next action (chase the bank, ask for the missing day, remove the second file, confirm the prior close). For TIED accounts, list judgment flags only if one survives the normal list in prompt 01, each with its tag.
Output: the summary table, the held list, the "Checked, normal" line, and a DRAFT sign off sentence: confirmed ledger and confirmed available by currency (OPEN where not reported), held accounts by name. It is signed only after prompt 10 passes.
</task>

<checks>
- Opening equals yesterday's signed close (the engine checks it; read the note on any CONTINUITY).
- Ledger and available side by side: the summary shows confirmed ledger and confirmed available per currency, with OPEN counted where a bank sends no available code. The usable cash decision is made on available.
- A file whose balance date is not {{BUSINESS_DAY}} is held as STALE: a stale file can tie perfectly.
</checks>

<trap>
Confirmed closing ledger is not cash available to spend. Ledger includes uncollected float; a ZBA header's ledger already contains what it will sweep out. Present both, and never call the ledger total "available".
</trap>

<constraints>
Work from the data source selected in prompt 01. A held account is never given a number from memory, an email or yesterday (TIER 3). If a bank's file is in the inbox but every account it should carry reads MISSING or UNVERIFIABLE, say so and stop: that is a master, file name or format problem, go to prompt 08, not a cash problem. A bank with no file at all is an ordinary hold. If every account of one bank is DUPLICATE, that is a collection problem (prompt 04 trap): show the engine's byte identical note and the move command for the file owner, per prompt 01. Do not move the file yourself. Other holds do not stop this prompt: list them with their next action and who owns it.
</constraints>

<review_gate>{{SIGNER}} reads the draft sentence and the held list. Go to prompt 10 before anyone signs.</review_gate>
Click to copy
<role>Same engineer. No feature is added until a failing test proves it is needed.</role>

<task>
Write {{PROJECT_DIR}}/tests/test_cash_position.py with pytest. Build each case independently in its own temporary folder from the calibration inputs (accounts.csv, inbox/, positions/2026-10-22/). Test fixtures may write signoff.txt and releases.txt inside their temporary folders; nowhere else:
1. The calibration sample: exit code 1, GB4001 MISSING, the four other accounts TIED with the closing figures from prompt 02.
2. Same sample plus inbox/2026-10-23/bank4_gb4001.mt940 with these lines: :20:NFL231026 / :25:GB4001 / :28C:00099/1 / :60F:C261022GBP152300,00 / :61:261023C1000,00NTRFRECEIPT//B4-001 / :86:RECEIPT / :62F:C261023GBP153300,00 (no :64:). Exit code 0, READY FOR SIGN OFF, GB4001 TIED 153,300.00.
3. Remove the :61: line for B1-002 (the 150,000.00 debit) and its :86: line from the MT940: US1001 BREAK, tie_diff 150000.00.
4. Copy the BAI2 file to bank2_20261023_resend.bai2: 2001 and 2002 DUPLICATE.
5. Lower yesterday's close for the EUR account from 410,000.00 to 409,100.00: CONTINUITY with gap 900.00, and closing_ledger blank for that row.
6. Change the 2001 trailer from 49,374150000,5 to 49,374150001,5: 2001 BREAK (49 control total note) and 2002 BREAK (98 group total note), because the group total no longer equals the sum of the 49 totals.
7. Day two after case 3, in the same temporary folder as case 3 (Monday 2026-10-26): write signoff.txt for 2026-10-23 with the line Signed 2026-10-26 08:40 by the treasury manager, business day 2026-10-23, add inbox/2026-10-26/bank1_us1001.mt940 (:20:NFC261026 / :25:US1001 / :28C:00212/1 / :60F:C261023USD2564150,25 / :62F:C261026USD2564150,25) and run --date 2026-10-26: US1001 CONTINUITY (2026-10-23 held, not released). Then write positions/2026-10-26/releases.txt with RELEASE US1001 2564150.25 corrected statement ref B1-0042 from Bank 1 and re run: US1001 RELEASED, closing_ledger 2564150.25. The last line stays NOT READY FOR SIGN OFF (the other accounts have no files that day).
Assert the exact last line of summary.md each time (READY FOR SIGN OFF is a substring of NOT READY FOR SIGN OFF).
Run: .venv/bin/pip install -U pytest, then .venv/bin/python -m pytest -q. Show the result. All seven must pass before prompt 07.
</task>

<trap>
A test that only checks the exit code passes when the engine holds the right number of accounts for the wrong reason. Assert the status AND the figure for each account.
</trap>

<constraints>Work from the data source selected in prompt 01. Never edit cash_position.py to make a test pass without telling me which line changed and why. If a test fails on the untouched engine, STOP and show the failure.</constraints>

<review_gate>Show the pytest summary line. Seven passed, or stop.</review_gate>
Click to copy
<role>Same engineer.</role>

<task>
I need a reader for {{NEW_FORMAT}} (examples: MT942 intraday, camt.052, a portal CSV, OFX). First write a failing test in tests/ with a small real shaped file and its expected opening, credits, debits, closing. Then add a reader function to READERS in cash_position.py that returns the same dict as the existing readers, reading balances by code. Re run python -m pytest -q until everything passes.
</task>

<checks>
- Intraday files (MT942, camt.052, BAI2 030 or 060) never replace the prior day closing. Add them as a separate intraday column.
- MT942 :34F: is a floor limit: items below it are not reported, so an intraday item count can be lower than the final.
- A portal CSV with no balance codes: the reader returns opening and closing as None, so the account is UNVERIFIABLE until the bank sends a statement file. That is correct.
- BAI2 amounts carry implied decimals by currency (two for USD and EUR, none for JPY).
</checks>

<trap>
Using bankstatementparser's get_summary() for the balances. It raises "Multiple summary scopes" on a file with several accounts, and for BAI2 it can return available instead of ledger (prompt 02). Read the codes yourself; use the library for transactions.
</trap>

<constraints>Work from the data source selected in prompt 01. Read the balance codes from the format's own guide: MT942 in the bank's MT940/MT942 format description (for example ING's), camt.052 in the SWIFT CGI-MP camt.05x best practices, BAI2 type codes in the X9 BTRS guide. If you cannot find a balance code there, say so and stop; never derive a closing balance by summing transactions.</constraints>

<review_gate>Show the new test, the passing run, and one real file tied by the new reader.</review_gate>
Click to copy
<role>Same engineer, debugging with me.</role>

<task>I will paste the summary and the cash-position.csv row. Match it to the list, show the evidence from the raw file lines, and propose the smallest fix. If it matches nothing, say so and ask for the raw file.</task>

<failure_modes>
1. Every account of one bank MISSING: file_glob or account_id does not match the file. Open the file, copy the identifier.
2. A BAI2 account breaks by exactly the float: someone swapped in the library summary (045 read as closing). Restore the 03 reader.
3. A BAI2 account off by 100x: implied decimals for that currency (MINOR in the engine). Add the currency, add a test.
4. A CAMT account UNVERIFIABLE: no OPBD or PRCD, or a multi page statement with CLBD on a page you lack. Collect every page.
5. CONTINUITY on Monday only: the prior folder is a day that bank did not report, or a back valued weekend item. Ask the bank.
6. ValueError "Multiple summary scopes": get_summary() on a multi account file. Use get_summaries() or the engine readers.
7. BAI2 BREAK with "49 control total" or "modifier 1/3 interim": a lost record, a hand edited file, or an interim file. Ask the bank to resend the final file; never edit the 49 record.
</failure_modes>

<trap>"It worked yesterday" points at the data, not the code: a bank changed its file. Diff today's file header against yesterday's before touching the engine.</trap>

<constraints>Work from the data source selected in prompt 01. Never fix a BREAK by changing a balance. If the fix would change a figure on an account that was TIED before, say so and stop.</constraints>

<review_gate>Re run python -m pytest -q after any engine change.</review_gate>
Click to copy
<role>Same engineer, building the rollup {{SIGNER}} reads.</role>

<task>
Write rollup.py that reads positions/{{BUSINESS_DAY}}/cash-position.csv and an FX file fx/{{BUSINESS_DAY}}.csv (currency, rate to {{REPORTING_CCY}}, source, time). Output positions/{{BUSINESS_DAY}}/rollup.csv: by entity and currency, native amount and {{REPORTING_CCY}} amount, for TIED accounts only, held accounts listed separately with no amount. Net ZBA subs into their header. Pools: show each member and the pool net per the pool agreement, never only the sum of positive members.
</task>

<trap>
Adding EUR, GBP and USD into one number without a rate source and time. Every converted figure carries the FX row it used (TIER 2). No rate file, no converted figure: native amounts only.
</trap>

<constraints>Work from the data source selected in prompt 01. A ZBA sub with a non zero close, or a sweep leg with no matching leg, is CHANGES THE DECISION and is listed, not netted. If the FX file lacks a currency that has TIED accounts, say so and stop for that currency.</constraints>

<review_gate>Show native totals next to converted totals; {{SIGNER}} checks one conversion by hand.</review_gate>
Click to copy
<role>An independent reviewer. You did not build cash_position.py and you do not trust it.</role>

<task>
For every TIED account in positions/{{BUSINESS_DAY}}/cash-position.csv, re derive the tie from the raw bank file lines only, not from the engine and not from the library:
- MT940: read :60F: or :60M: (first page) and :62F: (last page), list every :61: line: C credit, D debit, RC a reversed credit (negative), RD a reversed debit (positive). Read :64: for available.
- BAI2: read 010, 015 and 045 from the 03 record, list every 16 record (type codes 100 to 399 are credits, 400 to 699 debits), divide by the currency's minor unit (100 for USD and EUR). The 49 control total is the sum of every amount on the account's 03 and 16 records, in cents.
- CAMT: read OPBD (or PRCD), CLBD and CLAV, list every Ntry with CdtDbtInd.
Also check each TIED account's opening against yesterday's closing_ledger (or its RELEASE line) yourself.
Show the working per account in one table: account, opening, credits, debits, closing, your difference, engine difference.
Then check the confirmed total per currency equals the sum of TIED closings.
For every held account, confirm the reason from the raw lines (the difference for a BREAK, the two file names for a DUPLICATE, yesterday's signed close for a CONTINUITY, the absent file or code for MISSING and UNVERIFIABLE, the file date for STALE). For RELEASED, quote the releases.txt line. A held status you cannot confirm is SELF CHECK FAILED too.
</task>

<trap>
Re reading the engine's CSV and calling it a check. The check counts only if every figure comes from a file line you quote.
</trap>

<constraints>
Work from the data source selected in prompt 01. Any account where your figure differs from the engine's by any amount: write SELF CHECK FAILED with both figures and STOP. Do not produce the sign off sentence. Constructing an argument for why the difference does not matter is the failure. Only {{SIGNER}} resolving the difference in writing unblocks it.
</constraints>

<review_gate>SELF CHECK PASSED (all accounts listed) or SELF CHECK FAILED. Nothing in between. On PASSED, {{SIGNER}} (never Claude, never the script) writes positions/{{BUSINESS_DAY}}/signoff.txt, first line Signed <date time> by <name>, business day {{BUSINESS_DAY}}, then the confirmed totals, and INCOMPLETE with the held list if anything is held. Sign once the fixable holds were fixed and re run, or at {{CUTOFF}} as INCOMPLETE if they were not, per prompt 01.</review_gate>
Click to copy
<role>Same engineer.</role>

<task>
Write run_morning.sh (or .ps1) that: activates .venv, runs fetch_inbox, works out the previous business day from each bank's holiday calendar, runs python cash_position.py --date <that business day>, writes the summary and exit code to logs/YYYY-MM-DD.log, and keeps every day's positions folder. Schedule it before {{CUTOFF}} with launchd (macOS), cron (Linux) or Task Scheduler (Windows); print the exact entry and how to remove it. The script never signs anything: signoff.txt is written only by {{SIGNER}}.
</task>

<definition_of_done>
You are done when, on three business days in a row, the script runs before {{CUTOFF}} unattended, every account in accounts.csv appears in the position with a status, prompt 10 passes, and {{SIGNER}} signs before the cut off. Then build next: intraday files (prompt 07), today's known flows against yesterday's forecast, and the month end handoff (bonus B4).
</definition_of_done>

<trap>A scheduled job that fails silently looks like a quiet morning. The log must show the run time and exit code every day, and a missing log is itself a hold.</trap>

<constraints>Work from the data source selected in prompt 01. STOP and ask before the schedule touches any folder outside {{PROJECT_DIR}}.</constraints>

<review_gate>{{SIGNER}} confirms the schedule entry and where the logs live.</review_gate>
Click to copy
<role>Same engineer.</role>

<task>
Write view.py that reads positions/{{BUSINESS_DAY}}/cash-position.csv and summary.md. Every amount and total comes from the closing_ledger and available_balance columns of cash-position.csv; summary.md supplies only the READY or NOT READY line. Never use the file_* columns or the note, which carry file figures for held rows. It writes positions/{{BUSINESS_DAY}}/view.html: one self contained file, no network, no external fonts or scripts. Top: confirmed ledger AND confirmed available by currency, side by side, and the READY or NOT READY line. Then one row per account: bank, account (last four characters only), currency, status coloured (TIED green, RELEASED amber with its release_reason column, every held status red), closing ledger, available, source file and balance codes. Held accounts show "no figure". Run it and tell me the path.
</task>

<trap>A view that shows a number for a held account, even greyed out, gets read as cash. Held means no figure on the page.</trap>

<constraints>Work from the data source selected in prompt 01. Every number on the page comes from cash-position.csv; if a panel needs a figure the CSV does not have, say so and stop rather than substitute a file_* figure.</constraints>

<review_gate>Open view.html and compare up to three figures to the CSV (all of them on a day with fewer).</review_gate>
Source repo
https://github.com/sebastienrousseau/bankstatementparser

The code is public and free. The setup instruction above installs and wires it for you. You never need to open this link.

From a working build to an operating system.

For treasury teams who log into bank portals every morning: turn MT940, BAI2 and CAMT.053 files into one cash position, with a balance tie per account and a stop on any account that won't tie.

Path A · free

Run the build independently

The setup rail and every prompt above are free and stay free. The cost is your time, and the risk of wiring it wrong on live data.

Back to the prompts
Path B · done with you

Connect it to your operation

The pack is about 70% of the work. We wire the rest in your environment: bank file delivery by SFTP, SWIFT or API for each bank, the readers your banks need, scheduling and the audit trail. Reply "wire it" for a 30-minute slot.

Book a build call
data safety

Before you use live numbers

  • • Run last quarter's numbers first. Live data is not a test bed.
  • • Nothing here uploads to us. It runs in your own Claude account, on your own machine.
  • • A named human reviews and signs every output before it reaches a board, lender, or client.
  • • Wiring the open-source piece to real systems? Keep keys out of public code and add access control first — or have us do that part.
the fine print

Credit the original author

Prompt set and cash_position.py authored by consultance.ai. bankstatementparser is an independent open source project by Sebastien Rousseau, licensed Apache-2.0 OR MIT, no affiliation implied. Everything runs in your own Claude Code on your own machine. Your bank files go only to your own Claude account; we never see them. Use a Team or Enterprise plan, or turn off model training, before you load live bank data.

Bring this workflow into your operation.

Book a build callBack to the library

Want this wired into your stack instead of running it yourself? That is our AI deal desk and finance automation service.

Newsletter

New builds, as they ship

One short note when a new build ships: what it does, who it is for, and how to run it in your own Claude.

Free. One email a week at most. Unsubscribe in one click.

More like this

Related builds

All builds
Finance and data
Finance and data

Month End Close AI Agent Team

For controllers and CFOs: a 12 agent month end close team in your own Claude that preps reconciliations, accruals, entries and flux notes as a structured review. You approve every ledger post.

Setup guide
Finance and data
Finance and data

Ramp Claude Connector Setup Guide

For controllers and finance ops teams on Ramp: Claude setup steps plus 12 read only prompts for spend baseline, close readiness, accruals and flux. Replaces the manual Ramp export to Excel.

Setup guide
Finance and data
Finance and data

Claude and NetSuite Setup Guide

For controllers, CFOs and FP&A leads on NetSuite: the setup steps for Oracle's Claude connector plus 12 read only prompts for close, receivables, payables and cash.

Setup guide
Get the free kitBook a call

Share with your workflow owner.

Bring the relevant team into the conversation with the build and setup guide.

Forward by email
in one line

What is Daily Cash Position From Bank Files?

Daily Cash Position From Bank Files is a finance and data build in the consultance.ai AI Build Library. For treasury teams who log into bank portals every morning: turn MT940, BAI2 and CAMT.053 files into one cash position, with a balance tie per account and a stop on any account that won't tie. It fits Corporate treasurers, treasury analysts, controllers and finance teams at companies with accounts at several banks who build the morning cash position by hand from bank portals. Setup difficulty is Medium, with 3 plain-English steps.

What does Daily Cash Position From Bank Files do?

For treasury teams who log into bank portals every morning: turn MT940, BAI2 and CAMT.053 files into one cash position, with a balance tie per account and a stop on any account that won't tie.

Who is Daily Cash Position From Bank Files for?

It fits Corporate treasurers, treasury analysts, controllers and finance teams at companies with accounts at several banks who build the morning cash position by hand from bank portals.

How hard is Daily Cash Position From Bank Files to set up?

Medium to set up — one guided setup instruction covering 3 plain-English steps, plus 12 ready-to-run prompts on the resource page.

How would consultance.ai build this out?

The pack is about 70% of the work. We wire the rest in your environment: bank file delivery by SFTP, SWIFT or API for each bank, the readers your banks need, scheduling and the audit trail. Reply "wire it" for a 30-minute slot.

What are the licensing terms?

Prompt set and cash_position.py authored by consultance.ai. bankstatementparser is an independent open source project by Sebastien Rousseau, licensed Apache-2.0 OR MIT, no affiliation implied. Everything runs in your own Claude Code on your own machine. Your bank files go only to your own Claude account; we never see them. Use a Team or Enterprise plan, or turn off model training, before you load live bank data.

Connect this build to your workflow.

Discuss how Daily Cash Position From Bank Files fits your systems, controls, and team. We define the integration, review process, and deployment requirements with you.

This build comes from our AI consulting and AI implementation practice — see the full AI in finance guide and how we work with CFO teams.

Book an audit