Extract structured data from financial statement PDFs
Developed and maintained by AZdev - FinTech Innovation Execution Leaders
Financial developers and engineers waste countless hours manually extracting data from bank statements, credit card statements, and other financial PDFs. Each institution uses different formats, making automated processing a persistent challenge.
This library provides a standardized way to extract structured data from financial statement PDFs with minimal setup, saving developers significant time and effort.
- Universal PDF Extraction: Works with statements from major financial institutions
- Automatic Institution Detection: Identifies the source institution
- Comprehensive Data Extraction:
- Account information (number, type)
- Statement period (start/end dates)
- Balance information (opening/closing)
- Complete transaction lists
- Transaction Categorization: Automatically classifies transactions into categories
- Confidence Scoring: Reliability ratings for each extracted data point
- Parallel Processing: Efficient batch processing for multiple statements
- Debug Mode: Detailed logging for troubleshooting
- Clean, Consistent Output: Standardized JSON regardless of source format
git clone https://github.com/AZdv/finstatement-parser.git
cd finstatement-parser
pip install -e .This package is not published to PyPI. Install from source.
import finstatement
# Parse a statement PDF
result = finstatement.parse("statement.pdf")
# Access structured data
print(f"Account: {result.account_info.number}")
print(f"Period: {result.period.start} to {result.period.end}")
print(f"Closing Balance: ${result.balance.closing:.2f}")
# Get transactions
for tx in result.transactions:
print(f"{tx.date.strftime('%m/%d/%Y')} | ${tx.amount:.2f} | {tx.description}")
# Export as standardized JSON
json_data = result.to_json()The package includes a simple example script for quick demonstration:
python example.py path/to/your/statement.pdfProcess multiple statements efficiently:
import finstatement
import glob
# Get all PDFs in a directory
pdf_files = glob.glob("statements/*.pdf")
# Process in parallel (default)
results = finstatement.batch_parse(pdf_files)
# Process sequentially
results = finstatement.batch_parse(pdf_files, parallel=False)
# Control parallelism
results = finstatement.batch_parse(pdf_files, max_workers=4)
# Process results
for path, result in results.items():
print(f"Statement: {path}")
print(f"Found {len(result.transactions)} transactions")Enable detailed logging for troubleshooting:
import finstatement
# Enable debug mode
result = finstatement.parse("statement.pdf", debug=True)Analyze transactions by category:
import finstatement
from collections import defaultdict
result = finstatement.parse("statement.pdf")
# Group transactions by category
by_category = defaultdict(list)
for tx in result.transactions:
by_category[tx.category or "uncategorized"].append(tx)
# Calculate spending by category
category_totals = {}
for category, transactions in by_category.items():
category_totals[category] = sum(tx.amount for tx in transactions)
# Print summary
for category, total in sorted(category_totals.items(), key=lambda x: x[1]):
print(f"{category}: ${abs(total):.2f}")The library currently supports basic extraction for statements from:
- Chase Bank
- Bank of America
- Wells Fargo
- Citibank
- American Express
- Discover
Coverage varies by institution and statement layout. Treat the list above as "has been exercised against real statements", not as a compatibility guarantee.
The library provides a clean, structured data model:
StatementResult
├── account_info: AccountInfo
│ ├── number: str
│ ├── name: str (optional)
│ ├── institution: str
│ └── type: str (bank, credit_card, investment)
├── period: Period
│ ├── start: datetime
│ └── end: datetime
├── balance: Balance
│ ├── opening: float (optional)
│ └── closing: float
├── transactions: List[Transaction]
│ ├── date: datetime
│ ├── description: str
│ ├── amount: float
│ ├── balance: float (optional)
│ └── category: str (optional)
└── confidence: Dict[str, float]
- Personal Finance Apps: Import data from user's financial statements
- Expense Management Systems: Automatically process credit card statements
- Bookkeeping Software: Extract transaction data for reconciliation
- Financial Analysis Tools: Import historical statement data
- Loan Processing Systems: Analyze bank statements for affordability checks
This matters more than the happy path, so it is documented before the features.
The parser does not guess. If a field cannot be read it comes back as None,
the reason is appended to result.parse_errors, and the confidence score for
that field is 0.0. If the file cannot be read at all it raises
StatementParseError rather than returning an empty result.
That is a deliberate reversal. An earlier version of this code did the opposite and it was wrong in a way worth describing, because the same pattern shows up in a lot of extraction code:
| When it could not read | It used to return | It now returns |
|---|---|---|
| the PDF at all | the string "ERROR: Unable to extract text from PDF", parsed as if it were statement text |
raises StatementParseError |
| the statement period | the current calendar month | None, recorded in parse_errors |
| the closing balance | 0.0 |
None, recorded in parse_errors |
| a transaction date | today's date | the row is dropped and recorded |
a MM/DD date with no year |
the year the code was run | the year from the statement period, or the row is dropped |
| the account number | the sentinel string "Unknown", with nothing in parse_errors |
None, recorded in parse_errors |
| a partial account number | padded into xxxx-xxxx-xxxx-1234, inventing a 16-digit card shape even for a checking account |
the digits actually captured, with number_is_partial set |
The old confidence scores made it worse rather than catching it. A fabricated
0.00 balance scored 0.8, and thirty transactions all stamped with today
scored 0.9, because the score counted rows instead of measuring what was
known. Wrong figures carrying a high confidence score are worse than an error.
result = finstatement.parse("statement.pdf")
if result.parse_errors:
for problem in result.parse_errors:
print(problem)
if result.balance.closing is None:
... # not zero. unknown.Untrusted PDFs are bounded: 50 MB and 500 pages by default, and an encrypted file that will not open with an empty password raises rather than returning whatever fragments came out.
This is a reference implementation, not a supported product. It is published so the approach can be read and reused, and it is not on PyPI. Issues and pull requests are reviewed periodically rather than on a schedule. Corrections that come with a failing example are the fastest thing to get merged.
Contributions are welcome! Here's how you can help:
- Add Institution Support: Implement patterns for new financial institutions
- Improve Extraction Accuracy: Enhance pattern matching for existing institutions
- Add New Statement Types: Support for investment, mortgage, loan statements, etc.
- Bug Reports and Feature Requests: Open issues on GitHub
Please see CONTRIBUTING.md for detailed contribution guidelines.
- Machine learning enhancements for improved extraction accuracy
- Transaction categorization based on description patterns
- Support for international bank formats
- REST API for cloud-based processing
- Visual results dashboard
- Historical statement analysis
- Integration with personal finance tools
The library is optimized for accuracy rather than speed. For large batches, batch_parse supports parallel processing. No benchmark figures are published here because none have been measured under a documented methodology.
| Issue | Solution |
|---|---|
| Low confidence scores | Enable debug mode to see what patterns were matched. You might need to add institution-specific patterns. |
| Missing transactions | Some statements have unusual formatting. Try extracting the text manually to see the structure and submit a feature request. |
| Incorrect dates | If your statement uses an unusual date format, you may need to extend the date parsing patterns. |
| PDF extraction fails | Try using a different PDF reader like Poppler if PyPDF2 has issues with your document. |
| Memory issues with large batches | Reduce the max_workers parameter when using batch_parse. |
- Enable debug mode:
finstatement.parse("statement.pdf", debug=True) - Manually extract text:
python -c "import PyPDF2; print(PyPDF2.PdfReader('statement.pdf').pages[0].extract_text()[:500])" - Check if your PDF is compatible:
python -c "import PyPDF2; print(PyPDF2.PdfReader('statement.pdf').metadata)" - For encrypted PDFs, check if they can be opened:
python -c "import PyPDF2; r=PyPDF2.PdfReader('statement.pdf'); print(r.is_encrypted)"
This library processes financial documents locally. No data is sent to external servers. We recommend implementing additional security measures when handling sensitive financial information in your application.
This project is licensed under the MIT License - see the LICENSE file for details.
AZdev specializes in mission-critical FinTech engineering and CTO services. We help financial institutions and startups build innovative, scalable, and secure financial technology solutions.
For consulting, custom development, or enterprise support for this library, contact us at info@azdv.co.