The Project
Sector Guard — an AI-powered expense reimbursement platform. Employees upload receipts, a vision LLM extracts structured data, a rule-based fraud engine audits every claim, and monthly payouts are locked immutably.
Live: Sector Guard
GitHub:
Sector Guard v1.5
An AI-powered expense reimbursement auditing platform. Employees upload receipts, a hosted vision-language model extracts the data automatically, and a rule-based fraud engine catches policy violations before finance ever sees them.
Live demo: https://sector-guard.vercel.app/
Overview
Sector Guard automates the expense reimbursement workflow that finance teams usually handle manually:
- An employee uploads a receipt (ride, food, or accommodation)
- A hosted vision-language model (Groq's Qwen 3.8 27B) extracts structured data — merchant, amount, currency, date, category, line items — with zero manual entry
- A rule-based fraud engine audits the expense against the employee's recent history, checking for policy violations, receipt-splitting fraud, and temporally impossible claims
- Each expense is scored, categorized (
APPROVED/FLAGGED/DENIED), and the employee's trust rating adjusts accordingly - At month-end, approved expenses are aggregated and locked into an immutable payout record — capped at the employee's monthly tier limit, with a category-wise (Travel /…
Why I Built This
Expense auditing is manual, slow, and error-prone in most companies. Someone stares at receipt images, types amounts into spreadsheets, checks policy rules by hand. I wanted to automate the full loop:
- Receipt → Structured data (vision LLM, not OCR)
- Data → Audit decision (deterministic rules, explainable)
- Approved claims → Locked monthly payout (immutable, capped at tier limit)
- All other things could be queried (corporate overview, individual finances, normalized to USD).
Architecture
Receipt Upload
↓
Groq Vision LLM (Qwen 3.8 27B) → Structured JSON
↓
Currency Converter (fixed rates, 21 Oct 2026) → USD normalization
↓
Fraud Engine (3 rules, risk score 0-100)
↓
Status: APPROVED / FLAGGED / DENIED /
↓
Prisma + Neon Postgres (serverless)
↓
Monthly Payout Close → Immutable record
↓
Company Summary (admin) / Employee Financial Summary
Core Features
| Feature | What It Does |
|---|---|
| Vision LLM extraction | Receipt image → {merchant, amount, currency, date, category, line_items[]} — no templates, no regex |
| Fraud engine | 3 rules: tier-limit breach, split-receipt detection, temporal conflict. Risk score → status. Plain-language explanations. |
| Trust rating | Starts at 100. Clean audits +1, flags -5 to -15. Visible to admin. |
| Monthly payouts | Approved expenses aggregated, capped at tier limit, categorized (Travel/Food/Stay/Other), locked immutable. |
| Company dashboard (admin) | Summary cards, employee ratings table, monthly trends chart (Recharts). |
| Employee portal | Receipt folders by month, payout history, claimed/approved/paid in USD + original currency breakdown. |
| Rate limiting | 29 receipts/day, atomic upsert + in-function semaphore (race-condition safe). |
| Auth | Admin login + per-employee access codes (hashed). Employees scoped to own data. |
The Fraud Engine (Deterministic, Explainable)
// src/lib/engine/fraudCore.ts
function calculateRiskScore(receipt, employee, history): RiskResult {
let score = 0;
const reasons: string[] = [];
// Rule 1: Tier limit breach
if (monthlyTotal(history) + receipt.amount > employee.tierLimit) {
score += 40;
reasons.push(`Monthly tier limit exceeded (${employee.tierLimit})`);
}
// Rule 2: Split receipt fraud
if (sameVendorSameDay(history, receipt).reduce((a,b)=>a+b.amount,0) > SINGLE_RECEIPT_LIMIT) {
score += 35;
reasons.push('Split-receipt pattern detected');
}
// Rule 3: Temporal conflict
if (overlappingClaims(history, receipt)) {
score += 30;
reasons.push('Physically implausible overlapping claims');
}
return {
score,
status: score > 70 ? 'DENIED' : score > 30 ? 'FLAGGED' : 'APPROVED',
reasons,
calculatedReimbursement: score > 70 ? 0 : receipt.amount,
};
}
Why rule-based? Every flag has a clear, auditable reason. Compliance teams need "why," not "the model said so."
Currency Handling — The Pain Point I Didn't Expect
Initially hardcoded INR→USD. Real receipts come in EUR, GBP, AED...
Solution: Convert at write-time, not read-time.
// src/lib/currency/fixedRates.ts
export const FIXED_FX_RATES: Record<string, number> = {
USD: 1,
INR: 0.0119, // 1 INR = 0.0119 USD
EUR: 1.085, // 1 EUR = 1.085 USD
GBP: 1.315, // 1 GBP = 1.315 USD
AED: 0.272, // 1 AED = 0.272 USD
SGD: 0.745, // 1 SGD = 0.745 USD
// ... rates as of 21 Oct 2026
};
export function convertToUSD(amount: number, currency: string): {
usdAmount: number;
rate: number;
status: 'CONVERTED' | 'UNSUPPORTED';
} {
const rate = FIXED_FX_RATES[currency];
if (!rate) return { usdAmount: 0, rate: 0, status: 'UNSUPPORTED' };
return { usdAmount: amount * rate, rate, status: 'CONVERTED' };
}
Result: Fraud engine, payouts, summaries all work in USD. Original currency preserved. Unsupported currency = UNSUPPORTED status (currently not used — all test currencies covered).
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) | Server components, API routes, Vercel-native |
| Database | Neon (serverless Postgres) | Scales to zero, works on Vercel |
| ORM | Prisma + @prisma/adapter-pg | Required for serverless connection pooling |
| AI | Groq API (Qwen 3.8 27B Scout) | Fast, free tier, vision support |
| FX | Static rate table | no external dependency |
| Charts | Recharts | Lightweight, Tailwind-friendly |
| Auth | JWT (jose), httpOnly cookies | Simple, no external dependency |
| Hosting | Vercel | Zero-config for Next.js |
What I Learned
- Vision LLMs > OCR for receipts. OCR gives text; vision LLM gives structure. Prompt design matters more than model choice.
- Never trust LLM output. The fraud engine's validation layer caught hallucinated totals and impossible dates.
- Convert currency at ingestion. One USD column everywhere downstream > CASE WHEN chaos in every query.
- Serverless rate limiting needs a semaphore. Vercel functions run concurrently. DB counter alone races.
- Admin dashboard = aggregation that you have already. No new table required; just query the table differently from Expense/Audit/Monthly Payout.
Demo Walkthrough
- Open Sector Guard
- Click admin tab
- Login: password abc
- Explore: Company Summary - summary cards, employee ratings table, monthly trends chart, Employees - list, create, edit, delete (access codes are hashed) Payouts - monthly records, category breakdowns Click an employee → Financial Summary → claimed/approved/paid in USD + original currency breakdown
What's Next
- Automated payout approval (admin one-click → paidAt)
- Email/Slack notifications on payout close
- Configurable fraud rules per department
- E2E tests (Playwright)
- Dark mode toggle
Feedback, issues, and PRs welcome. If you're building AI-powered finance tools or vision LLM pipelines, let's connect.
GitHub:
Sector Guard v1.5
An AI-powered expense reimbursement auditing platform. Employees upload receipts, a hosted vision-language model extracts the data automatically, and a rule-based fraud engine catches policy violations before finance ever sees them.
Live demo: https://sector-guard.vercel.app/
Overview
Sector Guard automates the expense reimbursement workflow that finance teams usually handle manually:
- An employee uploads a receipt (ride, food, or accommodation)
- A hosted vision-language model (Groq's Qwen 3.8 27B) extracts structured data — merchant, amount, currency, date, category, line items — with zero manual entry
- A rule-based fraud engine audits the expense against the employee's recent history, checking for policy violations, receipt-splitting fraud, and temporally impossible claims
- Each expense is scored, categorized (
APPROVED/FLAGGED/DENIED), and the employee's trust rating adjusts accordingly - At month-end, approved expenses are aggregated and locked into an immutable payout record — capped at the employee's monthly tier limit, with a category-wise (Travel /…

Top comments (0)