""" 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("Macquarie Cash Management Account", 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)}")