F3.2 Invoice Processing — Smart Lean AI Pipeline

CRITICAL BUG FIX: Upload 400 Error

Root cause: server.js line 1342 uses let body = ''; to accumulate request body as a string. This corrupts binary multipart/form-data (PDF uploads). Django receives mangled data and says "No PDF file provided".

Fix: Change the proxy to use Buffer for binary-safe forwarding:

// Line 1342-1358 in server.js — replace string body with Buffer array
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => {
  const body = Buffer.concat(chunks);
  const proxyReq = http.request({
    hostname: '127.0.0.1', port: 3001,
    path: proxyUrl, method: req.method,
    headers: { ...req.headers, host: '127.0.0.1:3001' }
  }, (proxyRes) => {
    res.writeHead(proxyRes.statusCode, proxyRes.headers);
    proxyRes.pipe(res);
  });
  proxyReq.on('error', (err) => {
    console.error('Django proxy error:', err.message);
    res.writeHead(502, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Backend unavailable' }));
  });
  if (body.length > 0) proxyReq.write(body);
  proxyReq.end();
});

File: /home/claude/projects/unitcycle-demo/server.js lines 1340-1361 Verify: curl -X POST -F "pdf_file=@backend/media/invoices/1abb4cad698a_test_invoice.pdf" https://demo.unitcycle.com/api/invoices/upload/


Context

UnitCycle's invoice feature (F3.2) has the frontend and basic backend built, but the pipeline dead-ends at approve/reject. Invoices go nowhere after approval — no GL coding, no payment tracking, no AI matching. Competitors like Yardi Breeze and Entrata offer 94% AI accuracy with full AP workflows. This plan transforms the existing skeleton into a Smart Lean pipeline where AI handles the tedious work (vendor/property/GL matching, anomaly detection, duplicate flagging) and the PM just reviews and approves.

Design approved by Rafael: Smart Lean workflow (C), 2-panel layout, inline AI badges per field, light mode.


Architecture: Smart Lean Pipeline

Upload PDF → LlamaParse OCR (Cost Effective, 3 credits) → AI Extraction (Gemini Flash via OpenRouter)
    → Auto-match vendor/property/GL → Flag anomalies/dupes/WO matches
    → PM Reviews (inline AI badges, accept/override) → Approved → Scheduled → Paid

6-Stage State Machine

UPLOADED → AI_PROCESSING → PENDING_REVIEW → APPROVED → SCHEDULED → PAID
                                ↓
                          REJECTED / ON_HOLD

API Keys & Configuration

Environment Setup

Create /home/claude/projects/unitcycle-demo/backend/.env:

LLAMAPARSE_API_KEY=llx-TNRhGRWbPYPukOOnn2xo0nevpsGuLek4yC8Nmbt5f57TWadS
OPENROUTER_API_KEY=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

LlamaParse Config

OpenRouter Config

Django Settings Update


Design Spec

Invoice Detail Page (Redesign)

Layout: 2-panel (PDF left, details right) — keep existing structure

New components in right panel (top to bottom):

  1. Workflow Stepper — horizontal 6-stage bar at page top

    • Uploaded → AI Extracted → Review → Approved → Scheduled → Paid
    • Gold filled dot with pulse animation on active step
    • Gold checkmark on completed steps, muted outline on future
  2. Glass Summary Card — hero card with gold top border

    • Total amount (24px Manrope bold), AI confidence bar + percentage
    • Due date, payment terms
    • No glassmorphism blur (light mode), just subtle shadow + gold accent bar
  3. AI Alert Cards — only shown when relevant

    • Price anomaly (red): "34% above avg for this vendor"
    • Work order match (purple): links to matching WO
    • Duplicate warning (amber): fingerprint match detected
    • Each alert has icon, title, detail text, and action link
  4. Vendor & Property Card — inline AI badges per field

    • Each field: label | value | AI badge (check 95%, eye 78%, warning 45%) | "change" link on hover
    • Confidence tiers: High (>=85% gold), Medium (60-84% amber), Low (<60% red)
    • Low-confidence fields show alternatives ("Also considered: Oakmere Trace 61%")
    • "change" link opens dropdown with search for manual override
  5. Line Items Table — with GL code chips

    • Columns: #, Description, GL Code, Qty, Amount
    • GL code as clickable chip with inline confidence badge
    • Uncertain GL codes have amber border
    • Total row with Manrope bold, tabular-nums
  6. Action Bar — pinned at bottom of line items card

    • Approve (navy bg, white text, primary), Hold (neutral), Reject (red outline)
    • Approve triggers: accept all AI suggestions → move to APPROVED status

Invoice List Page (Minor updates)

Color System (Light Mode, oklch())


Implementation Plan

Phase 1: Foundation (Backend)

Step 1: Environment & config setup

Step 2: Fix LlamaParse mode (1-line change)

Step 3: Create OpenRouter service

Step 4: Update Invoice model status choices

Step 5: AI matching pipeline

Step 6: New API endpoints

Phase 2: Frontend Redesign

Step 7: Update types & service

Step 8: Workflow stepper component

Step 9: Redesign invoice-detail.component.ts

Step 10: Update invoice list

Phase 3: AI Integration & Polish

Step 11: Wire up upload → AI pipeline

Step 12: Field confirmation UX

Step 13: Duplicate detection

Phase 4: Testing & Verification

Step 14: Playwright tests

Step 15: Manual verification


Key Files to Modify

File Changes
backend/.env CREATE — API keys
backend/config/settings.py Load .env, add OPENROUTER settings
backend/invoices/llamaparse.py Fix mode to gpt4o, use new API key
backend/invoices/openrouter_service.py CREATE — OpenRouter client
backend/invoices/ai_pipeline.py CREATE — AI matching orchestrator
backend/invoices/models.py Add new status choices, new fields
backend/invoices/views.py New endpoints, update upload flow
backend/invoices/serializers.py Expand detail serializer
backend/invoices/urls.py New routes
backend/requirements.txt Add python-dotenv, openai
src/app/features/invoices/invoice.types.ts New fields, confidence types
src/app/features/invoices/invoice.service.ts New methods
src/app/features/invoices/invoice-detail.component.ts Full redesign per spec
src/app/features/invoices/invoices.component.ts New filter tabs
src/app/features/invoices/components/workflow-stepper.component.ts CREATE
Raw SQL script Add columns to invoices + invoice_line_items

Existing Code to Reuse

Verification

  1. python manage.py check — Django validation
  2. ng build — Angular compilation
  3. Upload test PDF → verify LlamaParse extraction (Cost Effective mode)
  4. Verify OpenRouter API call returns valid JSON
  5. Verify AI matching populates vendor/property/GL suggestions
  6. Walk through full UI flow: list → upload → scanning → review → approve
  7. Playwright test suite passes
  8. pm2 restart unitcycle && pm2 save — deploy
  9. Verify at https://demo.unitcycle.com/invoices