Careem for Business — Reconciliation Tooling in Apps Script
The corporate billing system produced a flat dump of invoices and companies. Nothing in it said what had changed. I built a diff engine in Google Apps Script that classifies every record as new, deleted or active between dumps, and an in-sheet review dialog that turns that comparison into a recorded decision.
No client names, invoice values, company counts or colleague names appear anywhere in this case study. The mechanism is the story, not the data that ran through it.
01The Problem
Careem for Business is the corporate side of the product: companies hold accounts, employees ride against them, and invoices settle monthly. Each cycle the billing system exported a fresh dump into a spreadsheet — the current, complete list of invoices and of companies.
A complete list answers the wrong question. What the team actually needed to know was the delta: which companies appeared since last cycle, which disappeared, and which were already known and simply carried forward. That difference is what drives follow-up, because a company that vanished from the dump is either churned or a data error, and both need a human to look.
Getting there meant eyeballing two long lists side by side, every cycle. That is exactly the task humans are worst at and least willing to redo carefully the second time.
02The Architecture
The workbook is a small state machine. Each entity — invoices and companies — moves through the same five sheets, and a shared View sheet renders whichever slice the operator asked for.
Two identical chains run in parallel — Invoice-* and Company-* — because invoices and companies churn independently and conflating them hides both.
03What I Built
The Diff Engine
An onEdit trigger fires when the Data Dump sheet changes. It reads the current dump and the previous one, and matches on the unique ID column in both directions: IDs present now but not before are appended to New; IDs present before but not now are appended to Deleted. Two passes, one per direction, because a one-directional check silently misses everything that left.
The Review Dialog
A modal built with HTML Service renders the pending rows as a table with a checkbox per row and a single status dropdown. The operator ticks what they have verified, chooses Tracked or Not Tracked, and submits. The dialog is deliberately narrow: it shows the unique ID and the name and nothing else, because a review screen that shows everything invites reading instead of deciding.
Decisions That Move Rows
On submit, each selected row is written to the Dashboard Filter sheet with its assigned status and removed from the pending sheet. A record cannot sit in two states at once, and the pending queue drains as it is worked — so the length of the New sheet is itself the backlog indicator, with no separate tracker to fall out of sync.
The Operator Menu
A custom C4B menu attaches to the spreadsheet on open, grouped by intent rather than by function name: the two update actions first, then the six view actions (new, deleted and active, for each entity), then per-account-manager views. Every action clears the shared View sheet before writing, so a stale render can never be mistaken for a current one.
04Engineering Decisions
05Why It Matters
This is the least glamorous thing in the portfolio and one of the most representative. It was not requested as a project. It existed because a recurring manual comparison was producing errors, and the fastest way to stop the errors was to make the comparison mechanical and the decision recorded.