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.
Files changed (41) hide show
  1. bankstatementparser-0.0.10/PKG-INFO +841 -0
  2. bankstatementparser-0.0.10/README.md +801 -0
  3. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/__init__.py +14 -16
  4. bankstatementparser-0.0.10/bankstatementparser/_amounts.py +54 -0
  5. bankstatementparser-0.0.10/bankstatementparser/_llm_common.py +170 -0
  6. bankstatementparser-0.0.10/bankstatementparser/additional_parsers.py +638 -0
  7. bankstatementparser-0.0.10/bankstatementparser/api.py +326 -0
  8. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/bank_statement_parsers.py +44 -61
  9. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/base_parser.py +13 -26
  10. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/camt_parser.py +162 -161
  11. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/cli.py +229 -283
  12. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/account_mapper.py +8 -5
  13. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/categorizer.py +42 -66
  14. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/exceptions.py +4 -0
  15. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/export/ledger.py +3 -9
  16. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/__init__.py +39 -3
  17. bankstatementparser-0.0.10/bankstatementparser/hybrid/evaluation.py +372 -0
  18. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/llm_extractor.py +39 -75
  19. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/ollama_direct.py +27 -36
  20. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/orchestrator.py +88 -30
  21. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/pdf_text.py +40 -16
  22. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/prompts.py +1 -0
  23. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/scanner.py +80 -13
  24. bankstatementparser-0.0.10/bankstatementparser/hybrid/verification.py +419 -0
  25. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/hybrid/vision.py +26 -15
  26. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/input_validator.py +25 -48
  27. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/pain001_parser.py +82 -168
  28. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/parallel.py +6 -12
  29. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/record_types.py +22 -8
  30. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/transaction_deduplicator.py +72 -62
  31. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/transaction_models.py +22 -17
  32. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/zip_security.py +6 -18
  33. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/pyproject.toml +64 -10
  34. bankstatementparser-0.0.8/PKG-INFO +0 -479
  35. bankstatementparser-0.0.8/README.md +0 -440
  36. bankstatementparser-0.0.8/bankstatementparser/additional_parsers.py +0 -376
  37. bankstatementparser-0.0.8/bankstatementparser/api.py +0 -185
  38. bankstatementparser-0.0.8/bankstatementparser/hybrid/verification.py +0 -184
  39. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/LICENSE +0 -0
  40. {bankstatementparser-0.0.8 → bankstatementparser-0.0.10}/bankstatementparser/enrichment/__init__.py +1 -1
  41. {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&lpar;path&rpar;] --> 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 &gt;= 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) | [![PyPI](https://img.shields.io/pypi/v/bankstatementparser-mcp.svg)](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) | [![PyPI](https://img.shields.io/pypi/v/bankstatementparser-lsp.svg)](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) | [![PyPI](https://img.shields.io/pypi/v/bankstatementparser-writer-xlsx.svg)](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) | [![PyPI](https://img.shields.io/pypi/v/bankstatementparser-loader-mt942.svg)](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) | [![PyPI](https://img.shields.io/pypi/v/bankstatementparser-loader-bai2.svg)](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
+