The Problem

Financial analysts and accountants often receive bank statements as scanned PDFs. Extracting line‑item data into a structured CSV is a manual, error‑prone task, especially when statements differ per bank and may be password‑protected.

What This Does

monopoly provides a Python library and a CLI that parse supported bank‑statement PDFs and emit CSV rows. The core logic lives under src/monopoly/, e.g.:

  • src/monopoly/pdf.py – reads raw PDF text, extracts metadata, and builds PdfPage objects.
  • src/monopoly/statements/base.py – implements generic line‑parsing, multiline handling, and transaction boundary detection.
  • Bank‑specific adapters in src/monopoly/banks/<bank>/ (e.g., amex.py, bmo.py) inherit from BankBase and supply regex patterns for that institution.

The CLI entry point is src/monopoly/cli/cli.py. The public command monopoly invokes process_statement, which builds a Pipeline (see src/monopoly/pipeline.py) that chains PDF extraction, transformation, and CSV writing.

How It Is Wired

Execution starts in src/monopoly/cli/cli.py:

  1. process_statement (line 16) – receives a file path, creates a PdfDocument (src/monopoly/pdf.py) and passes it to Pipeline.extract.
  2. Pipeline.extract – calls PdfParser (via PdfDocument) to produce a list of PdfPage objects (used in 32 distinct places).
  3. Each PdfPage is fed to the bank detector (src/monopoly/banks/detector.py). The detector constructs a BankDetector that runs a lightweight model inference (the only external inference call).
  4. The selected bank adapter runs its extract method, which heavily uses search, MetadataIdentifier, NumberExtractor, and DateResolver (the most called internal symbols, 28–14 occurrences).
  5. Parsed Transaction objects (src/monopoly/statements/transaction.py) flow into Pipeline.transform, applying generic and bank‑specific Pattern handlers (src/monopoly/generic/patterns.py).
  6. Finally, Pipeline.convert_date and src/monopoly/write.py serialize the transaction list to CSV.

The import graph shows 116 internal modules with 57 edges and no circular dependencies, so the code base is acyclic. Hub modules (pdf.py, enums.py, transaction.py) are touched by >15 other files, making them high‑impact change locations.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/monopoly
cd monopoly

# System dependencies for PDF text extraction
sudo apt-get install build-essential libpoppler-cpp-dev pkg-config ocrmypdf   # Debian/Ubuntu
# or on macOS
brew install gcc@11 pkg-config poppler ocrmypdf

# Install Python package (uses pyproject.toml)
uv pip install -e .          # or: pip install -e .

# Optional OCR support
pip install 'monopoly-core[ocr]'

# Copy env template if you need passwords for protected PDFs
cp .env.template .env
# Edit .env to set PDF_PASSWORDS as a JSON list, e.g. PDF_PASSWORDS=["pwd1","pwd2"]

Run the CLI on a single file or a directory:

monopoly src/monopoly/examples/example_statement.pdf
monopoly ./statements --output ./out --preserve-filename

The CLI flags are defined in src/monopoly/cli/models.py and displayed via monopoly --help.

Real‑World Use

A fintech onboarding pipeline can drop incoming statement PDFs into a shared folder. A nightly cron runs:

#!/usr/bin/env bash
cd /opt/monopoly
monopoly /data/incoming --output /data/processed

The resulting CSVs are then ingested by the downstream accounting system without manual re‑keying.

Code Health & Issues

Static findings (4 categories, 9 instances):

  • HIGH – Deep nestingsrc/monopoly/statements/base.py, tests/integration/banks/test_trust_transaction_pattern.py, src/monopoly/cli/cli.py (max indent 20).
  • HIGH – Duplicated code – identical 6‑line blocks across many bank adapters (amex.py, trust.py, bmo.py, dbs.py, …).
  • MEDIUM – File opened without context managertests/unit/test_bank_identifier/test_get_identifier.py.
  • MEDIUM – High branching densitysrc/monopoly/banks/bmo/bmo.py, src/monopoly/banks/cibc/cibc.py, src/monopoly/banks/rbc/rbc.py.

CI/CD health audit (4 findings):

  • HIGH – Pin GitHub Actions – actions referenced by tag (astral-sh/setup-uv@v7, etc.) should use a commit SHA.
  • MEDIUM – Least‑privilege GITHUB_TOKEN – workflows lack explicit permissions:.
  • MEDIUM – No dependency‑vulnerability scan – add dependency-review-action or osv-scanner.
  • LOW – Missing job timeouts – set timeout-minutes: in .github/workflows/*.yaml.

No lockfile is present (pyproject.toml only), which makes reproducible builds non‑deterministic.

The Bottom Line

monopoly delivers a functional PDF‑to‑CSV pipeline with extensive bank coverage and a usable CLI. The codebase is well‑structured but suffers from deep nesting, duplicated adapter logic, and modest CI hardening. It is suitable for teams that need quick PDF statement extraction and are comfortable tightening the CI configuration and refactoring the bank adapters for maintainability.