Systems Library / Finance Automation / How to Automate Expense Report Processing
Finance Automation invoicing billing

How to Automate Expense Report Processing

Process expense reports with automated receipt scanning and approval.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Expense reports pile up because processing them is tedious. I built a system to automate expense report processing that scans receipts, extracts amounts and categories, routes for approval, and posts to your accounting system. Submission to reimbursement without manual data entry.

The receipt goes in. The reimbursement comes out.

What You Need Before Starting

Step 1: Extract Data from Receipts

import anthropic
import base64
import json

client = anthropic.Anthropic()

def extract_receipt_data(image_path):
    with open(image_path, "rb") as f:
        image_data = base64.standard_b64encode(f.read()).decode("utf-8")

    message = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": image_data}},
                {"type": "text", "text": "Extract from this receipt: vendor name, date, total amount, tax amount, and category (food, travel, office, software, other). Return as JSON only."}
            ]
        }]
    )
    return json.loads(message.content[0].text)

Step 2: Create the Expense Database

import sqlite3
from datetime import datetime

def init_expense_db(db_path="expenses.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS expenses (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            employee TEXT,
            vendor TEXT,
            amount REAL,
            tax REAL,
            category TEXT,
            receipt_date TEXT,
            receipt_path TEXT,
            status TEXT DEFAULT 'pending',
            submitted_at TEXT,
            approved_at TEXT
        )
    """)
    conn.commit()
    return conn

def submit_expense(conn, employee, receipt_path):
    data = extract_receipt_data(receipt_path)
    conn.execute(
        "INSERT INTO expenses (employee, vendor, amount, tax, category, receipt_date, receipt_path, submitted_at) VALUES (?,?,?,?,?,?,?,?)",
        (employee, data.get("vendor", ""), data.get("total", 0), data.get("tax", 0),
         data.get("category", "other"), data.get("date", ""), receipt_path, datetime.now().isoformat())
    )
    conn.commit()
    return data

Step 3: Route for Approval

def route_for_approval(conn, expense_id):
    expense = conn.execute("SELECT amount, employee FROM expenses WHERE id=?", (expense_id,)).fetchone()
    amount, employee = expense

    if amount < 50:
        approve_expense(conn, expense_id, "auto-approved")
        return "auto_approved"
    elif amount < 500:
        return "manager_approval"
    else:
        return "director_approval"

def approve_expense(conn, expense_id, approver):
    conn.execute(
        "UPDATE expenses SET status='approved', approved_at=? WHERE id=?",
        (datetime.now().isoformat(), expense_id)
    )
    conn.commit()

Step 4: Generate Expense Reports

def monthly_expense_report(conn, month):
    rows = conn.execute("""
        SELECT category, SUM(amount) as total, COUNT(*) as count
        FROM expenses WHERE strftime('%Y-%m', receipt_date) = ? AND status='approved'
        GROUP BY category ORDER BY total DESC
    """, (month,)).fetchall()

    report = f"# Expense Report - {month}\n\n"
    report += "| Category | Total | Count |\n|---|---|---|\n"
    grand_total = 0
    for cat, total, count in rows:
        report += f"| {cat} | ${total:.2f} | {count} |\n"
        grand_total += total
    report += f"\n**Grand Total: ${grand_total:.2f}**\n"
    return report

Step 5: Run the Pipeline

def process_expense_batch(conn, receipts_dir):
    from pathlib import Path
    processed = 0
    for receipt in Path(receipts_dir).glob("*.jpg"):
        data = submit_expense(conn, "[email protected]", str(receipt))
        expense_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
        route_for_approval(conn, expense_id)
        processed += 1
    print(f"Processed {processed} expense receipts")

What to Build Next

Add policy enforcement that flags expenses outside company policy before they reach approval. Over-limit meals, unauthorized software purchases, and duplicate submissions should get caught automatically.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems