Golden Door Asset
Software Stocks
Gemini PortfolioAutomated Expense Auditor
Corporate Finance
Advanced

Automated Expense Auditor

Flag out-of-policy spending using RAG

Build Parameters
Firebase Genkit
6–8 hours Build

Automated Expense Auditor: Project Blueprint

1. The Business Problem (Why build this?)

In today's complex corporate landscape, managing employee expenses efficiently and compliantly poses a significant challenge for finance departments. Traditional expense auditing processes are predominantly manual, leading to a cascade of inefficiencies and risks. Finance teams often spend countless hours sifting through receipts, cross-referencing company policies, and manually flagging discrepancies. This not only consumes valuable human capital but is also prone to human error, leading to inconsistent policy enforcement, increased compliance risks, and potential financial leakage through unapproved or out-of-policy spending.

The manual approach results in slow reimbursement cycles, causing frustration among employees. Auditors face immense pressure, managing a high volume of submissions with limited tools, leading to burnout and a reactive, rather than proactive, auditing posture. Furthermore, the lack of immediate feedback loops means policy violations are often caught post-spend, making corrective action difficult and potentially impacting future budgeting. Without a robust, automated system, organizations struggle to gain real-time visibility into spending patterns, identify trends, and make data-driven decisions to optimize their financial operations and ensure regulatory adherence. The "Automated Expense Auditor" addresses these critical pain points by introducing intelligent automation, transparency, and efficiency to the entire expense management lifecycle.

2. Solution Overview

The Automated Expense Auditor is a sophisticated software application designed to revolutionize corporate expense management by leveraging cutting-edge AI and machine learning capabilities. At its core, the system aims to automate the process of expense submission, receipt parsing, policy compliance checks, and approval routing, drastically reducing manual effort and improving accuracy.

The high-level workflow is as follows:

  1. Expense Submission: Employees submit their expenses via a user-friendly web interface, optionally attaching receipt images.
  2. Receipt Parsing: An intelligent AI module (powered by Gemini Vision Pro) automatically extracts key data points from uploaded receipts, such as merchant name, total amount, date, and itemized details.
  3. RAG Policy Comparison: The extracted expense data, along with any contextual information, is then compared against the company's predefined expense policies. This is achieved using a Retrieval-Augmented Generation (RAG) system, where relevant policy documents (stored as vectorized embeddings) are retrieved and fed to Gemini Pro for contextual analysis.
  4. Automated Flagging: Based on the AI's analysis, expenses are automatically flagged for potential policy violations, anomalies, or high-risk spending, complete with detailed explanations. A proprietary audit score is assigned to each expense.
  5. Approval Workflow Routing: Expenses are then intelligently routed for approval based on a configurable workflow, considering factors like the assigned audit score, expense amount, category, and organizational hierarchy. This can range from auto-approval for low-risk, compliant expenses to escalation for high-risk or out-of-policy items.
  6. Audit Dashboard: A comprehensive dashboard provides real-time visibility for finance teams and managers, showcasing expense statuses, flagged items, audit trails, and performance metrics, enabling proactive oversight and informed decision-making.

By automating these steps, the solution empowers finance teams to shift from reactive auditing to strategic financial oversight, ensures consistent policy enforcement, and significantly accelerates the expense reimbursement process, fostering a more compliant and efficient financial ecosystem.

3. Architecture & Tech Stack Justification

The architecture for the Automated Expense Auditor is designed for scalability, modularity, and leveraging Google's AI capabilities, combined with robust open-source components.

Overall Architecture:

graph TD
    UserClient[Web Client (Next.js)] -->|Expense Submission, Dashboard| NextjsBackend[Next.js API Routes]
    NextjsBackend -->|Auth| SupabaseAuth[Supabase Auth]
    NextjsBackend -->|Data CRUD| SupabaseDB[Supabase PostgreSQL DB]
    NextjsBackend -->|File Upload| SupabaseStorage[Supabase Storage]
    NextjsBackend -->|AI Orchestration| GenkitBackend[Firebase Genkit / Cloud Functions]

    GenkitBackend -->|Gemini API Calls| GeminiAPI[Gemini API (Vision Pro, Pro Text)]
    GenkitBackend -->|Vector Search| SupabaseDB
    SupabaseDB -->|Realtime Subscriptions| NextjsBackend
    AdminClient[Admin Web Client (Next.js)] --> NextjsBackend
    SupabaseDB -->|Policy Storage, Expense Data, Embeddings| GenkitBackend

Tech Stack Justification:

  • Next.js (Frontend & API Routes):

    • Justification: As a full-stack React framework, Next.js provides excellent developer experience with server-side rendering (SSR), static site generation (SSG), and API routes. SSR/SSG are beneficial for performance and SEO (though less critical for an internal tool, still good practice). API routes allow for a unified codebase, handling authenticated requests from the frontend to the backend services (Genkit, Supabase) and acting as a thin proxy, ensuring better security and control over data flow. Its robust ecosystem and community support are also key factors.
    • Role: User interface for employees submitting expenses, managers approving, and finance teams auditing. API routes mediate communication with Supabase and Genkit backend services.
  • Firebase Genkit (AI Orchestration & Backend Logic):

    • Justification: Genkit is purpose-built for AI application development. It provides a structured way to define AI flows, manage prompts, handle model integrations (especially Gemini), and incorporate complex logic around AI calls (e.g., fallback models, structured output parsing). Running Genkit on Firebase Cloud Functions offers a serverless, scalable, and cost-effective execution environment, tightly integrated with Google Cloud. It reduces boilerplate for common AI tasks and offers built-in observability for AI interactions.
    • Role: The core orchestrator for all AI-driven processes: receipt parsing calls to Gemini Vision Pro, RAG-based policy comparison using Gemini Pro, and any other complex business logic that interacts with Gemini. It will host the main backend API endpoints consumed by Next.js.
  • Gemini API (Core AI Model):

    • Justification: Gemini is Google's most capable and multimodal AI model. Its ability to understand and generate text, and especially its Vision Pro variant for multimodal inputs (like images of receipts), is crucial for this project. Gemini Pro Text will be used for sophisticated policy comparison and flagging logic, while Gemini Vision Pro will handle the receipt parsing. This provides a single, powerful AI backbone for complex natural language and image processing tasks.
    • Role:
      • gemini-pro-vision: Receipt data extraction (merchant, amount, date, items).
      • gemini-pro-text: Contextual policy comparison, identifying violations, generating explanations, and summarizing audit findings.
      • Embedding models: Generating vector embeddings for policy documents and expense descriptions for the RAG system.
  • Supabase (Database, Auth, Storage, Realtime):

    • Justification: Supabase offers an open-source alternative to Firebase Firestore, providing a powerful PostgreSQL database with robust features. PostgreSQL is ideal for structured relational data (expenses, policies, users, audit logs), which is critical for this application. Its built-in pg_vector extension is essential for storing and querying vector embeddings for the RAG system. Supabase Auth provides secure user management, Supabase Storage handles receipt image uploads, and Supabase Realtime enables instant updates for dashboards and notifications without complex polling logic. This consolidates several backend needs into a single, scalable platform.
    • Role:
      • Database: Stores all structured data: user profiles, expense records, company policies (text and vector embeddings), audit trails, and approval statuses.
      • Auth: Manages user authentication and authorization (employees, managers, finance auditors).
      • Storage: Securely stores raw receipt images.
      • Realtime: Pushes updates to the audit dashboard and user interfaces as expense statuses change or new expenses are submitted.

Supporting Services:

  • Google Cloud Functions: While Genkit runs on Cloud Functions, other small, independent serverless functions for specific triggers (e.g., post-processing after an expense is approved) can also be deployed directly if not part of a core Genkit flow.

This integrated stack ensures a highly functional, scalable, and AI-powered solution capable of handling advanced corporate finance needs.

4. Core Feature Implementation Guide

4.1. Data Models (Supabase PostgreSQL)

The foundation of our application is a well-structured relational database. We'll leverage Supabase's PostgreSQL capabilities.

-- Users Table
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT UNIQUE NOT NULL,
    password_hash TEXT, -- If not using Supabase Auth directly for passwords
    full_name TEXT,
    role TEXT NOT NULL DEFAULT 'employee', -- 'employee', 'manager', 'finance_auditor', 'admin'
    manager_id UUID REFERENCES users(id),
    approval_limit_usd NUMERIC(10, 2) DEFAULT 0.00, -- Max amount user can approve
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Policies Table (for RAG context)
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE policies (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    policy_name TEXT NOT NULL,
    description TEXT,
    policy_text TEXT NOT NULL, -- Full original policy text (or chunk)
    effective_date DATE,
    tags TEXT[],
    vector_embedding VECTOR(768), -- Or dimension matching Gemini's embedding model
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX ON policies USING ivfflat (vector_embedding vector_l2_ops) WITH (lists = 100); -- For efficient similarity search

-- Expenses Table
CREATE TYPE expense_status AS ENUM ('submitted', 'parsing_failed', 'pending_manager_approval', 'pending_finance_approval', 'approved', 'rejected', 'reimbursed');
CREATE TYPE flag_severity AS ENUM ('low', 'medium', 'high', 'critical');
CREATE TABLE expenses (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) NOT NULL,
    status expense_status DEFAULT 'submitted' NOT NULL,
    merchant TEXT,
    amount NUMERIC(10, 2),
    currency TEXT DEFAULT 'USD',
    category TEXT, -- e.g., 'Travel', 'Meals', 'Software'
    transaction_date DATE,
    description TEXT,
    receipt_url TEXT, -- URL to Supabase Storage
    flags JSONB DEFAULT '[]'::jsonb, -- Array of {type: 'policy_violation', severity: 'high', explanation: '...', policy_excerpt: '...'}
    audit_score INTEGER DEFAULT 0, -- Higher score = more risk
    audit_trail JSONB DEFAULT '[]'::jsonb, -- Array of {event: '...', user: '...', timestamp: '...'}
    current_approver_id UUID REFERENCES users(id),
    rejection_reason TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Audit Log Table (for granular logging)
CREATE TABLE audit_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    expense_id UUID REFERENCES expenses(id) NOT NULL,
    user_id UUID REFERENCES users(id), -- User who performed the action
    event_type TEXT NOT NULL, -- e.g., 'expense_submitted', 'receipt_parsed', 'policy_flagged', 'approved', 'rejected'
    details JSONB, -- Additional context, e.g., flags identified, old_status, new_status
    timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

4.2. Receipt Parsing Pipeline

This pipeline extracts structured data from expense receipt images using Gemini Vision Pro.

  1. Frontend (Next.js):

    • User uploads a receipt image via an <input type="file" accept="image/*">.
    • The image file is sent to a Next.js API route.
  2. Next.js API Route:

    • Receives the image file.
    • Uploads the image to Supabase Storage, generating a public URL.
    • Creates an initial expenses record with user_id, receipt_url, and status = 'submitted'.
    • Invokes the Genkit parseReceipt endpoint with the Supabase receipt_url and expense_id.
    // pages/api/submit-expense.js (pseudo-code)
    import { createClient } from '@supabase/supabase-js';
    import formidable from 'formidable'; // For handling file uploads
    
    export const config = { api: { bodyParser: false } };
    
    export default async function handler(req, res) {
        if (req.method !== 'POST') return res.status(405).end();
    
        const form = formidable();
        form.parse(req, async (err, fields, files) => {
            if (err) return res.status(500).json({ error: 'File upload error' });
    
            const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);
            const userId = req.headers['x-user-id']; // Authenticated user ID
    
            try {
                const receiptFile = files.receipt[0]; // Assuming single file upload
                const { data: uploadData, error: uploadError } = await supabase.storage
                    .from('receipts')
                    .upload(`${userId}/${Date.now()}-${receiptFile.originalFilename}`, fs.createReadStream(receiptFile.filepath), {
                        contentType: receiptFile.mimetype,
                    });
    
                if (uploadError) throw uploadError;
    
                const receiptUrl = supabase.storage.from('receipts').getPublicUrl(uploadData.path).data.publicUrl;
    
                const { data: expenseData, error: expenseError } = await supabase
                    .from('expenses')
                    .insert({ user_id: userId, receipt_url: receiptUrl, status: 'submitted' })
                    .select('id')
                    .single();
    
                if (expenseError) throw expenseError;
    
                // Call Genkit backend for parsing
                await fetch('YOUR_GENKIT_BACKEND_URL/parseReceipt', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ expenseId: expenseData.id, receiptUrl }),
                });
    
                res.status(200).json({ message: 'Expense submitted, parsing initiated.', expenseId: expenseData.id });
    
            } catch (error) {
                console.error('Expense submission error:', error);
                res.status(500).json({ error: error.message });
            }
        });
    }
    
  3. Genkit parseReceipt Endpoint (Cloud Function):

    • Receives expenseId and receiptUrl.
    • Fetches the image data from receiptUrl.
    • Calls Gemini Vision Pro for data extraction.
    • Parses Gemini's JSON output.
    • Updates the expenses record with parsed data and sets status = 'pending_policy_check'.
    • Records an event in audit_log.
    • Triggers the RAG policy comparison pipeline.
    // genkit/flows/expenseFlow.ts (pseudo-code)
    import { generate, defineFlow } from '@genkit-ai/flow';
    import { geminiProVision } from '@genkit-ai/google-cloud';
    import { supabase } from '../lib/supabase'; // Supabase client setup
    
    export const parseReceipt = defineFlow(
      {
        name: 'parseReceipt',
        inputSchema: z.object({ expenseId: z.string(), receiptUrl: z.string() }),
        outputSchema: z.void(),
      },
      async ({ expenseId, receiptUrl }) => {
        try {
          // Fetch image data (consider caching or streaming for large images)
          const response = await fetch(receiptUrl);
          const arrayBuffer = await response.arrayBuffer();
          const base64Image = Buffer.from(arrayBuffer).toString('base64');
    
          const prompt = {
            text: `Extract the following fields from this receipt image in JSON format: merchant_name (string), total_amount (number), currency (string, e.g., "USD"), transaction_date (string, YYYY-MM-DD), category (string, infer if possible, e.g., "Food", "Travel"), itemized_list (array of {item_name: string, quantity: number, unit_price: number, total_price: number}). If a field is not found, use null.`,
            files: [{ type: 'image_jpeg', data: base64Image }], // Adjust mime type
          };
    
          const llmResponse = await generate({
            model: geminiProVision,
            prompt: prompt,
            config: { output: { format: 'json' } },
          });
    
          const parsedData = JSON.parse(llmResponse.text());
    
          // Update expense in Supabase
          const { error: updateError } = await supabase.from('expenses').update({
            merchant: parsedData.merchant_name,
            amount: parsedData.total_amount,
            currency: parsedData.currency,
            transaction_date: parsedData.transaction_date,
            category: parsedData.category,
            // Store itemized_list in a JSONB column if needed, or process further
            status: 'pending_policy_check',
          }).eq('id', expenseId);
    
          if (updateError) throw updateError;
    
          await supabase.from('audit_log').insert({
            expense_id: expenseId,
            event_type: 'receipt_parsed',
            details: { parsedData: parsedData }
          });
    
          // Trigger RAG policy check
          await generate({
            model: 'local-flow', // Call another Genkit flow
            prompt: { text: JSON.stringify({ expenseId }) },
            config: { flow: 'checkPolicyCompliance' }
          });
    
        } catch (error) {
          console.error(`Receipt parsing failed for expense ${expenseId}:`, error);
          await supabase.from('expenses').update({ status: 'parsing_failed' }).eq('id', expenseId);
          await supabase.from('audit_log').insert({
            expense_id: expenseId,
            event_type: 'parsing_failed',
            details: { error: error.message }
          });
        }
      }
    );
    

4.3. RAG Policy Comparison Pipeline

This pipeline compares parsed expense data against company policies using vector search and Gemini.

  1. Policy Ingestion (Admin Process):

    • Admin uploads policy documents (PDF, DOCX, TXT) via a dedicated UI.
    • Next.js API route handles upload, stores in Supabase Storage.
    • Trigger Genkit ingestPolicy endpoint.
    • Genkit uses a document loader (e.g., LangChain's PDFLoader) to extract text.
    • Chunks the text into smaller, meaningful segments (e.g., 500 characters with 100 character overlap) to maintain context.
    • For each chunk, generate a vector embedding using Gemini's embedding model.
    • Store policy_text chunk and vector_embedding in the policies table.
    // genkit/flows/policyFlow.ts (pseudo-code - excerpt for embedding)
    import { embed } from '@genkit-ai/ai';
    import { geminiPro } from '@genkit-ai/google-cloud'; // For embedding model
    
    // ... inside ingestPolicy flow ...
    for (const chunk of policyChunks) {
      const embeddingResponse = await embed({
        model: geminiPro, // Or specific embedding model if available
        text: chunk.pageContent,
      });
      const vector = embeddingResponse.embeddings[0].values; // Extract vector values
    
      await supabase.from('policies').insert({
        policy_name: policyName,
        policy_text: chunk.pageContent,
        vector_embedding: vector,
        // ... other metadata
      });
    }
    
  2. Expense Analysis & Vector Search (Genkit checkPolicyCompliance Flow):

    • Receives expenseId (triggered after parseReceipt).
    • Retrieves expense details from Supabase.
    • Generates an embedding for the expense description/summary using Gemini's embedding model.
    • Performs a similarity search (vector_embedding <-> :expense_embedding) in the policies table to retrieve top-k most relevant policy chunks.
    // genkit/flows/expenseFlow.ts (pseudo-code - checkPolicyCompliance flow)
    import { embed, generate } from '@genkit-ai/ai';
    import { geminiPro } from '@genkit-ai/google-cloud'; // For embedding and text model
    
    export const checkPolicyCompliance = defineFlow(
      {
        name: 'checkPolicyCompliance',
        inputSchema: z.object({ expenseId: z.string() }),
        outputSchema: z.void(),
      },
      async ({ expenseId }) => {
        const { data: expense, error: expenseError } = await supabase
          .from('expenses')
          .select('*')
          .eq('id', expenseId)
          .single();
    
        if (expenseError || !expense) throw new Error('Expense not found');
    
        const expenseSummary = `Expense for ${expense.merchant} on ${expense.transaction_date} for ${expense.amount} ${expense.currency} related to ${expense.category}. Description: ${expense.description || 'N/A'}.`;
    
        const embeddingResponse = await embed({
          model: geminiPro, // Use appropriate embedding model
          text: expenseSummary,
        });
        const expenseEmbedding = embeddingResponse.embeddings[0].values;
    
        // Vector search for relevant policies using pg_vector
        const { data: relevantPolicies, error: policyError } = await supabase
          .from('policies')
          .select('policy_text')
          .order('vector_embedding <-> ?', { ascending: true, foreignTable: expenseEmbedding })
          .limit(5); // Get top 5 relevant policy chunks
    
        if (policyError) throw policyError;
    
        const policyContext = relevantPolicies.map(p => p.policy_text).join('\n---\n');
    
        // Construct prompt for Gemini
        const llmResponse = await generate({
          model: geminiPro,
          prompt: {
            text: `Expense Details:\n${JSON.stringify(expense, null, 2)}\n\nRelevant Policy Excerpts:\n${policyContext}\n\nBased on the above, identify all policy violations. For each violation, state the specific policy violated, provide the relevant excerpt, and explain how the expense violates it. If no violations, state 'No violations found.' Respond in JSON format with a 'violations' array.`,
          },
          config: { output: { format: 'json' } },
        });
    
        const flaggingResult = JSON.parse(llmResponse.text());
        let flags = flaggingResult.violations || [];
        let auditScore = 0;
    
        // Process flags and calculate audit score
        flags = flags.map(flag => {
            const severity = inferSeverity(flag.explanation); // Custom function to infer severity
            auditScore += scoreMap[severity]; // Assign points based on severity
            return { ...flag, severity: severity };
        });
    
        // Update expense with flags and audit score
        await supabase.from('expenses').update({
          flags: flags,
          audit_score: auditScore,
          status: 'pending_approval' // Next status
        }).eq('id', expenseId);
    
        await supabase.from('audit_log').insert({
          expense_id: expenseId,
          event_type: 'policy_checked',
          details: { flags: flags, auditScore: auditScore }
        });
    
        // Trigger approval workflow routing
        await generate({
            model: 'local-flow',
            prompt: { text: JSON.stringify({ expenseId }) },
            config: { flow: 'routeForApproval' }
        });
      }
    );
    

4.4. Automated Flagging & Scoring

  • Rules Engine (Pre-Gemini or Post-Gemini):
    • Simple, deterministic rules can be applied before or after Gemini analysis.
    • Example: if expense.amount > user.approval_limit_usd then add a 'High Value' flag.
    • if expense.category == 'Alcohol' and expense.transaction_date.day_of_week == 'Sunday' then add a 'Restricted Category' flag.
  • Gemini-driven Flagging: As demonstrated in the RAG pipeline, Gemini directly identifies policy violations and provides explanations.
  • Audit Score Calculation:
    • Each flag (from rules engine or Gemini) is assigned a severity (low, medium, high, critical).
    • A numerical score is associated with each severity level (e.g., low=1, medium=5, high=10, critical=20).
    • The audit_score for an expense is the sum of scores of all identified flags. This score guides approval routing.

4.5. Approval Workflow Routing

This system determines the next approver based on expense characteristics and policy violations.

  1. Genkit routeForApproval Flow:

    • Receives expenseId.
    • Retrieves expense details, audit score, and associated user/manager info from Supabase.
    • Applies a set of routing rules:
      • Rule 1: Auto-Approve: If audit_score == 0 AND expense.amount <= user.self_approval_limit (e.g., for minor expenses below a threshold that need no manager review).
      • Rule 2: Manager Approval: If audit_score < X AND expense.amount <= manager.approval_limit_usd, route to user.manager_id.
      • Rule 3: Finance Auditor Review: If audit_score >= X (high risk) OR expense.amount > manager.approval_limit_usd (exceeds manager's limit), route to a finance_auditor (e.g., a specific user or team).
      • Rule 4: CEO Approval: For extremely high amounts/critical flags.
    • Updates expenses.status and expenses.current_approver_id.
    • Notifies the approver (via email/dashboard).
    • Supabase Realtime can push this update to the approver's dashboard.
    // genkit/flows/expenseFlow.ts (pseudo-code - routeForApproval flow)
    export const routeForApproval = defineFlow(
      {
        name: 'routeForApproval',
        inputSchema: z.object({ expenseId: z.string() }),
        outputSchema: z.void(),
      },
      async ({ expenseId }) => {
        const { data: expense, error: expenseError } = await supabase
          .from('expenses')
          .select('*, users!user_id(manager_id)') // Join to get manager_id
          .eq('id', expenseId)
          .single();
    
        if (expenseError || !expense) throw new Error('Expense not found');
    
        let nextStatus = 'pending_manager_approval';
        let approverId = expense.users.manager_id; // Default to direct manager
    
        // Fetch manager's approval limit and finance auditor info
        const { data: manager, error: managerError } = approverId ? await supabase.from('users').select('approval_limit_usd').eq('id', approverId).single() : {data: null, error: null};
        const { data: financeAuditors, error: financeError } = await supabase.from('users').select('id').eq('role', 'finance_auditor');
    
        if (expense.audit_score === 0 && expense.amount <= 100) { // Example: Auto-approve small, compliant expenses
            nextStatus = 'approved';
            approverId = null;
        } else if (expense.audit_score >= 10 || (manager && expense.amount > manager.approval_limit_usd) || !approverId) {
            nextStatus = 'pending_finance_approval';
            approverId = financeAuditors[0]?.id || null; // Assign to first available finance auditor
            if (!approverId) console.warn('No finance auditor found for routing!');
        }
        // ... more complex routing logic
    
        await supabase.from('expenses').update({
          status: nextStatus,
          current_approver_id: approverId
        }).eq('id', expenseId);
    
        await supabase.from('audit_log').insert({
          expense_id: expenseId,
          event_type: 'routed_for_approval',
          details: { nextStatus, approverId }
        });
    
        // Notify approver (e.g., via email or Supabase Realtime)
        // This could be another Genkit flow or a simple Supabase Realtime trigger
      }
    );
    

4.6. Audit Dashboard

  • Frontend (Next.js): Builds a rich UI for finance auditors and managers.
  • Data Fetching:
    • Uses Supabase client in Next.js pages/components to fetch data from expenses, users, audit_log tables.
    • Filters: By status (pending_manager_approval, pending_finance_approval), by user, by flags, by date range.
    • Pagination for large datasets.
  • Realtime Updates:
    • Leverages Supabase Realtime to subscribe to changes in the expenses table.
    • supabase.channel('expenses_changes').on('postgres_changes', { event: '*', schema: 'public', table: 'expenses' }, (payload) => { /* Update UI */ }).
    • This ensures the dashboard automatically updates when an expense status changes or a new expense is submitted, providing finance teams with live data.
  • Key Metrics Displayed:
    • Total submitted expenses, pending approvals, approved, rejected.
    • Average approval time.
    • Top flagged categories/merchants.
    • List of expenses with highest audit scores.
    • Drill-down view for individual expenses: parsed data, receipt image, full audit trail, identified flags and explanations, approval history.
  • Actionability: Buttons for auditors/managers to "Approve," "Reject," "Request More Info," which trigger corresponding Genkit flows or Supabase updates.

5. Gemini Prompting Strategy

Effective prompting is crucial for leveraging Gemini's capabilities accurately and consistently. We will employ structured prompts, few-shot examples (where beneficial), and explicitly request JSON output for robust programmatic parsing.

5.1. Receipt Parsing (Gemini Vision Pro)

  • Goal: Extract structured key-value pairs from an image.
  • Prompt Structure:
    • System Instruction: Establishes the AI's role and expected behavior. You are an expert OCR and data extraction agent specializing in financial receipts. Your task is to accurately identify and extract specific financial details from the provided receipt image. If a field is not present or cannot be confidently extracted, use 'null'.
    • User Prompt (with Image): Clearly specifies the desired fields and format. Extract the following fields in JSON format: { "merchant_name": "string", "total_amount": "number", "currency": "string", "transaction_date": "string (YYYY-MM-DD)", "category": "string (infer from merchant/items if possible, e.g., 'Food', 'Travel', 'Office Supplies')", "itemized_list": [ { "item_name": "string", "quantity": "number", "unit_price": "number", "total_price": "number" } ] } Analyze this receipt image and provide the JSON output.
  • Refinement: For highly variable receipt formats, fine-tuning or few-shot examples (if supported by Genkit's model invocation) could be added to demonstrate desired parsing for edge cases.

5.2. Policy Comparison (Gemini Pro Text with RAG)

  • Goal: Identify policy violations by comparing expense details against relevant policy excerpts.
  • Prompt Structure:
    • System Instruction: Defines the AI's persona and core objective. You are a meticulous corporate expense policy auditor. Your primary task is to review employee expense claims against specific company expense policy excerpts. Your analysis must be objective, precise, and directly reference the policy text provided. If an expense complies, explicitly state "No violations found." Otherwise, clearly identify and explain each violation.

    • User Prompt (with RAG context): Combines the expense details and the retrieved policy chunks. `Expense Details: ${JSON.stringify(expenseDetails, null, 2)}

      Relevant Company Expense Policy Excerpts: ${policyChunks.map(chunk => - ${chunk.policy_text}).join('\n---\n')}

      Based on the above expense details and ONLY the provided policy excerpts, identify all policy violations. For each violation, provide the following in a JSON array: { "violations": [ { "policy_name": "string (e.g., 'Travel Policy', 'Meal & Entertainment Policy')", "violation_type": "string (e.g., 'Amount Exceeded', 'Unapproved Category', 'Missing Justification')", "relevant_excerpt": "string (the exact policy text that was violated)", "explanation": "string (how the expense violates this policy)", "suggested_action": "string (e.g., 'Requires Manager Override', 'Reject')" } ] } If no violations are found, return: { "violations": [] }`

  • Key Considerations:
    • Grounding: Emphasize "ONLY the provided policy excerpts" to prevent hallucination.
    • Specificity: Request specific fields in the JSON output to make programmatic parsing reliable.
    • Clarity: Ensure expense details are presented clearly, potentially summarizing complex parts before feeding to the LLM.

5.3. Iterative Prompting & Refinement

For complex scenarios, a multi-turn or chained prompting strategy might be beneficial using Genkit's flow capabilities:

  1. Initial Flagging: Basic violation identification.
  2. Justification Analysis: If a flag is raised, a follow-up prompt could ask Gemini to analyze employee justifications against policy.
  3. Summarization: After flagging, a final prompt could summarize the audit findings for the approver.

By carefully crafting these prompts and leveraging Genkit's orchestration, we ensure that Gemini performs its tasks accurately, consistently, and in a way that generates actionable, structured insights for the Automated Expense Auditor.

6. Deployment & Scaling

A robust deployment and scaling strategy is essential for an advanced corporate application to ensure high availability, performance, and cost-efficiency.

6.1. Frontend (Next.js)

  • Deployment Target: Vercel or Google Cloud Run.
    • Vercel (Recommended): Optimized for Next.js, providing seamless deployment, global CDN, serverless functions for API routes, and automatic scaling. Best for rapid iteration and minimal ops overhead.
    • Google Cloud Run: For greater control and deeper integration within Google Cloud. Containerizes the Next.js application, running it as a serverless container. Benefits from automatic scaling from zero to thousands of instances based on traffic.
  • Static Assets: All static assets (images, CSS, JS bundles) will be served via a Content Delivery Network (CDN) for fast global delivery, implicitly handled by Vercel or explicitly configurable with Cloud Run and Cloud CDN.

6.2. Backend (Firebase Genkit / Google Cloud Functions)

  • Deployment Target: Firebase Cloud Functions (which run on Google Cloud Functions).
    • Scalability: Cloud Functions are serverless and scale automatically based on incoming request volume. This is ideal for handling variable loads from expense submissions and AI processing.
    • Integration: Deep integration with the broader Google Cloud ecosystem (monitoring, logging, IAM).
    • Cost-Effectiveness: Pay-per-execution model, meaning costs are directly proportional to usage, with generous free tiers.
  • Genkit specific: Genkit flows compile down to standard Cloud Functions, benefiting from the same scaling and deployment mechanisms.

6.3. Database (Supabase)

  • Deployment Target: Supabase's managed service.
    • Scalability: Supabase provides managed PostgreSQL instances that are designed to scale. For high-volume enterprise usage, consider their Pro or Enterprise plans which offer dedicated resources, advanced monitoring, and performance tuning.
    • High Availability: Supabase inherently offers replication and backup features to ensure data durability and uptime.
    • pg_vector Scaling: The pg_vector extension for RAG will scale with the PostgreSQL database. For extremely large policy datasets and high query volume, strategies like horizontal sharding or dedicated vector databases might be considered in the very long term, but pg_vector on a well-provisioned Supabase instance will suffice for initial and mid-tier scaling.

6.4. AI Models (Gemini API)

  • Scaling: Handled entirely by Google Cloud AI Platform. Access to Gemini Pro and Gemini Vision Pro APIs is inherently scalable. Google manages the underlying infrastructure, ensuring high throughput and low latency.
  • Rate Limits: While highly scalable, be aware of API rate limits. Implement exponential backoff and retry mechanisms in Genkit for robust interaction with the Gemini API to handle transient errors or occasional rate limit breaches.

6.5. Monitoring & Observability

  • Google Cloud Monitoring: Integrated with Cloud Functions and Genkit, providing metrics (invocations, errors, latency), dashboards, and alerting.
  • Google Cloud Logging (Cloud Logging): Centralized logging for all Cloud Functions and Genkit executions, invaluable for debugging and auditing.
  • Genkit UI: Provides specific visibility into AI calls, prompt evaluations, and model responses, which is critical for prompt engineering and debugging AI flows.
  • Supabase Monitoring: Built-in dashboard for database performance, connections, query logs, and storage usage.
  • Next.js (Vercel): Vercel provides integrated logging and metrics for frontend and API routes.

6.6. Security

  • Supabase Auth & Row Level Security (RLS): Crucial for securing data. Supabase Auth manages user authentication, and RLS ensures users can only access/modify data they are authorized for (e.g., employees can only see their own expenses, managers can see their direct reports' expenses).
  • API Keys & Service Accounts: Gemini API calls will be authenticated using Google Cloud Service Accounts or API keys, managed securely within Google Cloud IAM. Genkit running on Cloud Functions inherits the service account permissions, simplifying authentication.
  • Network Security: All communication between Next.js, Genkit, and Supabase will be over HTTPS.
  • Input Validation: Robust input validation on the frontend and backend (Genkit/Next.js API routes) to prevent common web vulnerabilities like SQL injection or cross-site scripting.

By meticulously planning and implementing these deployment and scaling strategies, the Automated Expense Auditor will be a high-performance, resilient, and secure application capable of meeting the demands of a modern enterprise.

Core Capabilities

  • Receipt parsing
  • RAG policy comparison
  • Automated flagging
  • Approval workflow routing
  • Audit dashboard

Technology Stack

Next.jsFirebase GenkitGemini APISupabase

Ready to build?

Deploy this architecture inside Firebase Genkit using the Gemini API.

Back to Portfolio
Golden Door Asset

Company

  • About
  • Contact
  • LLM Info

Tools

  • Agents
  • Trending Stocks

Resources

  • Software Industry
  • Software Pricing
  • Why Software?

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Golden Door Asset.  ·  Maintained by AI  ·  Updated Mar 2026  ·  Admin