Systems Library / Industry Applications / How to Create Automated Legal Billing and Time Tracking
Industry Applications legal

How to Create Automated Legal Billing and Time Tracking

Track billable hours and generate invoices for law firms automatically.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

Automating legal billing and time tracking fixes the revenue leak that every firm has. Attorneys forget to log time, descriptions are vague, and invoices go out late. I built this system to capture time entries as work happens, generate clear billing descriptions, and produce invoices automatically at the end of each billing cycle.

What You Need Before Starting

Step 1: Set Up the Billing Database

import sqlite3
from datetime import datetime

def init_billing_db(db_path="legal_billing.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS time_entries (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            attorney TEXT,
            client_id INTEGER,
            matter TEXT,
            description TEXT,
            hours REAL,
            rate REAL,
            amount REAL,
            billable INTEGER DEFAULT 1,
            status TEXT DEFAULT 'unbilled',
            date TEXT,
            created_at TEXT
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS invoices (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            client_id INTEGER,
            client_name TEXT,
            invoice_number TEXT,
            total_hours REAL,
            total_amount REAL,
            period_start TEXT,
            period_end TEXT,
            status TEXT DEFAULT 'draft',
            created_at TEXT
        )
    """)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS billing_rates (
            attorney TEXT,
            task_type TEXT,
            rate REAL,
            PRIMARY KEY (attorney, task_type)
        )
    """)
    conn.commit()
    conn.close()

Step 2: Build the Time Entry System

def log_time(attorney, client_id, matter, raw_description, hours, task_type="general", db_path="legal_billing.db"):
    conn = sqlite3.connect(db_path)
    
    rate_row = conn.execute(
        "SELECT rate FROM billing_rates WHERE attorney = ? AND task_type = ?",
        (attorney, task_type)
    ).fetchone()
    rate = rate_row[0] if rate_row else 350.00
    
    clean_description = improve_billing_description(raw_description, matter)
    amount = round(hours * rate, 2)
    
    conn.execute("""
        INSERT INTO time_entries (attorney, client_id, matter, description, hours, rate, amount, date, created_at)
        VALUES (?,?,?,?,?,?,?,?,?)
    """, (attorney, client_id, matter, clean_description, hours, rate, amount,
          datetime.utcnow().strftime("%Y-%m-%d"), datetime.utcnow().isoformat()))
    conn.commit()
    conn.close()
    
    return {"description": clean_description, "hours": hours, "rate": rate, "amount": amount}

Step 3: Improve Billing Descriptions with AI

import anthropic
from dotenv import load_dotenv
load_dotenv()

def improve_billing_description(raw_description, matter):
    client = anthropic.Anthropic()
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=200,
        system="""Rewrite this time entry description for a legal invoice.
Rules:
- Start with a verb (Drafted, Reviewed, Conducted, Prepared, Attended, etc.)
- Be specific about what was done
- Include who was involved if mentioned
- Keep under 50 words
- Professional tone
- No vague language like "worked on" or "various"
- Do not add information not in the original""",
        messages=[{
            "role": "user",
            "content": f"Matter: {matter}\nRaw entry: {raw_description}"
        }]
    )
    
    return response.content[0].text.strip()

Step 4: Generate Monthly Invoices

def generate_invoice(client_id, client_name, period_start, period_end, db_path="legal_billing.db"):
    conn = sqlite3.connect(db_path)
    
    entries = conn.execute("""
        SELECT id, attorney, matter, description, hours, rate, amount, date
        FROM time_entries
        WHERE client_id = ? AND status = 'unbilled' AND billable = 1
        AND date BETWEEN ? AND ?
        ORDER BY date, attorney
    """, (client_id, period_start, period_end)).fetchall()
    
    if not entries:
        conn.close()
        return None
    
    total_hours = sum(e[4] for e in entries)
    total_amount = sum(e[6] for e in entries)
    
    invoice_number = f"INV-{datetime.utcnow().strftime('%Y%m')}-{client_id:04d}"
    
    conn.execute("""
        INSERT INTO invoices (client_id, client_name, invoice_number, total_hours, total_amount,
            period_start, period_end, created_at)
        VALUES (?,?,?,?,?,?,?,?)
    """, (client_id, client_name, invoice_number, total_hours, total_amount,
          period_start, period_end, datetime.utcnow().isoformat()))
    
    entry_ids = [e[0] for e in entries]
    placeholders = ",".join(["?"] * len(entry_ids))
    conn.execute(f"UPDATE time_entries SET status = 'billed' WHERE id IN ({placeholders})", entry_ids)
    
    conn.commit()
    conn.close()
    
    return {
        "invoice_number": invoice_number,
        "client": client_name,
        "entries": [{"attorney": e[1], "matter": e[2], "desc": e[3], "hours": e[4], "rate": e[5], "amount": e[6], "date": e[7]} for e in entries],
        "total_hours": total_hours,
        "total_amount": total_amount
    }

Step 5: Format the Invoice Output

def format_invoice_text(invoice):
    lines = [
        f"INVOICE: {invoice['invoice_number']}",
        f"Client: {invoice['client']}",
        f"Total: ${invoice['total_amount']:,.2f} ({invoice['total_hours']:.1f} hours)",
        "",
        f"{'Date':<12} {'Attorney':<15} {'Description':<45} {'Hours':>6} {'Rate':>8} {'Amount':>10}",
        "-" * 96
    ]
    
    for e in invoice["entries"]:
        desc = e["desc"][:43] if len(e["desc"]) > 43 else e["desc"]
        lines.append(f"{e['date']:<12} {e['attorney']:<15} {desc:<45} {e['hours']:>6.1f} ${e['rate']:>7.0f} ${e['amount']:>9.2f}")
    
    lines.append("-" * 96)
    lines.append(f"{'TOTAL':>74} {invoice['total_hours']:>6.1f} {'':>8} ${invoice['total_amount']:>9.2f}")
    
    return "\n".join(lines)

if __name__ == "__main__":
    invoice = generate_invoice(1, "Acme Corp", "2025-11-01", "2025-11-30")
    if invoice:
        print(format_invoice_text(invoice))

What to Build Next

Add unbilled time alerts. If an attorney has logged zero hours for a client in 7+ days but has an active matter, send a reminder. That catches time entries that are being forgotten.

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