Build a multi-step expense-approval workflow: amount-based routing, an AI summary for the approver, a 48-hour reminder, and escalation on timeout — then prove it with test cases for approve, reject, timeout, and a duplicate submission. A workflow is only as good as the test matrix that survives it.
Ganesh Subramanian runs finance operations at Vertex Media Pvt Ltd, a fictional 150-person agency. Expense approvals live in email threads: submissions get lost, approvers sit on requests, nobody knows what is pending, and last month the same conference invoice was reimbursed twice. Ganesh is replacing the thread-chaos with a workflow: submissions in a form, routed by amount to the right approver, summarized by AI so approvers decide in seconds, chased automatically at 48 hours, and escalated if ignored. This is the most complex build in the track — not because any step is hard, but because the value lives in the branches: reminder logic, timeout handling, duplicate detection. And the test data below contains exactly the submissions that break naive builds.
Vertex's approval matrix, as agreed with finance:
VERTEX MEDIA - EXPENSE APPROVAL MATRIX (v1)
Amount (INR) Approver SLA On timeout
Up to 5,000 Auto-log (no approval) - -
5,001 - 50,000 Reporting manager (L1) 48 hours Remind at 48h,
escalate to L2
at 96h
Above 50,000 Finance head (L2) 48 hours Remind at 48h,
escalate to CFO
at 96h
Any amount, category L2 regardless of 48 hours Same as above
"Client entertainment" amount
Duplicates: same claimant + same amount + same expense date =
hold and flag for manual review. Never route a suspected duplicate
to an approver as if new.
Six synthetic expense claims. You will feed these through your form in step 7.
claimant,claimant_email,category,amount_inr,expense_date,description
Priya Shah,priya.s@vertexmedia.example,Software,3200,2026-07-28,Annual Canva Pro renewal
Amit Joshi,amit.j@vertexmedia.example,Travel,18500,2026-07-25,Client visit Mumbai - flights and cab
Sara Fernandes,sara.f@vertexmedia.example,Client entertainment,9800,2026-07-30,Dinner with Nexbrand client team (4 pax)
Vikas Rathi,vikas.r@vertexmedia.example,Equipment,72000,2026-08-01,Replacement editing workstation monitor x2
Amit Joshi,amit.j@vertexmedia.example,Travel,18500,2026-07-25,Mumbai client visit - flight plus taxi
Neha Kapoor,neha.k@vertexmedia.example,Training,50000,2026-08-02,Advanced motion design course
Draw the full flow before building. On paper: form → sheet → duplicate check → amount/category router (four exits: auto-log, L1, L2, hold) → AI summary → approval request → decision branch (approved → notify claimant + mark sheet; rejected → notify with reason + mark sheet) → and in parallel with the approval wait: 48h reminder, 96h escalation. Mark every place a human decides, every place time passes, and every terminal state a claim can end in. Count the terminal states — if you drew fewer than five (auto-logged, approved, rejected, escalated, held-as-duplicate), the diagram has a gap.
Build the intake. Form fields: claimant name, email, category (choice list including "Client entertainment"), amount (number), expense date (date), description. Responses to a sheet, plus three workflow columns you add: status, approver, decision_note. Submit and delete a throwaway entry to confirm the plumbing, exactly as in Lab 09.
Build the duplicate gate first — order matters. Before any routing: look up whether an earlier row shares claimant email + amount + expense date. Platform options: Power Automate "List rows" with a filter; Make/Zapier "Search rows"; free-tier fallback: a sheet formula =COUNTIFS(B:B,B2,D:D,D2,E:E,E2)>1 in a dup_flag column, read by the flow's first condition. On match: set status HELD-DUPLICATE, alert Ganesh (you) with both row references, and stop. The order is the lesson: a duplicate that reaches an approver as a fresh claim will often be approved — approvers check reasonableness, not history.
Build the router. Condition chain on the non-duplicate path, checking category before amount: category = "Client entertainment" → L2 queue. Else amount ≤ 5,000 → status AUTO-LOGGED, notify claimant, end. Else amount ≤ 50,000 → L1 queue. Else → L2 queue. Test the router logic mentally against each test row now, before wiring the approval steps — write down which exit each of the six rows should take. That prediction list becomes your test oracle in step 7.
Add the AI approver summary. Approvers reject workflows that make them open four attachments; the AI step is what makes this workflow liked. Prompt: "Summarize this expense claim for an approver in 3 lines: (1) who, what, how much, when; (2) anything unusual — round numbers, weekend dates, category/description mismatch, amount near an approval threshold; (3) recommend APPROVE / QUERY / REJECT with a 5-word reason. Never state policy limits you have not been given. Claim: [fields]." Template fallback without AI: a formatted 3-line digest of the fields plus a NEAR-THRESHOLD flag when the amount is within 10% of a routing boundary. That "anything unusual" line is doing real work — watch what it says about rows 4 and 6.
Wire the approval, the reminder, and the escalation. Power Automate: "Start and wait for an approval" to the queue's approver, with a parallel branch: delay 48h → if still pending, send reminder → delay to 96h → if still pending, reassign to the escalation approver and alert Ganesh. Zapier/Make: send the approval request by email with APPROVE/REJECT links or reply-codes, write pending_since to the sheet, and build a second scheduled flow (daily) that scans for rows pending >48h (remind) and >96h (escalate) — the scheduled-scan pattern is the free-tier workhorse and, honestly, the more debuggable design. Both decisions must write back: status, approver, decision note, timestamp.
Run the six test claims against your prediction list. Submit all six through the form. For the timeout tests, do not wait two real days: temporarily set the delays to 3 and 6 minutes (note the change, and change it back after). Play the approvers from your two addresses: approve row 2, reject row 6 with a reason ("needs training-budget pre-approval"), ignore row 4 until it reminds and escalates. Log every claim's journey: route taken, summary text, decision, notifications, final status and sheet write-back. Compare against your step-4 predictions and the answer key.
Probe the boundary and the gaps. Three questions to answer in your log with evidence: What happens at exactly Rs 5,000, and at exactly Rs 50,000 (submit a quick extra claim for one boundary — does your condition use ≤ or <, and does it match the policy table)? What happens if an approver approves after escalation already reassigned the claim (double-decision — who wins, and does the sheet show one final answer)? What happens if the AI step fails mid-flow (does the claim die silently, or proceed with a "summary unavailable" note — it must be the latter)?
Write the runbook and hand over the keys. Second sheet tab: what the flow does, the routing table, who the approvers and escalation contacts are, how to pause it, how to process a held duplicate, and the known limits (e.g., "boundary uses ≤; escalation reassigns rather than parallel-approves"). Then rename everything with owner and date. A workflow that routes money is organizational infrastructure the day it goes live — document it like you might get promoted away from it.
Build and test time-based branches with minutes-long delays, then restore real durations — and write the restored values in the runbook, because "why did it remind after 3 minutes" is a support ticket waiting to happen.
Approval workflows are decided in the branches: the duplicate gate before the router, category before amount, reminders that fire, escalations that reassign, and write-backs at every state change. The AI summary makes approvers fast; the test matrix — including the claims designed to break you — is what makes the workflow safe to trust with money.