Build an AI Financial Transaction Auditor

Use Python and Gemini AI to automate financial transaction auditing.

Introduction

30 Second Summary

Reviewing financial transactions for errors is one of those tasks that eats up hours of someone's week. You scroll through rows of spreadsheet data looking for duplicates, weird amounts, and missing details.

In this project, you will build a Python script that reads financial transactions from an Excel spreadsheet and uses Google's Gemini AI to automatically flag common audit issues.

What You'll Build

A command-line Python tool that reads an Excel file of financial transactions and uses Gemini AI to flag red flags like duplicates, unusual amounts, and missing descriptions.

By the end of this project, you'll have:

  • A sample Excel file with realistic financial transactions, including deliberately planted audit errors.
  • A Python script that reads and parses Excel data using openpyxl.
  • An AI-powered audit engine that sends formatted transactions to Gemini and generates a text-based report with flagged issues and reasoning.
  • Secret Mission: A severity scoring system that ranks flagged issues as High, Medium, or Low risk and outputs a priority summary at the top of the report.

Do I need any prior experience?

No prior Python experience is needed. This guide walks you through every line of code. You just need VS Code and Python already installed on your machine.

Set Up Your Project and Get Your API Key

Before you can build the auditor, you need a workspace on your computer and access to the AI that will do the analysis.

In this step, you will create a dedicated project folder, install two Python libraries, and grab a free API key from Google. Once this is done, your environment will be fully wired up and ready for code.

In this step, get ready to:
  • Create a project folder with a Python virtual environment.
  • Install the openpyxl and google-genai libraries.
  • Get a free Gemini API key and store it as an environment variable.
Create a project folder and virtual environment

A virtual environment keeps your project's libraries separate from the rest of your system. This prevents version clashes between projects.

  • Open VS Code's integrated terminal by pressing Cmd+` (macOS) or Ctrl+` (Windows).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir financial-auditor
cd financial-auditor
  • Create a virtual environment by running the appropriate command for your system:

macOS

python3 -m venv venv

Windows

python -m venv venv
  • Confirm the virtual environment was created by running:
ls

You should see a venv folder in the output.

  • Activate the virtual environment:

macOS

source venv/bin/activate

Windows

.\venv\Scripts\activate

Your terminal prompt now starts with (venv), confirming the virtual environment is active.

Windows: seeing a script execution error?

PowerShell may block activation scripts by default. Run this command first, then try activating again:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

help me fix a PowerShell execution policy error when activating my Python virtual environment

  • Open the financial-auditor folder in VS Code by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the financial-auditor folder, and click Open.

Why open the folder in VS Code?

Opening the folder makes VS Code treat it as your workspace. The integrated terminal will default to this folder, and the file explorer sidebar will show your project files.

  • Open a new terminal in VS Code with Cmd+` (macOS) or Ctrl+` (Windows).
  • Re-activate the virtual environment since the terminal reset:

macOS

source venv/bin/activate

Windows

.\venv\Scripts\activate

Confirm you see (venv) at the start of your terminal prompt before continuing.

Install the required Python libraries

Your auditor needs two libraries. openpyxl reads Excel files, and google-genai connects to Gemini's API.

  • Install both libraries in one go by running:
pip install openpyxl google-genai

What does this command do?

pip is Python's package installer. It downloads openpyxl (for reading and writing Excel files) and google-genai (Google's SDK for calling the Gemini API) into your virtual environment.

Wait for the install to finish. You should see Successfully installed followed by a list of package names at the end of the output.

Seeing a 'pip not found' error?

Make sure your virtual environment is active. Your terminal prompt should start with (venv). If it does not, re-run the activation command from the previous substep.

help me fix a pip not found error in my Python virtual environment

Get your free Gemini API key

Google AI Studio gives you a free API key for Gemini. No credit card is required.

  • Open your browser and go to Google AI Studio.
  • Sign in with your Gmail account.
  • Click Get API key in the left sidebar.
  • Click Create API key.
  • Copy the key that appears. You will only see it once.

Keep your API key private

Do not share your API key or commit it to version control. You will store it as an environment variable so it stays out of your code.

Now store the key as an environment variable so your Python script can pick it up automatically.

  • In your VS Code terminal (with (venv) still active), run the command for your system:

macOS

export GEMINI_API_KEY=your-api-key-here

Replace your-api-key-here with the key you just copied from Google AI Studio.

Windows

$env:GEMINI_API_KEY="your-api-key-here"

Replace your-api-key-here with the key you just copied from Google AI Studio.

Why an environment variable?

The google-genai library automatically reads a variable named GEMINI_API_KEY when it initializes. This keeps your key out of your source code while still making it available to the program.

This variable only lasts for the current terminal session. If you close and reopen your terminal later, you will need to set it again.

Not sure if the variable was set?

You can verify it exists (without printing the full key) by checking that the output is not empty. On macOS run echo $GEMINI_API_KEY and on Windows run echo $env:GEMINI_API_KEY. You should see your key printed back.

help me verify my GEMINI_API_KEY environment variable is set correctly

Your environment is fully set up. Next up, you will create a sample Excel spreadsheet loaded with realistic transactions and planted audit issues for the AI to find.

Create a Sample Transaction Spreadsheet

Your environment is ready with all the tools installed. Now you need data to audit.

In this step, you will create a Python script that generates a realistic Excel spreadsheet of financial transactions. You will also plant deliberate errors in the data so you can verify your AI auditor catches them later.

In this step, get ready to:
  • Write a script that generates a sample Excel file with 25 transactions.
  • Plant deliberate audit issues like duplicates, missing descriptions, and miscategorized expenses.
  • Run the script and verify the generated file.
Create the data generation script

To test an auditor, you need a spreadsheet full of realistic transactions. Instead of creating one manually, you will write a Python script that generates the file for you using openpyxl.

  • In VS Code's Explorer sidebar, right-click inside your financial-auditor folder and select New File.
  • Name the file generate_sample_data.py and press Enter.
  • Add the following code to set up the workbook and define the column headers:
from openpyxl import Workbook
from datetime import date

def create_sample_transactions():
    wb = Workbook()
    ws = wb.active
    ws.title = "Transactions"

    # Header row
    headers = ["Date", "Description", "Category", "Amount", "Reference"]
    ws.append(headers)

What does this code do?

  • Workbook() creates a new Excel file in memory. wb.active grabs the default worksheet so you can add data to it.
  • ws.append(headers) writes the first row of the spreadsheet. Each item in the list becomes a column header.

You won't see a change yet. The next chunk adds the actual transaction data.

Add transactions with planted audit issues

A real auditor needs messy data to work with. You will add 25 transactions that look realistic, but with deliberate errors hidden among them. This way you can confirm your AI auditor actually catches problems.

  • In generate_sample_data.py, add this transactions list directly below the ws.append(headers) line:
    # Sample transactions (including planted audit issues)
    transactions = [
        [date(2026, 4, 1), "Office Supplies - Staples", "Office Supplies", 234.56, "TXN-001"],
        [date(2026, 4, 2), "Monthly Rent Payment", "Rent", 4500.00, "TXN-002"],
        [date(2026, 4, 3), "Client Lunch - Olive Garden", "Meals & Entertainment", 87.32, "TXN-003"],
        [date(2026, 4, 4), "Software License - Adobe", "Software", 599.99, "TXN-004"],
        [date(2026, 4, 5), "Uber - Airport Transfer", "Travel", 45.20, "TXN-005"],
        [date(2026, 4, 7), "Monthly Rent Payment", "Rent", 4500.00, "TXN-006"],
        [date(2026, 4, 8), "Team Building Event", "Meals & Entertainment", 1250.00, "TXN-007"],
        [date(2026, 4, 9), "Printer Ink Cartridges", "Office Supplies", 89.99, "TXN-008"],
        [date(2026, 4, 10), "", "Consulting", 10000.00, "TXN-009"],
        [date(2026, 4, 11), "Google Ads Campaign", "Marketing", 3200.00, "TXN-010"],
        [date(2026, 4, 12), "Employee Reimbursement - Travel", "Travel", 567.80, "TXN-011"],
        [date(2026, 4, 14), "Cleaning Service", "Facilities", 350.00, "TXN-012"],
        [date(2026, 4, 15), "New Laptop - Dell XPS", "Office Supplies", 2499.00, "TXN-013"],
        [date(2026, 4, 16), "Phone Bill - Verizon", "Utilities", 189.45, "TXN-014"],
        [date(2026, 4, 17), "Conference Registration", "Training", 799.00, "TXN-015"],
        [date(2026, 4, 18), "Catering - Board Meeting", "Meals & Entertainment", 425.00, "TXN-016"],
        [date(2026, 4, 19), "AWS Cloud Hosting", "Software", 1876.34, "TXN-017"],
        [date(2026, 4, 21), "Office Furniture", "Office Supplies", 5000.00, "TXN-018"],
        [date(2026, 4, 22), "Legal Consultation", "Professional Services", 750.00, "TXN-019"],
        [date(2026, 4, 23), "Marketing Flyers Print", "Marketing", 320.00, "TXN-020"],
        [date(2026, 4, 24), "Taxi - Client Visit", "Travel", 32.50, "TXN-021"],
        [date(2026, 4, 25), "Monthly Internet Bill", "Utilities", 149.99, "TXN-022"],
        [date(2026, 4, 26), "Staff Training Workshop", "Training", 1500.00, "TXN-023"],
        [date(2026, 4, 28), "Miscellaneous Payment", "Consulting", 15000.00, "TXN-024"],
        [date(2026, 4, 29), "Office Supplies - Staples", "Office Supplies", 234.56, "TXN-025"],
    ]

What are the planted audit issues?

Six of these transactions contain red flags that a human auditor would catch. Here is what is hidden in the data:

  • TXN-006: Duplicate rent payment. It matches TXN-002 exactly (same vendor, same $4,500 amount).
  • TXN-009: Missing description with a suspiciously large round amount ($10,000).
  • TXN-013: A $2,499 laptop categorized as "Office Supplies" instead of "Equipment".
  • TXN-018: Suspiciously round $5,000 amount for furniture.
  • TXN-024: Vague "Miscellaneous Payment" description with a very large round amount ($15,000).
  • TXN-025: Duplicate of TXN-001 (same vendor and same $234.56 amount).

You won't see a change yet. The next chunk adds the code that saves the file to disk.

Complete the script and generate the Excel file

With the transaction data defined, you need code that writes each row into the spreadsheet and saves it. You will also add print statements that confirm what was created.

  • In generate_sample_data.py, add the following code directly below the closing ] of the transactions list:
    for txn in transactions:
        ws.append(txn)

    wb.save("transactions.xlsx")
    print("Sample transactions file created: transactions.xlsx")
    print(f"Total transactions: {len(transactions)}")
    print("\nPlanted audit issues:")
    print("- TXN-006: Duplicate rent payment (same as TXN-002)")
    print("- TXN-009: Missing description with large round amount ($10,000)")
    print("- TXN-013: Laptop categorized as 'Office Supplies' instead of 'Equipment'")
    print("- TXN-018: Suspiciously round amount ($5,000) for furniture")
    print("- TXN-024: Vague description with very large round amount ($15,000)")
    print("- TXN-025: Duplicate of TXN-001 (same vendor, same amount)")

if __name__ == "__main__":
    create_sample_transactions()

What does this code do?

  • The for loop calls ws.append(txn) on each transaction. This writes every list item as a new row in the spreadsheet.
  • wb.save("transactions.xlsx") writes the workbook to an actual file on your computer.
  • The if __name__ == "__main__": block ensures the function only runs when you execute this file directly. It will not run if another script imports from it.
  • Save generate_sample_data.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete generate_sample_data.py file. Make sure yours matches exactly:

from openpyxl import Workbook
from datetime import date

def create_sample_transactions():
    wb = Workbook()
    ws = wb.active
    ws.title = "Transactions"

    # Header row
    headers = ["Date", "Description", "Category", "Amount", "Reference"]
    ws.append(headers)

    # Sample transactions (including planted audit issues)
    transactions = [
        [date(2026, 4, 1), "Office Supplies - Staples", "Office Supplies", 234.56, "TXN-001"],
        [date(2026, 4, 2), "Monthly Rent Payment", "Rent", 4500.00, "TXN-002"],
        [date(2026, 4, 3), "Client Lunch - Olive Garden", "Meals & Entertainment", 87.32, "TXN-003"],
        [date(2026, 4, 4), "Software License - Adobe", "Software", 599.99, "TXN-004"],
        [date(2026, 4, 5), "Uber - Airport Transfer", "Travel", 45.20, "TXN-005"],
        [date(2026, 4, 7), "Monthly Rent Payment", "Rent", 4500.00, "TXN-006"],
        [date(2026, 4, 8), "Team Building Event", "Meals & Entertainment", 1250.00, "TXN-007"],
        [date(2026, 4, 9), "Printer Ink Cartridges", "Office Supplies", 89.99, "TXN-008"],
        [date(2026, 4, 10), "", "Consulting", 10000.00, "TXN-009"],
        [date(2026, 4, 11), "Google Ads Campaign", "Marketing", 3200.00, "TXN-010"],
        [date(2026, 4, 12), "Employee Reimbursement - Travel", "Travel", 567.80, "TXN-011"],
        [date(2026, 4, 14), "Cleaning Service", "Facilities", 350.00, "TXN-012"],
        [date(2026, 4, 15), "New Laptop - Dell XPS", "Office Supplies", 2499.00, "TXN-013"],
        [date(2026, 4, 16), "Phone Bill - Verizon", "Utilities", 189.45, "TXN-014"],
        [date(2026, 4, 17), "Conference Registration", "Training", 799.00, "TXN-015"],
        [date(2026, 4, 18), "Catering - Board Meeting", "Meals & Entertainment", 425.00, "TXN-016"],
        [date(2026, 4, 19), "AWS Cloud Hosting", "Software", 1876.34, "TXN-017"],
        [date(2026, 4, 21), "Office Furniture", "Office Supplies", 5000.00, "TXN-018"],
        [date(2026, 4, 22), "Legal Consultation", "Professional Services", 750.00, "TXN-019"],
        [date(2026, 4, 23), "Marketing Flyers Print", "Marketing", 320.00, "TXN-020"],
        [date(2026, 4, 24), "Taxi - Client Visit", "Travel", 32.50, "TXN-021"],
        [date(2026, 4, 25), "Monthly Internet Bill", "Utilities", 149.99, "TXN-022"],
        [date(2026, 4, 26), "Staff Training Workshop", "Training", 1500.00, "TXN-023"],
        [date(2026, 4, 28), "Miscellaneous Payment", "Consulting", 15000.00, "TXN-024"],
        [date(2026, 4, 29), "Office Supplies - Staples", "Office Supplies", 234.56, "TXN-025"],
    ]

    for txn in transactions:
        ws.append(txn)

    wb.save("transactions.xlsx")
    print("Sample transactions file created: transactions.xlsx")
    print(f"Total transactions: {len(transactions)}")
    print("\nPlanted audit issues:")
    print("- TXN-006: Duplicate rent payment (same as TXN-002)")
    print("- TXN-009: Missing description with large round amount ($10,000)")
    print("- TXN-013: Laptop categorized as 'Office Supplies' instead of 'Equipment'")
    print("- TXN-018: Suspiciously round amount ($5,000) for furniture")
    print("- TXN-024: Vague description with very large round amount ($15,000)")
    print("- TXN-025: Duplicate of TXN-001 (same vendor, same amount)")

if __name__ == "__main__":
    create_sample_transactions()
  • Run the script by entering this command in your terminal:
python generate_sample_data.py
  • You should see output listing the file that was created, the total number of transactions, and a summary of the six planted audit issues.
  • Confirm that transactions.xlsx appeared in your project folder by running:
ls
  • You should see transactions.xlsx and generate_sample_data.py listed in the output.

Seeing an error when you run the script?

  • If you see ModuleNotFoundError: No module named 'openpyxl', your virtual environment may not be active. Activate it again and re-run the script.
  • If you see IndentationError, check that all lines inside the function are indented with 4 spaces. Python is strict about indentation.
  • If you see SyntaxError, double check that every opening bracket in the transactions list has a matching closing bracket.

Help me debug my generate_sample_data.py script

Open the file to preview it

You can open transactions.xlsx in Excel, Google Sheets, or even VS Code (with a spreadsheet extension) to browse the data visually. Look for the empty description in row 10 and the duplicate entries.

Your sample spreadsheet is ready with realistic data and hidden red flags. Next up, you will write the code that reads this file back into Python and prepares the data for AI analysis.

Read and Parse the Excel Data

You have a sample Excel file full of transaction data. Now you need a way to read that data into Python so your script can pass it to the AI for analysis.

In this step, you will write functions that open the spreadsheet, pull out each row, and format the data into clean text that Gemini can understand.

In this step, get ready to:
  • Write a function that reads the Excel file and returns structured data.
  • Verify your function correctly loads all 25 transactions.
  • Add a formatting helper that converts transactions into readable text for the AI.
Write the function to load the workbook and extract rows

Your auditor script needs to open the Excel file, read each row, and convert the raw data into Python dictionaries that are easy to work with.

  • Create a new file called auditor.py in your financial-auditor folder.
  • Add the following import statements at the top of the file:
import os
from datetime import datetime
from openpyxl import load_workbook
from google import genai
from google.genai import types

What are these imports for?

  • os and datetime are standard Python libraries for file paths and timestamps.
  • load_workbook from openpyxl opens Excel files in Python.
  • genai and types from the google-genai SDK connect to the Gemini API. You will use these in the next step.
  • Below the imports, add the load_transactions function:
def load_transactions(filepath):
    """Load transactions from an Excel file and return as a list of dictionaries."""
    wb = load_workbook(filepath, read_only=True)
    ws = wb.active

    rows = list(ws.iter_rows(values_only=True))
    headers = rows[0]
    transactions = []

    for row in rows[1:]:
        txn = {}
        for i, header in enumerate(headers):
            value = row[i] if i < len(row) else None
            txn[header] = value
        transactions.append(txn)

    wb.close()
    return transactions

What does this function do?

This function handles the full pipeline from Excel file to Python data:

  • load_workbook(filepath, read_only=True) opens the Excel file in read-only mode, which uses less memory.
  • iter_rows(values_only=True) pulls out just the cell values (not the cell objects), giving you simple tuples of data.
  • rows[0] grabs the header row (Date, Description, Category, Amount, Reference).
  • The loop maps each remaining row to a dictionary keyed by those headers, so you can access values like txn["Amount"] instead of remembering column positions.
  • wb.close() releases the file when you are done reading.
  • Save auditor.py.

Seeing a red underline on the imports?

Make sure your virtual environment is activated. If VS Code shows import errors, click the Python interpreter selector in the bottom status bar and choose the interpreter inside your virtual environment folder.

Help me fix my Python interpreter selection in VS Code

Convert the raw rows into a structured list of dictionaries

The function you just wrote converts each Excel row into a dictionary. Let's verify it works correctly against your sample data.

  • Test your function by running this command in your terminal:
python -c "from auditor import load_transactions; txns = load_transactions('transactions.xlsx'); print(f'Loaded {len(txns)} transactions'); print(txns[0])"

You should see output showing 25 loaded transactions and a dictionary for the first row with keys like Date, Description, Category, Amount, and Reference.

Why dictionaries instead of raw tuples?

Each row from the spreadsheet comes back as a plain tuple like (date, 'Office Supplies', 'Office Supplies', 234.56, 'TXN-001'). That forces you to remember which position holds which value.

By mapping each row to a dictionary, you can write txn["Amount"] instead of row[3]. This makes the rest of your code easier to read and less error-prone.

Seeing 0 transactions or a FileNotFoundError?

Make sure transactions.xlsx is in the same folder as auditor.py. If you ran generate_sample_data.py from a different directory, the file may be elsewhere.

Help me fix the FileNotFoundError for transactions.xlsx

Add a helper that formats transactions into a readable text block

The AI cannot read an Excel file directly. You need to convert your transaction dictionaries into a plain text format that Gemini can process as part of a prompt.

  • In auditor.py, add the format_transactions function below load_transactions:
def format_transactions(transactions):
    """Format a list of transaction dictionaries into readable text for the AI."""
    lines = []
    for i, txn in enumerate(transactions, 1):
        date_str = txn["Date"].strftime("%Y-%m-%d") if txn["Date"] else "N/A"
        description = txn["Description"] if txn["Description"] else "[MISSING]"
        category = txn["Category"] if txn["Category"] else "[MISSING]"
        amount = f"${txn['Amount']:,.2f}" if txn["Amount"] else "[MISSING]"
        reference = txn["Reference"] if txn["Reference"] else "[MISSING]"

        lines.append(
            f"{i}. {date_str} | {description} | {category} | {amount} | {reference}"
        )

    return "\n".join(lines)

What does this function do?

This function turns each transaction dictionary into a single numbered line of text. It handles missing data gracefully by substituting [MISSING] or N/A so the AI can spot gaps.

The pipe-separated format (date | description | category | amount | reference) gives the AI a clear, consistent structure to parse. Each transaction gets its own numbered line so the AI can reference specific entries in its findings.

  • Save auditor.py.
  • Test the formatter by running this command in your terminal:
python -c "from auditor import load_transactions, format_transactions; txns = load_transactions('transactions.xlsx'); print(format_transactions(txns[:3]))"

You should see three numbered lines with dates, descriptions, categories, dollar amounts, and reference numbers separated by pipes.

Seeing an AttributeError on strftime?

This means the Date value is not a datetime object. Make sure you are loading transactions.xlsx (the file generated by your script), not a manually created spreadsheet. Openpyxl automatically reads Excel date cells as datetime objects.

Help me debug the AttributeError with strftime

✔️ Awesome, I've got everything!

Great. Double check you have saved auditor.py with both functions.

ⓧ I'd like to double check the full code

Here is the complete auditor.py file so far:

import os
from datetime import datetime
from openpyxl import load_workbook
from google import genai
from google.genai import types

def load_transactions(filepath):
    """Load transactions from an Excel file and return as a list of dictionaries."""
    wb = load_workbook(filepath, read_only=True)
    ws = wb.active

    rows = list(ws.iter_rows(values_only=True))
    headers = rows[0]
    transactions = []

    for row in rows[1:]:
        txn = {}
        for i, header in enumerate(headers):
            value = row[i] if i < len(row) else None
            txn[header] = value
        transactions.append(txn)

    wb.close()
    return transactions


def format_transactions(transactions):
    """Format a list of transaction dictionaries into readable text for the AI."""
    lines = []
    for i, txn in enumerate(transactions, 1):
        date_str = txn["Date"].strftime("%Y-%m-%d") if txn["Date"] else "N/A"
        description = txn["Description"] if txn["Description"] else "[MISSING]"
        category = txn["Category"] if txn["Category"] else "[MISSING]"
        amount = f"${txn['Amount']:,.2f}" if txn["Amount"] else "[MISSING]"
        reference = txn["Reference"] if txn["Reference"] else "[MISSING]"

        lines.append(
            f"{i}. {date_str} | {description} | {category} | {amount} | {reference}"
        )

    return "\n".join(lines)

Your data pipeline is ready. Next up, you will connect to Gemini and send these formatted transactions for AI-powered audit analysis.

Connect to Gemini and Analyze Transactions

Your script can now read an Excel file and format the data into clean, readable text.

The missing piece is the AI brain that actually spots problems in the data. In this step, you'll connect to Google's Gemini API and write a carefully crafted prompt that instructs the AI to act as a financial auditor.

In this step, get ready to:
  • Initialize the Gemini API client.
  • Write an audit prompt targeting specific financial red flags.
  • Test the analysis on your sample transactions.
Set up the Gemini client and write the audit prompt

The google-genai SDK makes connecting to Gemini straightforward. The Client() constructor automatically reads your GEMINI_API_KEY environment variable, so you don't need to pass the key directly in your code.

The real work is designing a prompt that guides the AI to look for specific issues rather than giving a vague "audit these transactions" instruction.

  • In auditor.py, find the end of your format_transactions function.
  • Add the following analyze_transactions function below it:
def analyze_transactions(transactions_text):
    """Send transactions to Gemini for audit analysis."""
    client = genai.Client()

    audit_prompt = f"""You are an experienced financial auditor reviewing transaction records.
Analyze the following transactions and flag any issues you find.

Look for these specific red flags:
1. Duplicate payments (same vendor and amount appearing more than once)
2. Unusually large round-number amounts that may indicate estimates rather than actual invoices
3. Missing or vague descriptions that lack accountability
4. Miscategorized expenses (items that clearly belong in a different category)
5. Transactions that seem unusual or warrant further investigation

For each issue found, provide:
- The transaction reference number
- What the issue is
- Why it matters
- A recommended action

Here are the transactions to audit:
{transactions_text}

Provide your findings in a clear, numbered list. If no issues are found, state that the records appear clean."""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=audit_prompt,
        config=types.GenerateContentConfig(
            temperature=0.2
        ),
    )

    return response.text

What does this code do?

  • genai.Client() creates a connection to Google's Gemini API. It automatically picks up the GEMINI_API_KEY you set earlier.
  • The audit_prompt is an f-string that embeds the formatted transactions directly into a detailed set of instructions. It tells Gemini exactly which 5 red flags to look for and how to format each finding.
  • client.models.generate_content() sends the prompt to the gemini-2.5-flash model and waits for a response.
  • temperature=0.2 keeps responses factual and consistent. Lower temperatures reduce creative variation, which is what you want for audit work.
  • response.text extracts the generated text from the API response object.
  • Save auditor.py.

Seeing a red underline under the import?

If VS Code shows an error under from google import genai, make sure your virtual environment is activated and that you installed google-genai in Step 1.

help me fix an import error for google-genai in my Python virtual environment

Send the formatted transactions to Gemini for analysis

Your function is ready. Now you'll feed it the sample transactions to see Gemini in action.

  • Test your analysis function by running this command in your terminal:
python -c "from auditor import load_transactions, format_transactions, analyze_transactions; t = load_transactions('transactions.xlsx'); print(analyze_transactions(format_transactions(t)))"

Why a one-line test command?

This imports your functions, loads the Excel file, formats the data, sends it to Gemini, and prints the result. You'll build a proper main() function in the next step. For now, this quick command confirms the AI connection works.

The command may take 5-10 seconds while Gemini processes your transactions.

Seeing an authentication error?

If you see an error about a missing or invalid API key, your GEMINI_API_KEY environment variable may not be set in this terminal session. Re-run the export command from Step 1 and try again.

help me fix a Gemini API authentication error in my Python script

Parse and display the AI's response

After a few seconds, you should see Gemini's audit findings printed in your terminal. The output is a numbered list of flagged issues with explanations.

Look for mentions of the issues you planted in your sample data: duplicate payments, missing descriptions, and miscategorized expenses.

What should I see?

Gemini should flag several of your planted issues. Each finding includes the transaction reference number (like TXN-006 or TXN-009), a description of the problem, and a recommended action.

The exact wording varies between runs, but you should see at least 3-4 of your 6 planted issues identified. The low temperature setting (0.2) keeps the output focused and consistent across runs.

✔️ Awesome, I've got everything!

Great. Double check you've saved auditor.py with the new analyze_transactions function.

ⓧ I'd like to double check the full code

import os
from datetime import datetime
from openpyxl import load_workbook
from google import genai
from google.genai import types

def load_transactions(filepath):
    """Load transactions from an Excel file and return as a list of dictionaries."""
    wb = load_workbook(filepath, read_only=True)
    ws = wb.active

    rows = list(ws.iter_rows(values_only=True))
    headers = rows[0]
    transactions = []

    for row in rows[1:]:
        txn = {}
        for i, header in enumerate(headers):
            value = row[i] if i < len(row) else None
            txn[header] = value
        transactions.append(txn)

    wb.close()
    return transactions


def format_transactions(transactions):
    """Format a list of transaction dictionaries into readable text for the AI."""
    lines = []
    for i, txn in enumerate(transactions, 1):
        date_str = txn["Date"].strftime("%Y-%m-%d") if txn["Date"] else "N/A"
        description = txn["Description"] if txn["Description"] else "[MISSING]"
        category = txn["Category"] if txn["Category"] else "[MISSING]"
        amount = f"${txn['Amount']:,.2f}" if txn["Amount"] else "[MISSING]"
        reference = txn["Reference"] if txn["Reference"] else "[MISSING]"

        lines.append(
            f"{i}. {date_str} | {description} | {category} | {amount} | {reference}"
        )

    return "\n".join(lines)


def analyze_transactions(transactions_text):
    """Send transactions to Gemini for audit analysis."""
    client = genai.Client()

    audit_prompt = f"""You are an experienced financial auditor reviewing transaction records.
Analyze the following transactions and flag any issues you find.

Look for these specific red flags:
1. Duplicate payments (same vendor and amount appearing more than once)
2. Unusually large round-number amounts that may indicate estimates rather than actual invoices
3. Missing or vague descriptions that lack accountability
4. Miscategorized expenses (items that clearly belong in a different category)
5. Transactions that seem unusual or warrant further investigation

For each issue found, provide:
- The transaction reference number
- What the issue is
- Why it matters
- A recommended action

Here are the transactions to audit:
{transactions_text}

Provide your findings in a clear, numbered list. If no issues are found, state that the records appear clean."""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=audit_prompt,
        config=types.GenerateContentConfig(
            temperature=0.2
        ),
    )

    return response.text

Your auditor now has AI-powered analysis. Next up, you'll wire everything together into a complete workflow and save the findings as a shareable report file.

Generate a Complete Audit Report

Your auditor can now read transactions and send them to Gemini for analysis. The last piece is turning those raw AI findings into a professional report you can save and share.

In this step, you will tie all the pieces together into a single script that runs end-to-end. You will also add a function that writes the audit findings to a formatted text file with a clear header and footer.

In this step, get ready to:
  • Write a main function that orchestrates the full audit workflow.
  • Save the AI findings to a formatted text report.
  • Run the auditor and verify it catches the planted issues.
Wrap everything into a main function

A main function acts as the entry point for your script. It defines the exact sequence of operations: load the file, format the data, call the AI, then save the results.

  • In auditor.py, scroll to the very bottom of the file (below the analyze_transactions function) and add the following code:
def main():
    filepath = "transactions.xlsx"

    print("Loading transactions...")
    transactions = load_transactions(filepath)
    print(f"Loaded {len(transactions)} transactions.\n")

    print("Formatting transactions for analysis...")
    transactions_text = format_transactions(transactions)

    print("Sending to Gemini for audit analysis...")
    print("(This may take a few seconds)\n")
    findings = analyze_transactions(transactions_text)

    print("Generating report...")
    report = save_report(findings, filepath, len(transactions))

    print("\n" + report)
    print("\nReport saved to: audit_report.txt")


if __name__ == "__main__":
    main()

What does this code do?

The main() function calls each piece of your auditor in sequence: load the Excel file, format the transactions into text, send them to Gemini, and save the report.

The if __name__ == "__main__": block ensures main() only runs when you execute this script directly. It prevents the function from running if you import this file from another script.

Notice that main() calls save_report() which does not exist yet. You will write that function next.

  • Save auditor.py.

You will not see a change yet. The next chunk adds the save_report function that makes the script runnable.

Save the audit findings to a text file

A plain text report is easy to email, attach to a ticket, or store for compliance records. This function builds a formatted string with metadata at the top and the AI findings in the body.

  • In auditor.py, find the blank line between your analyze_transactions function and your main function.
  • Add the following function in that gap (above def main():):
def save_report(findings, filepath, num_transactions):
    """Save the audit findings to a text file."""
    report_lines = [
        "=" * 60,
        "FINANCIAL TRANSACTION AUDIT REPORT",
        "=" * 60,
        f"Date Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
        f"Source File: {filepath}",
        f"Transactions Reviewed: {num_transactions}",
        "-" * 60,
        "",
        findings,
        "",
        "-" * 60,
        "END OF REPORT",
        "=" * 60,
    ]

    report_text = "\n".join(report_lines)

    with open("audit_report.txt", "w", encoding="utf-8") as f:
        f.write(report_text)

    return report_text

What does this code do?

The function builds a list of strings that form the report structure: a title banner, metadata lines (date, source file, transaction count), the AI findings in the middle, and a footer.

It joins all the lines with newline characters, then writes the result to audit_report.txt using UTF-8 encoding. The function also returns the report text so main() can print it to the console.

  • Save auditor.py.

Your script is now complete. The next substep runs it to generate the actual report.

Seeing a red underline under save_report in main?

Make sure save_report is defined above main() in the file. If it is below main(), some editors show a warning even though Python would still run fine.

Check that the indentation of def save_report(...): is at the top level (no leading spaces). It should line up with your other function definitions.

help me fix an issue with my save_report function placement in auditor.py

✔️ Awesome, I've got everything!

Great. Make sure you have saved auditor.py before moving on.

ⓧ I'd like to double check the full code

Here is the complete auditor.py file at this point:

import os
from datetime import datetime
from openpyxl import load_workbook
from google import genai
from google.genai import types

def load_transactions(filepath):
    """Load transactions from an Excel file and return as a list of dictionaries."""
    wb = load_workbook(filepath, read_only=True)
    ws = wb.active

    rows = list(ws.iter_rows(values_only=True))
    headers = rows[0]
    transactions = []

    for row in rows[1:]:
        txn = {}
        for i, header in enumerate(headers):
            value = row[i] if i < len(row) else None
            txn[header] = value
        transactions.append(txn)

    wb.close()
    return transactions


def format_transactions(transactions):
    """Format a list of transaction dictionaries into readable text for the AI."""
    lines = []
    for i, txn in enumerate(transactions, 1):
        date_str = txn["Date"].strftime("%Y-%m-%d") if txn["Date"] else "N/A"
        description = txn["Description"] if txn["Description"] else "[MISSING]"
        category = txn["Category"] if txn["Category"] else "[MISSING]"
        amount = f"${txn['Amount']:,.2f}" if txn["Amount"] else "[MISSING]"
        reference = txn["Reference"] if txn["Reference"] else "[MISSING]"

        lines.append(
            f"{i}. {date_str} | {description} | {category} | {amount} | {reference}"
        )

    return "\n".join(lines)


def analyze_transactions(transactions_text):
    """Send transactions to Gemini for audit analysis."""
    client = genai.Client()

    audit_prompt = f"""You are an experienced financial auditor reviewing transaction records.
Analyze the following transactions and flag any issues you find.

Look for these specific red flags:
1. Duplicate payments (same vendor and amount appearing more than once)
2. Unusually large round-number amounts that may indicate estimates rather than actual invoices
3. Missing or vague descriptions that lack accountability
4. Miscategorized expenses (items that clearly belong in a different category)
5. Transactions that seem unusual or warrant further investigation

For each issue found, provide:
- The transaction reference number
- What the issue is
- Why it matters
- A recommended action

Here are the transactions to audit:
{transactions_text}

Provide your findings in a clear, numbered list. If no issues are found, state that the records appear clean."""

    response = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=audit_prompt,
        config=types.GenerateContentConfig(
            temperature=0.2
        ),
    )

    return response.text


def save_report(findings, filepath, num_transactions):
    """Save the audit findings to a text file."""
    report_lines = [
        "=" * 60,
        "FINANCIAL TRANSACTION AUDIT REPORT",
        "=" * 60,
        f"Date Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
        f"Source File: {filepath}",
        f"Transactions Reviewed: {num_transactions}",
        "-" * 60,
        "",
        findings,
        "",
        "-" * 60,
        "END OF REPORT",
        "=" * 60,
    ]

    report_text = "\n".join(report_lines)

    with open("audit_report.txt", "w", encoding="utf-8") as f:
        f.write(report_text)

    return report_text


def main():
    filepath = "transactions.xlsx"

    print("Loading transactions...")
    transactions = load_transactions(filepath)
    print(f"Loaded {len(transactions)} transactions.\n")

    print("Formatting transactions for analysis...")
    transactions_text = format_transactions(transactions)

    print("Sending to Gemini for audit analysis...")
    print("(This may take a few seconds)\n")
    findings = analyze_transactions(transactions_text)

    print("Generating report...")
    report = save_report(findings, filepath, len(transactions))

    print("\n" + report)
    print("\nReport saved to: audit_report.txt")


if __name__ == "__main__":
    main()
Run the complete auditor and review the results

Time to see it all come together. You will run the script against your sample transaction data and check that Gemini identified the planted audit issues.

  • Make sure your terminal is in the financial-auditor folder with your virtual environment activated.
  • Run the auditor script by running this command:
python auditor.py

What should I see?

The script prints progress messages as it moves through each stage. After a few seconds, Gemini returns its analysis and the full report prints to your console.

You should see a formatted report with a header banner, metadata (date, source file, transaction count), and a numbered list of flagged issues. Gemini should identify most of the planted problems: the duplicate rent payment (TXN-006), the missing description (TXN-009), the miscategorized laptop (TXN-013), the suspicious round amounts (TXN-018, TXN-024), and the duplicate Staples purchase (TXN-025).

  • Confirm that audit_report.txt was created in your project folder by running:
ls

You should see audit_report.txt listed alongside auditor.py, generate_sample_data.py, and transactions.xlsx.

  • Open audit_report.txt in VS Code by clicking on it in the Explorer sidebar.
  • Read through the findings and check that Gemini identified at least 4 of the 6 planted issues.

AI output varies between runs

Gemini may phrase its findings differently each time you run the script. It might catch all 6 planted issues or miss one. The low temperature (0.2) keeps responses consistent but does not guarantee identical output.

If you run the script again, the report file gets overwritten with the new results.

Seeing an error when you run the script?

  • If you see a NameError mentioning save_report, make sure the function is defined above main() in the file and that you saved after adding it.
  • If you see a permissions or API key error, confirm your GEMINI_API_KEY environment variable is still set in this terminal session. Closing and reopening the terminal clears it.
  • If you see a FileNotFoundError for transactions.xlsx, make sure the terminal is in the financial-auditor folder where the file lives.

help me debug an error when running my auditor.py script

Your financial auditor is complete. You have a Python script that reads an Excel file, sends the data to an AI model, and produces a professional audit report in seconds. Next up is an optional Secret Mission to add severity scoring to your flagged issues.

Secret mission

Add Severity Scoring

Your auditor flags issues, but not all issues are equally urgent. A duplicate $45 taxi receipt is not the same as a $15,000 payment with a vague description. Can you upgrade your auditor to rank each finding by risk level?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally and uses the Gemini API free tier, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Project folder (contains your virtual environment, Python scripts, and generated files).
  • GEMINI_API_KEY environment variable set on your system.
  • Gemini API key in Google AI Studio.

Keep everything running

No action needed. Choose this if you are still actively building or plan to extend your auditor script.

  • Your project folder stays on your Desktop.
  • Your environment variable remains set for future Gemini API calls.
  • Your API key remains active in Google AI Studio on the free tier at no cost.

Pause - I'll come back to this later

Free up your terminal session and environment. Your files stay intact so you can pick up where you left off.

  • If your virtual environment is active, deactivate it by running:
  • Your project files and API key remain untouched for next time.
deactivate

Delete - I don't want to use this again

Remove all project resources and revoke access. This is a full cleanup.

  • Delete your project folder by running the appropriate command for your operating system.

macOS

rm -rf ~/Desktop/financial-auditor

Windows

rmdir /s /q %USERPROFILE%\Desktop\financial-auditor
  • Remove the GEMINI_API_KEY environment variable from your system.

macOS

Open your shell configuration file (~/.zshrc or ~/.bashrc) and delete the line that says:

export GEMINI_API_KEY=your-key-here

Save the file, then apply the change by running:

source ~/.zshrc

Windows

  • Search for "Environment Variables" in the Windows search bar.
  • Open **System Properties** and click **Environment Variables**.
  • Find GEMINI_API_KEY under User variables, select it, and click **Delete**.
  • Click **OK** to save.
  • Delete your API key from Google AI Studio.

Nice Work!

Nice Work!

Congratulations! You just built an AI-powered financial auditor from scratch. You took a spreadsheet of transactions and turned it into an automated audit workflow that catches real red flags in seconds.

Here's what you learned:

  • Read Excel files with openpyxl and parsed financial transaction data into structured Python dictionaries.
  • Connected to Google's Gemini AI using the google-genai SDK and wrote an effective audit prompt that catches duplicates, unusual amounts, and categorization errors.
  • Built a complete automation pipeline that generates a professional audit report from raw spreadsheet data. No manual review needed.
  • Added severity scoring to prioritize which flagged issues need immediate attention.

Ready to quiz yourself?