exportstatement / tests /generators /make_complex_pdf.py
rudrahf's picture
Claude Opus 4.8 (1M context)
Initial commit — Export Bank Statement extractor (FastAPI)
0ebef7d
Raw
History Blame Contribute Delete
5.22 kB
"""
make_complex_pdf.py — generate a BIG, complex, multi-page bank statement PDF
that must still reconcile and pass the (unedited) converter.
Complexity: ~120 transactions across multiple pages, large comma-separated
amounts, long/messy descriptions, mixed debits & credits. Balances are computed
in integer cents so reconciliation is exact (no float drift). The header row is
repeated on every page so the unedited converter parses every page's table.
"""
from __future__ import annotations
import random
from pathlib import Path
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
)
HERE = Path(__file__).resolve().parent
OUT_PDF = HERE / "complex_statement.pdf"
random.seed(42)
MERCHANTS = [
"AMAZON MARKETPLACE EU", "WOOLWORTHS 1234 SYDNEY NSW", "UBER *TRIP HELP.UBER.COM",
"DIRECT DEBIT - AGL ENERGY LTD", "SALARY - ACME CORP PTY LTD PAYROLL",
"TFR TO 062-000 12345678 RENT", "PAYPAL *STEAMGAMES 35314369001",
"BPAY To ASIC INV 9920183", "STRIPE TRANSFER ST-X8K2Q9", "ATM WDL CBA GEORGE ST",
"INTEREST PAID - CMA ACCOUNT", "NETFLIX.COM SUBSCRIPTION", "COLES 0456 CHATSWOOD",
"INTL TXN GITHUB.COM USD FEE", "DIVIDEND - VAS ETF DISTRIBUTION",
"TFR FROM SAVINGS 062-111 99887766", "QANTAS AIRWAYS DOMESTIC FARE",
"MEDICARE BENEFIT DEPOSIT", "OPTUS MOBILE POSTPAID 0400111222",
"REFUND - APPLE STORE ONLINE",
]
REFS = ["REF", "INV", "TXN", "ORD", "PMT", "RCPT"]
def gen_transactions(n=120):
txns = []
day = 1
month = 1
year = 2025
for i in range(n):
# advance the date a little each row
day += random.randint(0, 3)
if day > 28:
day -= 28
month += 1
if month > 12:
month = 1
year += 1
merchant = random.choice(MERCHANTS)
ref = f"{random.choice(REFS)}{random.randint(10000, 99999)}"
desc = f"{merchant} {ref}"
is_credit = random.random() < 0.42
if "SALARY" in merchant or "DIVIDEND" in merchant or "INTEREST" in merchant or "REFUND" in merchant or "BENEFIT" in merchant:
is_credit = True
if "BPAY" in merchant or "ATM" in merchant or "DIRECT DEBIT" in merchant or "RENT" in merchant:
is_credit = False
cents = random.randint(150, 850000) # $1.50 .. $8,500.00
txns.append({
"date": f"{day:02d}/{month:02d}/{year}",
"description": desc,
"debit_c": 0 if is_credit else cents,
"credit_c": cents if is_credit else 0,
})
return txns
def money(cents: int) -> str:
return f"${cents / 100:,.2f}"
def build_pdf(txns, out_path: Path, opening_cents: int = 5_000_000):
styles = getSampleStyleSheet()
doc = SimpleDocTemplate(
str(out_path), pagesize=A4,
topMargin=16 * mm, bottomMargin=16 * mm,
leftMargin=14 * mm, rightMargin=14 * mm,
)
running = opening_cents
rows = [["Date", "Description", "Debit", "Credit", "Balance"]]
for t in txns:
running += t["credit_c"] - t["debit_c"]
tag = "CR" if running >= 0 else "DR"
rows.append([
t["date"],
t["description"],
money(t["debit_c"]) if t["debit_c"] else "",
money(t["credit_c"]) if t["credit_c"] else "",
f"{money(abs(running))} {tag}",
])
closing = running
period = f"{txns[0]['date']} to {txns[-1]['date']}"
story = [
Paragraph("<b>Macquarie Cash Management Account</b>", styles["Title"]),
Spacer(1, 6),
Paragraph("Account: 987654321 - BUSINESS CASH MANAGEMENT ACCOUNT", styles["Normal"]),
Paragraph("Account Holder: COMPLEXICORP HOLDINGS PTY LTD", styles["Normal"]),
Paragraph(f"Statement Period: {period}", styles["Normal"]),
Paragraph(f"Opening Balance: {money(opening_cents)}", styles["Normal"]),
Paragraph(f"Closing Balance: {money(closing)}", styles["Normal"]),
Spacer(1, 10),
]
tbl = Table(
rows,
colWidths=[22 * mm, 78 * mm, 26 * mm, 26 * mm, 30 * mm],
repeatRows=1, # repeat header on every page so each page's table parses
)
tbl.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1f3a5f")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 7),
("ALIGN", (2, 0), (-1, -1), "RIGHT"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
]))
story.append(tbl)
doc.build(story)
credits = sum(t["credit_c"] for t in txns)
debits = sum(t["debit_c"] for t in txns)
return closing, credits, debits
if __name__ == "__main__":
txns = gen_transactions(120)
closing, credits, debits = build_pdf(txns, OUT_PDF)
print(f"Wrote {OUT_PDF}")
print(f"{len(txns)} transactions")
print(f"opening $50,000.00 + credits {money(credits)} - debits {money(debits)} = closing {money(closing)}")