bankstatementparser 0.0.8__tar.gz → 0.0.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bankstatementparser-0.0.10/PKG-INFO +841 -0
- bankstatementparser-0.0.10/README.md +801 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/__init__.py +14 -16
- bankstatementparser-0.0.10/bankstatementparser/_amounts.py +54 -0
- bankstatementparser-0.0.10/bankstatementparser/_llm_common.py +170 -0
- bankstatementparser-0.0.10/bankstatementparser/additional_parsers.py +638 -0
- bankstatementparser-0.0.10/bankstatementparser/api.py +326 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/bank_statement_parsers.py +44 -61
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/base_parser.py +13 -26
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/camt_parser.py +162 -161
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/cli.py +229 -283
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/account_mapper.py +8 -5
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/categorizer.py +42 -66
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/exceptions.py +4 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/export/ledger.py +3 -9
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/__init__.py +39 -3
- bankstatementparser-0.0.10/bankstatementparser/hybrid/evaluation.py +372 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/llm_extractor.py +39 -75
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/ollama_direct.py +27 -36
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/orchestrator.py +88 -30
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/pdf_text.py +40 -16
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/prompts.py +1 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/scanner.py +80 -13
- bankstatementparser-0.0.10/bankstatementparser/hybrid/verification.py +419 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/vision.py +26 -15
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/input_validator.py +25 -48
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/pain001_parser.py +82 -168
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/parallel.py +6 -12
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/record_types.py +22 -8
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/transaction_deduplicator.py +72 -62
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/transaction_models.py +22 -17
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/zip_security.py +6 -18
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/pyproject.toml +64 -10
- bankstatementparser-0.0.8/PKG-INFO +0 -479
- bankstatementparser-0.0.8/README.md +0 -440
- bankstatementparser-0.0.8/bankstatementparser/additional_parsers.py +0 -376
- bankstatementparser-0.0.8/bankstatementparser/api.py +0 -185
- bankstatementparser-0.0.8/bankstatementparser/hybrid/verification.py +0 -184
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/LICENSE +0 -0
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/__init__.py +1 -1
- {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/export/__init__.py +0 -0
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bankstatementparser
|
|
3
|
+
Version: 0.0.10
|
|
4
|
+
Summary: BankStatementParser is your essential tool for easy bank statement management. Designed with finance and treasury experts in mind, it offers a simple way to handle CAMT (ISO 20022) formats and more. Get quick, accurate insights from your financial data and spend less time on processing. It's the smart, hassle-free way to stay on top of your transactions.
|
|
5
|
+
License: Apache Software License
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Sebastien Rousseau
|
|
8
|
+
Author-email: sebastian.rousseau@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Provides-Extra: api
|
|
18
|
+
Provides-Extra: enrichment
|
|
19
|
+
Provides-Extra: excel
|
|
20
|
+
Provides-Extra: hybrid
|
|
21
|
+
Provides-Extra: hybrid-plus
|
|
22
|
+
Provides-Extra: hybrid-vision
|
|
23
|
+
Provides-Extra: polars
|
|
24
|
+
Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
|
|
25
|
+
Requires-Dist: fastapi (>=0.115.0) ; extra == "api"
|
|
26
|
+
Requires-Dist: litellm (>=1.83.10) ; (python_version < "3.14") and (extra == "hybrid" or extra == "hybrid-plus" or extra == "hybrid-vision" or extra == "enrichment")
|
|
27
|
+
Requires-Dist: lxml (>=5.0)
|
|
28
|
+
Requires-Dist: openpyxl (>=3.1.5,<4.0.0) ; extra == "excel"
|
|
29
|
+
Requires-Dist: pandas (>=2.3.3,<3.0.0)
|
|
30
|
+
Requires-Dist: pdfplumber (>=0.11.0) ; extra == "hybrid-plus"
|
|
31
|
+
Requires-Dist: polars (>=1.32.0,<2.0.0) ; extra == "polars"
|
|
32
|
+
Requires-Dist: pydantic (>=2.11.0,<3.0.0)
|
|
33
|
+
Requires-Dist: pypdf (>=4.0.0) ; extra == "hybrid" or extra == "hybrid-plus" or extra == "hybrid-vision"
|
|
34
|
+
Requires-Dist: pypdfium2 (>=4.30.0) ; extra == "hybrid-vision"
|
|
35
|
+
Requires-Dist: uvicorn (>=0.34.0) ; extra == "api"
|
|
36
|
+
Project-URL: Homepage, https://bankstatementparser.com
|
|
37
|
+
Project-URL: Repository, https://github.com/sebastienrousseau/bankstatementparser
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
41
|
+
|
|
42
|
+
<h1 align="center">Bank Statement Parser</h1>
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
Bank statement parsing for Python — six structured formats and
|
|
46
|
+
PDFs, digital or scanned, into one auditable
|
|
47
|
+
<code>Transaction</code> model.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<a href="https://github.com/sebastienrousseau/bankstatementparser/actions"><img src="https://img.shields.io/github/actions/workflow/status/sebastienrousseau/bankstatementparser/quality-gates.yml?style=for-the-badge&logo=github" alt="Build" /></a>
|
|
52
|
+
<a href="https://pypi.org/project/bankstatementparser/"><img src="https://img.shields.io/pypi/pyversions/bankstatementparser.svg?style=for-the-badge&v=0.0.10" alt="PyPI" /></a>
|
|
53
|
+
<a href="https://pypi.org/project/bankstatementparser/"><img src="https://img.shields.io/pypi/dm/bankstatementparser.svg?style=for-the-badge" alt="PyPI Downloads" /></a>
|
|
54
|
+
<a href="https://codecov.io/github/sebastienrousseau/bankstatementparser?branch=main"><img src="https://img.shields.io/codecov/c/github/sebastienrousseau/bankstatementparser?style=for-the-badge" alt="Codecov" /></a>
|
|
55
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/sebastienrousseau/bankstatementparser?style=for-the-badge" alt="License" /></a>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
Parse bank statements across **six structured formats** (CAMT,
|
|
61
|
+
PAIN.001, CSV, OFX/QFX, MT940) **and PDFs** — both digital and
|
|
62
|
+
scanned — into a single unified `Transaction` model. ISO 20022
|
|
63
|
+
files take the deterministic path; PDFs fall through to a
|
|
64
|
+
configurable LLM (Ollama by default, any LiteLLM-supported
|
|
65
|
+
provider) and finally to a multimodal vision model for
|
|
66
|
+
scanned/photocopied statements.
|
|
67
|
+
|
|
68
|
+
Built for finance teams, treasury analysts, and fintech developers
|
|
69
|
+
who need reliable, auditable extraction across the full spectrum
|
|
70
|
+
of bank statement formats — without sending data to external
|
|
71
|
+
services unless they explicitly opt in.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Contents
|
|
76
|
+
|
|
77
|
+
**Getting started**
|
|
78
|
+
|
|
79
|
+
- [Install](#install) — pip extras, requirements, local development
|
|
80
|
+
- [Quick Start](#quick-start) — parse a statement in three lines
|
|
81
|
+
|
|
82
|
+
**The pipeline**
|
|
83
|
+
|
|
84
|
+
- [How it works](#how-it-works) — routing diagram: deterministic first, LLM and vision as opt-in fallbacks
|
|
85
|
+
- [Key Features](#key-features) — parsing, hybrid PDF pipeline, verification, enrichment, security
|
|
86
|
+
|
|
87
|
+
**Library reference**
|
|
88
|
+
|
|
89
|
+
- [PII Redaction](#pii-redaction) — masked by default, opt in to see full data
|
|
90
|
+
- [Streaming](#streaming) — bounded memory at 50,000 transactions
|
|
91
|
+
- [Performance](#performance) — throughput and latency, CI-enforced
|
|
92
|
+
- [Parallel Parsing](#parallel-parsing) — multi-file batches across CPU cores
|
|
93
|
+
- [Command Line](#command-line) — console script and REST API
|
|
94
|
+
- [Deduplication](#deduplication) — exact hashes + suspected matches
|
|
95
|
+
- [Export](#export) — CSV, JSON, Excel, Polars, hledger, beancount
|
|
96
|
+
- [Examples](#examples) — 23 runnable scripts (14 deterministic + 9 hybrid)
|
|
97
|
+
- [XML Tag Mapping](#xml-tag-mapping) — ISO 20022 tags to DataFrame columns
|
|
98
|
+
- [Ecosystem](#ecosystem) — companion packages (MCP, LSP, writers, loaders)
|
|
99
|
+
|
|
100
|
+
**Operational**
|
|
101
|
+
|
|
102
|
+
- [Project Layout](#project-layout)
|
|
103
|
+
- [Security](#security) — XXE, ZIP-bomb, and path-traversal protection
|
|
104
|
+
- [Verify the Repository](#verify-the-repository) — the five local gates
|
|
105
|
+
- [Versioning](#versioning) — SemVer policy for the 0.y.z series
|
|
106
|
+
- [Contributing](#contributing)
|
|
107
|
+
- [FAQ](#faq)
|
|
108
|
+
- [Documentation](#documentation) — all reference docs
|
|
109
|
+
- [License](#license)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Install
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Core install — deterministic parsers only (CAMT, PAIN.001, CSV, OFX, QFX, MT940)
|
|
117
|
+
pip install bankstatementparser
|
|
118
|
+
|
|
119
|
+
# Add Excel (.xlsx) export support (openpyxl)
|
|
120
|
+
pip install 'bankstatementparser[excel]'
|
|
121
|
+
|
|
122
|
+
# Add the text-LLM path for digital PDFs (litellm + pypdf)
|
|
123
|
+
pip install 'bankstatementparser[hybrid]'
|
|
124
|
+
|
|
125
|
+
# Add higher-fidelity table extraction (adds pdfplumber)
|
|
126
|
+
pip install 'bankstatementparser[hybrid-plus]'
|
|
127
|
+
|
|
128
|
+
# Add the multimodal vision path for scanned/photocopied PDFs (adds pypdfium2)
|
|
129
|
+
pip install 'bankstatementparser[hybrid-vision]'
|
|
130
|
+
|
|
131
|
+
# Add LLM-powered transaction categorization
|
|
132
|
+
pip install 'bankstatementparser[enrichment]'
|
|
133
|
+
|
|
134
|
+
# Add the REST API microservice (FastAPI + uvicorn)
|
|
135
|
+
pip install 'bankstatementparser[api]'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The core install has zero AI dependencies. Every extra is opt-in
|
|
139
|
+
and pure-Python — no `poppler`, no system libraries, no GPU
|
|
140
|
+
required.
|
|
141
|
+
|
|
142
|
+
### Requirements
|
|
143
|
+
|
|
144
|
+
- Python **3.10** through **3.14** (Python 3.9 was dropped in
|
|
145
|
+
v0.0.6 — pin to v0.0.5 if you cannot upgrade your interpreter).
|
|
146
|
+
- Poetry (for local development).
|
|
147
|
+
|
|
148
|
+
> **Python version note.** The deterministic core (CAMT, PAIN.001,
|
|
149
|
+
> CSV, OFX, QFX, MT940) supports Python **3.10–3.14**. The optional
|
|
150
|
+
> `[hybrid]`, `[hybrid-plus]`, `[hybrid-vision]`, and
|
|
151
|
+
> `[enrichment]` extras pull in `litellm`, which currently requires
|
|
152
|
+
> **Python <3.14** upstream — on Python 3.14 the LLM extras will
|
|
153
|
+
> not install. Pin `Python 3.13` for the full hybrid pipeline;
|
|
154
|
+
> upgrade to 3.14 freely if you only need deterministic parsing.
|
|
155
|
+
|
|
156
|
+
### Local development
|
|
157
|
+
|
|
158
|
+
Clone and install on **macOS, Linux, or WSL**:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git clone https://github.com/sebastienrousseau/bankstatementparser.git
|
|
162
|
+
cd bankstatementparser
|
|
163
|
+
python3 -m venv .venv
|
|
164
|
+
source .venv/bin/activate
|
|
165
|
+
pip install poetry
|
|
166
|
+
poetry install --with dev -E excel
|
|
167
|
+
make install-hooks # pre-commit hook runs `make verify` before every commit
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Quick Start
|
|
173
|
+
|
|
174
|
+
### Parse a CAMT statement
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from bankstatementparser import CamtParser
|
|
178
|
+
|
|
179
|
+
parser = CamtParser("statement.xml")
|
|
180
|
+
transactions = parser.parse()
|
|
181
|
+
print(transactions)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
Amount Currency DrCr Debtor Creditor ValDt AccountId
|
|
186
|
+
105678.5 SEK CRDT MUELLER 2010-10-18 50000000054910
|
|
187
|
+
-200000.0 SEK DBIT 2010-10-18 50000000054910
|
|
188
|
+
30000.0 SEK CRDT 2010-10-18 50000000054910
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Parse a PAIN.001 payment file
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from bankstatementparser import Pain001Parser
|
|
195
|
+
|
|
196
|
+
parser = Pain001Parser("payment.xml")
|
|
197
|
+
payments = parser.parse()
|
|
198
|
+
print(payments)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
PmtInfId PmtMtd InstdAmt Currency CdtrNm EndToEndId
|
|
203
|
+
PMT-001 TRF 1500.00 EUR ACME Corp E2E-001
|
|
204
|
+
PMT-001 TRF 2300.50 EUR Global Ltd E2E-002
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Auto-detect the format
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from bankstatementparser import create_parser, detect_statement_format
|
|
211
|
+
|
|
212
|
+
fmt = detect_statement_format("transactions.ofx")
|
|
213
|
+
parser = create_parser("transactions.ofx", fmt)
|
|
214
|
+
records = parser.parse()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Works with `.xml`, `.csv`, `.ofx`, `.qfx`, and `.mt940` files.
|
|
218
|
+
|
|
219
|
+
### Hybrid extraction (PDFs included) *(v0.0.5)*
|
|
220
|
+
|
|
221
|
+
`smart_ingest()` is the single entry point that routes any file
|
|
222
|
+
through the cheapest viable extraction path:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from bankstatementparser.hybrid import smart_ingest
|
|
226
|
+
|
|
227
|
+
# Path A — deterministic parser (free, fastest, $0)
|
|
228
|
+
result = smart_ingest("statement.xml")
|
|
229
|
+
print(result.source_method) # "deterministic"
|
|
230
|
+
|
|
231
|
+
# Path B — text-LLM for digital PDFs (set BSP_HYBRID_MODEL=ollama/llama3)
|
|
232
|
+
result = smart_ingest("statement.pdf")
|
|
233
|
+
print(result.source_method) # "llm"
|
|
234
|
+
print(result.verification.status) # VERIFIED | DISCREPANCY | UNVERIFIABLE | FAILED
|
|
235
|
+
|
|
236
|
+
# Path C — multimodal vision for scanned PDFs (set BSP_HYBRID_VISION_MODEL)
|
|
237
|
+
# auto-routed when pypdf cannot extract enough text
|
|
238
|
+
result = smart_ingest("scan.pdf")
|
|
239
|
+
print(result.source_method) # "vision"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Every row carries:
|
|
243
|
+
|
|
244
|
+
- `source_method` — `"deterministic"`, `"llm"`, or `"vision"` for
|
|
245
|
+
full audit provenance
|
|
246
|
+
- `transaction_hash` — MD5 fingerprint of
|
|
247
|
+
`date | normalized_description | amount`, ready for idempotent
|
|
248
|
+
re-ingestion
|
|
249
|
+
- `confidence` — float between 0 and 1 for LLM rows, `None` for
|
|
250
|
+
deterministic
|
|
251
|
+
- `raw_source_text` — best-effort source-text slice for the v0.0.6
|
|
252
|
+
review-mode UI
|
|
253
|
+
|
|
254
|
+
A complete walkthrough with synthetic UK-bank PDFs, mock vs. live
|
|
255
|
+
mode, and a Mermaid flow diagram lives in
|
|
256
|
+
[`examples/hybrid/README.md`](examples/hybrid/README.md).
|
|
257
|
+
|
|
258
|
+
### Parse from memory (no disk I/O)
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
from bankstatementparser import CamtParser
|
|
262
|
+
|
|
263
|
+
xml_bytes = download_from_sftp() # your own function
|
|
264
|
+
parser = CamtParser.from_bytes(xml_bytes, source_name="daily.xml")
|
|
265
|
+
transactions = parser.parse()
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Pass only decompressed XML to `from_string()` or `from_bytes()`.
|
|
269
|
+
For ZIP archives, use `iter_secure_xml_entries()`.
|
|
270
|
+
|
|
271
|
+
### Parse XML files inside a ZIP archive
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from bankstatementparser import CamtParser, iter_secure_xml_entries
|
|
275
|
+
|
|
276
|
+
for entry in iter_secure_xml_entries("statements.zip"):
|
|
277
|
+
parser = CamtParser.from_bytes(entry.xml_bytes, source_name=entry.source_name)
|
|
278
|
+
transactions = parser.parse()
|
|
279
|
+
print(entry.source_name, len(transactions), "transactions")
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The iterator enforces size limits, blocks encrypted entries, and
|
|
283
|
+
rejects suspicious compression ratios before any XML parsing
|
|
284
|
+
occurs.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## How it works
|
|
289
|
+
|
|
290
|
+
`smart_ingest()` routes any input file through the cheapest viable
|
|
291
|
+
extraction path. Deterministic parsers always run first ($0 cost).
|
|
292
|
+
Text and vision LLMs are fallbacks for unstandardized PDFs — both
|
|
293
|
+
are opt-in via separate install extras and can be swapped between
|
|
294
|
+
any LiteLLM-supported provider (Ollama, Anthropic, OpenAI,
|
|
295
|
+
Gemini, …).
|
|
296
|
+
|
|
297
|
+
```mermaid
|
|
298
|
+
flowchart TD
|
|
299
|
+
A[smart_ingest(path)] --> B{detect_statement_format}
|
|
300
|
+
B -- CAMT/PAIN/OFX/MT940/CSV --> C[Path A: deterministic parser<br/>$0, fastest]
|
|
301
|
+
C --> Z[IngestResult<br/>source_method='deterministic']
|
|
302
|
+
|
|
303
|
+
B -- pdf or unknown --> D[pypdf extract_text]
|
|
304
|
+
D --> E{text len >= 50?}
|
|
305
|
+
|
|
306
|
+
E -- yes --> F[Path B: text-LLM<br/>default ollama/llama3]
|
|
307
|
+
F --> Y[IngestResult<br/>source_method='llm']
|
|
308
|
+
|
|
309
|
+
E -- no --> G[Path C: vision-LLM<br/>opt-in via BSP_HYBRID_VISION_MODEL]
|
|
310
|
+
G --> X[IngestResult<br/>source_method='vision']
|
|
311
|
+
|
|
312
|
+
Z --> V[verify_balance<br/>Golden Rule]
|
|
313
|
+
Y --> V
|
|
314
|
+
X --> V
|
|
315
|
+
V --> R[VERIFIED / DISCREPANCY / UNVERIFIABLE / FAILED]
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Every extracted row carries an immutable `transaction_hash`, an
|
|
319
|
+
audit-trail `source_method` tag, and (for LLM rows) a `confidence`
|
|
320
|
+
score — see
|
|
321
|
+
[Hybrid extraction](#hybrid-extraction-pdfs-included-v005) above
|
|
322
|
+
for the full surface.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Key Features
|
|
327
|
+
|
|
328
|
+
### Parsing
|
|
329
|
+
|
|
330
|
+
| Feature | Description |
|
|
331
|
+
|---|---|
|
|
332
|
+
| **6 structured formats** | CAMT.053, PAIN.001, CSV, OFX, QFX, MT940 |
|
|
333
|
+
| **Auto-detection** | `detect_statement_format()` identifies the format; `create_parser()` returns the right parser |
|
|
334
|
+
| **Streaming** | `parse_streaming()` at 27,000+ tx/s (CAMT) and 52,000+ tx/s (PAIN.001) with bounded memory |
|
|
335
|
+
| **Parallel** | `parse_files_parallel()` for multi-file batch processing across CPU cores |
|
|
336
|
+
| **In-memory parsing** | `from_string()` and `from_bytes()` parse XML without touching disk |
|
|
337
|
+
|
|
338
|
+
### Hybrid PDF pipeline (LLM-assisted)
|
|
339
|
+
|
|
340
|
+
| Feature | Description |
|
|
341
|
+
|---|---|
|
|
342
|
+
| **Hybrid PDF pipeline** *(v0.0.5)* | `smart_ingest()` routes digital PDFs through a text-LLM and scanned PDFs through a multimodal vision model. Deterministic parsers always tried first ($0 cost). |
|
|
343
|
+
| **Local-first LLM** *(v0.0.5)* | Ollama is the default backend; switch to Anthropic, OpenAI, or any LiteLLM provider via `BSP_HYBRID_MODEL`. Vision is opt-in via `BSP_HYBRID_VISION_MODEL` — no surprise downloads. |
|
|
344
|
+
| **Direct Ollama bridge** *(v0.0.7)* | Auto-bypasses the upstream LiteLLM ↔ Ollama hang on long vision prompts. `ollama/minicpm-v` recommended over `ollama/llava` for document OCR. |
|
|
345
|
+
| **Strip mode** *(v0.0.7)* | `VisionExtractor(strip_rows=True)` splits dense pages into overlapping bands for small local models — fixes sign-flip errors and improves accuracy on 15+ row statements. |
|
|
346
|
+
| **Bounding boxes** *(v0.0.6)* | `Transaction.source_bbox` carries per-row normalized coordinates from the vision path for downstream review UIs. |
|
|
347
|
+
| **Bulk directory scanner** *(v0.0.8)* | `scan_and_ingest(directory, pattern="**/*.pdf")` scans a folder tree, runs `smart_ingest` on every match, deduplicates across the entire batch. |
|
|
348
|
+
|
|
349
|
+
### Data quality & verification
|
|
350
|
+
|
|
351
|
+
| Feature | Description |
|
|
352
|
+
|---|---|
|
|
353
|
+
| **Golden Rule verification** *(v0.0.5)* | Every result carries `opening + credits − debits == closing` status: `VERIFIED`, `DISCREPANCY`, `UNVERIFIABLE`, or `FAILED`. |
|
|
354
|
+
| **Multi-currency verification** *(v0.0.8)* | `verify_balance_multi_currency()` groups transactions by currency and runs the Golden Rule independently per group — no more false `DISCREPANCY` on multi-currency statements. |
|
|
355
|
+
| **Idempotent dedup** *(v0.0.5)* | Every `Transaction` carries a stable `transaction_hash` (MD5 of date + normalized description + amount). `Deduplicator.dedupe_by_hash()` makes incremental ingestion safe to re-run. |
|
|
356
|
+
| **Interactive review** *(v0.0.6)* | `--type review` CLI walks through discrepancies with accept/edit/skip/delete/quit. `IngestResult.to_json()` / `.from_json()` for stable round-trip with embedded audit trail. |
|
|
357
|
+
|
|
358
|
+
### Enrichment & export
|
|
359
|
+
|
|
360
|
+
| Feature | Description |
|
|
361
|
+
|---|---|
|
|
362
|
+
| **Categorization** *(v0.0.6)* | `bankstatementparser.enrichment.Categorizer` tags transactions with a pluggable category schema (Plaid 13-category default) and an optional `is_business_expense` flag. Wrapper model — never mutates the original `Transaction`. |
|
|
363
|
+
| **Account mapping** *(v0.0.8)* | `AccountMapper` with ordered regex rules loaded from JSON config. First match wins. Pairs with the ledger exporter for end-to-end plaintext-accounting workflows. |
|
|
364
|
+
| **hledger + beancount export** *(v0.0.8)* | `to_hledger()` and `to_beancount()` produce journal strings for plaintext-accounting workflows. Uses `Transaction.category` as the contra-account when set. |
|
|
365
|
+
| **Export** | CSV, JSON, Excel (`.xlsx`), and optional Polars DataFrames |
|
|
366
|
+
| **REST API** *(v0.0.8)* | FastAPI microservice: `POST /ingest` a file, get JSON back. `GET /health` for monitoring. `pip install 'bankstatementparser[api]'`. |
|
|
367
|
+
|
|
368
|
+
### Security & quality
|
|
369
|
+
|
|
370
|
+
| Feature | Description |
|
|
371
|
+
|---|---|
|
|
372
|
+
| **PII redaction** | Names, IBANs, and addresses masked by default — opt in with `--show-pii` |
|
|
373
|
+
| **Secure ZIP** | `iter_secure_xml_entries()` rejects zip bombs, encrypted entries, and suspicious compression ratios |
|
|
374
|
+
| **Tested** | 844 tests, coverage gated at 100% in CI, property-based fuzzing with Hypothesis |
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## PII Redaction
|
|
379
|
+
|
|
380
|
+
PII (names, IBANs, addresses) is **redacted by default** in
|
|
381
|
+
console output and streaming mode.
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
# Redacted by default
|
|
385
|
+
for tx in parser.parse_streaming(redact_pii=True):
|
|
386
|
+
print(tx) # Names and addresses show as ***REDACTED***
|
|
387
|
+
|
|
388
|
+
# Opt in to see full data
|
|
389
|
+
for tx in parser.parse_streaming(redact_pii=False):
|
|
390
|
+
print(tx)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
File exports (CSV, JSON, Excel) always contain the full unredacted
|
|
394
|
+
data.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Streaming
|
|
399
|
+
|
|
400
|
+
Process large files incrementally. Memory stays bounded regardless
|
|
401
|
+
of file size — tested at 50,000 transactions with sub-2x memory
|
|
402
|
+
scaling.
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from bankstatementparser import CamtParser
|
|
406
|
+
|
|
407
|
+
parser = CamtParser("large_statement.xml")
|
|
408
|
+
for transaction in parser.parse_streaming():
|
|
409
|
+
process(transaction) # each transaction is a dict
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Works with both `CamtParser` and `Pain001Parser`. PAIN.001 files
|
|
413
|
+
over 50 MB use chunk-based namespace stripping via a temporary
|
|
414
|
+
file — the full document is never loaded into memory.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Performance
|
|
419
|
+
|
|
420
|
+
| Metric | CAMT | PAIN.001 |
|
|
421
|
+
|---|---|---|
|
|
422
|
+
| **Throughput** | 27,000+ tx/s | 52,000+ tx/s |
|
|
423
|
+
| **Per-transaction latency** | 37 us | 19 us |
|
|
424
|
+
| **Time to first result** | < 1 ms | < 2 ms |
|
|
425
|
+
| **Memory scaling** | Constant (1K–50K) | Constant (1K–50K) |
|
|
426
|
+
|
|
427
|
+
Performance is flat from 1,000 to 50,000 transactions. CI enforces
|
|
428
|
+
minimum TPS and latency thresholds, and a non-blocking benchmark
|
|
429
|
+
job compares every run against the last known-good `main`
|
|
430
|
+
baseline.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Parallel Parsing
|
|
435
|
+
|
|
436
|
+
Process multiple files simultaneously across CPU cores:
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
from bankstatementparser import parse_files_parallel
|
|
440
|
+
|
|
441
|
+
results = parse_files_parallel([
|
|
442
|
+
"statements/jan.xml",
|
|
443
|
+
"statements/feb.xml",
|
|
444
|
+
"statements/mar.xml",
|
|
445
|
+
])
|
|
446
|
+
|
|
447
|
+
for r in results:
|
|
448
|
+
print(r.path, r.status, len(r.transactions), "rows")
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Uses `ProcessPoolExecutor` to bypass the GIL. Each file is parsed
|
|
452
|
+
in its own worker process. Auto-detects format per file, or force
|
|
453
|
+
with `format_name="camt"`.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Command Line
|
|
458
|
+
|
|
459
|
+
After installation a `bankstatementparser` console script is
|
|
460
|
+
available on `PATH`:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# Parse and display
|
|
464
|
+
bankstatementparser --type camt --input statement.xml
|
|
465
|
+
|
|
466
|
+
# Export to CSV
|
|
467
|
+
bankstatementparser --type camt --input statement.xml --output transactions.csv
|
|
468
|
+
|
|
469
|
+
# Stream with PII visible
|
|
470
|
+
bankstatementparser --type camt --input statement.xml --streaming --show-pii
|
|
471
|
+
|
|
472
|
+
# v0.0.5 — hybrid pipeline (auto-routes deterministic / text-LLM / vision)
|
|
473
|
+
bankstatementparser --type ingest --input statement.pdf
|
|
474
|
+
bankstatementparser --type ingest --input statement.pdf --output ledger.csv
|
|
475
|
+
|
|
476
|
+
# v0.0.6 — interactive review of saved IngestResult JSON
|
|
477
|
+
bankstatementparser --type review --input result.json
|
|
478
|
+
bankstatementparser --type review --input result.json --output reviewed.json
|
|
479
|
+
|
|
480
|
+
# v0.0.9 — also review rows the LLM was unsure about (confidence < 0.8)
|
|
481
|
+
bankstatementparser --type review --input result.json --review-below 0.8
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Supports `--type camt`, `--type pain001`, `--type ingest`
|
|
485
|
+
(v0.0.5), and `--type review` (v0.0.6). The
|
|
486
|
+
`python -m bankstatementparser.cli ...` invocation form continues
|
|
487
|
+
to work for parity with older releases.
|
|
488
|
+
|
|
489
|
+
### REST API *(v0.0.8)*
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
pip install 'bankstatementparser[api]'
|
|
493
|
+
bankstatementparser-api --port 8000
|
|
494
|
+
|
|
495
|
+
# POST a file, get JSON back
|
|
496
|
+
curl -F file=@statement.pdf http://localhost:8000/ingest
|
|
497
|
+
|
|
498
|
+
# Health check
|
|
499
|
+
curl http://localhost:8000/health
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Default bind is `127.0.0.1` (localhost-only). Use
|
|
503
|
+
`--host 0.0.0.0` for container deployments.
|
|
504
|
+
|
|
505
|
+
#### Security defaults
|
|
506
|
+
|
|
507
|
+
- Uploads are streamed in chunks; the request is rejected with
|
|
508
|
+
**413 Payload Too Large** once the cumulative size exceeds
|
|
509
|
+
`BSP_API_MAX_UPLOAD_BYTES` (default **25 MB**).
|
|
510
|
+
- The uploaded filename is reduced to its basename —
|
|
511
|
+
caller-supplied path components are dropped — and the suffix
|
|
512
|
+
must match one of the allowed input extensions (`.xml`, `.csv`,
|
|
513
|
+
`.ofx`, `.qfx`, `.mt940`, `.sta`, `.pdf`, `.json`). Anything
|
|
514
|
+
else returns **400 Bad Request**.
|
|
515
|
+
- On parse failure the response carries a UUID `correlation_id`;
|
|
516
|
+
the raw exception is logged server-side only. Status
|
|
517
|
+
**422 Unprocessable Entity**.
|
|
518
|
+
- **Authentication, authorization, and rate limiting are out of
|
|
519
|
+
scope** for this microservice — wire them in your reverse proxy
|
|
520
|
+
(nginx `auth_basic` + `limit_req`, a WAF, or an API gateway).
|
|
521
|
+
The default `127.0.0.1` bind means a fresh
|
|
522
|
+
`bankstatementparser-api` is never publicly reachable unless you
|
|
523
|
+
explicitly opt in via `--host 0.0.0.0`.
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## Deduplication
|
|
528
|
+
|
|
529
|
+
Detect duplicate transactions across multiple sources:
|
|
530
|
+
|
|
531
|
+
```python
|
|
532
|
+
from bankstatementparser import CamtParser, Deduplicator
|
|
533
|
+
|
|
534
|
+
parser = CamtParser("statement.xml")
|
|
535
|
+
dedup = Deduplicator()
|
|
536
|
+
result = dedup.deduplicate(dedup.from_dataframe(parser.parse()))
|
|
537
|
+
|
|
538
|
+
print(f"Unique: {len(result.unique_transactions)}")
|
|
539
|
+
print(f"Exact duplicates: {len(result.exact_duplicates)}")
|
|
540
|
+
print(f"Suspected matches: {len(result.suspected_matches)}")
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
The `Deduplicator` uses deterministic hashing for exact matches
|
|
544
|
+
and configurable similarity thresholds for suspected matches. Each
|
|
545
|
+
match group includes a confidence score and reason for
|
|
546
|
+
auditability.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Export
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
parser = CamtParser("statement.xml")
|
|
554
|
+
parser.parse()
|
|
555
|
+
|
|
556
|
+
# CSV
|
|
557
|
+
parser.export_csv("output.csv")
|
|
558
|
+
|
|
559
|
+
# JSON (includes summary + transactions)
|
|
560
|
+
parser.export_json("output.json")
|
|
561
|
+
|
|
562
|
+
# Excel
|
|
563
|
+
parser.camt_to_excel("output.xlsx")
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Polars (optional)
|
|
567
|
+
|
|
568
|
+
Convert any parser output to a Polars DataFrame:
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
polars_df = parser.to_polars()
|
|
572
|
+
lazy_df = parser.to_polars_lazy()
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Install with `pip install bankstatementparser[polars]`.
|
|
576
|
+
|
|
577
|
+
### hledger + beancount *(v0.0.8)*
|
|
578
|
+
|
|
579
|
+
Export transactions to plaintext-accounting journal formats:
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
from bankstatementparser.export import to_hledger, to_beancount
|
|
583
|
+
|
|
584
|
+
journal = to_hledger(transactions, account="Assets:Bank:Checking")
|
|
585
|
+
Path("journal.ledger").write_text(journal)
|
|
586
|
+
|
|
587
|
+
# Or beancount format:
|
|
588
|
+
journal = to_beancount(transactions, account="Assets:Bank:Checking")
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Uses `Transaction.category` as the contra-account when set by the
|
|
592
|
+
enrichment module.
|
|
593
|
+
|
|
594
|
+
### Bulk directory scanner *(v0.0.8)*
|
|
595
|
+
|
|
596
|
+
Scan a folder tree and ingest every statement, deduplicating
|
|
597
|
+
across the batch:
|
|
598
|
+
|
|
599
|
+
```python
|
|
600
|
+
from bankstatementparser.hybrid import scan_and_ingest
|
|
601
|
+
|
|
602
|
+
batch = scan_and_ingest("statements/2026/", pattern="**/*.pdf")
|
|
603
|
+
print(f"{batch.file_count} files, {batch.total_unique} unique transactions")
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Account mapping *(v0.0.8)*
|
|
607
|
+
|
|
608
|
+
Map transactions to ledger accounts via configurable regex rules:
|
|
609
|
+
|
|
610
|
+
```python
|
|
611
|
+
from bankstatementparser.enrichment import AccountMapper
|
|
612
|
+
|
|
613
|
+
mapper = AccountMapper.from_json("mapping.json")
|
|
614
|
+
for tx, account in zip(transactions, mapper.map_batch(transactions)):
|
|
615
|
+
print(f"{tx.description} -> {account}")
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Multi-currency verification *(v0.0.8)*
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
from bankstatementparser.hybrid import verify_balance_multi_currency
|
|
622
|
+
|
|
623
|
+
results = verify_balance_multi_currency(
|
|
624
|
+
transactions,
|
|
625
|
+
balances={"GBP": (opening, closing), "EUR": (opening, closing)},
|
|
626
|
+
)
|
|
627
|
+
for currency, v in results.items():
|
|
628
|
+
print(f"{currency}: {v.status.value}")
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## Examples
|
|
634
|
+
|
|
635
|
+
See [`examples/`](examples/README.md) for 23 runnable scripts
|
|
636
|
+
(14 deterministic + 9 hybrid):
|
|
637
|
+
|
|
638
|
+
### Deterministic parsers
|
|
639
|
+
|
|
640
|
+
| Example | What it demonstrates |
|
|
641
|
+
|---|---|
|
|
642
|
+
| `parse_camt_basic.py` | Load a CAMT.053 file and print transactions |
|
|
643
|
+
| `parse_camt_from_string.py` | Parse CAMT from an in-memory XML string |
|
|
644
|
+
| `inspect_camt.py` | Extract balances, stats, and summaries |
|
|
645
|
+
| `export_camt.py` | Export to CSV and JSON |
|
|
646
|
+
| `export_camt_excel.py` | Export to Excel workbook |
|
|
647
|
+
| `stream_camt.py` | Stream transactions incrementally |
|
|
648
|
+
| `parse_camt_zip.py` | Secure ZIP archive processing |
|
|
649
|
+
| `parse_detected_formats.py` | Auto-detect CSV, OFX, MT940, and XML formats |
|
|
650
|
+
| `parse_pain001_basic.py` | Parse a PAIN.001 payment file |
|
|
651
|
+
| `export_pain001.py` | Export PAIN.001 to CSV and JSON |
|
|
652
|
+
| `stream_pain001.py` | Stream payments incrementally |
|
|
653
|
+
| `validate_input.py` | Validate file paths with InputValidator |
|
|
654
|
+
| `compatibility_wrappers.py` | Legacy API wrappers |
|
|
655
|
+
| `cli_examples.sh` | CLI commands for CAMT and PAIN.001 |
|
|
656
|
+
|
|
657
|
+
### Hybrid pipeline *(v0.0.5)*
|
|
658
|
+
|
|
659
|
+
| Example | What it demonstrates |
|
|
660
|
+
|---|---|
|
|
661
|
+
| `hybrid/generate_sample_pdfs.py` | Produce reproducible synthetic UK-bank PDFs (digital + scanned) |
|
|
662
|
+
| `hybrid/01_smart_ingest_deterministic.py` | Path A — `smart_ingest()` against a CAMT.053 fixture, $0 cost |
|
|
663
|
+
| `hybrid/02_smart_ingest_text_llm.py` | Path B — text-LLM extraction from a digital PDF (mock or live Ollama) |
|
|
664
|
+
| `hybrid/03_smart_ingest_vision.py` | Path C — multimodal vision extraction with `LOW_TEXT_DENSITY` auto-routing |
|
|
665
|
+
| `hybrid/04_golden_rule.py` | `verify_balance()`, `verify_transactions()`, and `verify_continuity()` across `VERIFIED` / `DISCREPANCY` / `UNVERIFIABLE` outcomes |
|
|
666
|
+
| `hybrid/05_dedupe_recurring.py` | `normalize_description()` + `dedupe_by_hash()` for idempotent batching |
|
|
667
|
+
| `hybrid/06_cli_walkthrough.sh` | Four flavours of the new `--type ingest` CLI subcommand |
|
|
668
|
+
| `hybrid/06_cli_walkthrough.ps1` | PowerShell sibling of the bash walkthrough (native Windows) |
|
|
669
|
+
| `hybrid/07_scan_and_ingest.py` | Bulk directory ingest with `scan_and_ingest()` — cross-file dedup + continuity check |
|
|
670
|
+
|
|
671
|
+
See [`examples/hybrid/README.md`](examples/hybrid/README.md) for
|
|
672
|
+
the full walkthrough including a Mermaid flow diagram, the
|
|
673
|
+
cross-platform verification matrix, and the Ollama smoke-test
|
|
674
|
+
results.
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## XML Tag Mapping
|
|
679
|
+
|
|
680
|
+
See [`docs/MAPPING.md`](docs/MAPPING.md) for a complete reference
|
|
681
|
+
of ISO 20022 XML tags to DataFrame columns across all six formats.
|
|
682
|
+
Use this when integrating with ERP systems or building
|
|
683
|
+
reconciliation pipelines.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## Ecosystem
|
|
688
|
+
|
|
689
|
+
`bankstatementparser` is the core engine. Optional, independently
|
|
690
|
+
versioned companion packages build on it — each is its own repository
|
|
691
|
+
and Python package, so you install only what you need and the core
|
|
692
|
+
stays dependency-light.
|
|
693
|
+
|
|
694
|
+
| Package | PyPI | Role | What it adds |
|
|
695
|
+
|---|---|---|---|
|
|
696
|
+
| [`bankstatementparser-mcp`](https://github.com/sebastienrousseau/bankstatementparser-mcp) | [](https://pypi.org/project/bankstatementparser-mcp/) | AI agents | [Model Context Protocol](https://modelcontextprotocol.io) server — exposes detect / parse / validate / summarize as tools for Claude Desktop and other LLM clients |
|
|
697
|
+
| [`bankstatementparser-lsp`](https://github.com/sebastienrousseau/bankstatementparser-lsp) | [](https://pypi.org/project/bankstatementparser-lsp/) | Editors | Language Server with live MT940 diagnostics (missing tags, malformed balance / `:61:` lines) over [pygls](https://github.com/openlawlibrary/pygls) |
|
|
698
|
+
| [`bankstatementparser-writer-xlsx`](https://github.com/sebastienrousseau/bankstatementparser-writer-xlsx) | [](https://pypi.org/project/bankstatementparser-writer-xlsx/) | Output | Write parsed transactions (DataFrame, `Transaction` list, or dicts) to a polished Excel `.xlsx` workbook |
|
|
699
|
+
| [`bankstatementparser-loader-mt942`](https://github.com/sebastienrousseau/bankstatementparser-loader-mt942) | [](https://pypi.org/project/bankstatementparser-loader-mt942/) | Input | Parse SWIFT **MT942** interim transaction reports into `Transaction` objects (a format the core does not read) |
|
|
700
|
+
| [`bankstatementparser-loader-bai2`](https://github.com/sebastienrousseau/bankstatementparser-loader-bai2) | [](https://pypi.org/project/bankstatementparser-loader-bai2/) | Input | Parse **BAI2** cash-management files into `Transaction` objects (a format the core does not read) |
|
|
701
|
+
|
|
702
|
+
Loaders turn an additional source format into the same unified
|
|
703
|
+
`Transaction` model; writers take parsed data back out to another
|
|
704
|
+
target. Each companion pins `bankstatementparser` as a dependency and
|
|
705
|
+
ships its own 100%-coverage test suite.
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
# Mix and match: e.g. read an MT942 file, then export to Excel
|
|
709
|
+
pip install bankstatementparser-loader-mt942 bankstatementparser-writer-xlsx
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
Loaders hand back the same `Transaction` model the core parsers
|
|
713
|
+
produce, so `load_mt942_file(...)` then `write_xlsx(...)` composes
|
|
714
|
+
cleanly — see each companion's README for runnable examples.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Project Layout
|
|
719
|
+
|
|
720
|
+
```text
|
|
721
|
+
bankstatementparser/ Source code (32 modules)
|
|
722
|
+
bankstatementparser/hybrid/ PDF pipeline: orchestrator, llm_extractor, vision, scanner, ollama_direct, verification
|
|
723
|
+
bankstatementparser/enrichment/ Categorizer, AccountMapper, EnrichedTransaction
|
|
724
|
+
bankstatementparser/export/ hledger + beancount journal export
|
|
725
|
+
bankstatementparser/api.py REST API microservice (FastAPI)
|
|
726
|
+
docs/compliance/ ISO 13485 validation, risk register, traceability matrix
|
|
727
|
+
examples/ 14 deterministic + 9 hybrid runnable example scripts
|
|
728
|
+
scripts/ SBOM generation, checksums, signature verification
|
|
729
|
+
tests/ 844 tests (unit, integration, property-based, security, hybrid mocks)
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
---
|
|
733
|
+
|
|
734
|
+
## Security
|
|
735
|
+
|
|
736
|
+
Bank statement files contain sensitive financial and personal
|
|
737
|
+
data. This library is designed with security as a primary
|
|
738
|
+
constraint:
|
|
739
|
+
|
|
740
|
+
- **XXE protection** — `resolve_entities=False`,
|
|
741
|
+
`no_network=True`, `load_dtd=False`
|
|
742
|
+
- **ZIP bomb protection** — compression ratio limits, entry size
|
|
743
|
+
caps, encrypted entry rejection
|
|
744
|
+
- **Path traversal prevention** — dangerous pattern blocklist,
|
|
745
|
+
symlink resolution
|
|
746
|
+
- **PII redaction** — default masking of names, IBANs, and
|
|
747
|
+
addresses
|
|
748
|
+
- **Signed commits** — enforced in CI via GitHub API verification
|
|
749
|
+
- **Supply chain** — SHA-256 hash-locked dependencies, CycloneDX
|
|
750
|
+
SBOM, build provenance attestation
|
|
751
|
+
|
|
752
|
+
For vulnerability reports, see [SECURITY.md](.github/SECURITY.md).
|
|
753
|
+
|
|
754
|
+
For the full compliance suite, see
|
|
755
|
+
[`docs/compliance/`](docs/compliance/).
|
|
756
|
+
|
|
757
|
+
---
|
|
758
|
+
|
|
759
|
+
## Verify the Repository
|
|
760
|
+
|
|
761
|
+
Run the full validation suite locally:
|
|
762
|
+
|
|
763
|
+
```bash
|
|
764
|
+
ruff check bankstatementparser tests examples scripts
|
|
765
|
+
ruff format --check bankstatementparser tests examples scripts
|
|
766
|
+
python -m mypy bankstatementparser
|
|
767
|
+
python -m pytest
|
|
768
|
+
bandit -r bankstatementparser examples scripts -q
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
---
|
|
772
|
+
|
|
773
|
+
## Versioning
|
|
774
|
+
|
|
775
|
+
This project follows [Semantic Versioning](https://semver.org).
|
|
776
|
+
While the version is `0.y.z`, any release may contain breaking
|
|
777
|
+
changes; they are always listed under a **Changed — BREAKING**
|
|
778
|
+
heading in the [CHANGELOG](CHANGELOG.md) with migration notes.
|
|
779
|
+
From `1.0.0`, breaking changes will require a major release.
|
|
780
|
+
Deprecations emit `DeprecationWarning` for at least one minor
|
|
781
|
+
release before removal.
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## Contributing
|
|
786
|
+
|
|
787
|
+
Signed commits required. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## FAQ
|
|
792
|
+
|
|
793
|
+
**What formats are supported?**
|
|
794
|
+
CAMT.053, PAIN.001, CSV, OFX, QFX, and MT940.
|
|
795
|
+
|
|
796
|
+
**Does any data leave my infrastructure?**
|
|
797
|
+
No. Zero network calls. XML parsers enforce `no_network=True`. No
|
|
798
|
+
cloud, no telemetry.
|
|
799
|
+
|
|
800
|
+
**Is PII redacted automatically?**
|
|
801
|
+
Yes. Names, IBANs, and addresses are masked by default in console
|
|
802
|
+
output and streaming. File exports retain full data.
|
|
803
|
+
|
|
804
|
+
**Is the extraction deterministic?**
|
|
805
|
+
Yes. Same input produces byte-identical output. Critical for
|
|
806
|
+
financial auditing.
|
|
807
|
+
|
|
808
|
+
**Can it handle large files?**
|
|
809
|
+
Yes. `parse_streaming()` is tested at 50,000 transactions (~25 MB)
|
|
810
|
+
with bounded memory. Files over 50 MB use chunk-based streaming.
|
|
811
|
+
|
|
812
|
+
See [FAQ.md](FAQ.md) for the complete FAQ covering data privacy,
|
|
813
|
+
technical specs, and treasury workflows.
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
## Documentation
|
|
818
|
+
|
|
819
|
+
| Document | Covers |
|
|
820
|
+
|---|---|
|
|
821
|
+
| [API reference](https://sebastienrousseau.github.io/bankstatementparser/) | Full API reference generated from docstrings (mkdocs + mkdocstrings). Build locally with `poetry install --with docs && poetry run mkdocs serve`. |
|
|
822
|
+
| [`docs/MAPPING.md`](docs/MAPPING.md) | ISO 20022 XML tag to DataFrame column mapping for all six formats. |
|
|
823
|
+
| [`FAQ.md`](FAQ.md) | Data privacy, determinism, technical specs, treasury workflows. |
|
|
824
|
+
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Signed-commit policy, the five local gates, PR guidelines. |
|
|
825
|
+
| [`CHANGELOG.md`](CHANGELOG.md) | Per-release notes following Keep a Changelog. |
|
|
826
|
+
| [`SECURITY.md`](.github/SECURITY.md) | Disclosure policy, supported versions, contact. |
|
|
827
|
+
| [`docs/compliance/`](docs/compliance/) | ISO 13485 validation, risk register, traceability matrix. |
|
|
828
|
+
| [`examples/hybrid/README.md`](examples/hybrid/README.md) | Hybrid-pipeline walkthrough: mock vs. live mode, verification matrix, Ollama smoke tests. |
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## License
|
|
833
|
+
|
|
834
|
+
Licensed under the
|
|
835
|
+
[Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
|
836
|
+
See [LICENSE](LICENSE).
|
|
837
|
+
|
|
838
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
839
|
+
|
|
840
|
+
<p align="right"><a href="#contents">Back to Top</a></p>
|
|
841
|
+
|