Reconciling revenue across three sources: an 8.4% gap explained down to 0.33%
A US outdoor-gear brand counted revenue in three places: the owner's working sheet, the merchandiser's warehouse book and the in-house CRM. Each showed a different number. A one-off comparison of all three explained the gap in site revenue and caught a broken year-over-year comparison. The check then became a daily job with a 3% threshold and one rule: if less than 70% of the money could be compared, the check gives no verdict.
Three reports, three revenues, and an argument over which one to trust
The owner keeps a daily sales sheet in Google Sheets: two stores, two marketplaces, ad spend by channel. The merchandiser keeps the warehouse book: one row per order line, with the purchase price, logistics and a sales channel marked by hand. The CRM takes its figures straight from Shopify. Over the same 19 days the three sources showed three different site revenues, and the owner's sheet differed from the CRM by 8.4%.
The owner put the question plainly: why don't these numbers match Shopify? Until that is answered, every budget decision turns into an argument about figures. Each side has its own report, and each is right by its own definitions.
The same sheet had a year-over-year comparison showing −12.84% in total revenue. People read it as the business shrinking.
The same code, the same period, and every gap traced to specific orders
The work had two stages. First came a one-off comparison of all three sources, which settled the argument and gave the owner a written summary. Then the same check became a daily job with a saved result and a threshold, and AI agents went through the new code looking for errors.
Shopify is queried for the same period with the same functions the CRM uses, so the check and the report calculate the same way
Every gap is traced to specific orders: cancellations, partial refunds, orders near midnight
The merchandiser's warehouse book is matched against the CRM for each sales channel: the site and two marketplaces
19 days of this year against the same 19 days of last year, so part of a month is never set against a full one
Six sections. Figures taken from each side's own reports are marked separately from figures checked against the raw data
The same word for two different amounts
The whole 8.4% gap on the site came from cancelled and partially refunded orders. The owner's sheet records the amount at the moment of ordering. The CRM shows the money left after cancellations and refunds. Both figures are correct, and they answer different questions: how much was sold, and how much money stayed. The 0.33% that remains comes from orders placed near midnight.
Along the way I fixed a gap in the CRM itself. It already excluded cancelled and fully refunded orders from revenue. A partially refunded order, though, was counted in full: 7 orders in 30 days, 0.47% of the total. The refund is now subtracted and shown on a separate line, so managers who see the original order amount in their own table can see why the numbers differ.
The warehouse book: the check now runs every day
On the first day I matched the warehouse book against the CRM once and saved the result in a temporary folder. By the next day it was gone. The new module makes it permanent. It reads the book by exporting a range of cells, calculates revenue and cost for each sales channel, and compares them line by line with Shopify, using the same code the CRM runs. The result is saved to a file. CRM pages read that file, because reading the book directly would mean exporting 16,000 rows every time a page opens.
The job runs once a day on a timer. The threshold is 3%, while the measured difference was 0.6%: that leaves room for orders near midnight and manual edits. When a discrepancy is found, the run still finishes normally and prints the difference. The job fails in one case only, when the check couldn't be done at all. Then an alert goes to the team chat.
The books are the merchandiser's working tool, so the module only reads them. Two tests scan the module's source code and confirm it has no function that writes.
It took four rounds to find the causes
The first live run showed a +0.62% difference for the site and marked each store's line in red. Every cause had to be found on specific matched orders:
- the book stores the price before the discount, and Shopify stores the amount paid; the book is now compared with "paid plus discount", and the match was checked order by order
- some orders have a row with no price in the book, for free accessories; such an order is left out of the comparison on both sides, and the size of that gap is saved as a separate field
- "manual" orders exist only in the CRM; in the book they sit under the regular store, so the comparison puts them back under their store
- the book writes one store's order numbers with a leading zero; before that was fixed, 100 orders out of 158 looked missing, and after it 153 of 158 matched
After four rounds the numbers settled: −0.81% for one store, −0.12% and 0.00% for the marketplaces, against a 3% threshold. On one marketplace all 62 orders out of 62 matched, with none lost on either side.
AI agents looked for errors in the new code
Soon after launch, AI agents went through the module with a single task: find what's wrong with it. The result was 68 findings, of which 42 were confirmed and 11 turned out to be real defects in the new code. I reproduced each defect before fixing it and covered it with a test. Each test was checked by putting the bug back and making sure the test fails.
- orders were requested by UTC time but filtered by the store's local date: 154 orders out of 959, 15% of the money, fell near midnight, and a one-day check showed a 15% gap even though the book was fine. Orders are now fetched with an extra day on each side and then filtered by the requested dates
- a write-off line for defective stock was counted as a sale because the text was compared as a whole, which turned a 50.0% margin into 59.3%; the category is now read from the start of the line
- the sales channel was compared case-sensitively, so one lowercase letter in a column filled in by hand showed "mismatch" for a correct book
- the book's line price was compared with an order total that includes tax and shipping; the comparison now uses the sum of the items
- the module said "matched" even when Shopify returned nothing: the book had money, Shopify didn't answer, and the list of mismatches was empty. "Matched" now requires at least one checked line and no unchecked ones
- the verdict ignored how much was actually compared: the test data (101 orders, 100 of them with an unpriced row) gave "matched" when only 0.25% of the money had been checked. The share of money compared is now saved with the result, and below 70% there is no verdict
- the result showed "money outside the comparison" as the value of the affected orders, five times the real sum of the differences; that is now three separate fields
- margin was calculated on a mix of rows; it now uses only rows where both the sale price and the purchase price were read
The module now has 33 tests. On launch day it had 15, and after the rounds of looking for causes it had 24. The test values are taken from real cells of the book.
The result made it onto the screen
The review also found a problem in how the work was organised: the result went into a file that nobody opened. A warning nobody sees protects nothing. A "Warehouse books" card on the CRM's integrations page now shows, for each line, the book amount, the Shopify amount, the difference and the verdict, along with the share of money compared and the margin based on purchase prices.
The same calculation as the CRM, and read-only access to the merchandiser's books
A module in the reporting code; Shopify revenue is calculated by the same functions the CRM uses
Orders from both stores for the period with an extra day on each side, filtered by the store's local date
The warehouse book is read by exporting a range; two tests confirm the module has no function that writes
Pages open the saved result instantly, and the book doesn't have to be exported again
Runs once a day; the job fails only if the check couldn't be done, and then the alert goes to the team chat
33 tests with values from real cells of the book; every fix was checked by putting the bug back
The agents look for errors in new code: 68 findings, 42 confirmed
Checked over the same period; recalculating on equal periods caught the year-over-year error
The argument is settled, and the check runs every day
what remains comes from orders placed near midnight
19 days of this year had been compared with a full month of last year
defects in the new code: 68 findings, 42 confirmed
Both figures are correct, and now they have different names
The 8.4% site gap is explained: it is cancelled orders and partial refunds, with 0.33% left over from orders near midnight. The warehouse book differed from the CRM by 0.6%. One marketplace matched exactly, and on the other all three sources agree within 3.5%.
I made one caveat clear from the start. Rows reach the warehouse book from Shopify by hand and through my own automatic transfer of orders into the warehouse sheets. So the 0.6% match confirms that the transfer is accurate: no order lost, no amount counted twice. The only data the book adds on its own is the purchase price and logistics.
The 12.84% "decline" turned out to be growth of about 30%
In the sheet, this year's August row added up 19 days, while last year's row held the whole month. So 19 days were being compared with 31. Over the same dates, 1–19 August of both years, site revenue grew by 29%, and by 30% with the marketplaces included.
The owner received a six-section summary. It marks separately what came from his own reports and what was checked against the raw data. I also withdrew one of my own claims in that summary. The first version said the sheet had no spend for one channel. When I read all 37 columns, the spend was there, and it differed from the platform's bill by just 0.04%. Before saying data is missing, you have to be sure you've read all of it.
The check now runs every day
After four rounds the store and marketplace lines settled at −0.81% / −0.12% / 0.00% against a 3% threshold. A live run a week after launch covered 701 rows, with differences of +2.18% / −0.02% / +0.00% and the verdict "matched". The result opens instantly on the CRM's integrations page, together with the share of money compared and the margin based on purchase prices.
A verdict only when enough was compared
The main rule of this work: "no differences found" and "couldn't compare" are two separate answers. "Matched" now means four things: no line differs, at least one line was actually checked, nothing is left unchecked, and the comparison covers at least 70% of the money. Below that there is no verdict, and the screen says "not checked". If Shopify doesn't respond, that is shown as a failure. If there is nothing to compare with, no percentage is calculated and the screen shows a dash.
For the owner, the argument over whose numbers are right is settled. Every difference is explained down to specific orders, and the check runs every day where anyone can look at it. End-to-end attribution shows which ads bring orders. This work shows which figures about money can be trusted.
Where else the same approach works
Behind this case is a common problem: "two or three systems use the same word for different numbers". The same method works wherever reports are put together from several sources:
- → An online store plus marketplaces. The store shows the order amount, and the marketplace pays out after its fee. The two can be added up only once they measure the same thing
- → An ERP, a CRM and a bank. Revenue counted by shipment, by payment and by money arriving in the account differs by definition, and a comparison over one period settles the argument
- → Reports built by hand in spreadsheets: periods of different lengths compared, part of a month set against a full one, columns summed without filtering the row type
- → Warehouse and purchasing books as the only source of cost prices: matching them with sales gives the real margin
- → A check of existing reports before budget decisions, so that a "decline" in a spreadsheet doesn't lead to cutting a channel that works
- The check uses the same code as the main report, so the report and the check calculate the same way
- The coverage rule: a verdict is given only when enough of the money was compared, because "no differences" and "couldn't compare" are separate answers
- A saved result plus a page that shows it; a daily timer; the job fails only when the check couldn't be done
- Other people's working books are only read, and tests on the module's source code make sure of it
- A review of new code by AI agents: reproduce the bug, fix it, cover it with a test, and check the test by putting the bug back
- A summary for the client that separates what came from each side's reports from what was checked against the raw data
Three reports and three revenues? They can be reconciled down to each order
Start with a one-off comparison over a single period. In a day it shows where the definitions differ and where the calculation itself is wrong. After that the check can run on a schedule, with a saved result, a threshold and the coverage rule.
Related cases and ready-made solutions
Researching a competitor from open sources: what you can see from outside
12 steps using free sources: WHOIS, DNS, certificates (crt.sh), URLscan and the web archive. A script…
An Astro landing page that scores B2B leads by company tax ID
A multi-step form asks for the company's tax ID. Within seconds an n8n workflow pulls data about the company…
Finding B2B clients with cold emails written by AI
Five steps. Apollo picks senior decision-makers who fit the customer profile, Clay collects 4–7 facts about…
Start with the 5,000 ₽ audit: a written report and a payback estimate
I'll tell you what to automate first, when it will pay for itself and whether your task needs AI at all. Sometimes it doesn't.
Or just ask a question. During business hours I reply within 2 hours